diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 9db332e72d..b3b9c46a53 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -45,7 +45,6 @@ import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; @@ -129,9 +128,15 @@ 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.jobs.GenericJob; import rs117.hd.utils.jobs.JobSystem; +import rs117.hd.utils.texture.GLAttachmentSlot; +import rs117.hd.utils.texture.GLFrameBuffer; +import rs117.hd.utils.texture.GLFrameBufferDesc; +import rs117.hd.utils.texture.GLSamplerMode; +import rs117.hd.utils.texture.GLTexture; +import rs117.hd.utils.texture.GLTextureFormat; +import rs117.hd.utils.texture.GLTextureParams; +import rs117.hd.utils.texture.GLTextureType; import static net.runelite.api.Constants.*; import static org.lwjgl.opengl.GL33C.*; @@ -139,10 +144,6 @@ import static rs117.hd.utils.MathUtils.*; import static rs117.hd.utils.ResourcePath.path; import static rs117.hd.utils.buffer.GLBuffer.DEBUG_MAC_OS; -import static rs117.hd.utils.buffer.GLBuffer.MAP_WRITE; -import static rs117.hd.utils.buffer.GLBuffer.STORAGE_IMMUTABLE; -import static rs117.hd.utils.buffer.GLBuffer.STORAGE_PERSISTENT; -import static rs117.hd.utils.buffer.GLBuffer.STORAGE_WRITE; @Slf4j @Singleton @@ -165,11 +166,11 @@ public class HdPlugin extends Plugin { 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; @@ -356,36 +357,18 @@ public class HdPlugin extends Plugin { public int vaoTri; private int vboTri; - @Getter - @Nullable - private int[] uiResolution; private final int[] actualUiResolution = { 0, 0 }; // Includes stretched mode and DPI scaling - private final GLBuffer[] pboUi = new GLBuffer[3]; - private int texUi; - private int uiWidth; - private int uiHeight; - private GenericJob uiCopyJob; + @Getter + private GLTexture texUi; @Nullable public int[] sceneViewport; public final float[] sceneViewportScale = { 1, 1 }; - public int msaaSamples; - - public int[] sceneResolution; - public int fboScene; - private int rboSceneColor; - private int rboSceneDepth; - public int fboSceneResolve; - private int rboSceneResolveColor; - public int shadowMapResolution; - public int fboShadowMap; - private int texShadowMap; - - public int[] tiledLightingResolution; - public int tiledLightingLayerCount; - public int fboTiledLighting; - public int texTiledLighting; + public GLFrameBuffer fboBackBuffer; + public GLFrameBuffer fboScene; + public GLFrameBuffer fboShadowMap; + public GLFrameBuffer fboTiledLighting; public UBOGlobal uboGlobal; public UBOUI uboUI; @@ -518,12 +501,6 @@ protected void startUp() { isActive = true; startupCount++; - fboScene = 0; - rboSceneColor = 0; - rboSceneDepth = 0; - fboSceneResolve = 0; - rboSceneResolveColor = 0; - fboShadowMap = 0; frame = 0; elapsedTime = 0; elapsedClientTime = 0; @@ -703,8 +680,56 @@ protected void startUp() { initializeShaders(); initializeShaderHotswapping(); - initializeUiTexture(); - initializeShadowMapFbo(); + + fboBackBuffer = GLFrameBuffer.wrap(awtContext.getFramebuffer(false), "backBuffer"); + + GLFrameBufferDesc backbufferDesc = fboBackBuffer.getDescriptor(); + if(backbufferDesc.colorDescriptors.isEmpty()) + throw new RuntimeException("Couldn't determine BackBuffer descriptor"); + + final int forcedAASamples = backbufferDesc.samples; + int msaaSamples = forcedAASamples != 0 ? forcedAASamples : min(config.antiAliasingMode().getSamples(), glGetInteger(GL_MAX_SAMPLES)); + + GLTextureFormat backbufferFormat = backbufferDesc.colorDescriptors.get(0).format; + fboScene = new GLFrameBuffer(new GLFrameBufferDesc() + .setWidth(canvas.getWidth()) + .setHeight(canvas.getHeight()) + .setMSAASamples(msaaSamples) + .setColorAttachment(GLAttachmentSlot.COLOR0, backbufferFormat) + .setDepthAttachment(GLTextureFormat.DEPTH32F) + .setDebugName("SceneColor")); + + if (!fboScene.isCreated()) + throw new RuntimeException("No supported " + (backbufferFormat.isSRGB() ? "sRGB" : "linear") + " formats"); + + texUi = new GLTexture(1, 1, GLTextureFormat.BGRA8, + new GLTextureParams() + .setSampler(GLSamplerMode.LINEAR_CLAMP) + .setTextureUnit(TEXTURE_UNIT_UI) + .setPixelPackBufferCount(3) + .setDebugName("UI")); + + int shadowMapResolution = configShadowsEnabled ? config.shadowResolution().getValue() : 1; + fboShadowMap = new GLFrameBuffer( + new GLFrameBufferDesc() + .setWidth(shadowMapResolution) + .setHeight(shadowMapResolution) + .setDepthAttachment(GLTextureFormat.DEPTH32F, + t -> t.setSampler(GLSamplerMode.NEAREST_CLAMP) + .setTextureUnit(TEXTURE_UNIT_SHADOW_MAP) + .setBorderColor(new float[] { 1.0f, 1.0f, 1.0f, 1.0f})) + .setDebugName("ShadowMap")); + + fboTiledLighting = new GLFrameBuffer(new GLFrameBufferDesc() + .setShouldConstructionCreate(false) + .setDepth(DynamicLights.MAX_LAYERS_PER_TILE) + .setColorAttachment( + GLAttachmentSlot.COLOR0, GLTextureFormat.RGBA16UI, + t -> t.setType(GLTextureType.TEXTURE2D_ARRAY) + .setSampler(GLSamplerMode.NEAREST_CLAMP) + .setTextureUnit(TEXTURE_UNIT_TILED_LIGHTING_MAP) + .setImageUnit(IMAGE_UNIT_TILED_LIGHTING, GL_WRITE_ONLY)) + .setDebugName("TiledLighting")); checkGLErrors(); @@ -767,13 +792,25 @@ protected void shutDown() { if (lwjglInitialized) { lwjglInitialized = false; - destroyUiTexture(); + if (texUi != null) + texUi.destroy(); + texUi = null; + + if(fboShadowMap != null) + fboShadowMap.destroy(); + fboShadowMap = null; + + if(fboScene != null) + fboScene.destroy(); + fboScene = null; + + if(fboTiledLighting != null) + fboTiledLighting.destroy(); + fboTiledLighting = null; + destroyShaders(); destroyVaos(); destroyUbos(); - destroySceneFbo(); - destroyShadowMapFbo(); - destroyTiledLightingFbo(); if (renderer != null) { eventBus.unregister(renderer); @@ -1161,111 +1198,13 @@ private void destroyUbos() { uboLightsCulling = null; } - private void initializeUiTexture() { - for (int i = 0; i < 3; i++) { - pboUi[i] = new GLBuffer( - "PBO::UI", - GL_PIXEL_UNPACK_BUFFER, - GL_STREAM_DRAW, - STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE - ); - pboUi[i].initialize(); - } - - 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); - - checkGLErrors(); - } - - private void destroyUiTexture() { - uiResolution = null; - - for (int i = 0; i < 3; i++) { - if (pboUi[i] != null) - pboUi[i].destroy(); - pboUi[i] = null; - } - - if (texUi != 0) - glDeleteTextures(texUi); - texUi = 0; - } - - public 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); - uboGlobal.upload(); // Ensure this is up to date with rendering - } - - private void destroyTiledLightingFbo() { - tiledLightingResolution = null; - - if (fboTiledLighting != 0) - glDeleteFramebuffers(fboTiledLighting); - fboTiledLighting = 0; - - if (texTiledLighting != 0) - glDeleteTextures(texTiledLighting); - texTiledLighting = 0; - } - public void updateSceneFbo() { - if (uiResolution == null) + if (texUi == null) return; int[] viewport = { client.getViewportXOffset(), - uiResolution[1] - (client.getViewportYOffset() + client.getViewportHeight()), + texUi.getHeight() - (client.getViewportYOffset() + client.getViewportHeight()), client.getViewportWidth(), client.getViewportHeight() }; @@ -1275,7 +1214,7 @@ public 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(texUi.getWidth(), texUi.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++) { @@ -1285,185 +1224,15 @@ public void updateSceneFbo() { viewport = round(multiply(vec(viewport), sceneViewportScale)); } - // Check if scene FBO needs to be recreated - if (Arrays.equals(sceneViewport, viewport)) - return; - - destroySceneFbo(); + final int forcedAASamples = fboBackBuffer.getDescriptor().samples; + int samples = forcedAASamples != 0 ? forcedAASamples : min(config.antiAliasingMode().getSamples(), glGetInteger(GL_MAX_SAMPLES)); 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))); + int[] sceneResolution = round(max(vec(1), multiply(slice(vec(sceneViewport), 2), resolutionScale))); uboGlobal.sceneResolution.set(sceneResolution); - uboGlobal.upload(); // Ensure this is up to date with rendering - - // 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_DEPTH_COMPONENT32F, 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 initializeShadowMapFbo() { - if (!configShadowsEnabled) { - initializeDummyShadowMap(); - 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 initializeDummyShadowMap() { - // 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; + fboScene.resize(sceneResolution[0], sceneResolution[1], samples); } public void initializeShaderHotswapping() { @@ -1474,63 +1243,40 @@ public void initializeShaderHotswapping() { } public void prepareInterfaceTexture() { - if (uiCopyJob != null) - uiCopyJob.waitForCompletion(true); - uiCopyJob = null; - - int[] resolution = { - max(1, client.getCanvasWidth()), - max(1, client.getCanvasHeight()) - }; - boolean resize = !Arrays.equals(uiResolution, resolution); - if (resize) { - uiResolution = resolution; - - 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); - } + final BufferProvider bufferProvider = client.getBufferProvider(); + if(bufferProvider == null) + return; if (client.isStretchedEnabled()) { Dimension dim = client.getStretchedDimensions(); actualUiResolution[0] = dim.width; actualUiResolution[1] = dim.height; } else { - copyTo(actualUiResolution, uiResolution); + actualUiResolution[0] = client.getCanvasWidth(); + actualUiResolution[1] = client.getCanvasHeight(); } round(actualUiResolution, multiply(vec(actualUiResolution), getDpiScaling())); - final BufferProvider bufferProvider = client.getBufferProvider(); - final int[] pixels = bufferProvider.getPixels(); - uiWidth = bufferProvider.getWidth(); - uiHeight = bufferProvider.getHeight(); - - frameTimer.begin(Timer.MAP_UI_BUFFER); - final GLBuffer pbo = pboUi[frame % 3]; - pbo.map(MAP_WRITE, 0, uiWidth * uiHeight * 4L); - frameTimer.end(Timer.MAP_UI_BUFFER); - if (!pbo.isMapped()) { - log.error("Unable to map interface PBO. Skipping UI..."); - } else if (uiWidth > uiResolution[0] || uiHeight > uiResolution[1]) { - log.error("UI texture resolution mismatch ({}x{} > {}). Skipping UI...", uiWidth, uiHeight, uiResolution); + texUi.resize(client.getCanvasWidth(), client.getCanvasHeight()); + if(isPowerSaving || !hasLoggedIn) { + texUi.uploadSubPixels2D( + bufferProvider.getWidth(), + bufferProvider.getHeight(), + bufferProvider.getPixels(), + GLTextureFormat.BGRA_INT_8_8_8_8 + ); } else { - uiCopyJob = GenericJob - .build( - "AsyncUICopy", - t -> { - long start = System.nanoTime(); - pbo.mapped().intView().put(pixels, 0, uiWidth * uiHeight); - frameTimer.add(Timer.COPY_UI_ASYNC, System.nanoTime() - start); - } - ) - .setExecuteAsync(!isPowerSaving) - .queue(); + texUi.uploadSubPixelsAsync2D( + bufferProvider.getWidth(), + bufferProvider.getHeight(), + bufferProvider.getPixels(), + GLTextureFormat.BGRA_INT_8_8_8_8 + ); } - pbo.unbind(); } public void drawUi(int overlayColor) { - if (uiResolution == null || developerTools.isHideUiEnabled() && hasLoggedIn) + if (texUi == null || developerTools.isHideUiEnabled() && hasLoggedIn) return; // Fix vanilla bug causing the overlay to remain on the login screen in areas like Fossil Island underwater @@ -1539,7 +1285,10 @@ public void drawUi(int overlayColor) { frameTimer.begin(Timer.RENDER_UI); - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + texUi.completeUploadSubPixelsAsync(); + texUi.setSampler(config.uiScalingMode().glSamplingMode); + + glBindFramebuffer(GL_FRAMEBUFFER, fboBackBuffer.getFboId()); // Disable alpha writes, just in case the default FBO has an alpha channel glColorMask(true, true, true, false); @@ -1548,38 +1297,11 @@ public void drawUi(int overlayColor) { tiledLightingOverlay.render(); uiProgram.use(); - uboUI.sourceDimensions.set(uiResolution); + uboUI.sourceDimensions.set(texUi.getWidth(), texUi.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); - - if (uiCopyJob != null) { - frameTimer.begin(Timer.COPY_UI); - uiCopyJob.waitForCompletion(true); - uiCopyJob = null; - frameTimer.end(Timer.COPY_UI); - - frameTimer.begin(Timer.UPLOAD_UI); - final GLBuffer pbo = pboUi[frame % 3]; - pbo.unmap(); - pbo.bind(); - - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, uiWidth, uiHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); - pbo.unbind(); - frameTimer.end(Timer.UPLOAD_UI); - } - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, function); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, function); - glEnable(GL_BLEND); glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); glBindVertexArray(vaoTri); @@ -1597,10 +1319,10 @@ public void drawUi(int overlayColor) { } /** - * Convert the front framebuffer to an Image - */ + * Convert the front framebuffer to an Image + */ public Image screenshot() { - if (uiResolution == null) + if (texUi == null) return null; int width = actualUiResolution[0]; @@ -1889,19 +1611,17 @@ public void processPendingConfigChanges() { if (recompilePrograms) recompilePrograms(); - if (recreateSceneFbo) { - destroySceneFbo(); + if (recreateSceneFbo) updateSceneFbo(); - } if (reloadScene) { renderer.clearCaches(); renderer.reloadScene(); } - if (recreateShadowMapFbo) { - destroyShadowMapFbo(); - initializeShadowMapFbo(); + if(recreateShadowMapFbo && fboShadowMap != null) { + int shadowMapResolution = configShadowsEnabled ? config.shadowResolution().getValue() : 1; + fboShadowMap.resize(shadowMapResolution, shadowMapResolution); } if (reloadEnvironments) diff --git a/src/main/java/rs117/hd/config/UIScalingMode.java b/src/main/java/rs117/hd/config/UIScalingMode.java index d062a21b69..eb235971c6 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.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), - HYBRID("Hybrid", 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), + HYBRID("Hybrid", GLSamplerMode.LINEAR_CLAMP); private final String name; - public final int glSamplingFunction; + public final GLSamplerMode glSamplingMode; @Override public String toString() diff --git a/src/main/java/rs117/hd/overlays/ShaderOverlay.java b/src/main/java/rs117/hd/overlays/ShaderOverlay.java index f01799b15b..707c409838 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.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 texUI = plugin.getTexUi(); + if (texUI == 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] /= texUI.getWidth(); + rect[i * 2 + 1] /= texUI.getHeight(); rect[i] -= 1; } rect[1] *= -1; diff --git a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java index 41ce789d62..741c9fd4b7 100644 --- a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java +++ b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java @@ -59,6 +59,8 @@ import rs117.hd.utils.buffer.GpuIntBuffer; import rs117.hd.utils.buffer.SharedGLBuffer; import rs117.hd.utils.jobs.JobSystem; +import rs117.hd.utils.texture.GLAttachmentSlot; +import rs117.hd.utils.texture.GLTexture; import static org.lwjgl.opencl.CL10.*; import static org.lwjgl.opengl.GL33C.*; @@ -67,6 +69,7 @@ import static rs117.hd.HdPlugin.NEAR_PLANE; import static rs117.hd.HdPlugin.ORTHOGRAPHIC_ZOOM; import static rs117.hd.HdPlugin.TEXTURE_UNIT_TILE_HEIGHT_MAP; +import static rs117.hd.HdPlugin.TILED_LIGHTING_TILE_SIZE; import static rs117.hd.HdPlugin.checkGLErrors; import static rs117.hd.HdPluginConfig.*; import static rs117.hd.utils.MathUtils.*; @@ -744,14 +747,19 @@ 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 (plugin.configTiledLighting) { - plugin.updateTiledLightingFbo(); - assert plugin.fboTiledLighting != 0; + assert plugin.fboTiledLighting != null; frameTimer.begin(Timer.DRAW_TILED_LIGHTING); frameTimer.begin(Timer.RENDER_TILED_LIGHTING); - glViewport(0, 0, plugin.tiledLightingResolution[0], plugin.tiledLightingResolution[1]); - glBindFramebuffer(GL_FRAMEBUFFER, plugin.fboTiledLighting); + int[] tiledLightingResolution = max(ivec(1), round(divide(vec(plugin.fboScene.getWidth(), plugin.fboScene.getHeight()), TILED_LIGHTING_TILE_SIZE))); + if (plugin.fboTiledLighting.resize(tiledLightingResolution[0], tiledLightingResolution[1])) { + plugin.uboGlobal.tiledLightingResolution.set(tiledLightingResolution); + plugin.uboGlobal.upload(); + } + + glViewport(0, 0, plugin.fboTiledLighting.getWidth(), plugin.fboTiledLighting.getHeight()); + glBindFramebuffer(GL_FRAMEBUFFER, plugin.fboTiledLighting.getFboId()); glBindVertexArray(plugin.vaoTri); @@ -761,10 +769,11 @@ public void drawScene(double cameraX, double cameraY, double cameraZ, double cam glDrawArrays(GL_TRIANGLES, 0, 3); } else { glDrawBuffer(GL_COLOR_ATTACHMENT0); - int layerCount = plugin.configDynamicLights.getTiledLightingLayers(); + final int layerCount = plugin.configDynamicLights.getTiledLightingLayers(); + final GLTexture texTiledLighting = plugin.fboTiledLighting.getColorTexture(GLAttachmentSlot.COLOR0); for (int layer = 0; layer < layerCount; layer++) { plugin.tiledLightingShaderPrograms.get(layer).use(); - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, plugin.texTiledLighting, 0, layer); + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texTiledLighting.getWidth(), 0, layer); glDrawArrays(GL_TRIANGLES, 0, 3); } } @@ -1078,13 +1087,13 @@ public void draw(int overlayColor) { plugin.uboGlobal.colorFilterFade.set(clamp(timeSinceChange / COLOR_FILTER_FADE_DURATION, 0, 1)); } - if (plugin.configShadowsEnabled && plugin.fboShadowMap != 0 + if (plugin.configShadowsEnabled && plugin.fboShadowMap != null && environmentManager.currentDirectionalStrength > 0) { frameTimer.begin(Timer.RENDER_SHADOWS); // Render to the shadow depth map - glViewport(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); - glBindFramebuffer(GL_FRAMEBUFFER, plugin.fboShadowMap); + glViewport(0, 0, plugin.fboShadowMap.getWidth(), plugin.fboShadowMap.getHeight()); + glBindFramebuffer(GL_FRAMEBUFFER, plugin.fboShadowMap.getFboId()); glClearDepth(1); glClear(GL_DEPTH_BUFFER_BIT); glDepthFunc(GL_LEQUAL); @@ -1134,13 +1143,13 @@ public void draw(int overlayColor) { plugin.uboGlobal.upload(); sceneProgram.use(); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.fboScene); - if (plugin.msaaSamples > 1) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.fboScene.getFboId()); + if (plugin.fboScene.getSamples() > 1) { glEnable(GL_MULTISAMPLE); } else { glDisable(GL_MULTISAMPLE); } - glViewport(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); + glViewport(0, 0, plugin.fboScene.getWidth(), plugin.fboScene.getHeight()); // Clear scene frameTimer.begin(Timer.CLEAR_SCENE); @@ -1216,35 +1225,18 @@ public void draw(int overlayColor) { glDepthMask(true); glUseProgram(0); - glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboScene); - if (plugin.fboSceneResolve != 0) { - // Blit from the scene FBO to the multisample resolve FBO - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.fboSceneResolve); - glBlitFramebuffer( - 0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1], - 0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1], - GL_COLOR_BUFFER_BIT, GL_NEAREST - ); - glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboSceneResolve); - } - - // Blit from the resolved FBO to the default FBO - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); - glBlitFramebuffer( - 0, - 0, - plugin.sceneResolution[0], - plugin.sceneResolution[1], + glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboScene.getFboId()); + plugin.fboScene.blitTo( + plugin.fboBackBuffer, + GLAttachmentSlot.COLOR0, + GLAttachmentSlot.BACK_LEFT, plugin.sceneViewport[0], plugin.sceneViewport[1], plugin.sceneViewport[0] + plugin.sceneViewport[2], plugin.sceneViewport[1] + plugin.sceneViewport[3], - GL_COLOR_BUFFER_BIT, - config.sceneScalingMode().glFilter - ); + config.sceneScalingMode().glFilter); } else { - glClearColor(0, 0, 0, 1f); - glClear(GL_COLOR_BUFFER_BIT); + plugin.fboBackBuffer.clearColor(0.0f, 0.0f, 0.0f, 1.0f); } plugin.drawUi(overlayColor); diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 13d42c32a6..684059d53d 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -68,6 +68,7 @@ import rs117.hd.utils.buffer.GpuIntBuffer; import rs117.hd.utils.collections.ConcurrentPool; import rs117.hd.utils.jobs.JobSystem; +import rs117.hd.utils.texture.GLAttachmentSlot; import static net.runelite.api.Constants.*; import static net.runelite.api.Perspective.*; @@ -76,6 +77,7 @@ import static rs117.hd.HdPlugin.COLOR_FILTER_FADE_DURATION; import static rs117.hd.HdPlugin.NEAR_PLANE; import static rs117.hd.HdPlugin.ORTHOGRAPHIC_ZOOM; +import static rs117.hd.HdPlugin.TILED_LIGHTING_TILE_SIZE; import static rs117.hd.HdPlugin.checkGLErrors; import static rs117.hd.HdPluginConfig.*; import static rs117.hd.renderer.zone.WorldViewContext.VAO_OPAQUE; @@ -89,7 +91,7 @@ public class ZoneRenderer implements Renderer { public static final int FRAMES_IN_FLIGHT = 3; private static int TEXTURE_UNIT_COUNT = HdPlugin.TEXTURE_UNIT_COUNT; - public static final int TEXTURE_UNIT_TEXTURED_FACES = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_TEXTURED_FACES = GL_TEXTURE1 + TEXTURE_UNIT_COUNT++; private static int UNIFORM_BLOCK_COUNT = HdPlugin.UNIFORM_BLOCK_COUNT; public static final int UNIFORM_BLOCK_WORLD_VIEWS = UNIFORM_BLOCK_COUNT++; @@ -451,7 +453,7 @@ private void preSceneDrawTopLevel( // Snap Position to Shadow Texel Grid to prevent shimmering directionalCamera.transformPoint(sceneCenter, sceneCenter); - float texelSize = (float) directionalSize / plugin.shadowMapResolution; + float texelSize = (float) directionalSize / plugin.fboShadowMap.getWidth(); sceneCenter[0] = (float) floor(sceneCenter[0] / texelSize + 0.5f) * texelSize; sceneCenter[1] = (float) floor(sceneCenter[1] / texelSize + 0.5f) * texelSize; @@ -668,27 +670,29 @@ private void tiledLightingPass() { if (!plugin.configTiledLighting || plugin.configDynamicLights == DynamicLights.NONE) return; - plugin.updateTiledLightingFbo(); - assert plugin.fboTiledLighting != 0; + assert plugin.fboTiledLighting != null; frameTimer.begin(Timer.DRAW_TILED_LIGHTING); frameTimer.begin(Timer.RENDER_TILED_LIGHTING); - renderState.framebuffer.set(GL_FRAMEBUFFER, plugin.fboTiledLighting); - renderState.viewport.set(0, 0, plugin.tiledLightingResolution[0], plugin.tiledLightingResolution[1]); + int[] tiledLightingResolution = max(ivec(1), round(divide(vec(plugin.fboScene.getWidth(), plugin.fboScene.getHeight()), TILED_LIGHTING_TILE_SIZE))); + if (plugin.fboTiledLighting.resize(tiledLightingResolution[0], tiledLightingResolution[1])) { + plugin.uboGlobal.tiledLightingResolution.set(tiledLightingResolution); + plugin.uboGlobal.upload(); + } renderState.vao.setVao(plugin.vaoTri); if (plugin.tiledLightingImageStoreProgram.isValid()) { + plugin.fboTiledLighting.bind(renderState, GL_DRAW_FRAMEBUFFER); renderState.program.set(plugin.tiledLightingImageStoreProgram); - renderState.drawBuffer.set(GL_NONE); renderState.apply(); glDrawArrays(GL_TRIANGLES, 0, 3); } else { renderState.drawBuffer.set(GL_COLOR_ATTACHMENT0); - int layerCount = plugin.configDynamicLights.getTiledLightingLayers(); + final int layerCount = plugin.configDynamicLights.getTiledLightingLayers(); for (int layer = 0; layer < layerCount; layer++) { + plugin.fboTiledLighting.bind(renderState, GL_DRAW_FRAMEBUFFER, GLAttachmentSlot.COLOR0, layer); renderState.program.set(plugin.tiledLightingShaderPrograms.get(layer)); - renderState.framebufferTextureLayer.set(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, plugin.texTiledLighting, 0, layer); renderState.apply(); glDrawArrays(GL_TRIANGLES, 0, 3); } @@ -699,19 +703,15 @@ private void tiledLightingPass() { } private void directionalShadowPass() { + if(plugin.fboShadowMap == null) + return; + final boolean shouldRenderShadows = plugin.configShadowsEnabled && - plugin.fboShadowMap != 0 && environmentManager.currentDirectionalStrength > 0; if (shouldRenderShadows || shouldClearShadowFbo) { - // Render to the shadow depth map - renderState.framebuffer.set(GL_FRAMEBUFFER, plugin.fboShadowMap); - renderState.viewport.set(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); - renderState.apply(); - - glClearDepth(1); - glClear(GL_DEPTH_BUFFER_BIT); + plugin.fboShadowMap.clearDepth(1.0f); shouldClearShadowFbo = false; } @@ -720,6 +720,7 @@ private void directionalShadowPass() { frameTimer.begin(Timer.RENDER_SHADOWS); + plugin.fboShadowMap.bind(renderState, GL_DRAW_FRAMEBUFFER); renderState.enable.set(GL_DEPTH_TEST); renderState.disable.set(GL_CULL_FACE); renderState.depthFunc.set(GL_LEQUAL); @@ -741,33 +742,23 @@ private void scenePass() { sceneProgram.use(); frameTimer.begin(Timer.DRAW_SCENE); - renderState.framebuffer.set(GL_DRAW_FRAMEBUFFER, plugin.fboScene); - if (plugin.msaaSamples > 1) { - renderState.enable.set(GL_MULTISAMPLE); - } else { - renderState.disable.set(GL_MULTISAMPLE); - } - renderState.viewport.set(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); - renderState.ido.set(indirectDrawCmds.id); - renderState.apply(); // Clear scene frameTimer.begin(Timer.CLEAR_SCENE); - float[] fogColor = ColorUtils.linearToSrgb(environmentManager.currentFogColor); float[] gammaCorrectedFogColor = pow(fogColor, plugin.getGammaCorrection()); - glClearColor( + plugin.fboScene.clear( gammaCorrectedFogColor[0], gammaCorrectedFogColor[1], gammaCorrectedFogColor[2], - 1f - ); - glClearDepth(0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + 1f, + 0.0f); frameTimer.end(Timer.CLEAR_SCENE); frameTimer.begin(Timer.RENDER_SCENE); + plugin.fboScene.bind(renderState, GL_DRAW_FRAMEBUFFER); + renderState.ido.set(indirectDrawCmds.id); renderState.enable.set(GL_BLEND); renderState.enable.set(GL_CULL_FACE); renderState.enable.set(GL_DEPTH_TEST); @@ -1072,37 +1063,18 @@ public void draw(int overlayColor) { scenePass(); } - if (sceneFboValid && plugin.sceneResolution != null && plugin.sceneViewport != null) { - glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboScene); - if (plugin.fboSceneResolve != 0) { - // Blit from the scene FBO to the multisample resolve FBO - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.fboSceneResolve); - glBlitFramebuffer( - 0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1], - 0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1], - GL_COLOR_BUFFER_BIT, GL_NEAREST - ); - glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboSceneResolve); - } - - // Blit from the resolved FBO to the default FBO - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); - glBlitFramebuffer( - 0, - 0, - plugin.sceneResolution[0], - plugin.sceneResolution[1], + if (sceneFboValid && plugin.fboScene != null && plugin.sceneViewport != null) { + plugin.fboScene.blitTo( + plugin.fboBackBuffer, + GLAttachmentSlot.COLOR0, + GLAttachmentSlot.BACK_LEFT, plugin.sceneViewport[0], plugin.sceneViewport[1], plugin.sceneViewport[0] + plugin.sceneViewport[2], plugin.sceneViewport[1] + plugin.sceneViewport[3], - GL_COLOR_BUFFER_BIT, - config.sceneScalingMode().glFilter - ); + config.sceneScalingMode().glFilter); } else { - glBindFramebuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); + plugin.fboBackBuffer.clearColor(0.0f, 0.0f, 0.0f, 1.0f); } plugin.drawUi(overlayColor); @@ -1128,7 +1100,7 @@ public void draw(int overlayColor) { log.error("Unable to swap buffers:", ex); } - glBindFramebuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_FRAMEBUFFER, plugin.fboBackBuffer.getFboId()); frameTimer.endFrameAndReset(); checkGLErrors(); diff --git a/src/main/java/rs117/hd/scene/MaterialManager.java b/src/main/java/rs117/hd/scene/MaterialManager.java index 1666e889e4..80c5cd6e36 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.opengl.uniforms.UBOMaterials; @@ -54,10 +53,13 @@ import rs117.hd.utils.HDVariables; import rs117.hd.utils.Props; import rs117.hd.utils.ResourcePath; +import rs117.hd.utils.texture.GLSamplerMode; +import rs117.hd.utils.texture.GLTexture; +import rs117.hd.utils.texture.GLTextureFormat; +import rs117.hd.utils.texture.GLTextureParams; +import rs117.hd.utils.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 +112,7 @@ public static class TextureLayer { public static Material[] MATERIALS; public static Material[] VANILLA_TEXTURE_MAPPING; - private int texMaterialTextureArray; - private int[] textureResolution; + private GLTexture texMaterial; public final List textureLayers = new ArrayList<>(); private FileWatcher.UnregisterCallback fileWatcher; @@ -125,9 +126,9 @@ public void shutDown() { fileWatcher.unregister(); fileWatcher = null; - if (texMaterialTextureArray != 0) - glDeleteTextures(texMaterialTextureArray); - texMaterialTextureArray = 0; + if (texMaterial != null) + texMaterial.destroy(); + texMaterial = null; textureLayers.clear(); if (uboMaterials != null) @@ -389,36 +390,30 @@ private void swapMaterials(Material[] parsedMaterials, boolean skipSceneReload) for (var mat : MATERIALS) 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); + final int textureSize = config.textureResolution().getSize(); + final boolean uploadAllLayers; + if (texMaterial == null) { + texMaterial = new GLTexture( + textureSize, textureSize, textureLayers.size(), + GLTextureFormat.SRGB8_ALPHA8, + new GLTextureParams() + .setType(GLTextureType.TEXTURE2D_ARRAY) + .setTextureUnit(TEXTURE_UNIT_GAME) + .setSampler(GLSamplerMode.LINEAR_REPEAT) + .setGenerateMipmaps(true) + ); + uploadAllLayers = true; + } else { + uploadAllLayers = texMaterial.resize(textureSize, textureSize, textureLayers.size()); + } + if(uploadAllLayers) { // Since we're reallocating the texture array, all layers need to be reuploaded + log.debug("Allocating {}x{} texture array with {} layers", textureSize, textureSize, textureLayers.size()); 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(); + texMaterial.setAnisotropySamples(config.anisotropicFilteringLevel()); uploadTextures(); @@ -470,15 +465,17 @@ private void invalidateMaterials(Material[] materials) { public void uploadTextures() { assert client.isClientThread(); - if (texMaterialTextureArray == 0) + if (texMaterial == null) return; + long startTime = System.currentTimeMillis(); // Set brightness to 1 to upload unmodified vanilla textures var textureProvider = client.getTextureProvider(); double vanillaBrightness = textureProvider.getBrightness(); textureProvider.setBrightness(1); - boolean uploadedAnything = false; + // Disable Mip map generation whilst we upload, since we can do it once at the end + texMaterial.getTextureParams().setGenerateMipmaps(false); for (var layer : textureLayers) { if (!layer.needsUpload) continue; @@ -489,12 +486,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(texMaterial, material.textureLayer, image); } catch (Exception ex) { log.error("Failed to upload texture {}:", material, ex); } @@ -503,8 +495,10 @@ public void uploadTextures() { // Reset the texture brightness textureProvider.setBrightness(vanillaBrightness); - if (uploadedAnything) - glGenerateMipmap(GL_TEXTURE_2D_ARRAY); + // Re-enable & Generate Mipmaps + texMaterial.getTextureParams().setGenerateMipmaps(true); + texMaterial.generateMipMaps(); + log.debug("Uploaded {} textures in {}ms", textureLayers.size(), System.currentTimeMillis() - startTime); } private static void checkForReplacementLoops(Material[] materials) { diff --git a/src/main/java/rs117/hd/scene/TextureManager.java b/src/main/java/rs117/hd/scene/TextureManager.java index 486b10871a..0725c5de1d 100644 --- a/src/main/java/rs117/hd/scene/TextureManager.java +++ b/src/main/java/rs117/hd/scene/TextureManager.java @@ -28,7 +28,6 @@ import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; -import java.nio.IntBuffer; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -38,14 +37,12 @@ import lombok.extern.slf4j.Slf4j; 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.texture.GLTexture; +import rs117.hd.utils.texture.GLTextureFormat; -import static org.lwjgl.opengl.GL33C.*; -import static rs117.hd.utils.MathUtils.*; import static rs117.hd.utils.ResourcePath.path; @Slf4j @@ -71,7 +68,6 @@ public class TextureManager { private MaterialManager materialManager; // Temporary variables for texture loading and generating material uniforms - private IntBuffer pixelBuffer; private BufferedImage scaledImage; private BufferedImage vanillaImage; @@ -103,7 +99,6 @@ public void startUp() { } public void shutDown() { - pixelBuffer = null; scaledImage = null; vanillaImage = null; } @@ -188,57 +183,31 @@ public BufferedImage loadTexture(String filename) { return null; } - public void uploadTexture(int target, int textureLayer, int[] textureSize, BufferedImage image) { + public void uploadTexture(GLTexture tex, int textureLayer, BufferedImage image) { assert client.isClientThread() : "Not thread safe"; // Allocate resources for storing temporary image data - int numPixels = product(textureSize); - 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() != tex.getWidth() || scaledImage.getHeight() != tex.getHeight()) + scaledImage = new BufferedImage(tex.getWidth(), tex.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(tex.getHeight(), 0); t.scale(-1, 1); } - t.scale((double) textureSize[0] / image.getWidth(), (double) textureSize[1] / image.getHeight()); + t.scale((double) tex.getWidth() / image.getWidth(), (double) tex.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)); - } + tex.uploadSubPixels3D( + textureLayer, + tex.getWidth(), + tex.getWidth(), + 1, + ((DataBufferInt) scaledImage.getRaster().getDataBuffer()).getData(), + GLTextureFormat.BGRA_INT_8_8_8_8); } } diff --git a/src/main/java/rs117/hd/utils/buffer/GLBuffer.java b/src/main/java/rs117/hd/utils/buffer/GLBuffer.java index 755ba238e3..06447115ef 100644 --- a/src/main/java/rs117/hd/utils/buffer/GLBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GLBuffer.java @@ -522,7 +522,7 @@ public boolean ensureCapacity(long byteOffset, long numBytes) { } public boolean isStorageBuffer() { - return storageFlags != STORAGE_NONE && SUPPORTS_STORAGE_BUFFERS; + return storageFlags != STORAGE_NONE && SUPPORTS_STORAGE_BUFFERS && !DEBUG_MAC_OS; } public boolean isMapped() { diff --git a/src/main/java/rs117/hd/utils/texture/GLAttachmentSlot.java b/src/main/java/rs117/hd/utils/texture/GLAttachmentSlot.java new file mode 100644 index 0000000000..0e3c8b8afc --- /dev/null +++ b/src/main/java/rs117/hd/utils/texture/GLAttachmentSlot.java @@ -0,0 +1,111 @@ +package rs117.hd.utils.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/texture/GLFrameBuffer.java b/src/main/java/rs117/hd/utils/texture/GLFrameBuffer.java new file mode 100644 index 0000000000..cada30f70f --- /dev/null +++ b/src/main/java/rs117/hd/utils/texture/GLFrameBuffer.java @@ -0,0 +1,561 @@ +package rs117.hd.utils.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.utils.Destructible; +import rs117.hd.utils.DestructibleHandler; +import rs117.hd.utils.RenderState; + +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.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 rs117.hd.HdPlugin.checkGLErrors; + +@Slf4j +public class GLFrameBuffer implements Destructible { + @Getter + private final GLFrameBufferDesc descriptor; + + private GLFrameBufferAttachment[] colorAttachments; + private GLFrameBufferAttachment depthAttachment; + private int[] drawBuffers; + + @Getter + private int fboId = 0; + @Getter + private final boolean wrapper; + + private float depthClearValue = 0.0f; + private float colorClearRed = 0.0f; + private float colorClearGreen = 0.0f; + private float colorClearBlue = 0.0f; + private float colorClearAlpha = 1.0f; + + private final StringBuilder sb = new StringBuilder(); + + private GLFrameBuffer() { + descriptor = new GLFrameBufferDesc(); + wrapper = true; + } + + public GLFrameBuffer(GLFrameBufferDesc descriptor) { + this.descriptor = descriptor; + wrapper = false; + create(); + } + + public boolean isCreated() {return fboId > 0 || wrapper; } + + public boolean create() { + if (isCreated()) + return false; + + fboId = glGenFramebuffers(); + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + + 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); + destroy(); + return false; + } + + return true; + } + + public boolean bind(RenderState renderState, int glFramebufferEnum, GLAttachmentSlot slot, int layer) { + renderState.framebuffer.set(glFramebufferEnum, fboId); + renderState.viewport.set(0, 0, descriptor.width, descriptor.height); + + if (descriptor.samples > 1) { + renderState.enable.set(GL_MULTISAMPLE); + } else { + renderState.disable.set(GL_MULTISAMPLE); + } + + if(slot != null) + glDrawBuffer(slot.glEnum); + + if(layer >= 0) { + GLFrameBufferAttachment att = getColorAttachment(slot); + if(att != null&& att.texture.depth < layer) + glFramebufferTextureLayer(GL_FRAMEBUFFER, att.slot.glEnum, att.texture.getId(), 0, layer); + } + + return false; + } + + public void bind(RenderState renderState, int glFramebufferEnum, GLAttachmentSlot slot) { + bind(renderState, glFramebufferEnum, slot, -1); + } + + public void bind(RenderState renderState, int glFramebufferEnum) { + bind(renderState, glFramebufferEnum, null, -1); + } + + public boolean resize(int width, int height) { + return resize(width, height, descriptor.samples, false); + } + + public boolean resize(int width, int height, int samples) { + return resize(width, height, samples, false); + } + + public boolean resize(int width, int height, int samples, boolean recreate) { + if (isCreated() && width == descriptor.width && height == descriptor.height && samples == descriptor.samples) + return false; + + descriptor.width = width; + descriptor.height = height; + descriptor.samples = samples; + + if(recreate) { + destroy(); + 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 int getSamples() { return descriptor.samples; } + + @Override + protected void finalize() { + if(fboId == 0) + return; + + DestructibleHandler.queueLeakedDestruction(this); + } + + @Override + public void destroy() { + 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); + } + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboId); + int clearMask = 0; + + if (clearColor) { + glDrawBuffers(drawBuffers); + glClearColor(colorClearRed, colorClearGreen, colorClearBlue, colorClearAlpha); + clearMask |= GL_COLOR_BUFFER_BIT; + } + + if (clearDepth) { + glClearDepth(depthClearValue); + clearMask |= GL_DEPTH_BUFFER_BIT; + } + + glClear(clearMask); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + } 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) { + colorClearRed = r; + colorClearGreen = g; + colorClearBlue = b; + colorClearAlpha = 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) { + colorClearRed = r; + colorClearGreen = g; + colorClearBlue = b; + colorClearAlpha = 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; + + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + + 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; + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + return wrapper; + } +} diff --git a/src/main/java/rs117/hd/utils/texture/GLFrameBufferAttachment.java b/src/main/java/rs117/hd/utils/texture/GLFrameBufferAttachment.java new file mode 100644 index 0000000000..5efc410937 --- /dev/null +++ b/src/main/java/rs117/hd/utils/texture/GLFrameBufferAttachment.java @@ -0,0 +1,174 @@ +package rs117.hd.utils.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.destroy(); + texture = null; + } + + if (resolveFboId != 0) { + glDeleteFramebuffers(resolveFboId); + resolveFboId = 0; + } + } +} diff --git a/src/main/java/rs117/hd/utils/texture/GLFrameBufferDesc.java b/src/main/java/rs117/hd/utils/texture/GLFrameBufferDesc.java new file mode 100644 index 0000000000..dc3a75c779 --- /dev/null +++ b/src/main/java/rs117/hd/utils/texture/GLFrameBufferDesc.java @@ -0,0 +1,82 @@ +package rs117.hd.utils.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) { return setColorAttachment(slot, format, null); } + + public GLFrameBufferDesc setColorAttachment(GLAttachmentSlot slot, GLTextureFormat format, Builder paramBuilder) { + assert !slot.isDepth(); + GLTextureParams params = paramBuilder != null ? paramBuilder.apply(GLTextureParams.DEFAULT()) : GLTextureParams.DEFAULT(); + colorDescriptors.add(new AttachmentDescriptor(slot, format, params)); + return this; + } + + public GLFrameBufferDesc setDepthAttachment(GLTextureFormat format) { return setDepthAttachment(format, null); } + + public GLFrameBufferDesc setDepthAttachment(GLTextureFormat format, Builder paramBuilder) { + GLTextureParams params = paramBuilder != null ? paramBuilder.apply(GLTextureParams.DEFAULT()) : GLTextureParams.DEFAULT(); + depthDescriptor = new AttachmentDescriptor(GLAttachmentSlot.DEPTH, format, params); + return this; + } + + @FunctionalInterface + public interface Builder { + GLTextureParams apply(GLTextureParams params); + } + + 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/texture/GLSamplerMode.java b/src/main/java/rs117/hd/utils/texture/GLSamplerMode.java new file mode 100644 index 0000000000..8ea17f5f20 --- /dev/null +++ b/src/main/java/rs117/hd/utils/texture/GLSamplerMode.java @@ -0,0 +1,25 @@ +package rs117.hd.utils.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/texture/GLTexture.java b/src/main/java/rs117/hd/utils/texture/GLTexture.java new file mode 100644 index 0000000000..74c573d9f2 --- /dev/null +++ b/src/main/java/rs117/hd/utils/texture/GLTexture.java @@ -0,0 +1,628 @@ +package rs117.hd.utils.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.Setter; +import lombok.extern.slf4j.Slf4j; +import org.lwjgl.opengl.*; +import rs117.hd.HdPlugin; +import rs117.hd.utils.Destructible; +import rs117.hd.utils.DestructibleHandler; +import rs117.hd.utils.buffer.GLBuffer; +import rs117.hd.utils.buffer.GLMappedBuffer; +import rs117.hd.utils.collections.ConcurrentPool; +import rs117.hd.utils.jobs.Job; + +import static org.lwjgl.opengl.GL11.GL_TEXTURE_BORDER_COLOR; +import static org.lwjgl.opengl.GL11.GL_UNPACK_ALIGNMENT; +import static org.lwjgl.opengl.GL11.glGetInteger; +import static org.lwjgl.opengl.GL11.glTexImage2D; +import static org.lwjgl.opengl.GL11.glTexParameterfv; +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.GL11C.glTexSubImage2D; +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.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.glBindTexture; +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.glTexParameteri; +import static org.lwjgl.opengl.GL30C.GL_TEXTURE_2D_ARRAY; +import static rs117.hd.HdPlugin.checkGLErrors; +import static rs117.hd.utils.MathUtils.*; +import static rs117.hd.utils.buffer.GLBuffer.STORAGE_IMMUTABLE; +import static rs117.hd.utils.buffer.GLBuffer.STORAGE_PERSISTENT; +import static rs117.hd.utils.buffer.GLBuffer.STORAGE_WRITE; + + +@Slf4j +public class GLTexture implements Destructible { + private static int UNPACK_ALIGNMENT = -1; + private static float MAX_TEXTURE_MAX_ANISOTROPY = -1.0f; + + @Getter + protected int id = 0; + + @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 PixelUploadJob uploadJob; + private int pboIdx = 0; + private GLBuffer[] unpackBuffers; + + 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 != 0; } + + 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(); + allocateTextureStorage(); + setupSamplerFiltering(); + + 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); + + if(textureParams.textureUnit > 0) + glActiveTexture(GL_TEXTURE0); + + unbind(); + 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 && 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.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(); + setupSamplerFiltering(); + unbind(); + } + } + + public void setAnisotropySamples(int samples) { + if(textureParams.anisotropySamples != samples) { + textureParams.anisotropySamples = samples; + bind(); + setupSamplerFiltering(); + unbind(); + } + } + + public int[] getSize() { + if(depth > 1) + return new int[] { width, height, depth }; + return new int[] { width, height }; + } + + public void resize(int width, int height) { + if (!isCreated()) throw new IllegalStateException("Texture not created"); + resize(width, height, 1); + } + + public boolean resize(int width, int height, int depth) { + if (!isCreated()) throw new IllegalStateException("Texture not created"); + + if(this.width == width && this.height == height && this.depth == depth) + return false; + + this.width = width; + this.height = height; + this.depth = depth; + + if(textureParams.immutable) { + destroy(); + create(); + return true; + } + + bind(); + allocateTextureStorage(); + + if(unpackBuffers != null) { + for(int i = 0; i < unpackBuffers.length; i++) + unpackBuffers[i].ensureCapacity((long) width * height * depth * textureFormat.channels); + } + + if (textureParams.generateMipmaps) + glGenerateMipmap(textureParams.type.glTarget); + + checkGLErrors(() -> textureParams.debugName); + + unbind(); + return true; + } + + public boolean bind() { + if (!isCreated()) + throw new IllegalStateException("Texture not created"); + glBindTexture(textureParams.type.glTarget, id); + return false; + } + + public void unbind() { glBindTexture(textureParams.type.glTarget, 0); } + + public GLMappedBuffer map(int flags) { + if(unpackBuffers == null) { + int pboCount = textureParams.pboCount > 0 ? textureParams.pboCount : 1; + unpackBuffers = new GLBuffer[pboCount]; + for (int i = 0; i < pboCount; i++){ + unpackBuffers[i] = new GLBuffer(textureParams.debugName + "::PBO", + GL21C.GL_PIXEL_UNPACK_BUFFER, + GL15C.GL_STREAM_DRAW, + STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE); + unpackBuffers[i].initialize((long) width * height * depth * textureFormat.channels); + } + + if(UNPACK_ALIGNMENT == -1) + UNPACK_ALIGNMENT = glGetInteger(GL_UNPACK_ALIGNMENT); + } + + pboIdx++; + if(pboIdx >= unpackBuffers.length) + pboIdx = 0; + + bind(); + unpackBuffers[pboIdx].map(flags); + unbind(); + + return unpackBuffers[pboIdx].mapped(); + } + + public void unmap() { + unmap(width, height); + } + + public void unmap(int width, int height) { + unmap(0, 0, 0, width, height, 1); + } + + 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) { unmap(xOffset, yOffset, zOffset, width, height, depth, textureFormat); } + + public void unmap(int xOffset, int yOffset, int zOffset, int width, int height, int depth, GLTextureFormat pixelFormat) { + if(unpackBuffers == null) + return; + + final GLBuffer unpackBuffer = unpackBuffers[pboIdx]; + if(!unpackBuffer.isMapped()) + return; + + bind(); + unpackBuffer.unmap(); + unpackBuffer.bind(); + + if(this.depth > 1) { + glTexSubImage3D(textureParams.type.glTarget, 0, xOffset, yOffset, zOffset, width, height, depth, pixelFormat.format, pixelFormat.type, 0); + } else { + glTexSubImage2D(textureParams.type.glTarget, 0, xOffset, yOffset, width, height, pixelFormat.format, pixelFormat.type, 0); + } + + unpackBuffer.unbind(); + if (textureParams.generateMipmaps) + glGenerateMipmap(textureParams.type.glTarget); + + unbind(); + checkGLErrors(() -> String.format("%s (%dx%dx%d %dx%dx%d)", textureParams.debugName, xOffset, yOffset, zOffset, width, height, depth)); + } + + @Override + protected void finalize() { + if(id == 0) + return; + + DestructibleHandler.queueLeakedDestruction(this); + } + + @Override + public void destroy() { + if(uploadJob != null) + uploadJob.waitForCompletion(); + uploadJob = null; + + if (isCreated()) + glDeleteTextures(id); + id = 0; + + if (unpackBuffers != null) { + for(int i = 0; i < unpackBuffers.length; i++) + unpackBuffers[i].destroy(); + } + unpackBuffers = null; + } + + public Job uploadSubPixelsAsync2D(int width, int height, byte[] pixelData, GLTextureFormat pixelFormat) { + return uploadSubPixelsAsync(0, 0, 0, width, height, 0, pixelFormat).setPixelsDataBytes(pixelData).queue(); + } + + public Job uploadSubPixelsAsync2D(int xOffset, int yOffset, int width, int height, byte[] pixelData, GLTextureFormat pixelFormat) { + return uploadSubPixelsAsync(xOffset, yOffset, 0, width, height, 0, pixelFormat).setPixelsDataBytes(pixelData).queue(); + } + + public Job uploadSubPixelsAsync3D(int xOffset, int yOffset, int zOffset, int width, int height, int depth, byte[] pixelData, GLTextureFormat pixelFormat) { + return uploadSubPixelsAsync(xOffset, yOffset, zOffset, width, height, depth, pixelFormat).setPixelsDataBytes(pixelData).queue(); + } + + public Job uploadSubPixelsAsync2D(int width, int height, int[] pixelData, GLTextureFormat pixelFormat) { + return uploadSubPixelsAsync(0, 0, 0, width, height, 0, pixelFormat).setPixelsDataInts(pixelData).queue(); + } + + public Job uploadSubPixelsAsync2D(int xOffset, int yOffset, int width, int height, int[] pixelData, GLTextureFormat pixelFormat) { + return uploadSubPixelsAsync(xOffset, yOffset, 0, width, height, 0, pixelFormat).setPixelsDataInts(pixelData).queue(); + } + + public Job uploadSubPixelsAsync3D(int xOffset, int yOffset, int zOffset, int width, int height, int depth, int[] pixelData, GLTextureFormat pixelFormat) { + return uploadSubPixelsAsync(xOffset, yOffset, zOffset, width, height, depth, pixelFormat).setPixelsDataInts(pixelData).queue(); + } + + public Job uploadSubPixelsAsync2D(int width, int height, float[] pixelData, GLTextureFormat pixelFormat) { + return uploadSubPixelsAsync(0, 0, 0, width, height, 1, pixelFormat).setPixelsDataFloats(pixelData).queue(); + } + + public Job uploadSubPixelsAsync2D(int xOffset, int yOffset, int width, int height, float[] pixelData, GLTextureFormat pixelFormat) { + return uploadSubPixelsAsync(xOffset, yOffset, 0, width, height, 1, pixelFormat).setPixelsDataFloats(pixelData).queue(); + } + + public Job uploadSubPixelsAsync3D(int xOffset, int yOffset, int zOffset, int width, int height, int depth, float[] pixelData, GLTextureFormat pixelFormat) { + return uploadSubPixelsAsync(xOffset, yOffset, zOffset, width, height, depth, pixelFormat).setPixelsDataFloats(pixelData).queue(); + } + + public Job uploadSubPixelsAsync2D(int width, int height, Buffer pixelData, GLTextureFormat pixelFormat) { + return uploadSubPixelsAsync(0, 0, 0, width, height, 1, pixelFormat).setPixelData(pixelData).queue(); + } + + public Job uploadSubPixelsAsync3D(int xOffset, int yOffset, int width, int height, Buffer pixelData, GLTextureFormat pixelFormat) { + return uploadSubPixelsAsync(xOffset, yOffset, 0, width, height, 1, pixelFormat).setPixelData(pixelData).queue(); + } + + private PixelUploadJob uploadSubPixelsAsync(int xOffset, int yOffset, int zOffset, int width, int height, int depth, GLTextureFormat pixelFormat) { + map(GLBuffer.MAP_WRITE); + uploadJob = PixelUploadJob.POOL.acquire(); + uploadJob.texture = this; + uploadJob.xOffset = xOffset; + uploadJob.yOffset = yOffset; + uploadJob.zOffset = zOffset; + uploadJob.width = width; + uploadJob.height = height; + uploadJob.depth = depth; + uploadJob.pixelFormat = pixelFormat; + uploadJob.pendingSubTextureUpload = true; + return uploadJob; + } + + public void completeUploadSubPixelsAsync() { + if(uploadJob == null || !uploadJob.pendingSubTextureUpload) + return; + + uploadJob.waitForCompletion(); + uploadJob.pendingSubTextureUpload = false; + + unmap(uploadJob.xOffset, uploadJob.yOffset, uploadJob.zOffset, uploadJob.width, uploadJob.height, uploadJob.depth, uploadJob.pixelFormat); + + PixelUploadJob.POOL.recycle(uploadJob); + uploadJob = null; + } + + public void uploadSubPixels2D(int width, int height, int[] pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(0, 0, 0, width, height, 0, pixelData, pixelFormat); + } + + public void uploadSubPixels2D(int width, int height, float[] pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(0, 0, 0, width, height, 0, pixelData, pixelFormat); + } + + 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, int[] pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(xOffset, yOffset, 0, width, height, 0, pixelData, pixelFormat); + } + + public void uploadSubPixels2D(int xOffset, int yOffset, int width, int height, float[] pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(xOffset, yOffset, 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, int[] pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(0, 0, zOffset, width, height, 1, pixelData, pixelFormat); + } + + public void uploadSubPixels3D(int zOffset, int width, int height, float[] pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(0, 0, zOffset, width, height, 1, 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, int[] pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(0, 0, zOffset, width, height, depth, pixelData, pixelFormat); + } + + public void uploadSubPixels3D(int zOffset, int width, int height, int depth, float[] pixelData, GLTextureFormat pixelFormat) { + uploadSubPixels(0, 0, zOffset, width, height, depth, 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, Object 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(); + + if (this.depth > 1) { + if (pixelData instanceof ByteBuffer) { + glTexSubImage3D(textureParams.type.glTarget, 0, + xOffset, yOffset, zOffset, + width, height, depth, + pixelFormat.format, pixelFormat.type, (ByteBuffer)pixelData); + } else if (pixelData instanceof ShortBuffer) { + glTexSubImage3D(textureParams.type.glTarget, 0, + xOffset, yOffset, zOffset, + width, height, depth, + pixelFormat.format, pixelFormat.type, (ShortBuffer)pixelData); + } else if (pixelData instanceof IntBuffer) { + glTexSubImage3D(textureParams.type.glTarget, 0, + xOffset, yOffset, zOffset, + width, height, depth, + pixelFormat.format, pixelFormat.type, (IntBuffer)pixelData); + } else if (pixelData instanceof FloatBuffer) { + glTexSubImage3D(textureParams.type.glTarget, 0, + xOffset, yOffset, zOffset, + width, height, depth, + pixelFormat.format, pixelFormat.type, (FloatBuffer)pixelData); + } else if (pixelData instanceof int[]) { + glTexSubImage3D(textureParams.type.glTarget, 0, + xOffset, yOffset, zOffset, + width, height, depth, + pixelFormat.format, pixelFormat.type, (int[])pixelData); + + } else if (pixelData instanceof float[]) { + glTexSubImage3D(textureParams.type.glTarget, 0, + xOffset, yOffset, zOffset, + width, height, depth, + pixelFormat.format, pixelFormat.type, (float[])pixelData); + } else { + throw new IllegalArgumentException("Unsupported pixel data type for glTexSubImage3D: " + pixelData.getClass()); + } + } else { + if (pixelData instanceof ByteBuffer) { + glTexSubImage2D(textureParams.type.glTarget, 0, + xOffset, yOffset, + width, height, + pixelFormat.format, pixelFormat.type, (ByteBuffer)pixelData); + } else if (pixelData instanceof ShortBuffer) { + glTexSubImage2D(textureParams.type.glTarget, 0, + xOffset, yOffset, + width, height, + pixelFormat.format, pixelFormat.type, (ShortBuffer)pixelData); + } else if (pixelData instanceof IntBuffer) { + glTexSubImage2D(textureParams.type.glTarget, 0, + xOffset, yOffset, + width, height, + pixelFormat.format, pixelFormat.type, (IntBuffer)pixelData); + } else if (pixelData instanceof FloatBuffer) { + glTexSubImage2D(textureParams.type.glTarget, 0, + xOffset, yOffset, + width, height, + pixelFormat.format, pixelFormat.type, (FloatBuffer)pixelData); + } else if(pixelData instanceof int[]) { + glTexSubImage2D(textureParams.type.glTarget, 0, + xOffset, yOffset, + width, height, + pixelFormat.format, pixelFormat.type, (int[])pixelData); + } else if(pixelData instanceof float[]) { + glTexSubImage2D(textureParams.type.glTarget, 0, + xOffset, yOffset, + width, height, + pixelFormat.format, pixelFormat.type, (float[])pixelData); + } else { + throw new IllegalArgumentException("Unsupported buffer type for glTexSubImage2D: " + pixelData.getClass()); + } + } + + unbind(); + 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(); + glGenerateMipmap(textureParams.type.glTarget); + unbind(); + checkGLErrors(() -> textureParams.debugName); + mipmapsDirty = false; + } + } + + private static class PixelUploadJob extends Job { + private static final ConcurrentPool POOL = new ConcurrentPool<>(PixelUploadJob::new); + + private GLTexture texture; + private int xOffset, yOffset, zOffset, width, height, depth; + private GLTextureFormat pixelFormat; + + @Setter + private byte[] pixelsDataBytes; + @Setter + private int[] pixelsDataInts; + @Setter + private float[] pixelsDataFloats; + @Setter + private Buffer pixelData; + + private boolean pendingSubTextureUpload = false; + + @Override + protected void onRun() { + int bpp = pixelFormat.bytesPerPixel(); + int unalignedRowSize = texture.getWidth() * bpp; + int rowStride = ((unalignedRowSize + UNPACK_ALIGNMENT - 1) / UNPACK_ALIGNMENT) * UNPACK_ALIGNMENT; + int sliceStride = rowStride * texture.getHeight(); + + final GLMappedBuffer mappedBuffer = texture.unpackBuffers[texture.pboIdx].mapped(); + mappedBuffer.setPositionBytes( + (zOffset * sliceStride) + + (yOffset * rowStride) + + (xOffset * bpp)); + + if(pixelData != null) { + if (pixelData instanceof ByteBuffer) { + mappedBuffer.byteView().put((ByteBuffer) pixelData); + } else if (pixelData instanceof IntBuffer) { + mappedBuffer.intView().put((IntBuffer) pixelData); + } else if (pixelData instanceof FloatBuffer) { + mappedBuffer.floatView().put((FloatBuffer) pixelData); + } + } else { + if(pixelsDataBytes != null) { + mappedBuffer.byteView().put(pixelsDataBytes); + } else if(pixelsDataInts != null) { + mappedBuffer.intView().put(pixelsDataInts); + } else if(pixelsDataFloats != null) { + mappedBuffer.floatView().put(pixelsDataFloats); + } + } + + mappedBuffer.syncViews(); + + texture = null; + pixelData = null; + pixelsDataBytes = null; + pixelsDataInts = null; + pixelsDataFloats = null; + } + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/utils/texture/GLTextureFormat.java b/src/main/java/rs117/hd/utils/texture/GLTextureFormat.java new file mode 100644 index 0000000000..70c09845cf --- /dev/null +++ b/src/main/java/rs117/hd/utils/texture/GLTextureFormat.java @@ -0,0 +1,141 @@ +package rs117.hd.utils.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_R16UI; +import static org.lwjgl.opengl.GL30.GL_R32I; +import static org.lwjgl.opengl.GL30.GL_R32UI; +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_RGBA32UI; +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_RGBA32UI, 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_R16UI, 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_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, 4, 32, 0, 0, 0), + + BGRA8(GL_BGRA, GL_BGRA, GL_UNSIGNED_BYTE, 4, 8, 8, 8, 8), + BGRA_INT_8_8_8_8(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 channels; + 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 int bytesPerPixel() { + return (redSize + greenSize + blueSize + alphaSize) / 8; + } + + 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/texture/GLTextureParams.java b/src/main/java/rs117/hd/utils/texture/GLTextureParams.java new file mode 100644 index 0000000000..a72dc8ae62 --- /dev/null +++ b/src/main/java/rs117/hd/utils/texture/GLTextureParams.java @@ -0,0 +1,76 @@ +package rs117.hd.utils.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 int pboCount = -1; + 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 setPixelPackBufferCount(int pboCount) { + this.pboCount = pboCount; + 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/texture/GLTextureType.java b/src/main/java/rs117/hd/utils/texture/GLTextureType.java new file mode 100644 index 0000000000..0da4bebeb1 --- /dev/null +++ b/src/main/java/rs117/hd/utils/texture/GLTextureType.java @@ -0,0 +1,16 @@ +package rs117.hd.utils.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; +}