diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 689c410aa5..60c475168b 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -37,7 +37,6 @@ import java.awt.image.DataBufferInt; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; @@ -90,6 +89,7 @@ import rs117.hd.model.ModelOffsets; import rs117.hd.model.ModelPusher; import rs117.hd.opengl.AsyncUICopy; +import rs117.hd.opengl.GLRenderState; import rs117.hd.opengl.compute.ComputeMode; import rs117.hd.opengl.compute.OpenCLManager; import rs117.hd.opengl.shader.ModelPassthroughComputeProgram; @@ -139,9 +139,17 @@ import rs117.hd.utils.Props; import rs117.hd.utils.ResourcePath; import rs117.hd.utils.ShaderRecompile; -import rs117.hd.utils.buffer.GLBuffer; -import rs117.hd.utils.buffer.GpuIntBuffer; -import rs117.hd.utils.buffer.SharedGLBuffer; +import rs117.hd.utils.opengl.buffer.GLBuffer; +import rs117.hd.utils.opengl.buffer.GpuIntBuffer; +import rs117.hd.utils.opengl.buffer.SharedGLBuffer; +import rs117.hd.utils.opengl.texture.GLAttachmentSlot; +import rs117.hd.utils.opengl.texture.GLFrameBuffer; +import rs117.hd.utils.opengl.texture.GLFrameBufferDesc; +import rs117.hd.utils.opengl.texture.GLSamplerMode; +import rs117.hd.utils.opengl.texture.GLTexture; +import rs117.hd.utils.opengl.texture.GLTextureFormat; +import rs117.hd.utils.opengl.texture.GLTextureParams; +import rs117.hd.utils.opengl.texture.GLTextureType; import static net.runelite.api.Constants.*; import static net.runelite.api.Constants.SCENE_SIZE; @@ -174,11 +182,11 @@ public class HdPlugin extends Plugin implements DrawCallbacks { public static int MAX_TEXTURE_UNITS; public static int TEXTURE_UNIT_COUNT = 0; - public static final int TEXTURE_UNIT_UI = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; - public static final int TEXTURE_UNIT_GAME = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; - public static final int TEXTURE_UNIT_SHADOW_MAP = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; - public static final int TEXTURE_UNIT_TILE_HEIGHT_MAP = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; - public static final int TEXTURE_UNIT_TILED_LIGHTING_MAP = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_UI = GL_TEXTURE1 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_GAME = GL_TEXTURE1 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_SHADOW_MAP = GL_TEXTURE1 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_TILE_HEIGHT_MAP = GL_TEXTURE1 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_TILED_LIGHTING_MAP = GL_TEXTURE1 + TEXTURE_UNIT_COUNT++; public static int MAX_IMAGE_UNITS; public static int IMAGE_UNIT_COUNT = 0; @@ -211,24 +219,6 @@ public class HdPlugin extends Plugin implements DrawCallbacks { private static final int[] eightIntWrite = new int[8]; - private static final int[] RENDERBUFFER_FORMATS_SRGB = { - GL_SRGB8, - GL_SRGB8_ALPHA8 // should be guaranteed - }; - private static final int[] RENDERBUFFER_FORMATS_SRGB_WITH_ALPHA = { - GL_SRGB8_ALPHA8 // should be guaranteed - }; - private static final int[] RENDERBUFFER_FORMATS_LINEAR = { - GL_RGB8, - GL_RGBA8, - GL_RGB, // should be guaranteed - GL_RGBA // should be guaranteed - }; - private static final int[] RENDERBUFFER_FORMATS_LINEAR_WITH_ALPHA = { - GL_RGBA8, - GL_RGBA // should be guaranteed - }; - @Getter private Gson gson; @@ -374,10 +364,8 @@ public class HdPlugin extends Plugin implements DrawCallbacks { @Getter @Nullable - private int[] uiResolution; + private GLTexture uiTex; private final int[] actualUiResolution = { 0, 0 }; // Includes stretched mode and DPI scaling - private int texUi; - private int pboUi; @Nullable private int[] sceneViewport; @@ -385,22 +373,13 @@ public class HdPlugin extends Plugin implements DrawCallbacks { private int msaaSamples; private int[] sceneResolution; - private int fboScene; - private int rboSceneColor; - private int rboSceneDepth; - private int fboSceneResolve; - private int rboSceneResolveColor; - - private int shadowMapResolution; - private int fboShadowMap; - private int texShadowMap; - private int[] tiledLightingResolution; - private int tiledLightingLayerCount; - private int fboTiledLighting; - private int texTiledLighting; + private GLFrameBuffer backBufferFBO; + private GLFrameBuffer sceneFBO; + private GLFrameBuffer shadowMapFBO; + private GLFrameBuffer tiledLightingFBO; - private int texTileHeightMap; + private GLTexture tileHeightMapTex; private final SharedGLBuffer hStagingBufferVertices = new SharedGLBuffer( "Staging Vertices", GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW, CL_MEM_READ_ONLY); @@ -536,12 +515,7 @@ protected void startUp() { return false; renderBufferOffset = 0; - fboScene = 0; - rboSceneColor = 0; - rboSceneDepth = 0; - fboSceneResolve = 0; - rboSceneResolveColor = 0; - fboShadowMap = 0; + numPassthroughModels = 0; numModelsToSort = null; elapsedTime = 0; @@ -690,10 +664,58 @@ protected void startUp() { materialManager.startUp(); waterTypeManager.startUp(); + backBufferFBO = GLFrameBuffer.wrap(awtContext.getFramebuffer(false), "backBuffer"); + + GLFrameBufferDesc backbufferDesc = backBufferFBO.getDescriptor(); + if(backbufferDesc.colorDescriptors.isEmpty()) + throw new RuntimeException("Couldn't determine BackBuffer descriptor"); + + final int forcedAASamples = backbufferDesc.samples; + msaaSamples = forcedAASamples != 0 ? forcedAASamples : min(config.antiAliasingMode().getSamples(), glGetInteger(GL_MAX_SAMPLES)); + + GLTextureFormat backbufferFormat = backbufferDesc.colorDescriptors.get(0).format; + sceneFBO = new GLFrameBuffer(new GLFrameBufferDesc() + .setWidth(canvas.getWidth()) + .setHeight(canvas.getHeight()) + .setMSAASamples(msaaSamples) + .setColorAttachment(GLAttachmentSlot.COLOR0, backbufferFormat, GLTextureParams.DEFAULT()) + .setDepthAttachment(GLTextureFormat.DEPTH32F, GLTextureParams.DEFAULT()) + .setDebugName("SceneColor")); + + if (!sceneFBO.isCreated()) + throw new RuntimeException("No supported " + (backbufferFormat.isSRGB() ? "sRGB" : "linear") + " formats"); + + int shadowFBOSize = configShadowsEnabled ? config.shadowResolution().getValue() : 1; + shadowMapFBO = new GLFrameBuffer( + new GLFrameBufferDesc() + .setWidth(shadowFBOSize) + .setHeight(shadowFBOSize) + .setDepthAttachment(GLTextureFormat.DEPTH32F, + new GLTextureParams() + .setSampler(GLSamplerMode.NEAREST_CLAMP) + .setTextureUnit(TEXTURE_UNIT_SHADOW_MAP) + .setBorderColor(new float[] { 1.0f, 1.0f, 1.0f, 1.0f})) + .setDebugName("ShadowMap")); + + tiledLightingFBO = new GLFrameBuffer(new GLFrameBufferDesc() + .setShouldConstructionCreate(false) + .setDepth(DynamicLights.MAX_LAYERS_PER_TILE) + .setColorAttachment( + GLAttachmentSlot.COLOR0, GLTextureFormat.RGBA16UI, new GLTextureParams() + .setType(GLTextureType.TEXTURE2D_ARRAY) + .setSampler(GLSamplerMode.NEAREST_CLAMP) + .setTextureUnit(TEXTURE_UNIT_TILED_LIGHTING_MAP) + .setImageUnit(IMAGE_UNIT_TILED_LIGHTING, GL_WRITE_ONLY)) + .setDebugName("TiledLighting")); + + tileHeightMapTex = new GLTexture(EXTENDED_SCENE_SIZE, EXTENDED_SCENE_SIZE, MAX_Z, GLTextureFormat.R16I, new GLTextureParams() + .setType(GLTextureType.TEXTURE3D) + .setSampler(GLSamplerMode.NEAREST_CLAMP) + .setTextureUnit(TEXTURE_UNIT_TILE_HEIGHT_MAP) + .setDebugName("TileHeightMap")); + initPrograms(); initShaderHotswapping(); - initUiTexture(); - initShadowMapFbo(); checkGLErrors(); @@ -779,14 +801,34 @@ protected void shutDown() { materialManager.shutDown(); textureManager.shutDown(); + if(sceneFBO != null) { + sceneFBO.delete(); + sceneFBO = null; + } + + if(shadowMapFBO != null) { + shadowMapFBO.delete(); + shadowMapFBO = null; + } + + if(tiledLightingFBO != null) { + tiledLightingFBO.delete(); + tiledLightingFBO = null; + } + + if(uiTex != null) { + uiTex.delete(); + uiTex = null; + } + + if(tileHeightMapTex != null) { + tileHeightMapTex.delete(); + tileHeightMapTex = null; + } + destroyBuffers(); - destroyUiTexture(); destroyPrograms(); destroyVaos(); - destroySceneFbo(); - destroyShadowMapFbo(); - destroyTiledLightingFbo(); - destroyTileHeightMap(); destroyModelSortingBins(); clManager.shutDown(); @@ -1282,98 +1324,13 @@ private void destroyBuffers() { uboUI.destroy(); } - private void initUiTexture() { - pboUi = glGenBuffers(); - - texUi = glGenTextures(); - glActiveTexture(TEXTURE_UNIT_UI); - glBindTexture(GL_TEXTURE_2D, texUi); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } - - private void destroyUiTexture() { - uiResolution = null; - - if (pboUi != 0) - glDeleteBuffers(pboUi); - pboUi = 0; - - if (texUi != 0) - glDeleteTextures(texUi); - texUi = 0; - } - - private void updateTiledLightingFbo() { - assert configTiledLighting; - - int[] newResolution = max(ivec(1), round(divide(vec(sceneResolution), TILED_LIGHTING_TILE_SIZE))); - int newLayerCount = configDynamicLights.getTiledLightingLayers(); - if (Arrays.equals(newResolution, tiledLightingResolution) && tiledLightingLayerCount == newLayerCount) - return; - - destroyTiledLightingFbo(); - - tiledLightingResolution = newResolution; - tiledLightingLayerCount = newLayerCount; - - fboTiledLighting = glGenFramebuffers(); - texTiledLighting = glGenTextures(); - glActiveTexture(TEXTURE_UNIT_TILED_LIGHTING_MAP); - glBindTexture(GL_TEXTURE_2D_ARRAY, texTiledLighting); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage3D( - GL_TEXTURE_2D_ARRAY, - 0, - GL_RGBA16UI, - tiledLightingResolution[0], - tiledLightingResolution[1], - tiledLightingLayerCount, - 0, - GL_RGBA_INTEGER, - GL_UNSIGNED_SHORT, - 0 - ); - - glBindFramebuffer(GL_FRAMEBUFFER, fboTiledLighting); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texTiledLighting, 0); - checkGLErrors(); - - if (tiledLightingImageStoreProgram.isValid()) - ARBShaderImageLoadStore.glBindImageTexture( - IMAGE_UNIT_TILED_LIGHTING, texTiledLighting, 0, true, 0, GL_WRITE_ONLY, GL_RGBA16UI); - - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); - - checkGLErrors(); - - uboGlobal.tiledLightingResolution.set(tiledLightingResolution); - } - - private void destroyTiledLightingFbo() { - tiledLightingResolution = null; - - if (fboTiledLighting != 0) - glDeleteFramebuffers(fboTiledLighting); - fboTiledLighting = 0; - - if (texTiledLighting != 0) - glDeleteTextures(texTiledLighting); - texTiledLighting = 0; - } - private void updateSceneFbo() { - if (uiResolution == null) + if (uiTex == null) return; int[] viewport = { client.getViewportXOffset(), - uiResolution[1] - (client.getViewportYOffset() + client.getViewportHeight()), + uiTex.getHeight() - (client.getViewportYOffset() + client.getViewportHeight()), client.getViewportWidth(), client.getViewportHeight() }; @@ -1383,7 +1340,7 @@ private void updateSceneFbo() { return; // DPI scaling and stretched mode also affects the game's viewport - divide(sceneViewportScale, vec(actualUiResolution), vec(uiResolution)); + divide(sceneViewportScale, vec(actualUiResolution), vec(uiTex.getWidth(), uiTex.getHeight())); if (sceneViewportScale[0] != 1 || sceneViewportScale[1] != 1) { // Pad the viewport before scaling, so it always covers the game's viewport in the UI for (int i = 0; i < 2; i++) { @@ -1397,189 +1354,17 @@ private void updateSceneFbo() { if (Arrays.equals(sceneViewport, viewport)) return; - destroySceneFbo(); sceneViewport = viewport; - // Bind default FBO to check whether anti-aliasing is forced - int defaultFramebuffer = awtContext.getFramebuffer(false); - glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); - final int forcedAASamples = glGetInteger(GL_SAMPLES); - msaaSamples = forcedAASamples != 0 ? forcedAASamples : min(config.antiAliasingMode().getSamples(), glGetInteger(GL_MAX_SAMPLES)); - - // Since there's seemingly no reliable way to check if the default framebuffer will do sRGB conversions with GL_FRAMEBUFFER_SRGB - // enabled, we always replace the default framebuffer with an sRGB one. We could technically support rendering to the default - // framebuffer when sRGB conversions aren't needed, but the goal is to transition to linear blending in the future anyway. - boolean sRGB = false; // This is currently unused - - // Some implementations (*cough* Apple) complain when blitting from an FBO without an alpha channel to a (default) FBO with alpha. - // To work around this, we select a format which includes an alpha channel, even though we don't need it. - int defaultColorAttachment = defaultFramebuffer == 0 ? GL_BACK_LEFT : GL_COLOR_ATTACHMENT0; - int alphaBits = glGetFramebufferAttachmentParameteri(GL_FRAMEBUFFER, defaultColorAttachment, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE); - checkGLErrors(); - boolean alpha = alphaBits > 0; - - int[] desiredFormats = sRGB ? - alpha ? RENDERBUFFER_FORMATS_SRGB_WITH_ALPHA : RENDERBUFFER_FORMATS_SRGB : - alpha ? RENDERBUFFER_FORMATS_LINEAR_WITH_ALPHA : RENDERBUFFER_FORMATS_LINEAR; - float resolutionScale = config.sceneResolutionScale() / 100f; sceneResolution = round(max(vec(1), multiply(slice(vec(sceneViewport), 2), resolutionScale))); uboGlobal.sceneResolution.set(sceneResolution); - // Create and bind the FBO - fboScene = glGenFramebuffers(); - glBindFramebuffer(GL_FRAMEBUFFER, fboScene); - - // Create color render buffer - rboSceneColor = glGenRenderbuffers(); - glBindRenderbuffer(GL_RENDERBUFFER, rboSceneColor); - - // Flush out all pending errors, so we can check whether the next step succeeds - clearGLErrors(); - - int format = 0; - for (int desiredFormat : desiredFormats) { - glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaaSamples, desiredFormat, sceneResolution[0], sceneResolution[1]); - - if (glGetError() == GL_NO_ERROR) { - format = desiredFormat; - break; - } - } - - if (format == 0) - throw new RuntimeException("No supported " + (sRGB ? "sRGB" : "linear") + " formats"); - - // Found a usable format. Bind the RBO to the scene FBO - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboSceneColor); - checkGLErrors(); - - // Create depth render buffer - rboSceneDepth = glGenRenderbuffers(); - glBindRenderbuffer(GL_RENDERBUFFER, rboSceneDepth); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaaSamples, GL_DEPTH24_STENCIL8, sceneResolution[0], sceneResolution[1]); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboSceneDepth); - checkGLErrors(); - - // If necessary, create an FBO for resolving multisampling - if (msaaSamples > 1 && resolutionScale != 1) { - fboSceneResolve = glGenFramebuffers(); - glBindFramebuffer(GL_FRAMEBUFFER, fboSceneResolve); - rboSceneResolveColor = glGenRenderbuffers(); - glBindRenderbuffer(GL_RENDERBUFFER, rboSceneResolveColor); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, 0, format, sceneResolution[0], sceneResolution[1]); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboSceneResolveColor); - checkGLErrors(); - } - - // Reset - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - } - - private void destroySceneFbo() { - sceneViewport = null; - - if (fboScene != 0) - glDeleteFramebuffers(fboScene); - fboScene = 0; - - if (rboSceneColor != 0) - glDeleteRenderbuffers(rboSceneColor); - rboSceneColor = 0; - - if (rboSceneDepth != 0) - glDeleteRenderbuffers(rboSceneDepth); - rboSceneDepth = 0; - - if (fboSceneResolve != 0) - glDeleteFramebuffers(fboSceneResolve); - fboSceneResolve = 0; - - if (rboSceneResolveColor != 0) - glDeleteRenderbuffers(rboSceneResolveColor); - rboSceneResolveColor = 0; - } - - private void initShadowMapFbo() { - if (!configShadowsEnabled) { - initDummyShadowMap(); - return; - } - - // Create and bind the FBO - fboShadowMap = glGenFramebuffers(); - glBindFramebuffer(GL_FRAMEBUFFER, fboShadowMap); - - // Create texture - texShadowMap = glGenTextures(); - glActiveTexture(TEXTURE_UNIT_SHADOW_MAP); - glBindTexture(GL_TEXTURE_2D, texShadowMap); - - shadowMapResolution = config.shadowResolution().getValue(); - int maxResolution = glGetInteger(GL_MAX_TEXTURE_SIZE); - if (maxResolution < shadowMapResolution) { - log.info("Capping shadow resolution from {} to {}", shadowMapResolution, maxResolution); - shadowMapResolution = maxResolution; - } - - glTexImage2D( - GL_TEXTURE_2D, - 0, - GL_DEPTH_COMPONENT24, - shadowMapResolution, - shadowMapResolution, - 0, - GL_DEPTH_COMPONENT, - GL_FLOAT, - 0 - ); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - - float[] color = { 1, 1, 1, 1 }; - glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color); - - // Bind texture - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texShadowMap, 0); - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - - // Reset FBO - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); - } - - private void initDummyShadowMap() { - // Create dummy texture - texShadowMap = glGenTextures(); - glActiveTexture(TEXTURE_UNIT_SHADOW_MAP); - glBindTexture(GL_TEXTURE_2D, texShadowMap); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 1, 1, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - } - - private void destroyShadowMapFbo() { - if (texShadowMap != 0) - glDeleteTextures(texShadowMap); - texShadowMap = 0; - - if (fboShadowMap != 0) - glDeleteFramebuffers(fboShadowMap); - fboShadowMap = 0; + sceneFBO.resize(sceneResolution[0], sceneResolution[1]); } private void initTileHeightMap(Scene scene) { - final int TILE_HEIGHT_BUFFER_SIZE = Constants.MAX_Z * EXTENDED_SCENE_SIZE * EXTENDED_SCENE_SIZE * Short.BYTES; - ShortBuffer tileBuffer = ByteBuffer - .allocateDirect(TILE_HEIGHT_BUFFER_SIZE) - .order(ByteOrder.nativeOrder()) - .asShortBuffer(); - + ShortBuffer tileBuffer = tileHeightMapTex.map(GL_WRITE_ONLY).asShortBuffer(); int[][][] tileHeights = scene.getTileHeights(); for (int z = 0; z < Constants.MAX_Z; ++z) { for (int y = 0; y < EXTENDED_SCENE_SIZE; ++y) { @@ -1592,24 +1377,7 @@ private void initTileHeightMap(Scene scene) { } } tileBuffer.flip(); - - texTileHeightMap = glGenTextures(); - glActiveTexture(TEXTURE_UNIT_TILE_HEIGHT_MAP); - glBindTexture(GL_TEXTURE_3D, texTileHeightMap); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage3D(GL_TEXTURE_3D, 0, GL_R16I, - EXTENDED_SCENE_SIZE, EXTENDED_SCENE_SIZE, Constants.MAX_Z, - 0, GL_RED_INTEGER, GL_SHORT, tileBuffer - ); - } - - private void destroyTileHeightMap() { - if (texTileHeightMap != 0) - glDeleteTextures(texTileHeightMap); - texTileHeightMap = 0; + tileHeightMapTex.unmap(0, 0, 0, EXTENDED_SCENE_SIZE, EXTENDED_SCENE_SIZE, Constants.MAX_Z); } @Override @@ -1829,14 +1597,18 @@ public void drawScene(double cameraX, double cameraY, double cameraZ, double cam // Perform tiled lighting culling before the compute memory barrier, so it's performed asynchronously if (configTiledLighting) { - updateTiledLightingFbo(); - assert fboTiledLighting != 0; + tiledLightingFBO.bind(); + + int[] tiledLightingResolution = max(ivec(1), round(divide(vec(sceneResolution), TILED_LIGHTING_TILE_SIZE))); + if (tiledLightingFBO.resize(tiledLightingResolution[0], tiledLightingResolution[1])) { + uboGlobal.tiledLightingResolution.set(tiledLightingResolution); + uboGlobal.upload(); + } frameTimer.begin(Timer.DRAW_TILED_LIGHTING); frameTimer.begin(Timer.RENDER_TILED_LIGHTING); - glViewport(0, 0, tiledLightingResolution[0], tiledLightingResolution[1]); - glBindFramebuffer(GL_FRAMEBUFFER, fboTiledLighting); + tiledLightingFBO.clearColor(); glBindVertexArray(vaoTri); @@ -1849,11 +1621,13 @@ public void drawScene(double cameraX, double cameraY, double cameraZ, double cam int layerCount = configDynamicLights.getTiledLightingLayers(); for (int layer = 0; layer < layerCount; layer++) { tiledLightingShaderPrograms.get(layer).use(); - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texTiledLighting, 0, layer); + tiledLightingFBO.bindLayer(GLAttachmentSlot.COLOR0, layer); glDrawArrays(GL_TRIANGLES, 0, 3); } } + tiledLightingFBO.unbind(); + frameTimer.end(Timer.RENDER_TILED_LIGHTING); frameTimer.end(Timer.DRAW_TILED_LIGHTING); } @@ -2054,17 +1828,17 @@ private void prepareInterfaceTexture() { max(1, client.getCanvasWidth()), max(1, client.getCanvasHeight()) }; - boolean resize = !Arrays.equals(uiResolution, resolution); - if (resize) { - uiResolution = resolution; - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi); - glBufferData(GL_PIXEL_UNPACK_BUFFER, uiResolution[0] * uiResolution[1] * 4L, GL_STREAM_DRAW); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - glActiveTexture(TEXTURE_UNIT_UI); - glBindTexture(GL_TEXTURE_2D, texUi); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, uiResolution[0], uiResolution[1], 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + boolean resized = false; + if(uiTex == null) { + uiTex = new GLTexture(resolution[0], resolution[1], GLTextureFormat.BGRA8, + new GLTextureParams() + .setSampler(GLSamplerMode.LINEAR_CLAMP) + .setTextureUnit(TEXTURE_UNIT_UI) + .setDebugName("UI")); + } else if (uiTex.getWidth() != resolution[0] || uiTex.getHeight() != resolution[1]) { + uiTex.resize(resolution[0], resolution[1]); + resized = true; } if (client.isStretchedEnabled()) { @@ -2072,15 +1846,16 @@ private void prepareInterfaceTexture() { actualUiResolution[0] = dim.width; actualUiResolution[1] = dim.height; } else { - copyTo(actualUiResolution, uiResolution); + actualUiResolution[0] = uiTex.getWidth(); + actualUiResolution[1] = uiTex.getHeight(); } round(actualUiResolution, multiply(vec(actualUiResolution), getDpiScaling())); if (configAsyncUICopy) { // Start copying the UI on a different thread, to be uploaded during the next frame - asyncUICopy.prepare(pboUi, texUi); + asyncUICopy.prepare(uiTex); // If the window was just resized, upload once synchronously so there is something to show - if (resize) + if (resized) asyncUICopy.complete(); return; } @@ -2089,28 +1864,21 @@ private void prepareInterfaceTexture() { final int[] pixels = bufferProvider.getPixels(); final int width = bufferProvider.getWidth(); final int height = bufferProvider.getHeight(); - frameTimer.begin(Timer.MAP_UI_BUFFER); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi); - ByteBuffer mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); + ByteBuffer mappedBuffer = uiTex.map(GL_WRITE_ONLY); frameTimer.end(Timer.MAP_UI_BUFFER); if (mappedBuffer == null) { log.error("Unable to map interface PBO. Skipping UI..."); - } else if (width > uiResolution[0] || height > uiResolution[1]) { - log.error("UI texture resolution mismatch ({}x{} > {}). Skipping UI...", width, height, uiResolution); + } else if(width > uiTex.getWidth() || height > uiTex.getHeight()) { + log.error("UI texture resolution mismatch ({}x{} > {}x{}). Skipping UI...", width, height, uiTex.getWidth(), uiTex.getHeight()); } else { frameTimer.begin(Timer.COPY_UI); mappedBuffer.asIntBuffer().put(pixels, 0, width * height); frameTimer.end(Timer.COPY_UI); - frameTimer.begin(Timer.UPLOAD_UI); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - glActiveTexture(TEXTURE_UNIT_UI); - glBindTexture(GL_TEXTURE_2D, texUi); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); + uiTex.unmap(0, 0, width, height); frameTimer.end(Timer.UPLOAD_UI); } - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } @Override @@ -2254,14 +2022,12 @@ public void draw(int overlayColor) { uboGlobal.colorFilterFade.set(clamp(timeSinceChange / COLOR_FILTER_FADE_DURATION, 0, 1)); } - if (configShadowsEnabled && fboShadowMap != 0 && environmentManager.currentDirectionalStrength > 0) { + if (configShadowsEnabled && shadowMapFBO != null && environmentManager.currentDirectionalStrength > 0) { frameTimer.begin(Timer.RENDER_SHADOWS); // Render to the shadow depth map - glViewport(0, 0, shadowMapResolution, shadowMapResolution); - glBindFramebuffer(GL_FRAMEBUFFER, fboShadowMap); - glClearDepth(1); - glClear(GL_DEPTH_BUFFER_BIT); + shadowMapFBO.bind(); + shadowMapFBO.clearDepth(1.0f); glDepthFunc(GL_LEQUAL); shadowProgram.use(); @@ -2301,28 +2067,20 @@ public void draw(int overlayColor) { glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); + shadowMapFBO.unbind(); + frameTimer.end(Timer.RENDER_SHADOWS); } uboGlobal.upload(); sceneProgram.use(); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboScene); - glToggle(GL_MULTISAMPLE, msaaSamples > 1); - glViewport(0, 0, sceneResolution[0], sceneResolution[1]); + sceneFBO.bind(); // Clear scene frameTimer.begin(Timer.CLEAR_SCENE); - float[] gammaCorrectedFogColor = pow(fogColor, getGammaCorrection()); - glClearColor( - gammaCorrectedFogColor[0], - gammaCorrectedFogColor[1], - gammaCorrectedFogColor[2], - 1f - ); - glClearDepth(0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + sceneFBO.clear(gammaCorrectedFogColor[0], gammaCorrectedFogColor[1], gammaCorrectedFogColor[2], 1.0f, 0.0f); frameTimer.end(Timer.CLEAR_SCENE); frameTimer.begin(Timer.RENDER_SCENE); @@ -2383,30 +2141,19 @@ public void draw(int overlayColor) { glDisable(GL_MULTISAMPLE); glDisable(GL_DEPTH_TEST); glDepthMask(true); - glUseProgram(0); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, fboScene); - if (fboSceneResolve != 0) { - // Blit from the scene FBO to the multisample resolve FBO - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboSceneResolve); - glBlitFramebuffer( - 0, 0, sceneResolution[0], sceneResolution[1], - 0, 0, sceneResolution[0], sceneResolution[1], - GL_COLOR_BUFFER_BIT, GL_NEAREST - ); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fboSceneResolve); - } - - // Blit from the resolved FBO to the default FBO - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, awtContext.getFramebuffer(false)); - glBlitFramebuffer( - 0, 0, sceneResolution[0], sceneResolution[1], - sceneViewport[0], sceneViewport[1], sceneViewport[0] + sceneViewport[2], sceneViewport[1] + sceneViewport[3], - GL_COLOR_BUFFER_BIT, config.sceneScalingMode().glFilter - ); + GLRenderState.clearActiveShader(); + + sceneFBO.blitTo( + backBufferFBO, + GLAttachmentSlot.COLOR0, + GLAttachmentSlot.BACK_LEFT, + sceneViewport[0], + sceneViewport[1], + sceneViewport[0] + sceneViewport[2], + sceneViewport[1] + sceneViewport[3], + config.sceneScalingMode().glFilter); } else { - glClearColor(0, 0, 0, 1f); - glClear(GL_COLOR_BUFFER_BIT); + backBufferFBO.clearColor(0.0f, 0.0f, 0.0f, 1.0f); } drawUi(overlayColor); @@ -2426,7 +2173,9 @@ public void draw(int overlayColor) { log.error("Unable to swap buffers:", ex); } - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + GLRenderState.clear(); + + backBufferFBO.bind(); frameTimer.end(Timer.DRAW_FRAME); frameTimer.end(Timer.RENDER_FRAME); @@ -2440,7 +2189,7 @@ public void draw(int overlayColor) { } private void drawUi(int overlayColor) { - if (uiResolution == null || developerTools.isHideUiEnabled() && hasLoggedIn) + if (uiTex == null || developerTools.isHideUiEnabled() && hasLoggedIn) return; // Fix vanilla bug causing the overlay to remain on the login screen in areas like Fossil Island underwater @@ -2449,7 +2198,7 @@ private void drawUi(int overlayColor) { frameTimer.begin(Timer.RENDER_UI); - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + backBufferFBO.bind(); // Disable alpha writes, just in case the default FBO has an alpha channel glColorMask(true, true, true, false); @@ -2458,20 +2207,12 @@ private void drawUi(int overlayColor) { tiledLightingOverlay.render(); uiProgram.use(); - uboUI.sourceDimensions.set(uiResolution); + uboUI.sourceDimensions.set(uiTex.getWidth(), uiTex.getHeight()); uboUI.targetDimensions.set(actualUiResolution); uboUI.alphaOverlay.set(ColorUtils.srgba(overlayColor)); uboUI.upload(); - // Set the sampling function used when stretching the UI. - // This is probably better done with sampler objects instead of texture parameters, but this is easier and likely more portable. - // See https://www.khronos.org/opengl/wiki/Sampler_Object for details. - // GL_NEAREST makes sampling for bicubic/xBR simpler, so it should be used whenever linear/pixel isn't - final int function = config.uiScalingMode().glSamplingFunction; - glActiveTexture(TEXTURE_UNIT_UI); - glBindTexture(GL_TEXTURE_2D, texUi); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, function); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, function); + uiTex.setSampler(config.uiScalingMode().getSamplerMode()); glEnable(GL_BLEND); glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); @@ -2490,10 +2231,10 @@ private void drawUi(int overlayColor) { } /** - * Convert the front framebuffer to an Image - */ + * Convert the front framebuffer to an Image + */ private Image screenshot() { - if (uiResolution == null) + if (uiTex == null) return null; int width = actualUiResolution[0]; @@ -2769,7 +2510,6 @@ private void processPendingConfigChanges() { boolean recompilePrograms = false; boolean recreateSceneFbo = false; - boolean recreateShadowMapFbo = false; boolean reloadTexturesAndMaterials = false; boolean reloadEnvironments = false; boolean reloadModelOverrides = false; @@ -2830,7 +2570,8 @@ private void processPendingConfigChanges() { recompilePrograms = true; // fall-through case KEY_SHADOW_RESOLUTION: - recreateShadowMapFbo = true; + int shadowFBOSize = configShadowsEnabled ? config.shadowResolution().getValue() : 1; + shadowMapFBO.resize(shadowFBOSize, shadowFBOSize); break; case KEY_ATMOSPHERIC_LIGHTING: case KEY_LEGACY_TOB_ENVIRONMENT: @@ -2910,7 +2651,6 @@ private void processPendingConfigChanges() { recompilePrograms(); if (recreateSceneFbo) { - destroySceneFbo(); updateSceneFbo(); } @@ -2924,11 +2664,6 @@ private void processPendingConfigChanges() { if (reloadScene) reuploadScene(); - if (recreateShadowMapFbo) { - destroyShadowMapFbo(); - initShadowMapFbo(); - } - if (reloadEnvironments) environmentManager.reload(); } @@ -3052,8 +2787,8 @@ public boolean tileInFrustum( } /** - * Check is a model is visible and should be drawn. - */ + * Check is a model is visible and should be drawn. + */ private boolean isOutsideViewport(Model model, int modelRadius, float pitchSin, float pitchCos, float yawSin, float yawCos, int x, int y, int z) { if (sceneContext == null) return true; @@ -3091,17 +2826,17 @@ private boolean isOutsideViewport(Model model, int modelRadius, float pitchSin, } /** - * Draw a Renderable in the scene - * - * @param projection - * @param scene - * @param renderable Can be an Actor (Player or NPC), DynamicObject, GraphicsObject, TileItem, Projectile or a raw Model. - * @param orientation Rotation around the up-axis, from 0 to 2048 exclusive, 2048 indicating a complete rotation. - * @param x The Renderable's X offset relative to {@link Client#getCameraX()}. - * @param y The Renderable's Y offset relative to {@link Client#getCameraZ()}. - * @param z The Renderable's Z offset relative to {@link Client#getCameraY()}. - * @param hash A unique hash of the renderable consisting of some useful information. See {@link rs117.hd.utils.ModelHash} for more details. - */ + * Draw a Renderable in the scene + * + * @param projection + * @param scene + * @param renderable Can be an Actor (Player or NPC), DynamicObject, GraphicsObject, TileItem, Projectile or a raw Model. + * @param orientation Rotation around the up-axis, from 0 to 2048 exclusive, 2048 indicating a complete rotation. + * @param x The Renderable's X offset relative to {@link Client#getCameraX()}. + * @param y The Renderable's Y offset relative to {@link Client#getCameraZ()}. + * @param z The Renderable's Z offset relative to {@link Client#getCameraY()}. + * @param hash A unique hash of the renderable consisting of some useful information. See {@link rs117.hd.utils.ModelHash} for more details. + */ @Override public void draw(Projection projection, @Nullable Scene scene, Renderable renderable, int orientation, int x, int y, int z, long hash) { if (sceneContext == null) @@ -3322,8 +3057,8 @@ public void draw(Projection projection, @Nullable Scene scene, Renderable render } /** - * returns the correct buffer based on triangle count and updates model count - */ + * returns the correct buffer based on triangle count and updates model count + */ private GpuIntBuffer bufferForTriangles(int triangles) { for (int i = 0; i < numSortingBins; i++) { if (modelSortingBinFaceCounts[i] >= triangles) { @@ -3419,7 +3154,16 @@ public static void clearGLErrors() { // @formatter:on } + @FunctionalInterface + public interface DebugContextGetter { + String get(); + } + public static void checkGLErrors() { + checkGLErrors(null); + } + + public static void checkGLErrors(DebugContextGetter getter) { if (SKIP_GL_ERROR_CHECKS) return; @@ -3453,7 +3197,11 @@ public static void checkGLErrors() { break; } - log.debug("glGetError:", new Exception(errStr)); + if(getter == null) { + log.debug("glGetError:", new Exception(errStr)); + } else { + log.debug("{} - glGetError:", getter.get(), new Exception(errStr)); + } } } diff --git a/src/main/java/rs117/hd/config/UIScalingMode.java b/src/main/java/rs117/hd/config/UIScalingMode.java index cbbc8fb739..4a3338543b 100644 --- a/src/main/java/rs117/hd/config/UIScalingMode.java +++ b/src/main/java/rs117/hd/config/UIScalingMode.java @@ -26,21 +26,20 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; - -import static org.lwjgl.opengl.GL33C.*; +import rs117.hd.utils.opengl.texture.GLSamplerMode; @Getter @RequiredArgsConstructor public enum UIScalingMode { - NEAREST("Nearest", GL_NEAREST), - LINEAR("Bilinear", GL_LINEAR), - MITCHELL("Mitchell", GL_NEAREST), - CATMULL_ROM("Catmull-Rom", GL_NEAREST), - XBR("xBR", GL_NEAREST), - PIXEL("Pixel", GL_LINEAR); + NEAREST("Nearest", GLSamplerMode.NEAREST_CLAMP), + LINEAR("Bilinear", GLSamplerMode.LINEAR_CLAMP), + MITCHELL("Mitchell", GLSamplerMode.NEAREST_CLAMP), + CATMULL_ROM("Catmull-Rom", GLSamplerMode.NEAREST_CLAMP), + XBR("xBR", GLSamplerMode.NEAREST_CLAMP), + PIXEL("Pixel", GLSamplerMode.LINEAR_CLAMP); private final String name; - public final int glSamplingFunction; + public final GLSamplerMode samplerMode; @Override public String toString() diff --git a/src/main/java/rs117/hd/opengl/AsyncUICopy.java b/src/main/java/rs117/hd/opengl/AsyncUICopy.java index 4f8a347789..b84398e368 100644 --- a/src/main/java/rs117/hd/opengl/AsyncUICopy.java +++ b/src/main/java/rs117/hd/opengl/AsyncUICopy.java @@ -12,6 +12,7 @@ import rs117.hd.HdPlugin; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; +import rs117.hd.utils.opengl.texture.GLTexture; import static org.lwjgl.opengl.GL33C.*; @@ -31,8 +32,7 @@ public class AsyncUICopy implements Runnable { private IntBuffer mappedBuffer; private int[] pixels; - private int interfacePbo; - private int interfaceTexture; + private GLTexture uiTex; private int width; private int height; @@ -45,23 +45,20 @@ public void run() { timer.add(Timer.COPY_UI, time); } - public void prepare(int interfacePbo, int interfaceTex) { + public void prepare(GLTexture uiTex) { // Ensure there isn't already another UI copy in progress if (mappedBuffer != null) return; timer.begin(Timer.MAP_UI_BUFFER); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, interfacePbo); - ByteBuffer buffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + ByteBuffer buffer = uiTex.map(GL_WRITE_ONLY); timer.end(Timer.MAP_UI_BUFFER); if (buffer == null) { log.error("Unable to map interface PBO. Skipping UI..."); return; } - this.interfacePbo = interfacePbo; - this.interfaceTexture = interfaceTex; + this.uiTex = uiTex; this.mappedBuffer = buffer.asIntBuffer(); var provider = client.getBufferProvider(); @@ -85,18 +82,13 @@ public boolean complete() { throw new RuntimeException(e); } - var uiResolution = plugin.getUiResolution(); - if (uiResolution == null || width > uiResolution[0] || height > uiResolution[1]) { - log.error("UI texture resolution mismatch ({}x{} > {}). Skipping UI...", width, height, uiResolution); + if (width > uiTex.getWidth() || height > uiTex.getHeight()) { + log.error("UI texture resolution mismatch ({}x{} > {}x{}). Skipping UI...", width, height, uiTex.getWidth(), uiTex.getHeight()); return false; } timer.begin(Timer.UPLOAD_UI); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, interfacePbo); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - glActiveTexture(HdPlugin.TEXTURE_UNIT_UI); - glBindTexture(GL_TEXTURE_2D, interfaceTexture); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); + uiTex.unmap(0, 0, width, height); timer.end(Timer.UPLOAD_UI); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); diff --git a/src/main/java/rs117/hd/opengl/GLRenderState.java b/src/main/java/rs117/hd/opengl/GLRenderState.java new file mode 100644 index 0000000000..7abbaa4184 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/GLRenderState.java @@ -0,0 +1,73 @@ +package rs117.hd.opengl; + +import java.util.ArrayDeque; +import lombok.RequiredArgsConstructor; +import rs117.hd.opengl.shader.ShaderProgram; +import rs117.hd.utils.opengl.texture.GLFrameBuffer; +import rs117.hd.utils.opengl.texture.GLTexture; + +import static org.lwjgl.opengl.GL20C.glUseProgram; + +public class GLRenderState { + public static final BindStack frameBuffer = new BindStack<>(GLFrameBuffer::bind); + public static final BindStack texture = new BindStack<>(GLTexture::bind); + + public static ShaderProgram activeShaderProgram; + + public static void clearActiveShader() { + if(activeShaderProgram != null) { + glUseProgram(0); + activeShaderProgram = null; + } + } + + public static void clear() { + frameBuffer.clear(); + texture.clear(); + + clearActiveShader(); + } + + @FunctionalInterface + public interface BinderInterface { + void bind(T val); + } + + @RequiredArgsConstructor + public static class BindStack { + private T active; + private final ArrayDeque previouslyActive = new ArrayDeque<>(); + private final BinderInterface binderFunction; + + public boolean isActive(T val) { return active == val;} + + public void push(T val){ + if(val == null) { + active = null; + return; + } + + if(active != null) { + previouslyActive.push(active); + } + + previouslyActive.remove(val); + active = val; + } + + public void pop() { + if(previouslyActive.isEmpty()) { + active = null; + return; + } + + T newActive = previouslyActive.pop(); + binderFunction.bind(newActive); + active = newActive; + } + + public void clear() { + previouslyActive.clear(); + } + } +} diff --git a/src/main/java/rs117/hd/opengl/compute/OpenCLManager.java b/src/main/java/rs117/hd/opengl/compute/OpenCLManager.java index 241b8a4379..9dd14c54dc 100644 --- a/src/main/java/rs117/hd/opengl/compute/OpenCLManager.java +++ b/src/main/java/rs117/hd/opengl/compute/OpenCLManager.java @@ -50,7 +50,7 @@ import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; import rs117.hd.opengl.uniforms.UBOCompute; -import rs117.hd.utils.buffer.SharedGLBuffer; +import rs117.hd.utils.opengl.buffer.SharedGLBuffer; import static org.lwjgl.opencl.APPLEGLSharing.CL_CGL_DEVICE_FOR_CURRENT_VIRTUAL_SCREEN_APPLE; import static org.lwjgl.opencl.APPLEGLSharing.clGetGLContextInfoAPPLE; diff --git a/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java index fbfba55fbf..7efcf10a7d 100644 --- a/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java @@ -7,6 +7,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import rs117.hd.opengl.GLRenderState; import rs117.hd.opengl.uniforms.UniformBuffer; import static org.lwjgl.opengl.GL33C.*; @@ -91,10 +92,13 @@ public > T getUniformBufferBlock(int UniformBlockInde public void use() { assert program != 0; - glUseProgram(program); + if(GLRenderState.activeShaderProgram != this) { + GLRenderState.activeShaderProgram = this; + glUseProgram(program); - for (UniformBufferBlockPair pair : uniformBlockMappings) - glUniformBlockBinding(program, pair.uboProgramIndex, pair.buffer.getBindingIndex()); + for (UniformBufferBlockPair pair : uniformBlockMappings) + glUniformBlockBinding(program, pair.uboProgramIndex, pair.buffer.getBindingIndex()); + } } public void destroy() { diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOCompute.java b/src/main/java/rs117/hd/opengl/uniforms/UBOCompute.java index 5dee80aa6c..47904ffbbd 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOCompute.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOCompute.java @@ -3,7 +3,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import rs117.hd.utils.buffer.SharedGLBuffer; +import rs117.hd.utils.opengl.buffer.SharedGLBuffer; import static org.lwjgl.opencl.CL10.*; import static org.lwjgl.opengl.GL33C.*; diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java b/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java index c15310168a..485a59969e 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java @@ -1,6 +1,6 @@ package rs117.hd.opengl.uniforms; -import rs117.hd.utils.buffer.GLBuffer; +import rs117.hd.utils.opengl.buffer.GLBuffer; import static org.lwjgl.opengl.GL33C.*; diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOLights.java b/src/main/java/rs117/hd/opengl/uniforms/UBOLights.java index 9edea938c0..1f7d6f21a5 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOLights.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOLights.java @@ -1,6 +1,6 @@ package rs117.hd.opengl.uniforms; -import rs117.hd.utils.buffer.GLBuffer; +import rs117.hd.utils.opengl.buffer.GLBuffer; import static org.lwjgl.opengl.GL33C.*; diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOMaterials.java b/src/main/java/rs117/hd/opengl/uniforms/UBOMaterials.java index b2df0e87f4..d7c40a2932 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOMaterials.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOMaterials.java @@ -5,7 +5,7 @@ import rs117.hd.HdPlugin; import rs117.hd.model.ModelPusher; import rs117.hd.scene.materials.Material; -import rs117.hd.utils.buffer.GLBuffer; +import rs117.hd.utils.opengl.buffer.GLBuffer; import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.utils.MathUtils.*; diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOUI.java b/src/main/java/rs117/hd/opengl/uniforms/UBOUI.java index 084af5d386..d9332ac483 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOUI.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOUI.java @@ -1,6 +1,6 @@ package rs117.hd.opengl.uniforms; -import rs117.hd.utils.buffer.GLBuffer; +import rs117.hd.utils.opengl.buffer.GLBuffer; import static org.lwjgl.opengl.GL33C.*; diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOWaterTypes.java b/src/main/java/rs117/hd/opengl/uniforms/UBOWaterTypes.java index 52f42fd983..f0c9e7b043 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOWaterTypes.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOWaterTypes.java @@ -2,7 +2,7 @@ import rs117.hd.HdPlugin; import rs117.hd.scene.water_types.WaterType; -import rs117.hd.utils.buffer.GLBuffer; +import rs117.hd.utils.opengl.buffer.GLBuffer; import static org.lwjgl.opengl.GL33C.*; diff --git a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java index af651ee8b0..ef51698086 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java @@ -10,8 +10,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.lwjgl.BufferUtils; -import rs117.hd.utils.buffer.GLBuffer; -import rs117.hd.utils.buffer.SharedGLBuffer; +import rs117.hd.utils.opengl.buffer.GLBuffer; +import rs117.hd.utils.opengl.buffer.SharedGLBuffer; import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.utils.MathUtils.*; diff --git a/src/main/java/rs117/hd/overlays/ShaderOverlay.java b/src/main/java/rs117/hd/overlays/ShaderOverlay.java index f01799b15b..547c41acd1 100644 --- a/src/main/java/rs117/hd/overlays/ShaderOverlay.java +++ b/src/main/java/rs117/hd/overlays/ShaderOverlay.java @@ -51,6 +51,7 @@ import rs117.hd.opengl.shader.ShaderProgram; import rs117.hd.opengl.shader.ShaderTemplate; import rs117.hd.utils.ShaderRecompile; +import rs117.hd.utils.opengl.texture.GLTexture; import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.utils.MathUtils.*; @@ -377,8 +378,8 @@ private void updateTransform() { if (isFullscreen()) { shader.uniTransform.set(0, 0, 1, 1); } else { - int[] resolution = plugin.getUiResolution(); - if (resolution == null) + GLTexture uiTex = plugin.getUiTex(); + if (uiTex == null) return; var bounds = getBounds(); // Calculate translation and scale in NDC @@ -386,8 +387,8 @@ private void updateTransform() { rect[0] += rect[0] + rect[2]; rect[1] += rect[1] + rect[3]; for (int i = 0; i < 2; i++) { - rect[i * 2] /= resolution[0]; - rect[i * 2 + 1] /= resolution[1]; + rect[i * 2] /= uiTex.getWidth(); + rect[i * 2 + 1] /= uiTex.getHeight(); rect[i] -= 1; } rect[1] *= -1; diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index d6c839a1c1..fa7b073f0b 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -29,7 +29,7 @@ public enum Timer { UPLOAD_GEOMETRY(true), UPLOAD_UI(true, "Upload UI"), COMPUTE(true), - CLEAR_SCENE(true), + CLEAR_SCENE(true, false), RENDER_SHADOWS(true), RENDER_SCENE(true), RENDER_UI(true, "Render UI"), diff --git a/src/main/java/rs117/hd/scene/MaterialManager.java b/src/main/java/rs117/hd/scene/MaterialManager.java index f3eb9f5dd1..b3e1f854b9 100644 --- a/src/main/java/rs117/hd/scene/MaterialManager.java +++ b/src/main/java/rs117/hd/scene/MaterialManager.java @@ -43,7 +43,6 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; import net.runelite.client.callback.ClientThread; -import org.lwjgl.opengl.*; import rs117.hd.HdPlugin; import rs117.hd.HdPluginConfig; import rs117.hd.model.ModelPusher; @@ -54,10 +53,12 @@ import rs117.hd.utils.HDVariables; import rs117.hd.utils.Props; import rs117.hd.utils.ResourcePath; +import rs117.hd.utils.opengl.texture.GLTexture; +import rs117.hd.utils.opengl.texture.GLTextureFormat; +import rs117.hd.utils.opengl.texture.GLTextureParams; +import rs117.hd.utils.opengl.texture.GLTextureType; -import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPlugin.TEXTURE_UNIT_GAME; -import static rs117.hd.utils.MathUtils.*; import static rs117.hd.utils.ResourcePath.path; @Slf4j @@ -110,8 +111,7 @@ public static class TextureLayer { public static Material[] MATERIALS; public static Material[] VANILLA_TEXTURE_MAPPING; - private int texMaterialTextureArray; - private int[] textureResolution; + private GLTexture texMaterialTextureArray; public final List textureLayers = new ArrayList<>(); private FileWatcher.UnregisterCallback fileWatcher; @@ -125,9 +125,9 @@ public void shutDown() { fileWatcher.unregister(); fileWatcher = null; - if (texMaterialTextureArray != 0) - glDeleteTextures(texMaterialTextureArray); - texMaterialTextureArray = 0; + if (texMaterialTextureArray != null) + texMaterialTextureArray.delete(); + texMaterialTextureArray = null; textureLayers.clear(); if (uboMaterials != null) @@ -380,35 +380,24 @@ private void swapMaterials(Material[] parsedMaterials) { mat.textureLayer = mat.resolveTextureOwner().textureLayer; int textureSize = config.textureResolution().getSize(); - textureResolution = ivec(textureSize, textureSize); - glActiveTexture(TEXTURE_UNIT_GAME); - if (texMaterialTextureArray == 0 || previousLayerCount != textureLayers.size()) { - if (texMaterialTextureArray != 0) - glDeleteTextures(texMaterialTextureArray); - texMaterialTextureArray = glGenTextures(); - glBindTexture(GL_TEXTURE_2D_ARRAY, texMaterialTextureArray); + if (texMaterialTextureArray == null || previousLayerCount != textureLayers.size()) { + if (texMaterialTextureArray != null) + texMaterialTextureArray.delete(); + + int anisotropySamples = config.anisotropicFilteringLevel(); + texMaterialTextureArray = new GLTexture(textureSize, textureSize, textureLayers.size(), GLTextureFormat.SRGB8_ALPHA8, + GLTextureParams.DEFAULT() + .setType(GLTextureType.TEXTURE2D_ARRAY) + .setTextureUnit(TEXTURE_UNIT_GAME) + .setGenerateMipmaps(anisotropySamples > 0) + .setAnisotropySamples(anisotropySamples) + .setImmutable(true) + .setDebugName("Material Textures Array")); // Since we're reallocating the texture array, all layers need to be reuploaded for (var layer : textureLayers) layer.needsUpload = true; - - log.debug("Allocating {}x{} texture array with {} layers", textureSize, textureSize, textureLayers.size()); - int mipLevels = 1 + floor(log2(textureSize)); - int format = GL_SRGB8_ALPHA8; - if (HdPlugin.GL_CAPS.glTexStorage3D != 0) { - ARBTextureStorage.glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipLevels, format, textureSize, textureSize, textureLayers.size()); - } else { - // Allocate each mip level separately - for (int i = 0; i < mipLevels; i++) { - int size = textureSize >> i; - glTexImage3D(GL_TEXTURE_2D_ARRAY, i, format, size, size, textureLayers.size(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - } - } - - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT); } - textureManager.setAnisotropicFilteringLevel(); uploadTextures(); @@ -460,7 +449,7 @@ private void invalidateMaterials(Material[] materials) { public void uploadTextures() { assert client.isClientThread(); - if (texMaterialTextureArray == 0) + if (texMaterialTextureArray == null) return; // Set brightness to 1 to upload unmodified vanilla textures @@ -468,7 +457,6 @@ public void uploadTextures() { double vanillaBrightness = textureProvider.getBrightness(); textureProvider.setBrightness(1); - boolean uploadedAnything = false; for (var layer : textureLayers) { if (!layer.needsUpload) continue; @@ -479,12 +467,7 @@ public void uploadTextures() { continue; try { - if (!uploadedAnything) { - glActiveTexture(TEXTURE_UNIT_GAME); - glBindTexture(GL_TEXTURE_2D_ARRAY, texMaterialTextureArray); - uploadedAnything = true; - } - textureManager.uploadTexture(GL_TEXTURE_2D_ARRAY, material.textureLayer, textureResolution, image); + textureManager.uploadTexture(texMaterialTextureArray, material.textureLayer, image); } catch (Exception ex) { log.error("Failed to upload texture {}:", material, ex); } @@ -492,9 +475,7 @@ public void uploadTextures() { // Reset the texture brightness textureProvider.setBrightness(vanillaBrightness); - - if (uploadedAnything) - glGenerateMipmap(GL_TEXTURE_2D_ARRAY); + texMaterialTextureArray.generateMipMaps(); } private static void checkForReplacementLoops(Material[] materials) { diff --git a/src/main/java/rs117/hd/scene/SceneContext.java b/src/main/java/rs117/hd/scene/SceneContext.java index 372acf4dcf..a1cb0cb8cc 100644 --- a/src/main/java/rs117/hd/scene/SceneContext.java +++ b/src/main/java/rs117/hd/scene/SceneContext.java @@ -19,8 +19,8 @@ import rs117.hd.scene.materials.Material; import rs117.hd.scene.tile_overrides.TileOverrideVariables; import rs117.hd.utils.HDUtils; -import rs117.hd.utils.buffer.GpuFloatBuffer; -import rs117.hd.utils.buffer.GpuIntBuffer; +import rs117.hd.utils.opengl.buffer.GpuFloatBuffer; +import rs117.hd.utils.opengl.buffer.GpuIntBuffer; import static net.runelite.api.Constants.*; import static net.runelite.api.Constants.SCENE_SIZE; diff --git a/src/main/java/rs117/hd/scene/TextureManager.java b/src/main/java/rs117/hd/scene/TextureManager.java index 2bae723da6..3ac33cc557 100644 --- a/src/main/java/rs117/hd/scene/TextureManager.java +++ b/src/main/java/rs117/hd/scene/TextureManager.java @@ -39,12 +39,12 @@ import net.runelite.api.*; import net.runelite.client.callback.ClientThread; import org.lwjgl.BufferUtils; -import org.lwjgl.opengl.*; import rs117.hd.HdPluginConfig; import rs117.hd.utils.Props; import rs117.hd.utils.ResourcePath; +import rs117.hd.utils.opengl.texture.GLTexture; +import rs117.hd.utils.opengl.texture.GLTextureFormat; -import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.utils.MathUtils.*; import static rs117.hd.utils.ResourcePath.path; @@ -184,57 +184,30 @@ public BufferedImage loadTexture(String filename) { return null; } - public void uploadTexture(int target, int textureLayer, int[] textureSize, BufferedImage image) { + public void uploadTexture(GLTexture texture, int textureLayer, BufferedImage image) { assert client.isClientThread() : "Not thread safe"; // Allocate resources for storing temporary image data - int numPixels = product(textureSize); + int numPixels = product(texture.getWidth(), texture.getHeight()); if (pixelBuffer == null || pixelBuffer.capacity() < numPixels) pixelBuffer = BufferUtils.createIntBuffer(numPixels); - if (scaledImage == null || scaledImage.getWidth() != textureSize[0] || scaledImage.getHeight() != textureSize[1]) - scaledImage = new BufferedImage(textureSize[0], textureSize[1], BufferedImage.TYPE_INT_ARGB); + if (scaledImage == null || scaledImage.getWidth() != texture.getWidth() || scaledImage.getHeight() != texture.getHeight()) + scaledImage = new BufferedImage(texture.getWidth(), texture.getHeight(), BufferedImage.TYPE_INT_ARGB); // TODO: scale and transform on the GPU for better performance (would save 400+ ms) AffineTransform t = new AffineTransform(); if (image != vanillaImage) { // Flip non-vanilla textures horizontally to match vanilla UV orientation - t.translate(textureSize[1], 0); + t.translate(texture.getHeight(), 0); t.scale(-1, 1); } - t.scale((double) textureSize[0] / image.getWidth(), (double) textureSize[1] / image.getHeight()); + t.scale((double) texture.getWidth() / image.getWidth(), (double) texture.getHeight() / image.getHeight()); AffineTransformOp scaleOp = new AffineTransformOp(t, AffineTransformOp.TYPE_BICUBIC); scaleOp.filter(image, scaledImage); int[] pixels = ((DataBufferInt) scaledImage.getRaster().getDataBuffer()).getData(); pixelBuffer.put(pixels).flip(); - // Go from TYPE_4BYTE_ABGR in the BufferedImage to RGBA - glTexSubImage3D( - target, 0, 0, 0, - textureLayer, textureSize[0], textureSize[1], 1, - GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer - ); - } - - public void setAnisotropicFilteringLevel() { - int level = config.anisotropicFilteringLevel(); - if (level == 0) { - //level = 0 means no mipmaps and no anisotropic filtering - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } else { - // level = 1 means with mipmaps but without anisotropic filtering GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT defaults to 1.0 which is off - // level > 1 enables anisotropic filtering. It's up to the vendor what the values mean - // Even if anisotropic filtering isn't supported, mipmaps will be enabled with any level >= 1 - // Trilinear filtering is used for HD textures as linear filtering produces noisy textures - // that are very noticeable on terrain - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } - - if (GL.getCapabilities().GL_EXT_texture_filter_anisotropic) { - final float maxSamples = glGetFloat(EXTTextureFilterAnisotropic.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT); - glTexParameterf(GL_TEXTURE_2D_ARRAY, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, clamp(level, 1, maxSamples)); - } + texture.uploadSubPixels3D(textureLayer, texture.getWidth(), texture.getHeight(), pixelBuffer, GLTextureFormat.BGRA8); } } diff --git a/src/main/java/rs117/hd/utils/buffer/GLBuffer.java b/src/main/java/rs117/hd/utils/opengl/buffer/GLBuffer.java similarity index 98% rename from src/main/java/rs117/hd/utils/buffer/GLBuffer.java rename to src/main/java/rs117/hd/utils/opengl/buffer/GLBuffer.java index 4557f09c53..78d486dfd7 100644 --- a/src/main/java/rs117/hd/utils/buffer/GLBuffer.java +++ b/src/main/java/rs117/hd/utils/opengl/buffer/GLBuffer.java @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package rs117.hd.utils.buffer; +package rs117.hd.utils.opengl.buffer; import java.nio.ByteBuffer; import java.nio.FloatBuffer; @@ -103,8 +103,9 @@ public void ensureCapacity(long byteOffset, long numBytes) { if (log.isDebugEnabled() && HdPlugin.GL_CAPS.OpenGL43) { GL43C.glObjectLabel(GL43C.GL_BUFFER, id, name); - checkGLErrors(); } + + checkGLErrors(() -> name); } public void upload(ByteBuffer data) { diff --git a/src/main/java/rs117/hd/utils/buffer/GpuFloatBuffer.java b/src/main/java/rs117/hd/utils/opengl/buffer/GpuFloatBuffer.java similarity index 98% rename from src/main/java/rs117/hd/utils/buffer/GpuFloatBuffer.java rename to src/main/java/rs117/hd/utils/opengl/buffer/GpuFloatBuffer.java index 0cdd4f2b14..67cb3ed562 100644 --- a/src/main/java/rs117/hd/utils/buffer/GpuFloatBuffer.java +++ b/src/main/java/rs117/hd/utils/opengl/buffer/GpuFloatBuffer.java @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package rs117.hd.utils.buffer; +package rs117.hd.utils.opengl.buffer; import java.nio.FloatBuffer; import org.lwjgl.system.MemoryUtil; diff --git a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java b/src/main/java/rs117/hd/utils/opengl/buffer/GpuIntBuffer.java similarity index 98% rename from src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java rename to src/main/java/rs117/hd/utils/opengl/buffer/GpuIntBuffer.java index 7389e05a0e..17643f234d 100644 --- a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java +++ b/src/main/java/rs117/hd/utils/opengl/buffer/GpuIntBuffer.java @@ -22,7 +22,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package rs117.hd.utils.buffer; +package rs117.hd.utils.opengl.buffer; import java.nio.IntBuffer; import org.lwjgl.system.MemoryUtil; diff --git a/src/main/java/rs117/hd/utils/buffer/SharedGLBuffer.java b/src/main/java/rs117/hd/utils/opengl/buffer/SharedGLBuffer.java similarity index 96% rename from src/main/java/rs117/hd/utils/buffer/SharedGLBuffer.java rename to src/main/java/rs117/hd/utils/opengl/buffer/SharedGLBuffer.java index 35c246bef6..8468e3f4f1 100644 --- a/src/main/java/rs117/hd/utils/buffer/SharedGLBuffer.java +++ b/src/main/java/rs117/hd/utils/opengl/buffer/SharedGLBuffer.java @@ -1,4 +1,4 @@ -package rs117.hd.utils.buffer; +package rs117.hd.utils.opengl.buffer; import java.nio.IntBuffer; import rs117.hd.opengl.compute.OpenCLManager; diff --git a/src/main/java/rs117/hd/utils/opengl/texture/GLAttachmentSlot.java b/src/main/java/rs117/hd/utils/opengl/texture/GLAttachmentSlot.java new file mode 100644 index 0000000000..9a26dc7a44 --- /dev/null +++ b/src/main/java/rs117/hd/utils/opengl/texture/GLAttachmentSlot.java @@ -0,0 +1,111 @@ +package rs117.hd.utils.opengl.texture; + +import static org.lwjgl.opengl.GL11.GL_BACK_LEFT; +import static org.lwjgl.opengl.GL11.GL_BACK_RIGHT; +import static org.lwjgl.opengl.GL11.GL_FRONT_LEFT; +import static org.lwjgl.opengl.GL11.GL_FRONT_RIGHT; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT0; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT1; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT10; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT11; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT12; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT13; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT14; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT15; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT16; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT17; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT18; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT19; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT2; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT20; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT21; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT22; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT23; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT24; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT25; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT26; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT27; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT28; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT29; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT3; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT30; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT31; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT4; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT5; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT6; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT7; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT8; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT9; +import static org.lwjgl.opengl.GL30C.GL_DEPTH_ATTACHMENT; +import static org.lwjgl.opengl.GL30C.GL_DEPTH_STENCIL_ATTACHMENT; +import static org.lwjgl.opengl.GL30C.GL_STENCIL_ATTACHMENT; + +public enum GLAttachmentSlot { + COLOR0(GL_COLOR_ATTACHMENT0), + COLOR1(GL_COLOR_ATTACHMENT1), + COLOR2(GL_COLOR_ATTACHMENT2), + COLOR3(GL_COLOR_ATTACHMENT3), + COLOR4(GL_COLOR_ATTACHMENT4), + COLOR5(GL_COLOR_ATTACHMENT5), + COLOR6(GL_COLOR_ATTACHMENT6), + COLOR7(GL_COLOR_ATTACHMENT7), + COLOR8(GL_COLOR_ATTACHMENT8), + COLOR9(GL_COLOR_ATTACHMENT9), + COLOR10(GL_COLOR_ATTACHMENT10), + COLOR11(GL_COLOR_ATTACHMENT11), + COLOR12(GL_COLOR_ATTACHMENT12), + COLOR13(GL_COLOR_ATTACHMENT13), + COLOR14(GL_COLOR_ATTACHMENT14), + COLOR15(GL_COLOR_ATTACHMENT15), + COLOR16(GL_COLOR_ATTACHMENT16), + COLOR17(GL_COLOR_ATTACHMENT17), + COLOR18(GL_COLOR_ATTACHMENT18), + COLOR19(GL_COLOR_ATTACHMENT19), + COLOR20(GL_COLOR_ATTACHMENT20), + COLOR21(GL_COLOR_ATTACHMENT21), + COLOR22(GL_COLOR_ATTACHMENT22), + COLOR23(GL_COLOR_ATTACHMENT23), + COLOR24(GL_COLOR_ATTACHMENT24), + COLOR25(GL_COLOR_ATTACHMENT25), + COLOR26(GL_COLOR_ATTACHMENT26), + COLOR27(GL_COLOR_ATTACHMENT27), + COLOR28(GL_COLOR_ATTACHMENT28), + COLOR29(GL_COLOR_ATTACHMENT29), + COLOR30(GL_COLOR_ATTACHMENT30), + COLOR31(GL_COLOR_ATTACHMENT31), + + DEPTH(GL_DEPTH_ATTACHMENT, true), + STENCIL(GL_STENCIL_ATTACHMENT), + DEPTH_STENCIL(GL_DEPTH_STENCIL_ATTACHMENT), + + FRONT_LEFT(GL_FRONT_LEFT, true), + FRONT_RIGHT(GL_FRONT_RIGHT, true), + BACK_LEFT(GL_BACK_LEFT, true), + BACK_RIGHT(GL_BACK_RIGHT, true); + + public final int glEnum; + public final boolean defaultFrameBufferSupport; + + GLAttachmentSlot(int glEnum) { + this.glEnum = glEnum; + this.defaultFrameBufferSupport = false; + } + + GLAttachmentSlot(int glEnum, boolean defaultFrameBufferSupport) { + this.glEnum = glEnum; + this.defaultFrameBufferSupport = defaultFrameBufferSupport; + } + + public boolean isDepth() { + return this == DEPTH || this == STENCIL || this == DEPTH_STENCIL; + } + + public static GLAttachmentSlot fromGLEnum(int glEnum) { + for (GLAttachmentSlot slot : values()) { + if (slot.glEnum == glEnum) { + return slot; + } + } + return GLAttachmentSlot.COLOR0; // Default to Zero + } +} diff --git a/src/main/java/rs117/hd/utils/opengl/texture/GLFrameBuffer.java b/src/main/java/rs117/hd/utils/opengl/texture/GLFrameBuffer.java new file mode 100644 index 0000000000..0ecf69f60a --- /dev/null +++ b/src/main/java/rs117/hd/utils/opengl/texture/GLFrameBuffer.java @@ -0,0 +1,576 @@ +package rs117.hd.utils.opengl.texture; + +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.*; +import rs117.hd.HdPlugin; +import rs117.hd.opengl.GLRenderState; + +import static org.lwjgl.opengl.GL11.GL_TEXTURE; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_HEIGHT; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_INTERNAL_FORMAT; +import static org.lwjgl.opengl.GL11.GL_TEXTURE_WIDTH; +import static org.lwjgl.opengl.GL11.glBindTexture; +import static org.lwjgl.opengl.GL11.glClearColor; +import static org.lwjgl.opengl.GL11.glClearDepth; +import static org.lwjgl.opengl.GL11.glGetTexLevelParameteriv; +import static org.lwjgl.opengl.GL11C.glDisable; +import static org.lwjgl.opengl.GL11C.glEnable; +import static org.lwjgl.opengl.GL13C.GL_MULTISAMPLE; +import static org.lwjgl.opengl.GL21.GL_SRGB; +import static org.lwjgl.opengl.GL30.GL_COLOR_BUFFER_BIT; +import static org.lwjgl.opengl.GL30.GL_DEPTH_BUFFER_BIT; +import static org.lwjgl.opengl.GL30.GL_DRAW_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_COMPLETE; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_DEFAULT; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_UNDEFINED; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_UNSUPPORTED; +import static org.lwjgl.opengl.GL30.GL_NEAREST; +import static org.lwjgl.opengl.GL30.GL_NONE; +import static org.lwjgl.opengl.GL30.GL_READ_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER; +import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER_HEIGHT; +import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER_INTERNAL_FORMAT; +import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER_SAMPLES; +import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER_WIDTH; +import static org.lwjgl.opengl.GL30.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL30.glBindFramebuffer; +import static org.lwjgl.opengl.GL30.glBindRenderbuffer; +import static org.lwjgl.opengl.GL30.glBlitFramebuffer; +import static org.lwjgl.opengl.GL30.glCheckFramebufferStatus; +import static org.lwjgl.opengl.GL30.glClear; +import static org.lwjgl.opengl.GL30.glDeleteFramebuffers; +import static org.lwjgl.opengl.GL30.glDrawBuffer; +import static org.lwjgl.opengl.GL30.glDrawBuffers; +import static org.lwjgl.opengl.GL30.glFramebufferTextureLayer; +import static org.lwjgl.opengl.GL30.glGenFramebuffers; +import static org.lwjgl.opengl.GL30.glGetFramebufferAttachmentParameteriv; +import static org.lwjgl.opengl.GL30.glGetRenderbufferParameteriv; +import static org.lwjgl.opengl.GL30.glReadBuffer; +import static org.lwjgl.opengl.GL30.glViewport; +import static rs117.hd.HdPlugin.checkGLErrors; + +@Slf4j +public class GLFrameBuffer { + @Getter + private final GLFrameBufferDesc descriptor; + + private GLFrameBufferAttachment[] colorAttachments; + private GLFrameBufferAttachment depthAttachment; + private int[] drawBuffers; + + @Getter + private int fboId = -1; + @Getter + private boolean wrapper = false; + + private boolean internallyBinded = false; + private float depthClearValue = 0.0f; + private float[] colorClearValue = {0.0f, 0.0f, 0.0f, 1.0f}; + + private final StringBuilder sb = new StringBuilder(); + + private GLFrameBuffer() { + descriptor = new GLFrameBufferDesc(); + } + + public GLFrameBuffer(GLFrameBufferDesc descriptor) { + this.descriptor = descriptor; + create(); + } + + public boolean isCreated() {return fboId != -1; } + + public boolean create() { + if (isCreated()) { + return false; + } + + fboId = glGenFramebuffers(); + bind(true); + + if (HdPlugin.GL_CAPS.OpenGL43 && !descriptor.debugName.isEmpty()) { + GL43C.glObjectLabel(GL_FRAMEBUFFER, fboId, descriptor.debugName); + checkGLErrors(() -> descriptor.debugName); + } + + if(!descriptor.colorDescriptors.isEmpty()) { + descriptor.colorDescriptors.sort(Comparator.comparingInt(a -> a.slot.glEnum)); + + colorAttachments = new GLFrameBufferAttachment[descriptor.colorDescriptors.size()]; + drawBuffers = new int[colorAttachments.length]; + for(int i = 0; i < descriptor.colorDescriptors.size(); i++) { + colorAttachments[i] = new GLFrameBufferAttachment(this).create( descriptor.colorDescriptors.get(i)); + if(colorAttachments[i] != null) { + drawBuffers[i] = colorAttachments[i].slot.glEnum; + } + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + } + glDrawBuffers(drawBuffers); + } else { + // No color attachments; must specify NONE explicitly + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + } + + if(descriptor.depthDescriptor != null) { + depthAttachment = new GLFrameBufferAttachment(this).create(descriptor.depthDescriptor); + } + + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + + int status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + String reason; + switch (status) { + case GL_FRAMEBUFFER_UNDEFINED: reason = "GL_FRAMEBUFFER_UNDEFINED"; break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: reason = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: reason = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: reason = "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"; break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: reason = "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"; break; + case GL_FRAMEBUFFER_UNSUPPORTED: reason = "GL_FRAMEBUFFER_UNSUPPORTED"; break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: reason = "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"; break; + default: reason = "UNKNOWN_ERROR_" + status; break; + }; + log.error("Framebuffer validation failed: {}", reason); + delete(); + return false; + } + + unbind(true); + + return true; + } + + public boolean bind() { return bind(false);} + + private boolean bind(boolean internal) { + if(!GLRenderState.frameBuffer.isActive(this)) { + glViewport(0, 0, descriptor.width, descriptor.height); + if (descriptor.samples > 1) { + glEnable(GL_MULTISAMPLE); + } else { + glDisable(GL_MULTISAMPLE); + } + + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + internallyBinded = internal; + + GLRenderState.frameBuffer.push(this); + return true; + } + return false; + } + + public boolean bind(GLAttachmentSlot slot) { + if(bind()) { + glDrawBuffer(slot.glEnum); + } + return false; + } + + public void bindLayer(GLAttachmentSlot slot, int layer) { + bind(); + GLFrameBufferAttachment att = getColorAttachment(slot); + if(att != null && layer >= 0 && att.texture.depth < layer) { + glFramebufferTextureLayer(GL_FRAMEBUFFER, att.slot.glEnum, att.texture.getId(), 0, layer); + } + } + + public void unbind() { unbind(false);} + + public void unbind(boolean internal) { + if(GLRenderState.frameBuffer.isActive(this)) { + if(internal && !internallyBinded) { + return; // Texture was bound outside of class, so should be left alone + } + internallyBinded = false; + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + GLRenderState.frameBuffer.pop(); + } + } + + public boolean resize(int newWidth, int newHeight) { + return resize(newWidth, newHeight, false); + } + + public boolean resize(int newWidth, int newHeight, boolean recreate) { + if (isCreated() && newWidth == descriptor.width && newHeight == descriptor.height) + return false; + + descriptor.width = newWidth; + descriptor.height = newHeight; + + if(recreate) { + delete(); + create(); + } else { + if (colorAttachments != null) { + for (GLFrameBufferAttachment att : colorAttachments) { + att.resize(); + } + } + + if (depthAttachment != null) { + depthAttachment.resize(); + } + } + + return true; + } + + public int getWidth() { + return descriptor.width; + } + + public int getHeight() { + return descriptor.height; + } + + public int getDepth() { + return descriptor.depth; + } + + public void delete() { + if (!isCreated() || wrapper) return; + + if (fboId != 0) { + glDeleteFramebuffers(fboId); + fboId = 0; + } + + // Delete color renderBuffers + if(colorAttachments != null) { + for(GLFrameBufferAttachment att : colorAttachments) { + att.delete(); + } + colorAttachments = null; + } + + if(depthAttachment != null) { + depthAttachment.delete(); + depthAttachment = null; + } + + drawBuffers = null; + } + + private GLFrameBufferAttachment getColorAttachment(GLAttachmentSlot slot) { + for(GLFrameBufferAttachment att : colorAttachments) { + if(att.slot == slot) { + return att; + } + } + return null; + } + + public GLTexture getColorTexture(GLAttachmentSlot slot) { + GLFrameBufferAttachment colorAtt = getColorAttachment(slot); + return colorAtt != null ? colorAtt.texture : null; + } + + public GLTexture getDepthTexture() { + return depthAttachment.texture; + } + + private void clearInternal(boolean clearColor, boolean clearDepth) { + if (!isCreated()) return; + if (!clearColor && !clearDepth) return; + if (clearColor && colorAttachments == null) clearColor = false; + if (clearDepth && depthAttachment == null) clearDepth = false; + if (!clearColor && !clearDepth) return; + + try { + if (log.isDebugEnabled() && HdPlugin.GL_CAPS.OpenGL43 && !descriptor.debugName.isEmpty()) { + sb.setLength(0); + sb.append(clearColor && clearDepth ? "Clear(Color, Depth) - " : clearColor ? "Clear(Color) - " : "Clear(Depth) - "); + sb.append(descriptor.debugName); + GL43C.glPushDebugGroup(GL43C.GL_DEBUG_SOURCE_APPLICATION, -1, sb); + } + + bind(true); + int clearMask = 0; + + if (clearColor) { + glDrawBuffers(drawBuffers); + glClearColor(colorClearValue[0], colorClearValue[1], colorClearValue[2], colorClearValue[3]); + clearMask |= GL_COLOR_BUFFER_BIT; + } + + if (clearDepth) { + glClearDepth(depthClearValue); + clearMask |= GL_DEPTH_BUFFER_BIT; + } + + glClear(clearMask); + + unbind(true); + } finally { + if (log.isDebugEnabled() && HdPlugin.GL_CAPS.OpenGL43 && !descriptor.debugName.isEmpty()) { + GL43C.glPopDebugGroup(); + } + } + } + + public void clearColor() { + clearColor(0.0f, 0.0f, 0.0f, 0.0f); + } + + public void clearColor(float r, float g, float b, float a) { + colorClearValue[0] = r; + colorClearValue[1] = g; + colorClearValue[2] = b; + colorClearValue[3] = a; + clearInternal(true, false); + } + + public void clearDepth(float depth) { + depthClearValue = depth; + clearInternal(false, true); + } + + public void clear(float r, float g, float b, float a, float depth) { + colorClearValue[0] = r; + colorClearValue[1] = g; + colorClearValue[2] = b; + colorClearValue[3] = a; + depthClearValue = depth; + clearInternal(true, true); + } + + private void resolveMSAA(GLAttachmentSlot slot) { + if (descriptor.samples <= 1 || colorAttachments == null) return; + + GLFrameBufferAttachment att = getColorAttachment(slot); + if(att != null && att.resolveFboId != 0) { + try { + if (log.isDebugEnabled() && HdPlugin.GL_CAPS.OpenGL43 && !descriptor.debugName.isEmpty()) { + GL43C.glPushDebugGroup(GL43C.GL_DEBUG_SOURCE_APPLICATION, -1, "MSAA Resolve"); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, fboId); + glReadBuffer(att.slot.glEnum); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, att.resolveFboId); + glDrawBuffer(att.slot.glEnum); + + glBlitFramebuffer( + 0, 0, descriptor.width, descriptor.height, + 0, 0, descriptor.width, descriptor.height, + GL_COLOR_BUFFER_BIT, + GL_NEAREST + ); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + checkGLErrors(() -> descriptor.debugName); + } finally { + if (log.isDebugEnabled() && HdPlugin.GL_CAPS.OpenGL43 && !descriptor.debugName.isEmpty()) { + GL43C.glPopDebugGroup(); + } + } + } + } + + public void blitTo(GLFrameBuffer target, GLAttachmentSlot srcSlot, GLAttachmentSlot dstSlot) { + blitTo(target, srcSlot, dstSlot, 0, 0, target.descriptor.width, target.descriptor.height); + } + + public void blitTo(GLFrameBuffer target, GLAttachmentSlot srcSlot, GLAttachmentSlot dstSlot, int dstX, int dstY, int dstWidth, int dstHeight) { + blitTo(target, srcSlot, dstSlot, dstX, dstY, dstWidth, dstHeight, GL_NEAREST); + } + + public void blitTo(GLFrameBuffer target, GLAttachmentSlot srcSlot, GLAttachmentSlot dstSlot, int dstX, int dstY, int dstWidth, int dstHeight, int glFilterMode) { + GLFrameBufferAttachment srcAtt = getColorAttachment(srcSlot); + GLFrameBufferAttachment dstAtt = target.getColorAttachment(dstSlot); + + if(srcAtt != null && dstAtt != null && srcAtt.format == dstAtt.format) { + try { + if (log.isDebugEnabled() && HdPlugin.GL_CAPS.OpenGL43 && !descriptor.debugName.isEmpty()) { + sb.setLength(0); + sb.append("Blit "); + sb.append(descriptor.debugName); + if(target.descriptor.debugName.isEmpty()) { + GL43C.glPushDebugGroup(GL43C.GL_DEBUG_SOURCE_APPLICATION, -1, sb); + } else { + sb.append(" -> "); + sb.append(target.descriptor.debugName); + GL43C.glPushDebugGroup(GL43C.GL_DEBUG_SOURCE_APPLICATION, -1, sb); + } + } + + int readFbo = (descriptor.samples > 1 && srcAtt.resolveFboId != 0) ? srcAtt.resolveFboId : this.fboId; + if (readFbo == srcAtt.resolveFboId) { + resolveMSAA(srcSlot); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo); + glReadBuffer(srcAtt.slot.glEnum); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target.getFboId()); + glDrawBuffer(dstAtt.slot.glEnum); + + glBlitFramebuffer( + 0, 0, descriptor.width, descriptor.height, + dstX, dstY, dstWidth, dstHeight, + srcAtt.format.isDepth() ? GL_DEPTH_BUFFER_BIT : GL_COLOR_BUFFER_BIT, + glFilterMode + ); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + checkGLErrors(() -> String.format("Blit %s (%s) -> %s (%s)", descriptor.debugName, srcSlot.toString(), target.descriptor.debugName, dstSlot.toString())); + } finally { + if (log.isDebugEnabled() && HdPlugin.GL_CAPS.OpenGL43 && !descriptor.debugName.isEmpty()) { + GL43C.glPopDebugGroup(); + } + } + } + } + + public static GLFrameBuffer wrap(int fboId, String debugName) { + GLFrameBuffer wrapper = new GLFrameBuffer(); + wrapper.fboId = fboId; + wrapper.descriptor.debugName = debugName; + + wrapper.bind(true); + + IntBuffer param = BufferUtils.createIntBuffer(1); + List colorAttachments = new ArrayList<>(); + + boolean isDefaultFrameBuffer = fboId == 0; + for (GLAttachmentSlot slot : GLAttachmentSlot.values()) { + if (slot.isDepth() || slot.defaultFrameBufferSupport != isDefaultFrameBuffer) { + continue; + } + + int glSlot = slot.glEnum; + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, glSlot, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, param); + int objType = param.get(0); + param.rewind(); + + if(objType == GL_RENDERBUFFER || objType == GL_TEXTURE) { + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, glSlot, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, param); + int objectName = param.get(0); + param.rewind(); + + if (objType == GL_RENDERBUFFER) { + glBindRenderbuffer(GL_RENDERBUFFER, objectName); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, param); + wrapper.descriptor.width = param.get(0); + param.rewind(); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, param); + wrapper.descriptor.height = param.get(0); + param.rewind(); + + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, param); + wrapper.descriptor.samples = param.get(0); + param.rewind(); + + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, param); + int internalFormat = param.get(0); + param.rewind(); + + GLFrameBufferDesc.AttachmentDescriptor attDesc = new GLFrameBufferDesc.AttachmentDescriptor(); + attDesc.slot = slot; + attDesc.format = GLTextureFormat.fromInternalFormat(internalFormat); + + wrapper.descriptor.colorDescriptors.add(attDesc); + + GLFrameBufferAttachment att = new GLFrameBufferAttachment(wrapper); + att.slot = slot; + att.format = attDesc.format; + att.renderBuffer = objectName; + colorAttachments.add(att); + + } else { + glBindTexture(GL_TEXTURE_2D, objectName); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, param); + wrapper.descriptor.width = param.get(0); + param.rewind(); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, param); + wrapper.descriptor.height = param.get(0); + param.rewind(); + + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, param); + int internalFormat = param.get(0); + param.rewind(); + + GLFrameBufferDesc.AttachmentDescriptor attDesc = new GLFrameBufferDesc.AttachmentDescriptor(); + attDesc.slot = slot; + attDesc.format = GLTextureFormat.fromInternalFormat(internalFormat); + + wrapper.descriptor.colorDescriptors.add(attDesc); + + GLFrameBufferAttachment att = new GLFrameBufferAttachment(wrapper); + att.slot = slot; + att.format = attDesc.format; + colorAttachments.add(att); + } + } else if(objType == GL_FRAMEBUFFER_DEFAULT) { + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, glSlot, GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE, param); + int rsize = param.get(0); + param.rewind(); + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, glSlot, GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE, param); + int gsize = param.get(0); + param.rewind(); + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, glSlot, GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE, param); + int bsize = param.get(0); + param.rewind(); + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, glSlot, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE, param); + int asize = param.get(0); + param.rewind(); + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, glSlot, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, param); + boolean isSRGB = (param.get(0) == GL_SRGB); + param.rewind(); + + GLFrameBufferDesc.AttachmentDescriptor attDesc = new GLFrameBufferDesc.AttachmentDescriptor(); + attDesc.slot = slot; + attDesc.format = GLTextureFormat.fromComponentSizes(rsize, gsize, bsize, asize, isSRGB); + wrapper.descriptor.colorDescriptors.add(attDesc); + + GLFrameBufferAttachment att = new GLFrameBufferAttachment(wrapper); + att.slot = slot; + att.format = attDesc.format; + att.renderBuffer = 0; + att.texture = null; + + colorAttachments.add(att); + } + checkGLErrors(() -> debugName); + } + + wrapper.colorAttachments = new GLFrameBufferAttachment[colorAttachments.size()]; + colorAttachments.toArray(wrapper.colorAttachments); + + wrapper.drawBuffers = new int[wrapper.colorAttachments.length]; + for(int i = 0; i < wrapper.colorAttachments.length; i++) { + wrapper.drawBuffers[i] = wrapper.colorAttachments[i].slot.glEnum; + } + + wrapper.unbind(true); + + return wrapper; + } +} diff --git a/src/main/java/rs117/hd/utils/opengl/texture/GLFrameBufferAttachment.java b/src/main/java/rs117/hd/utils/opengl/texture/GLFrameBufferAttachment.java new file mode 100644 index 0000000000..b127f54eeb --- /dev/null +++ b/src/main/java/rs117/hd/utils/opengl/texture/GLFrameBufferAttachment.java @@ -0,0 +1,174 @@ +package rs117.hd.utils.opengl.texture; + +import lombok.extern.slf4j.Slf4j; +import org.lwjgl.opengl.*; +import rs117.hd.HdPlugin; + +import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER; +import static org.lwjgl.opengl.GL30.glBindFramebuffer; +import static org.lwjgl.opengl.GL30.glBindRenderbuffer; +import static org.lwjgl.opengl.GL30.glDeleteFramebuffers; +import static org.lwjgl.opengl.GL30.glFramebufferRenderbuffer; +import static org.lwjgl.opengl.GL30.glFramebufferTexture2D; +import static org.lwjgl.opengl.GL30.glGenFramebuffers; +import static org.lwjgl.opengl.GL30.glGenRenderbuffers; +import static org.lwjgl.opengl.GL30.glRenderbufferStorageMultisample; +import static org.lwjgl.opengl.GL32.glFramebufferTexture; +import static rs117.hd.HdPlugin.checkGLErrors; + +@Slf4j +public class GLFrameBufferAttachment { + public GLFrameBuffer frameBuffer; + public GLAttachmentSlot slot; + public GLTextureFormat format; + public GLTexture texture; + public int renderBuffer; + public int resolveFboId; + + public GLFrameBufferAttachment(GLFrameBuffer frameBuffer) { + this.frameBuffer = frameBuffer; + } + + public GLFrameBufferAttachment create(GLFrameBufferDesc.AttachmentDescriptor attDesc){ + final GLFrameBufferDesc descriptor = frameBuffer.getDescriptor(); + + slot = attDesc.slot; + format = attDesc.format; + + if (descriptor.samples > 0) { + renderBuffer = glGenRenderbuffers(); + glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer); + glRenderbufferStorageMultisample( + GL_RENDERBUFFER, + descriptor.samples, + attDesc.format.internalFormat, + descriptor.width, + descriptor.height + ); + + if (HdPlugin.GL_CAPS.OpenGL43 && !descriptor.debugName.isEmpty()) { + GL43C.glObjectLabel(GL_RENDERBUFFER, renderBuffer, descriptor.debugName + " - " + attDesc.slot.toString()); + checkGLErrors(); + } + + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attDesc.slot.glEnum, GL_RENDERBUFFER, renderBuffer); + texture = new GLTexture(descriptor.width, descriptor.height, descriptor.depth, attDesc.format, attDesc.params); + + resolveFboId = glGenFramebuffers(); + glBindFramebuffer(GL_FRAMEBUFFER, resolveFboId); + + switch (attDesc.params.type) { + case TEXTURE2D: + glFramebufferTexture2D(GL_FRAMEBUFFER, attDesc.slot.glEnum, GL_TEXTURE_2D, texture.getId(), 0); + break; + case TEXTURE2D_ARRAY: + glFramebufferTexture(GL_FRAMEBUFFER, attDesc.slot.glEnum, texture.getId(), 0); + break; + default: + log.error("Unsupported GLTextureType: {}", attDesc.params.type); + break; + } + + if (HdPlugin.GL_CAPS.OpenGL43 && !descriptor.debugName.isEmpty()) { + texture.setDebugName(descriptor.debugName + " - " + attDesc.slot.toString() + " (MSAA Resolve)"); + GL43C.glObjectLabel(GL_FRAMEBUFFER, resolveFboId, descriptor.debugName + " - " + attDesc.slot.toString() + " (MSAA Resolve)"); + checkGLErrors(); + } + } else { + texture = new GLTexture(descriptor.width, descriptor.height, descriptor.depth, attDesc.format, attDesc.params); + switch (attDesc.params.type) { + case TEXTURE2D: + glFramebufferTexture2D(GL_FRAMEBUFFER, attDesc.slot.glEnum, GL_TEXTURE_2D, texture.getId(), 0); + break; + case TEXTURE2D_ARRAY: + glFramebufferTexture(GL_FRAMEBUFFER, attDesc.slot.glEnum, texture.getId(), 0); + break; + default: + log.error("GLFormat doesn't support GLTextureType: {}", attDesc.params.type); + break; + } + + if (HdPlugin.GL_CAPS.OpenGL43 && !descriptor.debugName.isEmpty()) { + texture.setDebugName(descriptor.debugName + " - " + attDesc.slot.toString()); + checkGLErrors(); + } + } + checkGLErrors(); + glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer.getFboId()); + + return this; + } + + public void resize() { + final GLFrameBufferDesc descriptor = frameBuffer.getDescriptor(); + final GLTextureParams params = texture.getTextureParams(); + + if (descriptor.samples > 0 && renderBuffer != 0) { + glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer); + glRenderbufferStorageMultisample( + GL_RENDERBUFFER, + descriptor.samples, + format.internalFormat, + descriptor.width, + descriptor.height + ); + checkGLErrors(); + + glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer.getFboId()); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, slot.glEnum, GL_RENDERBUFFER, renderBuffer); + + // Resize resolve texture + texture.resize(descriptor.width, descriptor.height, descriptor.depth); + + // Reattach to resolve framebuffer + if (resolveFboId != 0) { + glBindFramebuffer(GL_FRAMEBUFFER, resolveFboId); + switch (params.type) { + case TEXTURE2D: + glFramebufferTexture2D(GL_FRAMEBUFFER, slot.glEnum, GL_TEXTURE_2D, texture.getId(), 0); + break; + case TEXTURE2D_ARRAY: + glFramebufferTexture(GL_FRAMEBUFFER, slot.glEnum, texture.getId(), 0); + break; + default: + log.error("Unsupported GLTextureType during MSAA resize: {}", params.type); + break; + } + } + } else { + texture.resize(descriptor.width, descriptor.height, descriptor.depth); + + glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer.getFboId()); + switch (params.type) { + case TEXTURE2D: + glFramebufferTexture2D(GL_FRAMEBUFFER, slot.glEnum, GL_TEXTURE_2D, texture.getId(), 0); + break; + case TEXTURE2D_ARRAY: + glFramebufferTexture(GL_FRAMEBUFFER, slot.glEnum, texture.getId(), 0); + break; + default: + log.error("Unsupported GLTextureType during non-MSAA resize: {}", params.type); + break; + } + } + } + + public void delete() { + if (renderBuffer != 0) { + glDeleteFramebuffers(renderBuffer); + renderBuffer = 0; + } + + if (texture != null) { + texture.delete(); + texture = null; + } + + if (resolveFboId != 0) { + glDeleteFramebuffers(resolveFboId); + resolveFboId = 0; + } + } +} diff --git a/src/main/java/rs117/hd/utils/opengl/texture/GLFrameBufferDesc.java b/src/main/java/rs117/hd/utils/opengl/texture/GLFrameBufferDesc.java new file mode 100644 index 0000000000..681f4fde06 --- /dev/null +++ b/src/main/java/rs117/hd/utils/opengl/texture/GLFrameBufferDesc.java @@ -0,0 +1,71 @@ +package rs117.hd.utils.opengl.texture; + +import java.util.ArrayList; +import java.util.List; + +public class GLFrameBufferDesc { + public int width = 1; + public int height = 1; + public int depth = 1; + public int samples = 0; + public boolean shouldConstructionCreate = true; + public String debugName = ""; + + public final List colorDescriptors = new ArrayList<>(); + public AttachmentDescriptor depthDescriptor; + + public GLFrameBufferDesc setWidth(int width) { + this.width = width; + return this; + } + + public GLFrameBufferDesc setHeight(int height) { + this.height = height; + return this; + } + + public GLFrameBufferDesc setDepth(int depth) { + this.depth = depth; + return this; + } + + public GLFrameBufferDesc setMSAASamples(int samples) { + this.samples = samples; + return this; + } + + public GLFrameBufferDesc setDebugName(String debugName) { + this.debugName = debugName; + return this; + } + + public GLFrameBufferDesc setShouldConstructionCreate(boolean shouldConstructionCreate) { + this.shouldConstructionCreate = shouldConstructionCreate; + return this; + } + + public GLFrameBufferDesc setColorAttachment(GLAttachmentSlot slot, GLTextureFormat format, GLTextureParams textureParams) { + assert !slot.isDepth(); + colorDescriptors.add(new AttachmentDescriptor(slot, format, textureParams)); + return this; + } + + public GLFrameBufferDesc setDepthAttachment(GLTextureFormat format, GLTextureParams textureParams) { + depthDescriptor = new AttachmentDescriptor(GLAttachmentSlot.DEPTH, format, textureParams); + return this; + } + + public static class AttachmentDescriptor { + public GLAttachmentSlot slot; + public GLTextureFormat format; + public GLTextureParams params; + + public AttachmentDescriptor() {} + + public AttachmentDescriptor(GLAttachmentSlot slot, GLTextureFormat format, GLTextureParams params) { + this.slot = slot; + this.format = format; + this.params = params; + } + } +} diff --git a/src/main/java/rs117/hd/utils/opengl/texture/GLSamplerMode.java b/src/main/java/rs117/hd/utils/opengl/texture/GLSamplerMode.java new file mode 100644 index 0000000000..659deb815e --- /dev/null +++ b/src/main/java/rs117/hd/utils/opengl/texture/GLSamplerMode.java @@ -0,0 +1,25 @@ +package rs117.hd.utils.opengl.texture; + +import lombok.RequiredArgsConstructor; + +import static org.lwjgl.opengl.GL11.GL_LINEAR; +import static org.lwjgl.opengl.GL11.GL_LINEAR_MIPMAP_LINEAR; +import static org.lwjgl.opengl.GL11.GL_NEAREST; +import static org.lwjgl.opengl.GL11.GL_NEAREST_MIPMAP_NEAREST; +import static org.lwjgl.opengl.GL11.GL_REPEAT; +import static org.lwjgl.opengl.GL12.GL_CLAMP_TO_EDGE; + +@RequiredArgsConstructor +public enum GLSamplerMode { + NEAREST_REPEAT(GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_REPEAT, GL_REPEAT), + NEAREST_CLAMP(GL_NEAREST, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE), + + LINEAR_REPEAT(GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT), + LINEAR_CLAMP(GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + + public final int minFilter; + public final int minFilterMip; + public final int magFilter; + public final int wrapS; + public final int wrapT; +} diff --git a/src/main/java/rs117/hd/utils/opengl/texture/GLTexture.java b/src/main/java/rs117/hd/utils/opengl/texture/GLTexture.java new file mode 100644 index 0000000000..dcf6ce44d8 --- /dev/null +++ b/src/main/java/rs117/hd/utils/opengl/texture/GLTexture.java @@ -0,0 +1,416 @@ +package rs117.hd.utils.opengl.texture; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.lwjgl.opengl.*; +import rs117.hd.HdPlugin; +import rs117.hd.opengl.GLRenderState; + +import static org.lwjgl.opengl.GL11.GL_TEXTURE_BORDER_COLOR; +import static org.lwjgl.opengl.GL11.glTexImage2D; +import static org.lwjgl.opengl.GL11.glTexParameterfv; +import static org.lwjgl.opengl.GL11.glTexSubImage2D; +import static org.lwjgl.opengl.GL11C.GL_TEXTURE; +import static org.lwjgl.opengl.GL11C.glGetFloat; +import static org.lwjgl.opengl.GL11C.glTexParameterf; +import static org.lwjgl.opengl.GL12.glTexImage3D; +import static org.lwjgl.opengl.GL12.glTexSubImage3D; +import static org.lwjgl.opengl.GL13.GL_TEXTURE1; +import static org.lwjgl.opengl.GL13C.GL_TEXTURE0; +import static org.lwjgl.opengl.GL13C.glActiveTexture; +import static org.lwjgl.opengl.GL15.GL_READ_WRITE; +import static org.lwjgl.opengl.GL15.GL_STREAM_DRAW; +import static org.lwjgl.opengl.GL15.GL_WRITE_ONLY; +import static org.lwjgl.opengl.GL15.glBufferData; +import static org.lwjgl.opengl.GL15.glGenBuffers; +import static org.lwjgl.opengl.GL15C.GL_READ_ONLY; +import static org.lwjgl.opengl.GL30.GL_PIXEL_UNPACK_BUFFER; +import static org.lwjgl.opengl.GL30.GL_TEXTURE_MAG_FILTER; +import static org.lwjgl.opengl.GL30.GL_TEXTURE_MIN_FILTER; +import static org.lwjgl.opengl.GL30.GL_TEXTURE_WRAP_S; +import static org.lwjgl.opengl.GL30.GL_TEXTURE_WRAP_T; +import static org.lwjgl.opengl.GL30.glBindBuffer; +import static org.lwjgl.opengl.GL30.glBindTexture; +import static org.lwjgl.opengl.GL30.glDeleteBuffers; +import static org.lwjgl.opengl.GL30.glDeleteTextures; +import static org.lwjgl.opengl.GL30.glGenTextures; +import static org.lwjgl.opengl.GL30.glGenerateMipmap; +import static org.lwjgl.opengl.GL30.glMapBuffer; +import static org.lwjgl.opengl.GL30.glTexParameteri; +import static org.lwjgl.opengl.GL30.glUnmapBuffer; +import static org.lwjgl.opengl.GL30C.GL_TEXTURE_2D_ARRAY; +import static rs117.hd.HdPlugin.checkGLErrors; +import static rs117.hd.utils.MathUtils.*; + + +@Slf4j +public class GLTexture { + private static float MAX_TEXTURE_MAX_ANISOTROPY = -1.0f; + + @Getter + protected int id = -1; + + @Getter + protected int width; + + @Getter + protected int height; + + @Getter + protected int depth; + + @Getter + private boolean mipmapsDirty = false; + + @Getter + protected final GLTextureFormat textureFormat; + + @Getter + protected final GLTextureParams textureParams; + + private boolean internallyBinded = false; + + protected ByteBuffer mappedPBO; + protected int pboId = 0; + + + public GLTexture(int width, int height, GLTextureFormat textureFormat, GLTextureParams textureParams) { + this(width, height, 1, textureFormat, textureParams); + } + + public GLTexture(int width, int height, int depth, GLTextureFormat textureFormat, GLTextureParams textureParams) { + this.width = width; + this.height = height; + this.depth = depth; + this.textureFormat = textureFormat; + this.textureParams = textureParams != null ? textureParams : new GLTextureParams(); + + create(); + } + + public boolean isCreated() { return id != -1; } + + private GLTexture create() { + if (isCreated()) throw new IllegalStateException("Texture already created"); + + if(textureParams.textureUnit > 0) { + assert textureParams.textureUnit >= GL_TEXTURE1; + glActiveTexture(textureParams.textureUnit); + } + + id = glGenTextures(); + + bind(true); + allocateTextureStorage(); + setupSamplerFiltering(); + + if(textureParams.generateMipmaps && textureParams.anisotropySamples > 0) { + if(MAX_TEXTURE_MAX_ANISOTROPY < 0) { + MAX_TEXTURE_MAX_ANISOTROPY = glGetFloat(EXTTextureFilterAnisotropic.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT); + } + glTexParameterf(GL_TEXTURE_2D_ARRAY, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, clamp(textureParams.anisotropySamples, 1, MAX_TEXTURE_MAX_ANISOTROPY)); + } + + if(textureParams.borderColor != null) { + glTexParameterfv(textureParams.type.glTarget, GL_TEXTURE_BORDER_COLOR, textureParams.borderColor); + } + + if (textureParams.generateMipmaps) { + glGenerateMipmap(textureParams.type.glTarget); + } + + if(textureParams.imageUnit >= 0 && HdPlugin.GL_CAPS.GL_ARB_shader_image_load_store) { + boolean layered = textureParams.imageUnitLayer >= 0; + int layer = layered ? textureParams.imageUnitLayer : 0; + ARBShaderImageLoadStore.glBindImageTexture(textureParams.imageUnit, id, 0, layered, layer, textureParams.imageUnitWriteMode, textureFormat.internalFormat); + } + + if(!textureParams.debugName.isEmpty()) + setDebugName(textureParams.debugName); + + unbind(true); + + checkGLErrors(() -> textureParams.debugName); + + return this; + } + + private void allocateTextureStorage() { + int mipLevels = textureParams.generateMipmaps ? (1 + (int) Math.floor(Math.log(Math.max(width, height)) / Math.log(2))) : 1; + boolean supportStorage = depth > 1 ? HdPlugin.GL_CAPS.glTexStorage3D != 0 : HdPlugin.GL_CAPS.glTexStorage2D != 0; + if (supportStorage && textureParams.immutable) { + if (depth > 1) { + ARBTextureStorage.glTexStorage3D(textureParams.type.glTarget, mipLevels, textureFormat.internalFormat, width, height, depth); + } else { + ARBTextureStorage.glTexStorage2D(textureParams.type.glTarget, mipLevels, textureFormat.internalFormat, width, height); + } + } else { + for (int mip = 0; mip < mipLevels; mip++) { + int mipWidth = Math.max(1, width >> mip); + int mipHeight = Math.max(1, height >> mip); + + if (depth > 1) { + glTexImage3D(textureParams.type.glTarget, mip, textureFormat.internalFormat, mipWidth, mipHeight, depth, 0, textureFormat.format, textureFormat.type, 0); + } else { + glTexImage2D(textureParams.type.glTarget, mip, textureFormat.internalFormat, mipWidth, mipHeight, 0, textureFormat.format, textureFormat.type, 0); + } + } + } + checkGLErrors(() -> textureParams.debugName); + } + + private void setupSamplerFiltering() { + if(textureParams.generateMipmaps) { + glTexParameteri(textureParams.type.glTarget, GL_TEXTURE_MIN_FILTER, textureParams.sampler.minFilterMip); + } else { + glTexParameteri(textureParams.type.glTarget, GL_TEXTURE_MIN_FILTER, textureParams.sampler.minFilter); + } + glTexParameteri(textureParams.type.glTarget, GL_TEXTURE_MAG_FILTER, textureParams.sampler.magFilter); + glTexParameteri(textureParams.type.glTarget, GL_TEXTURE_WRAP_S, textureParams.sampler.wrapS); + glTexParameteri(textureParams.type.glTarget, GL_TEXTURE_WRAP_T, textureParams.sampler.wrapT); + } + + public GLTexture setDebugName(String debugName) { + if (HdPlugin.GL_CAPS.OpenGL43 && !debugName.isEmpty()) { + textureParams.debugName = debugName; + GL43C.glObjectLabel(GL_TEXTURE, id, debugName); + checkGLErrors(); + } + return this; + } + + public void setSampler(GLSamplerMode sampler) { + if(textureParams.sampler != sampler) { + textureParams.sampler = sampler; + bind(true); + setupSamplerFiltering(); + unbind(true); + } + } + + public void resize(int width, int height) { + if (!isCreated()) throw new IllegalStateException("Texture not created"); + resize(width, height, 1); + } + + public void resize(int width, int height, int depth) { + if (!isCreated()) throw new IllegalStateException("Texture not created"); + + if(this.width == width && this.width == height && this.depth == depth){ + return; + } + + this.width = width; + this.height = height; + this.depth = depth; + + if(textureParams.immutable) { + delete(); + create(); + return; + } + + bind(true); + allocateTextureStorage(); + + if(pboId != 0) { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboId); + glBufferData(GL_PIXEL_UNPACK_BUFFER, (long) width * height * depth * textureFormat.pixelSize, GL_STREAM_DRAW); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + + if (textureParams.generateMipmaps) { + glGenerateMipmap(textureParams.type.glTarget); + } + + checkGLErrors(() -> textureParams.debugName); + + unbind(true); + } + + public boolean bind() { return bind(false); } + + private boolean bind(boolean internal) { + if (!isCreated()) + throw new IllegalStateException("Texture not created"); + if(!GLRenderState.texture.isActive(this)) { + glBindTexture(textureParams.type.glTarget, id); + internallyBinded = internal; + GLRenderState.texture.push(this); + return true; + } + return false; + } + + public void unbind() { unbind(false); } + + public void unbind(boolean internal) { + if(GLRenderState.texture.isActive(this)) { + if(internal && !internallyBinded) { + return; // Texture was bound outside of class, so should be left alone + } + internallyBinded = false; + + if(textureParams.textureUnit > 0) { + glActiveTexture(GL_TEXTURE0); // Switch back to GL_Texture0 before unbinding to avoid clearing a texture Unity accidentally + } + + glBindTexture(textureParams.type.glTarget, 0); + GLRenderState.texture.pop(); + } + } + + public ByteBuffer map(int access) { + if(pboId == 0) { + pboId = glGenBuffers(); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboId); + glBufferData(GL_PIXEL_UNPACK_BUFFER, (long) width * height * depth * textureFormat.pixelSize, GL_STREAM_DRAW); + } else { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboId); + } + mappedPBO = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, access, mappedPBO); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + checkGLErrors(() -> String.format("%s Access: %s", textureParams.debugName, + access == GL_READ_ONLY ? "GL_READ_ONLY" : + access == GL_WRITE_ONLY ? "GL_WRITE_ONLY" : + access == GL_READ_WRITE ? "GL_READ_WRITE" : "UNKNOWN")); + + return mappedPBO; + } + + public void unmap(int xOffset, int yOffset, int width, int height) { + unmap(xOffset, yOffset, 0, width, height, 1); + } + + public void unmap(int xOffset, int yOffset, int zOffset, int width, int height, int depth) { + if(pboId != 0) { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboId); + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + + bind(true); + + if(this.depth > 1) { + glTexSubImage3D(textureParams.type.glTarget, 0, xOffset, yOffset, zOffset, width, height, depth, textureFormat.format, textureFormat.type, 0); + } else { + glTexSubImage2D(textureParams.type.glTarget, 0, xOffset, yOffset, width, height, textureFormat.format, textureFormat.type, 0); + } + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + if (textureParams.generateMipmaps) { + glGenerateMipmap(textureParams.type.glTarget); + } + + unbind(true); + checkGLErrors(() -> String.format("%s (%dx%dx%d %dx%dx%d)", textureParams.debugName, xOffset, yOffset, zOffset, width, height, depth)); + } + } + + public void delete() { + if (isCreated()) { + glDeleteTextures(id); + id = -1; + } + if (pboId != 0) { + glDeleteBuffers(pboId); + pboId = 0; + } + } + + public void uploadSubPixels2D(int width, int height, Buffer pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(0, 0, 0, width, height, 0, pixelData, pixelFormat); + } + + public void uploadSubPixels2D(int xOffset, int yOffset, int width, int height, Buffer pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(xOffset, yOffset, 0, width, height, 0, pixelData, pixelFormat); + } + + public void uploadSubPixels3D(int zOffset, int width, int height, Buffer pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(0, 0, zOffset, width, height, 1, pixelData, pixelFormat); + } + + public void uploadSubPixels3D(int zOffset, int width, int height, int depth, Buffer pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(0, 0, zOffset, width, height, depth, pixelData, pixelFormat); + } + + public void uploadSubPixels(int width, int height, int depth, Buffer pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(0, 0, 0, width, height, depth, pixelData, pixelFormat); + } + + public void uploadSubPixels(int xOffset, int yOffset, int zOffset, int width, int height, int depth, Buffer pixelData, GLTextureFormat pixelFormat) { + if (xOffset + width > this.width || yOffset + height > this.height || zOffset + depth > this.depth) + throw new IllegalArgumentException("Upload region exceeds texture dimensions"); + + bind(true); + + if (this.depth > 1) { + if (pixelData instanceof ByteBuffer) { + glTexSubImage3D(textureParams.type.glTarget, 0, + xOffset, yOffset, zOffset, + width, height, depth, + pixelFormat.internalFormat, pixelFormat.type, (ByteBuffer)pixelData); + } else if (pixelData instanceof ShortBuffer) { + glTexSubImage3D(textureParams.type.glTarget, 0, + xOffset, yOffset, zOffset, + width, height, depth, + pixelFormat.internalFormat, pixelFormat.type, (ShortBuffer)pixelData); + } else if (pixelData instanceof IntBuffer) { + glTexSubImage3D(textureParams.type.glTarget, 0, + xOffset, yOffset, zOffset, + width, height, depth, + pixelFormat.internalFormat, pixelFormat.type, (IntBuffer)pixelData); + } else if (pixelData instanceof FloatBuffer) { + glTexSubImage3D(textureParams.type.glTarget, 0, + xOffset, yOffset, zOffset, + width, height, depth, + pixelFormat.internalFormat, pixelFormat.type, (FloatBuffer)pixelData); + } else { + throw new IllegalArgumentException("Unsupported buffer type for glTexSubImage3D: " + pixelData.getClass()); + } + } else { + if (pixelData instanceof ByteBuffer) { + glTexSubImage2D(textureParams.type.glTarget, 0, + xOffset, yOffset, + width, height, + pixelFormat.internalFormat, pixelFormat.type, (ByteBuffer)pixelData); + } else if (pixelData instanceof ShortBuffer) { + glTexSubImage2D(textureParams.type.glTarget, 0, + xOffset, yOffset, + width, height, + pixelFormat.internalFormat, pixelFormat.type, (ShortBuffer)pixelData); + } else if (pixelData instanceof IntBuffer) { + glTexSubImage2D(textureParams.type.glTarget, 0, + xOffset, yOffset, + width, height, + pixelFormat.internalFormat, pixelFormat.type, (IntBuffer)pixelData); + } else if (pixelData instanceof FloatBuffer) { + glTexSubImage2D(textureParams.type.glTarget, 0, + xOffset, yOffset, + width, height, + pixelFormat.internalFormat, pixelFormat.type, (FloatBuffer)pixelData); + } else { + throw new IllegalArgumentException("Unsupported buffer type for glTexSubImage2D: " + pixelData.getClass()); + } + } + + unbind(true); + checkGLErrors(() -> String.format("%s (%dx%dx%d %dx%dx%d)", textureParams.debugName, xOffset, yOffset, zOffset, width, height, depth)); + + mipmapsDirty = true; + } + + public void generateMipMaps() { + if (textureParams.generateMipmaps && mipmapsDirty) { + bind(true); + glGenerateMipmap(textureParams.type.glTarget); + unbind(true); + checkGLErrors(() -> textureParams.debugName); + mipmapsDirty = false; + } + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/utils/opengl/texture/GLTextureFormat.java b/src/main/java/rs117/hd/utils/opengl/texture/GLTextureFormat.java new file mode 100644 index 0000000000..058ae7dd5a --- /dev/null +++ b/src/main/java/rs117/hd/utils/opengl/texture/GLTextureFormat.java @@ -0,0 +1,133 @@ +package rs117.hd.utils.opengl.texture; + +import lombok.RequiredArgsConstructor; + +import static org.lwjgl.opengl.GL11.GL_INT; +import static org.lwjgl.opengl.GL11.GL_SHORT; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_SHORT; +import static org.lwjgl.opengl.GL12.GL_UNSIGNED_INT_8_8_8_8_REV; +import static org.lwjgl.opengl.GL12C.GL_BGRA; +import static org.lwjgl.opengl.GL21.GL_SRGB8; +import static org.lwjgl.opengl.GL21.GL_SRGB8_ALPHA8; +import static org.lwjgl.opengl.GL30.GL_R16F; +import static org.lwjgl.opengl.GL30.GL_R16I; +import static org.lwjgl.opengl.GL30.GL_R32I; +import static org.lwjgl.opengl.GL30.GL_RED_INTEGER; +import static org.lwjgl.opengl.GL30.GL_RGBA16I; +import static org.lwjgl.opengl.GL30.GL_RGBA16UI; +import static org.lwjgl.opengl.GL30.GL_RGBA_INTEGER; +import static org.lwjgl.opengl.GL30C.GL_DEPTH_COMPONENT; +import static org.lwjgl.opengl.GL30C.GL_DEPTH_COMPONENT24; +import static org.lwjgl.opengl.GL30C.GL_DEPTH_COMPONENT32F; +import static org.lwjgl.opengl.GL30C.GL_FLOAT; +import static org.lwjgl.opengl.GL30C.GL_HALF_FLOAT; +import static org.lwjgl.opengl.GL30C.GL_R32F; +import static org.lwjgl.opengl.GL30C.GL_R8; +import static org.lwjgl.opengl.GL30C.GL_RED; +import static org.lwjgl.opengl.GL30C.GL_RGB; +import static org.lwjgl.opengl.GL30C.GL_RGB8; +import static org.lwjgl.opengl.GL30C.GL_RGBA; +import static org.lwjgl.opengl.GL30C.GL_RGBA16F; +import static org.lwjgl.opengl.GL30C.GL_RGBA32F; +import static org.lwjgl.opengl.GL30C.GL_RGBA32I; +import static org.lwjgl.opengl.GL30C.GL_RGBA8; +import static org.lwjgl.opengl.GL30C.GL_UNSIGNED_BYTE; +import static org.lwjgl.opengl.GL30C.GL_UNSIGNED_INT; + +@RequiredArgsConstructor +public enum GLTextureFormat { + RGBA8(GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, 4, 8, 8, 8, 8), + RGBA16F(GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, 8, 16, 16, 16, 16), + RGBA16UI(GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, 8, 16, 16, 16, 16), + RGBA16I(GL_RGBA16I, GL_RGBA_INTEGER, GL_SHORT, 8, 16, 16, 16, 16), + RGBA32F(GL_RGBA32F, GL_RGBA, GL_FLOAT, 16, 32, 32, 32, 32), + RGBA32UI(GL_RGBA32I, GL_RGBA_INTEGER, GL_UNSIGNED_INT, 16, 32, 32, 32, 32), + RGBA32I(GL_RGBA32I, GL_RGBA_INTEGER, GL_INT, 16, 32, 32, 32, 32), + + RGB8(GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, 3, 8, 8, 8, 0), + RGB(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, 3, 8, 8, 8, 0), + SRGB8(GL_SRGB8, GL_RGB, GL_UNSIGNED_BYTE, 3, 8, 8, 8, 0), + + SRGB8_ALPHA8(GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, 4, 8, 8, 8, 8), + RGBA(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, 4, 8, 8, 8, 8), + + R8(GL_R8, GL_RED, GL_UNSIGNED_BYTE, 1, 8, 0, 0, 0), + R16F(GL_R16F, GL_RED, GL_HALF_FLOAT, 2, 16, 0, 0, 0), + R16I(GL_R16I, GL_RED_INTEGER, GL_SHORT, 2, 16, 0, 0, 0), + R16UI(GL_R16I, GL_RED_INTEGER, GL_UNSIGNED_SHORT, 2, 16, 0, 0, 0), + R32F(GL_R32F, GL_RED, GL_FLOAT, 4, 32, 0, 0, 0), + R32I(GL_R32I, GL_RED_INTEGER, GL_INT, 4, 32, 0, 0, 0), + R32UI(GL_R32I, GL_RED_INTEGER, GL_UNSIGNED_INT, 4, 32, 0, 0, 0), + + BGRA8(GL_BGRA, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 4, 8, 8, 8, 8), + + DEPTH24(GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 4, 0, 0, 0, 0), + DEPTH32F(GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, 4, 0, 0, 0, 0); + + public final int internalFormat; + public final int format; + public final int type; + public final int pixelSize; + public final int redSize; + public final int greenSize; + public final int blueSize; + public final int alphaSize; + + public boolean isDepth() { + switch (internalFormat) { + case GL_DEPTH_COMPONENT24: + case GL_DEPTH_COMPONENT32F: + return true; + default: + return false; + } + } + + public boolean isSRGB() { + switch (internalFormat) { + case GL_SRGB8: + case GL_SRGB8_ALPHA8: + return true; + default: + return false; + } + } + + public boolean hasAlpha() { + switch (internalFormat) { + case GL_RGBA8: + case GL_RGBA16F: + case GL_RGBA16I: + case GL_RGBA32F: + case GL_RGBA32I: + case GL_BGRA: + case GL_SRGB8_ALPHA8: + case GL_RGBA: + return true; + default: + return false; + } + } + + public static GLTextureFormat fromInternalFormat(int internalFormat) { + for (GLTextureFormat fmt : values()) { + if (fmt.internalFormat == internalFormat) { + return fmt; + } + } + return null; + } + + public static GLTextureFormat fromComponentSizes(int r, int g, int b, int a, boolean isSRGB) { + for (GLTextureFormat fmt : values()) { + if (fmt.isSRGB() == isSRGB && + fmt.redSize == r && + fmt.greenSize == g && + fmt.blueSize == b && + fmt.alphaSize == a) { + return fmt; + } + } + return null; + } +} diff --git a/src/main/java/rs117/hd/utils/opengl/texture/GLTextureParams.java b/src/main/java/rs117/hd/utils/opengl/texture/GLTextureParams.java new file mode 100644 index 0000000000..b928a10cc2 --- /dev/null +++ b/src/main/java/rs117/hd/utils/opengl/texture/GLTextureParams.java @@ -0,0 +1,70 @@ +package rs117.hd.utils.opengl.texture; + +public class GLTextureParams { + public GLSamplerMode sampler = GLSamplerMode.LINEAR_REPEAT; + public GLTextureType type = GLTextureType.TEXTURE2D; + public int textureUnit = -1; + public int imageUnit = -1; + public int imageUnitLayer = -1; + public int imageUnitWriteMode; + public float[] borderColor = null; + public boolean generateMipmaps = false; + public boolean immutable = false; + public int anisotropySamples = -1; + public String debugName = ""; + + public static GLTextureParams DEFAULT() { return new GLTextureParams(); } + + public GLTextureParams setImmutable(boolean immutable) { + this.immutable = immutable; + return this; + } + + public GLTextureParams setAnisotropySamples(int anisotropySamples) { + this.anisotropySamples = anisotropySamples; + return this; + } + + public GLTextureParams setType(GLTextureType type) { + this.type = type; + return this; + } + + public GLTextureParams setSampler(GLSamplerMode sampler) { + this.sampler = sampler; + return this; + } + + public GLTextureParams setTextureUnit(int textureUnit) { + this.textureUnit = textureUnit; + return this; + } + + public GLTextureParams setImageUnit(int imageUnit, int writeMode) { + this.imageUnit = imageUnit; + this.imageUnitWriteMode = writeMode; + return this; + } + + public GLTextureParams setImageUnit(int imageUnit, int writeMode, int imageUnitLayer) { + this.imageUnit = imageUnit; + this.imageUnitLayer = imageUnitLayer; + this.imageUnitWriteMode = writeMode; + return this; + } + + public GLTextureParams setBorderColor(float[] borderColor) { + this.borderColor = borderColor.clone(); + return this; + } + + public GLTextureParams setGenerateMipmaps(boolean generateMipmaps) { + this.generateMipmaps = generateMipmaps; + return this; + } + + public GLTextureParams setDebugName(String debugName) { + this.debugName = debugName; + return this; + } +} diff --git a/src/main/java/rs117/hd/utils/opengl/texture/GLTextureType.java b/src/main/java/rs117/hd/utils/opengl/texture/GLTextureType.java new file mode 100644 index 0000000000..c1f70c573c --- /dev/null +++ b/src/main/java/rs117/hd/utils/opengl/texture/GLTextureType.java @@ -0,0 +1,16 @@ +package rs117.hd.utils.opengl.texture; + +import lombok.RequiredArgsConstructor; + +import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL12.GL_TEXTURE_3D; +import static org.lwjgl.opengl.GL30.GL_TEXTURE_2D_ARRAY; + +@RequiredArgsConstructor +public enum GLTextureType { + TEXTURE2D(GL_TEXTURE_2D), + TEXTURE3D(GL_TEXTURE_3D), + TEXTURE2D_ARRAY(GL_TEXTURE_2D_ARRAY); + + public final int glTarget; +}