From ad50bf21d41eaaa815fd9ccc5af5466396e9bf22 Mon Sep 17 00:00:00 2001 From: JuiceyDev Date: Thu, 5 Mar 2026 21:31:54 +0100 Subject: [PATCH] shit renderer --- 4J.Render/4J_Render.cpp | 120 +++++++++++++++--- 4J.Render/4J_Render.h | 1 + .../Platform/Linux/Linux_Minecraft.cpp | 66 ++++++++++ Minecraft.Client/Rendering/GameRenderer.cpp | 36 +++--- Minecraft.Client/Rendering/LevelRenderer.cpp | 16 ++- Minecraft.World/Build/st3iWWKx | 1 + 6 files changed, 199 insertions(+), 41 deletions(-) create mode 100644 Minecraft.World/Build/st3iWWKx diff --git a/4J.Render/4J_Render.cpp b/4J.Render/4J_Render.cpp index 4516ea575..7d52b9563 100644 --- a/4J.Render/4J_Render.cpp +++ b/4J.Render/4J_Render.cpp @@ -8,6 +8,7 @@ #include #include #include +#include C4JRender RenderManager; @@ -16,6 +17,24 @@ static int s_textureLevels = 1; static int s_windowWidth = 1920; static int s_windowHeight = 1080; +// Thread-local storage for per-thread shared GL contexts. +// The main thread uses s_window directly; worker threads get invisible +// windows that share objects (textures, display lists) with s_window. +static pthread_key_t s_glCtxKey; +static pthread_once_t s_glCtxKeyOnce = PTHREAD_ONCE_INIT; +static void makeGLCtxKey() { pthread_key_create(&s_glCtxKey, nullptr); } + +// Pre-created pool of shared contexts for worker threads +static const int MAX_SHARED_CONTEXTS = 8; +static GLFWwindow *s_sharedContexts[MAX_SHARED_CONTEXTS] = {}; +static int s_sharedContextCount = 0; +static int s_nextSharedContext = 0; +static pthread_mutex_t s_sharedCtxMutex = PTHREAD_MUTEX_INITIALIZER; + +// Track which thread is the main (rendering) thread +static pthread_t s_mainThread; +static bool s_mainThreadSet = false; + void C4JRender::Initialise() { if (!glfwInit()) { @@ -60,11 +79,67 @@ void C4JRender::Initialise() (const char*)::glGetString(GL_VERSION), (const char*)::glGetString(GL_RENDERER)); fflush(stdout); + + // Tag this as the main rendering thread + pthread_once(&s_glCtxKeyOnce, makeGLCtxKey); + s_mainThread = pthread_self(); + s_mainThreadSet = true; + pthread_setspecific(s_glCtxKey, s_window); + + // Pre-create shared GL contexts for worker threads (chunk builders etc.) + // Must be done on the main thread because GLFW requires it. + for (int i = 0; i < MAX_SHARED_CONTEXTS; i++) { + glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); + s_sharedContexts[i] = glfwCreateWindow(1, 1, "", nullptr, s_window); + glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); + if (s_sharedContexts[i]) { + s_sharedContextCount++; + } else { + fprintf(stderr, "[4J_Render] WARN: only created %d/%d shared contexts\n", i, MAX_SHARED_CONTEXTS); + break; + } + } + // Ensure main thread still has the context + glfwMakeContextCurrent(s_window); + fprintf(stderr, "[4J_Render] Created %d shared GL contexts for worker threads\n", s_sharedContextCount); + fflush(stderr); } void C4JRender::InitialiseContext() { - if (s_window) glfwMakeContextCurrent(s_window); + if (!s_window) return; + pthread_once(&s_glCtxKeyOnce, makeGLCtxKey); + + // Main thread reclaiming context (e.g. after startup thread finishes) + if (s_mainThreadSet && pthread_equal(pthread_self(), s_mainThread)) { + glfwMakeContextCurrent(s_window); + pthread_setspecific(s_glCtxKey, s_window); + return; + } + + // Worker thread: check if it already has a shared context + GLFWwindow *ctx = (GLFWwindow*)pthread_getspecific(s_glCtxKey); + if (ctx) { + glfwMakeContextCurrent(ctx); + return; + } + + // Grab a pre-created shared context from the pool + pthread_mutex_lock(&s_sharedCtxMutex); + GLFWwindow *shared = nullptr; + if (s_nextSharedContext < s_sharedContextCount) { + shared = s_sharedContexts[s_nextSharedContext++]; + } + pthread_mutex_unlock(&s_sharedCtxMutex); + + if (!shared) { + fprintf(stderr, "[4J_Render] ERROR: no shared GL contexts left for worker thread!\n"); + return; + } + glfwMakeContextCurrent(shared); + pthread_setspecific(s_glCtxKey, shared); + fprintf(stderr, "[4J_Render] Assigned shared GL context %p to worker thread\n", (void*)shared); + fflush(stderr); } void C4JRender::StartFrame() @@ -103,6 +178,7 @@ void C4JRender::SetClearColour(const float colourRGBA[4]) bool C4JRender::IsWidescreen() { return true; } bool C4JRender::IsHiDef() { return true; } +void C4JRender::GetFramebufferSize(int &width, int &height) { width = s_windowWidth; height = s_windowHeight; } void C4JRender::CaptureThumbnail(ImageFileBuffer *) {} void C4JRender::CaptureScreen(ImageFileBuffer *, XSOCIAL_PREVIEWIMAGE *) {} void C4JRender::BeginConditionalSurvey(int) {} @@ -174,23 +250,19 @@ void C4JRender::DrawVertices(ePrimitiveType PrimitiveType, int count, { if (count <= 0 || !dataIn) return; - static int _dbgDVCount = 0; - _dbgDVCount++; - if (_dbgDVCount <= 10 || (_dbgDVCount % 5000 == 0)) { - GLenum err = ::glGetError(); - fprintf(stderr, "[RENDER] DrawVertices call=%d count=%d prim=%d vType=%d displayList=%d glErr=%d\n", - _dbgDVCount, count, (int)PrimitiveType, (int)vType, isCompilingDisplayList(), err); - fflush(stderr); - } - GLenum mode = mapPrimType((int)PrimitiveType); unsigned char *data = (unsigned char *)dataIn; // Vertex layout: 3 floats pos, 2 floats tex, 4 bytes color, 4 bytes normal, 4 bytes padding = 32 bytes const int stride = 32; + // Color byte-order fix for little-endian (x86/x64): + // Console code (Xbox 360 / PS3, big-endian) stores color as int col = (r<<24)|(g<<16)|(b<<8)|a + // Big-endian memory: [r, g, b, a] — correct for glColor4ub(col[0], col[1], col[2], col[3]) + // Little-endian memory: [a, b, g, r] — bytes are reversed! + // Fix: read bytes in reverse order col[3]=r, col[2]=g, col[1]=b, col[0]=a + if (isCompilingDisplayList()) { - // run. ::glBegin(mode); for (int i = 0; i < count; i++) { unsigned char *v = data + i * stride; @@ -200,13 +272,20 @@ void C4JRender::DrawVertices(ePrimitiveType PrimitiveType, int count, signed char *nrm = (signed char *)(v + 24); ::glNormal3f(nrm[0] / 127.0f, nrm[1] / 127.0f, nrm[2] / 127.0f); - ::glColor4ub(col[0], col[1], col[2], col[3]); + ::glColor4ub(col[3], col[2], col[1], col[0]); // LE fix: r,g,b,a from reversed bytes ::glTexCoord2f(tex[0], tex[1]); ::glVertex3f(pos[0], pos[1], pos[2]); } ::glEnd(); } else { - // waiter ! fast vertex pls ! + // For vertex array path, swap color bytes in-place to RGBA order + for (int i = 0; i < count; i++) { + unsigned char *col = data + i * stride + 20; + unsigned char tmp; + tmp = col[0]; col[0] = col[3]; col[3] = tmp; // swap a<->r + tmp = col[1]; col[1] = col[2]; col[2] = tmp; // swap b<->g + } + ::glEnableClientState(GL_VERTEX_ARRAY); ::glEnableClientState(GL_TEXTURE_COORD_ARRAY); ::glEnableClientState(GL_COLOR_ARRAY); @@ -218,6 +297,14 @@ void C4JRender::DrawVertices(ePrimitiveType PrimitiveType, int count, ::glNormalPointer(GL_BYTE, stride, data + 24); ::glDrawArrays(mode, 0, count); + + // Swap back to preserve original data + for (int i = 0; i < count; i++) { + unsigned char *col = data + i * stride + 20; + unsigned char tmp; + tmp = col[0]; col[0] = col[3]; col[3] = tmp; + tmp = col[1]; col[1] = col[2]; col[2] = tmp; + } } } @@ -254,13 +341,6 @@ void C4JRender::CBuffEnd() bool C4JRender::CBuffCall(int index, bool /*full*/) { if (index <= 0) return false; - static int _dbgCBCount = 0; - _dbgCBCount++; - if (_dbgCBCount <= 5 || (_dbgCBCount % 5000 == 0)) { - fprintf(stderr, "[RENDER] CBuffCall call=%d index=%d isList=%d\n", - _dbgCBCount, index, ::glIsList(index)); - fflush(stderr); - } if (::glIsList(index)) { ::glCallList(index); return true; } return false; } diff --git a/4J.Render/4J_Render.h b/4J.Render/4J_Render.h index db91a9c9c..657edaf99 100644 --- a/4J.Render/4J_Render.h +++ b/4J.Render/4J_Render.h @@ -70,6 +70,7 @@ public: void SetClearColour(const float colourRGBA[4]); bool IsWidescreen(); bool IsHiDef(); + void GetFramebufferSize(int &width, int &height); void CaptureThumbnail(ImageFileBuffer *pngOut); void CaptureScreen(ImageFileBuffer *jpgOut, XSOCIAL_PREVIEWIMAGE *previewOut); void BeginConditionalSurvey(int identifier); diff --git a/Minecraft.Client/Platform/Linux/Linux_Minecraft.cpp b/Minecraft.Client/Platform/Linux/Linux_Minecraft.cpp index 39d2c705f..e9f704374 100644 --- a/Minecraft.Client/Platform/Linux/Linux_Minecraft.cpp +++ b/Minecraft.Client/Platform/Linux/Linux_Minecraft.cpp @@ -10,6 +10,7 @@ #include #include #include +#include static void sigsegv_handler(int sig) { const char msg[] = "\n=== SIGNAL CAUGHT: "; write(STDERR_FILENO, msg, sizeof(msg)-1); @@ -868,6 +869,71 @@ pDevice->SetSamplerState(0,SamplerStateModes[i],SamplerStateA[i]); RenderManager.Set_matrixDirty(); #endif + +// DEBUG: Dump framebuffer to file once after game has been running a while +{ + static int _fbDumpFrame = 0; + _fbDumpFrame++; + if (_fbDumpFrame == 2000 || _fbDumpFrame == 4000) { + // Test: is GL context current? Force red clear before read. + void* curCtx = glfwGetCurrentContext(); + fprintf(stderr, "[FBDUMP] frame=%d glfwGetCurrentContext()=%p\n", _fbDumpFrame, curCtx); + fflush(stderr); + // Read the ACTUAL rendered framebuffer first + int fbW, fbH; + RenderManager.GetFramebufferSize(fbW, fbH); + int sz = fbW * fbH * 3; + unsigned char *pixels = (unsigned char*)malloc(sz); + if (pixels) { + ::glReadPixels(0, 0, fbW, fbH, GL_RGB, GL_UNSIGNED_BYTE, pixels); + GLenum err = glGetError(); + fprintf(stderr, "[FBDUMP] glReadPixels err=0x%x size=%dx%d\n", err, fbW, fbH); + // Check bound framebuffer + GLint boundFBO = -1; + #ifndef GL_FRAMEBUFFER_BINDING + #define GL_FRAMEBUFFER_BINDING 0x8CA6 + #endif + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &boundFBO); + GLint drawBuf = 0; + glGetIntegerv(GL_DRAW_BUFFER, &drawBuf); + GLint readBuf = 0; + glGetIntegerv(GL_READ_BUFFER, &readBuf); + fprintf(stderr, "[FBDUMP] FBO=%d drawBuf=0x%x readBuf=0x%x\n", boundFBO, drawBuf, readBuf); + fflush(stderr); + char fname[128]; + snprintf(fname, sizeof(fname), "/tmp/fb_dump_%d.ppm", _fbDumpFrame); + FILE *fp = fopen(fname, "wb"); + if (fp) { + fprintf(fp, "P6\n%d %d\n255\n", fbW, fbH); + for (int y = fbH - 1; y >= 0; y--) { + fwrite(pixels + y * fbW * 3, 1, fbW * 3, fp); + } + fclose(fp); + fprintf(stderr, "[RENDER] Dumped framebuffer %dx%d to %s\n", fbW, fbH, fname); + fflush(stderr); + } + // Now force a red clear and dump again to verify GL context works + ::glClearColor(1.0f, 0.0f, 0.0f, 1.0f); + ::glClear(GL_COLOR_BUFFER_BIT); + ::glFinish(); + ::glReadPixels(0, 0, fbW, fbH, GL_RGB, GL_UNSIGNED_BYTE, pixels); + char fname2[128]; + snprintf(fname2, sizeof(fname2), "/tmp/fb_dump_%d_RED.ppm", _fbDumpFrame); + FILE *fp2 = fopen(fname2, "wb"); + if (fp2) { + fprintf(fp2, "P6\n%d %d\n255\n", fbW, fbH); + for (int y = fbH - 1; y >= 0; y--) { + fwrite(pixels + y * fbW * 3, 1, fbW * 3, fp2); + } + fclose(fp2); + fprintf(stderr, "[RENDER] Dumped RED test %dx%d to %s\n", fbW, fbH, fname2); + fflush(stderr); + } + free(pixels); + } + } +} + // Present the frame. RenderManager.Present(); diff --git a/Minecraft.Client/Rendering/GameRenderer.cpp b/Minecraft.Client/Rendering/GameRenderer.cpp index b99d11296..d5cdd361e 100644 --- a/Minecraft.Client/Rendering/GameRenderer.cpp +++ b/Minecraft.Client/Rendering/GameRenderer.cpp @@ -995,13 +995,6 @@ int GameRenderer::getLightTexture(int iPad, Level *level) void GameRenderer::render(float a, bool bFirst) { - static int _dbgRenderCount = 0; - _dbgRenderCount++; - if (_dbgRenderCount <= 5 || (_dbgRenderCount % 300 == 0)) { - fprintf(stderr, "[RENDER] GameRenderer::render frame=%d level=%p screen=%p width=%d height=%d\n", - _dbgRenderCount, (void*)mc->level, (void*)mc->screen, mc->width, mc->height); - fflush(stderr); - } if( _updateLightTexture && bFirst) updateLightTexture(a); if (Display::isActive()) { @@ -1087,6 +1080,7 @@ void GameRenderer::render(float a, bool bFirst) else { glViewport(0, 0, mc->width, mc->height); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); @@ -1261,17 +1255,6 @@ void GameRenderer::DisableUpdateThread() void GameRenderer::renderLevel(float a, __int64 until) { - static int _dbgRLCount = 0; - _dbgRLCount++; - if (_dbgRLCount <= 3 || (_dbgRLCount % 300 == 0)) { - fprintf(stderr, "[RENDER] renderLevel frame=%d player=%p levelRenderer=%p\n", - _dbgRLCount, (void*)mc->player.get(), (void*)mc->levelRenderer); - if (mc->player) { - fprintf(stderr, "[RENDER] playerPos=(%.1f, %.1f, %.1f)\n", - mc->player->x, mc->player->y, mc->player->z); - } - fflush(stderr); - } // if (updateLightTexture) updateLightTexture(); // 4J - TODO - Java 1.0.1 has this line enabled, should check why - don't want to put it in now in case it breaks split-screen glEnable(GL_CULL_FACE); @@ -1311,6 +1294,23 @@ void GameRenderer::renderLevel(float a, __int64 until) setupCamera(a, i); Camera::prepare(mc->player, mc->player->ThirdPersonView() == 2); + // DEBUG: Log camera-relevant state + static int _dbgCam = 0; + _dbgCam++; + if (_dbgCam <= 5 || (_dbgCam % 300 == 0)) { + float mv[16]; ::glGetFloatv(GL_MODELVIEW_MATRIX, mv); + float pj[16]; ::glGetFloatv(GL_PROJECTION_MATRIX, pj); + float cc[4]; ::glGetFloatv(GL_COLOR_CLEAR_VALUE, cc); + int vp[4]; ::glGetIntegerv(GL_VIEWPORT, vp); + fprintf(stderr, "[RENDER] CAM frame=%d viewport=(%d,%d,%d,%d) clearColor=(%.2f,%.2f,%.2f) renderDist=%.0f\n", + _dbgCam, vp[0], vp[1], vp[2], vp[3], cc[0], cc[1], cc[2], renderDistance); + fprintf(stderr, "[RENDER] MV[12..14]=(%f,%f,%f) PJ[0,5,10]=(%f,%f,%f)\n", + mv[12], mv[13], mv[14], pj[0], pj[5], pj[10]); + fprintf(stderr, "[RENDER] cameraEntity pos=(%.2f,%.2f,%.2f) heightOff=%.2f\n", + mc->cameraTargetPlayer->x, mc->cameraTargetPlayer->y, mc->cameraTargetPlayer->z, + mc->cameraTargetPlayer->heightOffset); + fflush(stderr); + } Frustum::getFrustum(); if (mc->options->viewDistance < 2) diff --git a/Minecraft.Client/Rendering/LevelRenderer.cpp b/Minecraft.Client/Rendering/LevelRenderer.cpp index 9906a3c85..71b2f79e0 100644 --- a/Minecraft.Client/Rendering/LevelRenderer.cpp +++ b/Minecraft.Client/Rendering/LevelRenderer.cpp @@ -778,24 +778,34 @@ int LevelRenderer::renderChunks(int from, int to, int layer, double alpha) bool first = true; int count = 0; + int dbgNotVisible = 0, dbgNoIdx = 0, dbgEmpty = 0, dbgCalled = 0, dbgCallOk = 0; ClipChunk *pClipChunk = chunks[playerIndex].data; unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer; for( int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++ ) { - if( !pClipChunk->visible ) continue; // This will be set if the chunk isn't visible, or isn't compiled, or has both empty flags set - if( pClipChunk->globalIdx == -1 ) continue; // Not sure if we should ever encounter this... TODO check - if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) continue; // Check that this particular layer isn't empty + if( !pClipChunk->visible ) { dbgNotVisible++; continue; } + if( pClipChunk->globalIdx == -1 ) { dbgNoIdx++; continue; } + if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) { dbgEmpty++; continue; } // List can be calculated directly from the chunk's global idex int list = pClipChunk->globalIdx * 2 + layer; list += chunkLists; + dbgCalled++; if(RenderManager.CBuffCall(list, first)) { first = false; + dbgCallOk++; } count++; } + static int _dbgRC = 0; + _dbgRC++; + if (_dbgRC <= 5 || (_dbgRC % 600 == 0)) { + fprintf(stderr, "[RENDER] renderChunks frame=%d layer=%d total=%d notVis=%d noIdx=%d empty=%d called=%d callOk=%d xOff=%.1f yOff=%.1f zOff=%.1f chunkLists=%d\n", + _dbgRC, layer, chunks[playerIndex].length, dbgNotVisible, dbgNoIdx, dbgEmpty, dbgCalled, dbgCallOk, xOff, yOff, zOff, chunkLists); + fflush(stderr); + } #ifdef __PSVITA__ // AP - alpha cut out is expensive on vita. Now we render all the alpha cut outs diff --git a/Minecraft.World/Build/st3iWWKx b/Minecraft.World/Build/st3iWWKx new file mode 100644 index 000000000..8b277f0dd --- /dev/null +++ b/Minecraft.World/Build/st3iWWKx @@ -0,0 +1 @@ +!