Files
ViewpointTurrets/source/localmods/mtkviewpoint/TracerRenderer.java
T
2026-10-27 17:16:21 -04:00

122 lines
6.2 KiB
Java

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;
}
}