Multidraw

This commit is contained in:
TheBrokenRail 2024-07-10 23:15:58 -04:00
parent f08afbf654
commit d4c0f54245
24 changed files with 515 additions and 20 deletions

@ -1 +1 @@
Subproject commit 68561e0404a4ad09e523129bca67cf6be8cec2fc Subproject commit fedc5ac21865fe44dcbc7adb0818af612205cd57

View File

@ -69,3 +69,4 @@ TRUE Add Reborn Info To Options
FALSE Log FPS FALSE Log FPS
TRUE Add Welcome Screen TRUE Add Welcome Screen
TRUE F3 Debug Information TRUE F3 Debug Information
TRUE Multidraw Rendering

View File

@ -565,3 +565,12 @@ void media_get_framebuffer_size(int *width, int *height) {
*height = DEFAULT_HEIGHT; *height = DEFAULT_HEIGHT;
} }
} }
// Check OpenGL Extension
int media_has_extension(const char *name) {
if (glfw_window) {
return glfwExtensionSupported(name);
} else {
return 0;
}
}

View File

@ -22,6 +22,7 @@ void media_set_interactable(int is_interactable);
void media_disable_vsync(); void media_disable_vsync();
void media_force_egl(); void media_force_egl();
void media_set_raw_mouse_motion_enabled(int enabled); void media_set_raw_mouse_motion_enabled(int enabled);
int media_has_extension(const char *name);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -138,6 +138,23 @@ CALL(15, glDrawArrays, void, (GLenum mode, GLint first, GLsizei count))
#endif #endif
} }
#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
CALL(70, glMultiDrawArrays, void, (GLenum mode, const GLint *first, const GLsizei *count, GLsizei drawcount))
#ifdef MEDIA_LAYER_TRAMPOLINE_GUEST
trampoline(true, gl_state, mode, copy_array(drawcount, first), copy_array(drawcount, count));
#else
gl_state_t gl_state = args.next<gl_state_t>();
gl_state.send_to_driver();
GLenum mode = args.next<GLenum>();
uint32_t drawcount;
const GLint *first = args.next_arr<GLint>(&drawcount);
const GLsizei *count = args.next_arr<GLsizei>();
func(mode, first, count, GLsizei(drawcount));
return 0;
#endif
}
#endif
CALL(16, glColor4f, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) CALL(16, glColor4f, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha))
#ifdef MEDIA_LAYER_TRAMPOLINE_GUEST #ifdef MEDIA_LAYER_TRAMPOLINE_GUEST
trampoline(true, red, green, blue, alpha); trampoline(true, red, green, blue, alpha);
@ -162,14 +179,14 @@ CALL(17, glClear, void, (GLbitfield mask))
CALL(18, glBufferData, void, (GLenum target, GLsizeiptr size, const void *data, GLenum usage)) CALL(18, glBufferData, void, (GLenum target, GLsizeiptr size, const void *data, GLenum usage))
#ifdef MEDIA_LAYER_TRAMPOLINE_GUEST #ifdef MEDIA_LAYER_TRAMPOLINE_GUEST
trampoline(true, gl_state.bound_array_buffer, target, copy_array(size, (unsigned char *) data), usage); trampoline(true, gl_state.bound_array_buffer, target, int32_t(size), copy_array(size, (unsigned char *) data), usage);
#else #else
glBindBuffer(GL_ARRAY_BUFFER, args.next<GLuint>()); glBindBuffer(GL_ARRAY_BUFFER, args.next<GLuint>());
GLenum target = args.next<GLenum>(); GLenum target = args.next<GLenum>();
uint32_t size; int32_t size = args.next<int32_t>();
const unsigned char *data = args.next_arr<unsigned char>(&size); const unsigned char *data = args.next_arr<unsigned char>();
GLenum usage = args.next<GLenum>(); GLenum usage = args.next<GLenum>();
func(target, GLsizeiptr(size), data, usage); func(target, size, data, usage);
return 0; return 0;
#endif #endif
} }
@ -738,3 +755,17 @@ CALL(67, glGenBuffers, void, (GLsizei n, GLuint *buffers))
return 0; return 0;
#endif #endif
} }
CALL(69, glBufferSubData, void, (GLenum target, GLintptr offset, GLsizeiptr size, const void *data))
#ifdef MEDIA_LAYER_TRAMPOLINE_GUEST
trampoline(true, gl_state.bound_array_buffer, target, int32_t(offset), int32_t(size), copy_array(size, (unsigned char *) data));
#else
glBindBuffer(GL_ARRAY_BUFFER, args.next<GLuint>());
GLenum target = args.next<GLenum>();
int32_t offset = args.next<int32_t>();
int32_t size = args.next<int32_t>();
const unsigned char *data = args.next_arr<unsigned char>();
func(target, offset, size, data);
return 0;
#endif
}

View File

@ -38,6 +38,9 @@ struct TrampolineArguments {
if (length != nullptr) { if (length != nullptr) {
*length = size / sizeof(T); *length = size / sizeof(T);
} }
if (size == 0) {
return nullptr;
}
const T *ret = (const T *) raw_args; const T *ret = (const T *) raw_args;
raw_args += size; raw_args += size;
return ret; return ret;

View File

@ -182,3 +182,11 @@ CALL(68, media_ensure_loaded, void, ())
return 0; return 0;
#endif #endif
} }
CALL(71, media_has_extension, int, (const char *name))
#ifdef MEDIA_LAYER_TRAMPOLINE_GUEST
return trampoline(false, copy_array(name));
#else
return func(args.next_arr<char>());
#endif
}

View File

@ -89,6 +89,10 @@ set(SRC
src/init/init.cpp src/init/init.cpp
# f3 # f3
src/f3/f3.cpp src/f3/f3.cpp
# multidraw
src/multidraw/glue.cpp
src/multidraw/buffer.cpp
src/multidraw/storage.cpp
) )
# Install Splashes # Install Splashes
install( install(

View File

@ -29,4 +29,5 @@ void init_home();
void init_override(); void init_override();
void init_screenshot(); void init_screenshot();
void init_f3(); void init_f3();
void init_multidraw();
} }

View File

@ -43,5 +43,6 @@ __attribute__((constructor)) static void init() {
if (!reborn_is_headless()) { if (!reborn_is_headless()) {
init_screenshot(); init_screenshot();
init_f3(); init_f3();
init_multidraw();
} }
} }

View File

@ -724,7 +724,7 @@ static void sort_chunks(Chunk **chunks_begin, Chunk **chunks_end, DistanceChunkS
// Display Date In Select World Screen // Display Date In Select World Screen
static std::string AppPlatform_linux_getDateString_injection(__attribute__((unused)) AppPlatform_linux *app_platform, int time) { static std::string AppPlatform_linux_getDateString_injection(__attribute__((unused)) AppPlatform_linux *app_platform, int time) {
// From https://github.com/ReMinecraftPE/mcpe/blob/56e51027b1c2e67fe5a0e8a091cefe51d4d11926/platforms/sdl/base/AppPlatform_sdl_base.cpp#L68-L84 // From https://github.com/ReMinecraftPE/mcpe/blob/56e51027b1c2e67fe5a0e8a091cefe51d4d11926/platforms/sdl/base/AppPlatform_sdl_base.cpp#L68-L84
time_t tt = time; const time_t tt = time;
tm t = {}; tm t = {};
gmtime_r(&tt, &t); gmtime_r(&tt, &t);
char buf[2048]; char buf[2048];

View File

@ -0,0 +1,29 @@
#include <cstring>
#include "buffer.h"
// Setup Buffer
Buffer::Buffer(const ssize_t size) {
// Client-Side Data
client_side_data = new unsigned char[size];
// Server-Side Data
server_side_data = 0;
glGenBuffers(1, &server_side_data);
glBindBuffer(GL_ARRAY_BUFFER, server_side_data);
glBufferData(GL_ARRAY_BUFFER, size, nullptr, GL_DYNAMIC_DRAW);
}
Buffer::~Buffer() {
// Client-Side Data
delete[] client_side_data;
// Server-Side Data
glDeleteBuffers(1, &server_side_data);
}
// Upload Data
void Buffer::upload(const intptr_t offset, const ssize_t size, const void *data) const {
// Client-Side Data
memcpy(client_side_data + offset, data, size);
// Server-Side Data
glBindBuffer(GL_ARRAY_BUFFER, server_side_data);
glBufferSubData(GL_ARRAY_BUFFER, offset, size, data);
}

View File

@ -0,0 +1,21 @@
#pragma once
#include <cstdint>
#include <GLES/gl.h>
// Keep An OpenGL Buffer And Client-Side Memory Synchronized
struct Buffer {
explicit Buffer(ssize_t size);
~Buffer();
// Prevent Copying
Buffer(const Buffer &) = delete;
Buffer &operator=(const Buffer &) = delete;
// Data
GLuint server_side_data;
unsigned char *client_side_data;
// Upload Data
void upload(intptr_t offset, ssize_t size, const void *data) const;
};

135
mods/src/multidraw/glue.cpp Normal file
View File

@ -0,0 +1,135 @@
#include <GLES/gl.h>
#include <symbols/minecraft.h>
#include <libreborn/libreborn.h>
#include <media-layer/core.h>
#include <mods/init/init.h>
#include <mods/feature/feature.h>
#include "storage.h"
// Fake Buffer IDs To Correspond To The Multidraw Storage
#define MULTIDRAW_BASE 1073741823
// Setup
static Storage *storage = nullptr;
static void setup_multidraw(int chunks, GLuint *buffers) {
delete storage;
storage = new Storage(chunks);
for (int i = 0; i < chunks; i++) {
buffers[i] = i + MULTIDRAW_BASE;
}
}
HOOK(glDeleteBuffers, void, (GLsizei n, const GLuint *buffers)) {
if (buffers[0] >= MULTIDRAW_BASE) {
delete storage;
} else {
ensure_glDeleteBuffers();
real_glDeleteBuffers(n, buffers);
}
}
// Usage
static int current_chunk = -1;
HOOK(glBindBuffer, void, (const GLenum target, GLuint buffer)) {
if (target == GL_ARRAY_BUFFER && buffer >= MULTIDRAW_BASE && storage != nullptr) {
current_chunk = int(buffer - MULTIDRAW_BASE);
buffer = storage->buffer->server_side_data;
} else {
current_chunk = -1;
}
ensure_glBindBuffer();
real_glBindBuffer(target, buffer);
}
HOOK(glBufferData, void, (GLenum target, GLsizeiptr size, const void *data, GLenum usage)) {
if (target == GL_ARRAY_BUFFER && current_chunk >= 0 && storage != nullptr) {
storage->upload(current_chunk, size, data);
} else {
ensure_glBufferData();
real_glBufferData(target, size, data, usage);
}
}
#define VERTEX_SIZE 24
#define MAX_RENDER_CHUNKS 4096
static bool supports_multidraw() {
static int ret = -1;
if (ret == -1) {
ret = media_has_extension("GL_EXT_multi_draw_arrays");
}
return ret;
}
static int LevelRenderer_renderChunks_injection(__attribute__((unused)) LevelRenderer_renderChunks_t original, LevelRenderer *self, const int start, const int end, const int a, const float b) {
// Prepare Offset
self->render_list.clear();
const Mob *camera = self->minecraft->camera;
const float x = camera->old_x + ((camera->x - camera->old_x) * b);
const float y = camera->old_y + ((camera->y - camera->old_y) * b);
const float z = camera->old_z + ((camera->z - camera->old_z) * b);
glPushMatrix();
glTranslatef(-x, -y, -z);
// Setup OpenGL
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, storage->buffer->server_side_data);
glVertexPointer(3, GL_FLOAT, VERTEX_SIZE, (void *) 0);
glTexCoordPointer(2, GL_FLOAT, VERTEX_SIZE, (void *) 0xc);
glColorPointer(4, GL_UNSIGNED_BYTE, VERTEX_SIZE, (void *) 0x14);
// Batch
static GLint firsts[MAX_RENDER_CHUNKS];
static GLsizei counts[MAX_RENDER_CHUNKS];
GLsizei total = 0;
for (int i = start; i < end; i++) {
Chunk *chunk = self->chunks[i];
// Check If Chunk Is Visible
if (!chunk->field_1c[a] && chunk->visible) {
const RenderChunk *render_chunk = chunk->getRenderChunk(a);
// Get Data Block
const int chunk_id = int(render_chunk->buffer - MULTIDRAW_BASE);
const Block *block = storage->chunk_to_block[chunk_id];
if (block == nullptr) {
continue;
}
// Queue
const int j = total++;
firsts[j] = block->offset / VERTEX_SIZE;
counts[j] = render_chunk->vertices;
}
}
// Draw
#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
if (supports_multidraw()) {
glMultiDrawArrays(GL_TRIANGLES, firsts, counts, total);
} else {
#endif
for (int i = 0; i < total; i++) {
glDrawArrays(GL_TRIANGLES, firsts[i], counts[i]);
}
#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
}
#endif
// Cleanup
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glPopMatrix();
// Return
return total;
}
// Init
void init_multidraw() {
// Setup
if (feature_has("Multidraw Rendering", server_disabled)) {
overwrite_call((void *) 0x4e51c, (void *) setup_multidraw);
overwrite_call((void *) 0x4e6f8, (void *) setup_multidraw);
overwrite_calls(LevelRenderer_renderChunks, LevelRenderer_renderChunks_injection);
unsigned char nop_patch[4] = {0x00, 0xf0, 0x20, 0xe3}; // "nop"
patch((void *) 0x479fc, nop_patch);
}
}

View File

@ -0,0 +1,185 @@
#include <algorithm>
#include "storage.h"
#include <libreborn/libreborn.h>
// Setup
#define DEFAULT_SIZE 16777216 // 16 MiB
Storage::Storage(const int chunks) {
// Allocate
total_size = DEFAULT_SIZE;
used_size = 0;
for (int i = 0; i < chunks; i++) {
chunk_to_block.push_back(nullptr);
}
buffer = new Buffer(total_size);
// First Free Block
Block *block = new Block;
block->offset = 0;
block->size = total_size;
free_blocks.push_back(block);
}
Storage::~Storage() {
delete buffer;
for (const std::vector<Block *> &blocks : {free_blocks, used_blocks}) {
for (const Block *block : blocks) {
delete block;
}
}
}
// Free Block
void Storage::free_block(Block *block) {
// Check
if (block == nullptr) {
return;
}
// Find Block
std::vector<Block *>::iterator it = std::find(used_blocks.begin(), used_blocks.end(), block);
if (it == used_blocks.end()) {
return;
}
used_blocks.erase(it);
// Update Size
used_size -= block->size;
// Merge With Next/Previous Blocks
for (it = free_blocks.begin(); it < free_blocks.end(); ++it) {
const Block *x = *it;
if (x->offset == (block->offset + block->size)) {
// Found Free Block After Target
block->size += x->size;
free_blocks.erase(it);
delete x;
break;
}
}
for (it = free_blocks.begin(); it < free_blocks.end(); ++it) {
const Block *x = *it;
if ((x->offset + x->size) == block->offset) {
// Found Free Block Before Target
block->size += x->size;
block->offset -= x->size;
free_blocks.erase(it);
delete x;
break;
}
}
// Add To Free Block List
free_blocks.push_back(block);
// Fragmentation
check_fragmentation();
}
// Check Fragmentation
ssize_t Storage::get_end() const {
ssize_t end = 0;
for (const Block *block : used_blocks) {
const ssize_t new_end = block->offset + block->size;
if (new_end > end) {
end = new_end;
}
}
return end;
}
void Storage::check_fragmentation() {
const ssize_t end = get_end();
if (end == 0) {
return;
}
const float fragmentation = 1.0f - (float(used_size) / float(end));
if (fragmentation >= 0.5f) {
// 50% Fragmentation
recreate();
}
}
// Upload Chunk
void Storage::upload(const int chunk, const ssize_t size, const void *data) {
// Free Old Block
free_block(chunk_to_block[chunk]);
// Check Size
if (size == 0) {
chunk_to_block[chunk] = nullptr;
return;
}
// Find Free Block
std::vector<Block *>::iterator it;
while (true) {
for (it = free_blocks.begin(); it < free_blocks.end(); ++it) {
if ((*it)->size >= size) {
break;
}
}
if (it == free_blocks.end()) {
// Get Extra Space
recreate(size);
} else {
// Done!
break;
}
}
Block *old_free_block = *it;
// Create New Used Block
Block *new_used_block = new Block;
new_used_block->offset = old_free_block->offset;
new_used_block->size = size;
used_blocks.push_back(new_used_block);
// Update Old Free Block
old_free_block->offset += size;
old_free_block->size -= size;
if (old_free_block->size == 0) {
free_blocks.erase(it);
delete old_free_block;
}
// Upload
buffer->upload(new_used_block->offset, size, data);
// Assign Block To Chunk
chunk_to_block[chunk] = new_used_block;
// Update Size
used_size += size;
// Check Fragmentation
check_fragmentation();
}
// Recreate/Defragment
void Storage::recreate(const ssize_t extra_size) {
// Create New Buffer
const ssize_t new_size = (used_size + extra_size) * 2;
Buffer *new_buffer = new Buffer(new_size);
// Copy Used Blocks
intptr_t offset = 0;
for (Block *block : used_blocks) {
new_buffer->upload(offset, block->size, buffer->client_side_data + block->offset);
block->offset = offset;
offset += block->size;
}
// Update Free Blocks
for (const Block *block : free_blocks) {
delete block;
}
free_blocks.clear();
Block *block = new Block;
block->offset = offset;
block->size = new_size - offset;
free_blocks.push_back(block);
// Use New Buffer
delete buffer;
buffer = new_buffer;
total_size = new_size;
}

View File

@ -0,0 +1,41 @@
#pragma once
#include <vector>
#include "buffer.h"
// Block Of Data
struct Block {
intptr_t offset;
ssize_t size;
// Prevent Copying
Block() {
offset = 0;
size = 0;
}
Block(const Block &) = delete;
Block &operator=(const Block &) = delete;
};
// Storage
struct Storage {
explicit Storage(int chunks);
~Storage();
// Buffer
Buffer *buffer;
// Chunks
std::vector<Block *> chunk_to_block;
void upload(int chunk, ssize_t size, const void *data);
// Management
ssize_t total_size;
ssize_t used_size;
std::vector<Block *> free_blocks;
std::vector<Block *> used_blocks;
void free_block(Block *block);
void recreate(ssize_t extra_size = 0);
void check_fragmentation();
ssize_t get_end() const;
};

View File

@ -91,11 +91,11 @@ void screenshot_take(Gui *gui) {
if (save_png(file.c_str(), pixels, line_size, width, height)) { if (save_png(file.c_str(), pixels, line_size, width, height)) {
WARN("Screenshot Failed: %s", file.c_str()); WARN("Screenshot Failed: %s", file.c_str());
} else { } else {
std::string msg = "Screenshot Saved: "; INFO("Screenshot Saved: %s", file.c_str());
INFO("%s%s", msg.c_str(), file.c_str());
if (gui) { if (gui) {
msg += filename; std::string chat_msg = "Saved screenshot as ";
gui->addMessage(&msg); chat_msg += filename;
gui->addMessage(&chat_msg);
} }
} }

View File

@ -79,9 +79,9 @@ set(SRC
src/level/ChunkStorage.def src/level/ChunkStorage.def
src/level/LightLayer.def src/level/LightLayer.def
src/level/Level.def src/level/Level.def
src/level/LevelRenderer.def src/level/renderer/LevelRenderer.def
src/level/LevelStorageSource.def src/level/LevelStorageSource.def
src/level/ParticleEngine.def src/level/renderer/ParticleEngine.def
src/level/RandomLevelSource.def src/level/RandomLevelSource.def
src/level/LevelData.def src/level/LevelData.def
src/level/LevelSettings.def src/level/LevelSettings.def
@ -90,7 +90,7 @@ set(SRC
src/level/MultiPlayerLevel.def src/level/MultiPlayerLevel.def
src/level/LevelSummary.def src/level/LevelSummary.def
src/level/DistanceChunkSorter.def src/level/DistanceChunkSorter.def
src/level/Chunk.def src/level/renderer/Chunk.def
src/item/ItemRenderer.def src/item/ItemRenderer.def
src/item/ItemInHandRenderer.def src/item/ItemInHandRenderer.def
src/item/AuxDataTileItem.def src/item/AuxDataTileItem.def
@ -161,6 +161,8 @@ set(SRC
src/misc/Vec3.def src/misc/Vec3.def
src/misc/HitResult.def src/misc/HitResult.def
src/misc/PerfRenderer.def src/misc/PerfRenderer.def
src/level/renderer/RenderList.def
src/level/renderer/RenderChunk.def
src/textures/Texture.def src/textures/Texture.def
src/textures/Textures.def src/textures/Textures.def
src/textures/DynamicTexture.def src/textures/DynamicTexture.def

View File

@ -1,5 +0,0 @@
property int x = 0x4;
property int y = 0x8;
property int z = 0xc;
method float distanceToSqr(Entity *entity) = 0x47ba0;

View File

@ -0,0 +1,9 @@
property int x = 0x4;
property int y = 0x8;
property int z = 0xc;
property bool field_1c[3] = 0x1c;
property bool visible = 0x4c;
method float distanceToSqr(Entity *entity) = 0x47ba0;
method int getList(int a) = 0x47e48;
method RenderChunk *getRenderChunk(int a) = 0x47e74;

View File

@ -4,5 +4,8 @@ method void renderDebug(AABB *aabb, float delta) = 0x4d310;
method void generateSky() = 0x4d0d4; method void generateSky() = 0x4d0d4;
method void renderHitSelect(Player *player, HitResult *hit_result, int i, void *vp, float f) = 0x4e318; method void renderHitSelect(Player *player, HitResult *hit_result, int i, void *vp, float f) = 0x4e318;
method void renderHitOutline(Player *player, HitResult *hit_result, int i, void *vp, float f) = 0x4dc14; method void renderHitOutline(Player *player, HitResult *hit_result, int i, void *vp, float f) = 0x4dc14;
method int renderChunks(int start, int end, int a, float b) = 0x4f35c;
property Minecraft *minecraft = 0x4; property Minecraft *minecraft = 0xb4;
property RenderList render_list = 0x34;
property Chunk **chunks = 0x98;

View File

@ -0,0 +1,7 @@
size 0x18;
property uint buffer = 0x0;
property int vertices = 0x4;
property float x = 0xc;
property float y = 0x10;
property float z = 0x14;

View File

@ -0,0 +1,9 @@
method void render() = 0x52800;
method void renderChunks() = 0x52708;
method void clear() = 0x5288c;
method void init(float x, float y, float z) = 0x526bc;
method void addR(RenderChunk *chunk) = 0x526dc;
property int size = 0x1c;
property RenderChunk *chunks = 0x10;
property int field_14 = 0x14;