package localmods.mtkviewpoint; import java.lang.reflect.Field; import java.nio.FloatBuffer; import org.lwjgl.BufferUtils; import static org.lwjgl.opengl.GL33.*; import viewpoint.render.FrameContext; import viewpoint.render.SceneData; import viewpoint.render.WorldRenderer; /** Native Viewpoint HDR pass, before post processing, with normal scene depth testing. */ public final class TracerRenderer { private static final FloatBuffer vertices=BufferUtils.createFloatBuffer(ProjectileBridge.LIMIT*6*TracerMesh.STRIDE); private static Field frameField,irisField; private static int program,vao,vbo; private static volatile boolean failed; private TracerRenderer() {} public static void fail(Throwable error) { if (!failed) System.out.println("[MTKViewpointTurrets] 3D tracers disabled after render error: "+error); failed=true; } public static void draw(SceneData scene) { if (failed || scene==null) return; var shots=ProjectileBridge.segments(scene); if (shots.isEmpty()) return; try { if (frameField==null) { frameField=WorldRenderer.class.getDeclaredField("frame"); frameField.setAccessible(true); irisField=WorldRenderer.class.getDeclaredField("irisDrawn"); irisField.setAccessible(true); } if (irisField.getBoolean(null)) return; // Minecraft/Iris uses separate private targets. FrameContext frame=(FrameContext)frameField.get(null); if (frame==null || frame.scene!=scene) return; vertices.clear(); int count=0; for (var shot:shots) count+=TracerMesh.append(vertices,shot,frame.viewProjection,frame.width,frame.height); vertices.flip(); if (count==0) return; drawBuffer(vertices,count,frame.width,frame.height,true); } catch (Throwable error) { fail(error); } } // Kept separate so the same production GPU path can be checked in a tiny // hidden OpenGL test context without launching Project Zomboid. static void drawBuffer(FloatBuffer data,int count,int width,int height,boolean bindViewpointTarget) { int oldProgram=glGetInteger(GL_CURRENT_PROGRAM),oldVAO=glGetInteger(GL_VERTEX_ARRAY_BINDING); int oldVBO=glGetInteger(GL_ARRAY_BUFFER_BINDING),oldDraw=glGetInteger(GL_DRAW_FRAMEBUFFER_BINDING); int oldRead=glGetInteger(GL_READ_FRAMEBUFFER_BINDING),depthFunc=glGetInteger(GL_DEPTH_FUNC); int[] viewport=new int[4]; glGetIntegerv(GL_VIEWPORT,viewport); boolean depth=glIsEnabled(GL_DEPTH_TEST),blend=glIsEnabled(GL_BLEND),cull=glIsEnabled(GL_CULL_FACE); boolean scissor=glIsEnabled(GL_SCISSOR_TEST),depthMask=glGetBoolean(GL_DEPTH_WRITEMASK); int sr=glGetInteger(GL_BLEND_SRC_RGB),dr=glGetInteger(GL_BLEND_DST_RGB); int sa=glGetInteger(GL_BLEND_SRC_ALPHA),da=glGetInteger(GL_BLEND_DST_ALPHA); int er=glGetInteger(GL_BLEND_EQUATION_RGB),ea=glGetInteger(GL_BLEND_EQUATION_ALPHA); try { if (program==0) init(); if (bindViewpointTarget) WorldRenderer.bindTarget(); else glViewport(0,0,width,height); glUseProgram(program); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER,vbo); glBufferData(GL_ARRAY_BUFFER,data,GL_STREAM_DRAW); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glDepthMask(false); glDisable(GL_CULL_FACE); glDisable(GL_SCISSOR_TEST); glEnable(GL_BLEND); glBlendEquationSeparate(GL_FUNC_ADD,GL_FUNC_ADD); glBlendFuncSeparate(GL_ONE,GL_ONE_MINUS_SRC_ALPHA,GL_ZERO,GL_ONE); glDrawArrays(GL_TRIANGLES,0,count); } finally { glUseProgram(oldProgram); glBindVertexArray(oldVAO); glBindBuffer(GL_ARRAY_BUFFER,oldVBO); glBlendEquationSeparate(er,ea); glBlendFuncSeparate(sr,dr,sa,da); toggle(GL_BLEND,blend); toggle(GL_DEPTH_TEST,depth); toggle(GL_CULL_FACE,cull); toggle(GL_SCISSOR_TEST,scissor); glDepthFunc(depthFunc); glDepthMask(depthMask); glBindFramebuffer(GL_DRAW_FRAMEBUFFER,oldDraw); glBindFramebuffer(GL_READ_FRAMEBUFFER,oldRead); glViewport(viewport[0],viewport[1],viewport[2],viewport[3]); } } private static void toggle(int cap,boolean on) { if(on) glEnable(cap); else glDisable(cap); } private static void init() { int vs=shader(GL_VERTEX_SHADER,""" #version 330 core layout(location=0) in vec4 clipPosition; layout(location=1) in vec2 uv; layout(location=2) in vec4 color; out vec2 texCoord; out vec4 tint; void main(){gl_Position=clipPosition; texCoord=uv; tint=color;} """); int fs=shader(GL_FRAGMENT_SHADER,""" #version 330 core in vec2 texCoord; in vec4 tint; out vec4 fragColor; void main(){ float edge=1.0-smoothstep(0.25,1.0,abs(texCoord.y)); float a=tint.a*edge*smoothstep(0.0,0.25,texCoord.x); fragColor=vec4(tint.rgb*5.0*a,a); } """); int linked=glCreateProgram(); try { glAttachShader(linked,vs); glAttachShader(linked,fs); glLinkProgram(linked); if(glGetProgrami(linked,GL_LINK_STATUS)==0) throw new IllegalStateException(glGetProgramInfoLog(linked)); } catch(RuntimeException error) {glDeleteProgram(linked); throw error;} finally {glDeleteShader(vs); glDeleteShader(fs);} vao=glGenVertexArrays(); vbo=glGenBuffers(); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER,vbo); int stride=TracerMesh.STRIDE*Float.BYTES; glEnableVertexAttribArray(0); glVertexAttribPointer(0,4,GL_FLOAT,false,stride,0L); glEnableVertexAttribArray(1); glVertexAttribPointer(1,2,GL_FLOAT,false,stride,16L); glEnableVertexAttribArray(2); glVertexAttribPointer(2,4,GL_FLOAT,false,stride,24L); program=linked; } private static int shader(int type,String source) { int id=glCreateShader(type); glShaderSource(id,source); glCompileShader(id); if(glGetShaderi(id,GL_COMPILE_STATUS)==0) { String error=glGetShaderInfoLog(id); glDeleteShader(id); throw new IllegalStateException(error); } return id; } }