Java tutorial
/* * Copyright LWJGL. All rights reserved. * License terms: http://lwjgl.org/license.php */ package org.lwjgl.demo.opengl.raytracing; import org.lwjgl.BufferUtils; import org.lwjgl.demo.opengl.util.DemoUtils; import org.lwjgl.glfw.*; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GLUtil; import org.lwjgl.system.Callback; import org.joml.Matrix4f; import org.joml.Vector3f; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import static java.lang.Math.*; import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL15.*; import static org.lwjgl.opengl.GL20.*; import static org.lwjgl.opengl.GL21.*; import static org.lwjgl.opengl.GL30.*; import static org.lwjgl.opengl.GL33.*; import static org.lwjgl.opengl.GL42.*; import static org.lwjgl.opengl.GL43.*; import static org.lwjgl.system.MathUtil.*; import static org.lwjgl.system.MemoryUtil.*; /** * Raytracing demo. * <p> * The same as {@link Demo} but using a Shader Storage Buffer Object (SSBO) to * specify the scene dynamically from the host program instead of hardcoded in * the shader. * <p> * Also, the compute shader does not directly write into an image but instead * into a SSBO buffer that is afterwards uploaded via Pixel Buffer Object into a * texture, which is then eventually displayed on the screen. This was just some * left-over from a long debugging session, but I think it can stay this way to * showcase writing into an SSBO. * * @author Kai Burjack */ public class DemoSsbo { /** * The boxes for ray tracing. */ private static Vector3f[] boxes = Scene.boxes; private long window; private int width = 1024; private int height = 768; private boolean resetFramebuffer = true; private int tex; private int imageBuffer; private int vao; private int computeProgram; private int quadProgram; private int ssbo; private int sampler; private int eyeUniform; private int ray00Uniform; private int ray10Uniform; private int ray01Uniform; private int ray11Uniform; private int sizeUniform; private int timeUniform; private int boxesSsboBinding; private int outputImageBinding; private int workGroupSizeX; private int workGroupSizeY; private float mouseDownX; private float mouseX; private boolean mouseDown; private float currRotationAboutY = 0.0f; private float rotationAboutY = 0.8f; private long firstTime; private Matrix4f projMatrix = new Matrix4f(); private Matrix4f viewMatrix = new Matrix4f(); private Matrix4f invViewProjMatrix = new Matrix4f(); private Vector3f tmpVector = new Vector3f(); private Vector3f cameraPosition = new Vector3f(); private Vector3f cameraLookAt = new Vector3f(0.0f, 0.5f, 0.0f); private Vector3f cameraUp = new Vector3f(0.0f, 1.0f, 0.0f); GLFWErrorCallback errCallback; GLFWKeyCallback keyCallback; GLFWFramebufferSizeCallback fbCallback; GLFWCursorPosCallback cpCallback; GLFWMouseButtonCallback mbCallback; Callback debugProc; static { /* * Tell LWJGL that we only want 4.3 functionality. */ System.setProperty("org.lwjgl.opengl.maxVersion", "4.3"); } private void init() throws IOException { glfwSetErrorCallback(errCallback = new GLFWErrorCallback() { private GLFWErrorCallback delegate = GLFWErrorCallback.createPrint(System.err); @Override public void invoke(int error, long description) { if (error == GLFW_VERSION_UNAVAILABLE) System.err.println( "This demo requires OpenGL 4.3 or higher. The Demo33 version works on OpenGL 3.3 or higher."); delegate.invoke(error, description); } @Override public void free() { delegate.free(); } }); if (!glfwInit()) throw new IllegalStateException("Unable to initialize GLFW"); glfwDefaultWindowHints(); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); window = glfwCreateWindow(width, height, "Raytracing Demo (compute shader)", NULL, NULL); if (window == NULL) { throw new AssertionError("Failed to create the GLFW window"); } glfwSetKeyCallback(window, keyCallback = new GLFWKeyCallback() { @Override public void invoke(long window, int key, int scancode, int action, int mods) { if (action != GLFW_RELEASE) return; if (key == GLFW_KEY_ESCAPE) glfwSetWindowShouldClose(window, true); } }); glfwSetFramebufferSizeCallback(window, fbCallback = new GLFWFramebufferSizeCallback() { @Override public void invoke(long window, int width, int height) { if (width > 0 && height > 0 && (DemoSsbo.this.width != width || DemoSsbo.this.height != height)) { DemoSsbo.this.width = width; DemoSsbo.this.height = height; DemoSsbo.this.resetFramebuffer = true; } } }); glfwSetCursorPosCallback(window, cpCallback = new GLFWCursorPosCallback() { @Override public void invoke(long window, double x, double y) { DemoSsbo.this.mouseX = (float) x; } }); glfwSetMouseButtonCallback(window, mbCallback = new GLFWMouseButtonCallback() { @Override public void invoke(long window, int button, int action, int mods) { if (action == GLFW_PRESS) { DemoSsbo.this.mouseDownX = DemoSsbo.this.mouseX; DemoSsbo.this.mouseDown = true; } else if (action == GLFW_RELEASE) { DemoSsbo.this.mouseDown = false; DemoSsbo.this.rotationAboutY = DemoSsbo.this.currRotationAboutY; } } }); GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor()); glfwSetWindowPos(window, (vidmode.width() - width) / 2, (vidmode.height() - height) / 2); glfwMakeContextCurrent(window); glfwSwapInterval(0); glfwShowWindow(window); IntBuffer framebufferSize = BufferUtils.createIntBuffer(2); nglfwGetFramebufferSize(window, memAddress(framebufferSize), memAddress(framebufferSize) + 4); width = framebufferSize.get(0); height = framebufferSize.get(1); GL.createCapabilities(); debugProc = GLUtil.setupDebugMessageCallback(); /* Create all needed GL resources */ createFramebufferTexture(); createSampler(); quadFullScreenVao(); createComputeProgram(); initComputeProgram(); createQuadProgram(); initQuadProgram(); createSceneSSBO(); createFramebufferBuffer(); } /** * Create a VAO with a full-screen quad VBO. */ private void quadFullScreenVao() { this.vao = glGenVertexArrays(); int vbo = glGenBuffers(); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); ByteBuffer bb = BufferUtils.createByteBuffer(4 * 2 * 6); FloatBuffer fv = bb.asFloatBuffer(); fv.put(-1.0f).put(-1.0f); fv.put(1.0f).put(-1.0f); fv.put(1.0f).put(1.0f); fv.put(1.0f).put(1.0f); fv.put(-1.0f).put(1.0f); fv.put(-1.0f).put(-1.0f); glBufferData(GL_ARRAY_BUFFER, bb, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0L); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } /** * Create the full-scren quad shader. * * @throws IOException */ private void createQuadProgram() throws IOException { int program = glCreateProgram(); int vshader = DemoUtils.createShader("org/lwjgl/demo/opengl/raytracing/quad.vs", GL_VERTEX_SHADER, "330"); int fshader = DemoUtils.createShader("org/lwjgl/demo/opengl/raytracing/quad.fs", GL_FRAGMENT_SHADER, "330"); glAttachShader(program, vshader); glAttachShader(program, fshader); glBindAttribLocation(program, 0, "vertex"); glBindFragDataLocation(program, 0, "color"); glLinkProgram(program); int linked = glGetProgrami(program, GL_LINK_STATUS); String programLog = glGetProgramInfoLog(program); if (programLog.trim().length() > 0) { System.err.println(programLog); } if (linked == 0) { throw new AssertionError("Could not link program"); } this.quadProgram = program; } /** * Create the tracing compute shader program. * * @throws IOException */ private void createComputeProgram() throws IOException { int program = glCreateProgram(); int cshader = DemoUtils.createShader("org/lwjgl/demo/opengl/raytracing/raytracingSsbo.glslcs", GL_COMPUTE_SHADER); glAttachShader(program, cshader); glLinkProgram(program); int linked = glGetProgrami(program, GL_LINK_STATUS); String programLog = glGetProgramInfoLog(program); if (programLog.trim().length() > 0) { System.err.println(programLog); } if (linked == 0) { throw new AssertionError("Could not link program"); } this.computeProgram = program; } /** * Initialize the full-screen-quad program. */ private void initQuadProgram() { glUseProgram(quadProgram); int texUniform = glGetUniformLocation(quadProgram, "tex"); glUniform1i(texUniform, 0); glUseProgram(0); } /** * Initialize the compute shader. */ private void initComputeProgram() { glUseProgram(computeProgram); IntBuffer workGroupSize = BufferUtils.createIntBuffer(3); glGetProgramiv(computeProgram, GL_COMPUTE_WORK_GROUP_SIZE, workGroupSize); workGroupSizeX = workGroupSize.get(0); workGroupSizeY = workGroupSize.get(1); eyeUniform = glGetUniformLocation(computeProgram, "eye"); ray00Uniform = glGetUniformLocation(computeProgram, "ray00"); ray10Uniform = glGetUniformLocation(computeProgram, "ray10"); ray01Uniform = glGetUniformLocation(computeProgram, "ray01"); ray11Uniform = glGetUniformLocation(computeProgram, "ray11"); sizeUniform = glGetUniformLocation(computeProgram, "size"); timeUniform = glGetUniformLocation(computeProgram, "time"); /* Query the binding point of the SSBO */ IntBuffer props = BufferUtils.createIntBuffer(1); IntBuffer params = BufferUtils.createIntBuffer(1); props.put(0, GL_BUFFER_BINDING); /* * First, obtain the "resource index" used for further queries on the * resources. */ int boxesResourceIndex = glGetProgramResourceIndex(computeProgram, GL_SHADER_STORAGE_BLOCK, "Boxes"); int outputImageResourceIndex = glGetProgramResourceIndex(computeProgram, GL_SHADER_STORAGE_BLOCK, "OutputImage"); /* Now query the "BUFFER_BINDING" of those resources */ glGetProgramResourceiv(computeProgram, GL_SHADER_STORAGE_BLOCK, boxesResourceIndex, props, null, params); boxesSsboBinding = params.get(0); glGetProgramResourceiv(computeProgram, GL_SHADER_STORAGE_BLOCK, outputImageResourceIndex, props, null, params); outputImageBinding = params.get(0); glUseProgram(0); } /** * Create the texture that will serve as our framebuffer. */ private void createFramebufferTexture() { this.tex = glGenTextures(); glBindTexture(GL_TEXTURE_2D, tex); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, width, height); glBindTexture(GL_TEXTURE_2D, 0); } /** * Create the sampler to sample the framebuffer texture within the shader. */ private void createSampler() { this.sampler = glGenSamplers(); glSamplerParameteri(this.sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glSamplerParameteri(this.sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } /** * Create the buffer that will hold our framebuffer image. */ private void createFramebufferBuffer() { this.imageBuffer = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, this.imageBuffer); glBufferData(GL_ARRAY_BUFFER, 4 * 4 * width * height, GL_STREAM_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); } /** * Recreate the framebuffer when the window size changes. */ private void resizeFramebufferTexture() { glDeleteTextures(tex); glDeleteBuffers(imageBuffer); createFramebufferTexture(); createFramebufferBuffer(); } /** * Create a Shader Storage Buffer Object which will hold our boxes to be * read by our Compute Shader. */ private void createSceneSSBO() { this.ssbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, ssbo); ByteBuffer ssboData = BufferUtils.createByteBuffer(4 * (4 + 4) * boxes.length / 2); FloatBuffer fv = ssboData.asFloatBuffer(); for (int i = 0; i < boxes.length; i += 2) { Vector3f min = boxes[i]; Vector3f max = boxes[i + 1]; /* * NOTE: We need to write vec4 here, because SSBOs have specific * alignment requirements for struct members (vec3 is always treated * as vec4 in memory!) * * See: * "https://www.safaribooksonline.com/library/view/opengl-programming-guide/9780132748445/app09lev1sec3.html" */ fv.put(min.x).put(min.y).put(min.z).put(0.0f); fv.put(max.x).put(max.y).put(max.z).put(0.0f); } glBufferData(GL_ARRAY_BUFFER, ssboData, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); } /** * Compute one frame by tracing the scene using our compute shader and * presenting that image on the screen. */ private void trace() { glUseProgram(computeProgram); if (mouseDown) { /* * If mouse is down, compute the camera rotation based on mouse * cursor location. */ currRotationAboutY = rotationAboutY + (mouseX - mouseDownX) * 0.01f; } else { currRotationAboutY = rotationAboutY; } /* Rotate camera about Y axis. */ cameraPosition.set((float) sin(-currRotationAboutY) * 3.0f, 2.0f, (float) cos(-currRotationAboutY) * 3.0f); viewMatrix.setLookAt(cameraPosition, cameraLookAt, cameraUp); if (resetFramebuffer) { projMatrix.setPerspective((float) Math.toRadians(60.0f), (float) width / height, 1f, 2f); resizeFramebufferTexture(); resetFramebuffer = false; } projMatrix.invertPerspectiveView(viewMatrix, invViewProjMatrix); long thisTime = System.nanoTime(); float elapsedSeconds = (thisTime - firstTime) / 1E9f; glUniform1f(timeUniform, elapsedSeconds); /* Set viewing frustum corner rays in shader */ glUniform3f(eyeUniform, cameraPosition.x, cameraPosition.y, cameraPosition.z); invViewProjMatrix.transformProject(tmpVector.set(-1, -1, 0)).sub(cameraPosition); glUniform3f(ray00Uniform, tmpVector.x, tmpVector.y, tmpVector.z); invViewProjMatrix.transformProject(tmpVector.set(-1, 1, 0)).sub(cameraPosition); glUniform3f(ray01Uniform, tmpVector.x, tmpVector.y, tmpVector.z); invViewProjMatrix.transformProject(tmpVector.set(1, -1, 0)).sub(cameraPosition); glUniform3f(ray10Uniform, tmpVector.x, tmpVector.y, tmpVector.z); invViewProjMatrix.transformProject(tmpVector.set(1, 1, 0)).sub(cameraPosition); glUniform3f(ray11Uniform, tmpVector.x, tmpVector.y, tmpVector.z); glUniform2i(sizeUniform, width, height); /* Bind the SSBO containing our boxes */ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, boxesSsboBinding, ssbo); /* Bind the SSBO of our output image */ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, outputImageBinding, imageBuffer); /* Compute appropriate invocation dimension. */ int worksizeX = mathRoundPoT(width); int worksizeY = mathRoundPoT(height); /* Invoke the compute shader. */ glDispatchCompute(worksizeX / workGroupSizeX, worksizeY / workGroupSizeY, 1); /* * Synchronize all writes to the SSBO image buffer before we copy the * buffer into a texture via a PBO. */ glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); /* Reset bindings. */ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, boxesSsboBinding, 0); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, outputImageBinding, 0); /* Upload texels */ glBindTexture(GL_TEXTURE_2D, tex); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, imageBuffer); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_FLOAT, 0L); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); glUseProgram(0); } /** * Present the final image on the screen/viewport. */ private void present() { /* * Draw the rendered image on the screen using textured full-screen * quad. */ glUseProgram(quadProgram); glBindVertexArray(vao); glBindTexture(GL_TEXTURE_2D, tex); glBindSampler(0, this.sampler); glDrawArrays(GL_TRIANGLES, 0, 6); glBindSampler(0, 0); glBindTexture(GL_TEXTURE_2D, 0); glBindVertexArray(0); glUseProgram(0); } private void loop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); glViewport(0, 0, width, height); trace(); present(); glfwSwapBuffers(window); } } private void run() { try { init(); loop(); if (debugProc != null) debugProc.free(); errCallback.free(); keyCallback.free(); fbCallback.free(); cpCallback.free(); mbCallback.free(); glfwDestroyWindow(window); } catch (Throwable t) { t.printStackTrace(); } finally { glfwTerminate(); } } public static void main(String[] args) { new DemoSsbo().run(); } }