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
TRUE Add Welcome Screen
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;
}
}
// 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_force_egl();
void media_set_raw_mouse_motion_enabled(int enabled);
int media_has_extension(const char *name);
#ifdef __cplusplus
}

View File

@ -138,6 +138,23 @@ CALL(15, glDrawArrays, void, (GLenum mode, GLint first, GLsizei count))
#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))
#ifdef MEDIA_LAYER_TRAMPOLINE_GUEST
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))
#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
glBindBuffer(GL_ARRAY_BUFFER, args.next<GLuint>());
GLenum target = args.next<GLenum>();
uint32_t size;
const unsigned char *data = args.next_arr<unsigned char>(&size);
int32_t size = args.next<int32_t>();
const unsigned char *data = args.next_arr<unsigned char>();
GLenum usage = args.next<GLenum>();
func(target, GLsizeiptr(size), data, usage);
func(target, size, data, usage);
return 0;
#endif
}
@ -738,3 +755,17 @@ CALL(67, glGenBuffers, void, (GLsizei n, GLuint *buffers))
return 0;
#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) {
*length = size / sizeof(T);
}
if (size == 0) {
return nullptr;
}
const T *ret = (const T *) raw_args;
raw_args += size;
return ret;

View File

@ -182,3 +182,11 @@ CALL(68, media_ensure_loaded, void, ())
return 0;
#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
# f3
src/f3/f3.cpp
# multidraw
src/multidraw/glue.cpp
src/multidraw/buffer.cpp
src/multidraw/storage.cpp
)
# Install Splashes
install(

View File

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

View File

@ -43,5 +43,6 @@ __attribute__((constructor)) static void init() {
if (!reborn_is_headless()) {
init_screenshot();
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
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
time_t tt = time;
const time_t tt = time;
tm t = {};
gmtime_r(&tt, &t);
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)) {
WARN("Screenshot Failed: %s", file.c_str());
} else {
std::string msg = "Screenshot Saved: ";
INFO("%s%s", msg.c_str(), file.c_str());
INFO("Screenshot Saved: %s", file.c_str());
if (gui) {
msg += filename;
gui->addMessage(&msg);
std::string chat_msg = "Saved screenshot as ";
chat_msg += filename;
gui->addMessage(&chat_msg);
}
}

View File

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