122 lines
6.2 KiB
Java
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;
|
|
}
|
|
}
|