diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 3a677604fa..893cbe7bfc 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -28,7 +28,6 @@ import com.google.gson.Gson; import com.google.inject.Provides; -import java.awt.Canvas; import java.awt.Dimension; import java.awt.GraphicsConfiguration; import java.awt.Image; @@ -49,12 +48,14 @@ import javax.inject.Inject; import javax.swing.SwingUtilities; import lombok.Getter; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; import net.runelite.api.events.*; import net.runelite.api.hooks.*; import net.runelite.client.RuneLite; import net.runelite.client.callback.ClientThread; +import net.runelite.client.callback.Hooks; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.EventBus; import net.runelite.client.eventbus.Subscribe; @@ -68,7 +69,6 @@ import net.runelite.client.ui.ClientUI; import net.runelite.client.util.LinkBrowser; import net.runelite.client.util.OSType; -import net.runelite.rlawt.AWTContext; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.*; import org.lwjgl.system.Callback; @@ -80,7 +80,11 @@ import rs117.hd.config.ShadingMode; import rs117.hd.config.ShadowMode; import rs117.hd.config.VanillaShadowMode; -import rs117.hd.opengl.AsyncUICopy; +import rs117.hd.opengl.commandbuffer.AWTContextWrapper; +import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.commandbuffer.CommandBufferPool; +import rs117.hd.opengl.commandbuffer.FrameSync; +import rs117.hd.opengl.commandbuffer.RenderThread; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; import rs117.hd.opengl.shader.ShadowShaderProgram; @@ -246,9 +250,6 @@ public class HdPlugin extends Plugin { @Inject private ModelOverrideManager modelOverrideManager; - @Inject - private AsyncUICopy asyncUICopy; - @Inject private FishingSpotReplacer fishingSpotReplacer; @@ -282,6 +283,18 @@ public class HdPlugin extends Plugin { @Inject private TiledLightingOverlay tiledLightingOverlay; + @Inject + private RenderThread renderThread; + + @Inject + private AWTContextWrapper awtContextWrapper; + + @Inject + private CommandBufferPool commandBufferPool; + + @Inject + private Hooks hooks; + @Inject public HDVariables vars; @@ -289,10 +302,9 @@ public class HdPlugin extends Plugin { public static boolean SKIP_GL_ERROR_CHECKS; public static GLCapabilities GL_CAPS; + public static GLCapabilities GL_RENDER_THREAD_CAPS; public static boolean AMD_GPU; - public Canvas canvas; - public AWTContext awtContext; private Callback debugCallback; private static final String LINUX_VERSION_HEADER = @@ -316,7 +328,9 @@ public class HdPlugin extends Plugin { private int[] uiResolution; private final int[] actualUiResolution = { 0, 0 }; // Includes stretched mode and DPI scaling private int texUi; - private int pboUi; + private int[] pboUi = new int[2]; + private int pboUiIdx = 0; + private final FrameSync uiUploadSync = new FrameSync(); @Nullable public int[] sceneViewport; @@ -361,11 +375,11 @@ public class HdPlugin extends Plugin { public boolean configUseFasterModelHashing; public boolean configUndoVanillaShading; public boolean configPreserveVanillaNormals; - public boolean configAsyncUICopy; public boolean configWindDisplacement; public boolean configCharacterDisplacement; public boolean configTiledLighting; public boolean configTiledLightingImageLoadStore; + public boolean configRenderThread; public DynamicLights configDynamicLights; public ShadowMode configShadowMode; public SeasonalTheme configSeasonalTheme; @@ -444,21 +458,8 @@ protected void startUp() { lastFrameTimeMillis = 0; lastFrameClientTime = 0; - AWTContext.loadNatives(); - canvas = client.getCanvas(); - - synchronized (canvas.getTreeLock()) { - // Delay plugin startup until the client's canvas is valid - if (!canvas.isValid()) - return false; - - awtContext = new AWTContext(canvas); - awtContext.configurePixelFormat(0, 0, 0); - } - - awtContext.createGLContext(); - - canvas.setIgnoreRepaint(true); + if(!awtContextWrapper.initialize()) + return false; // lwjgl defaults to lwjgl- + user.name, but this breaks if the username would cause an invalid path // to be created. @@ -558,6 +559,8 @@ protected void startUp() { } } + renderThread.initialize(); + updateCachedConfigs(); developerTools.activate(); @@ -637,7 +640,6 @@ protected void shutDown() { client.setUnlockedFps(false); client.setExpandedMapLoading(0); - asyncUICopy.complete(); developerTools.deactivate(); tileOverrideManager.shutDown(); groundMaterialManager.shutDown(); @@ -673,9 +675,11 @@ protected void shutDown() { renderer = null; } - if (awtContext != null) - awtContext.destroy(); - awtContext = null; + renderThread.shutdown(); + awtContextWrapper.shutdown(); + + GL_CAPS = null; + GL_RENDER_THREAD_CAPS = null; if (debugCallback != null) debugCallback.free(); @@ -706,8 +710,6 @@ public void stopPlugin() { public void restartPlugin() { clientThread.invoke(() -> { shutDown(); - // Validate the canvas so it becomes valid without having to manually resize the client - canvas.validate(); startUp(); }); } @@ -1006,7 +1008,8 @@ private void destroyUbos() { } private void initializeUiTexture() { - pboUi = glGenBuffers(); + for(int i = 0; i < pboUi.length; i++) + pboUi[i] = glGenBuffers(); texUi = glGenTextures(); glActiveTexture(TEXTURE_UNIT_UI); @@ -1020,9 +1023,12 @@ private void initializeUiTexture() { private void destroyUiTexture() { uiResolution = null; - if (pboUi != 0) - glDeleteBuffers(pboUi); - pboUi = 0; + for(int i = 0; i < pboUi.length; i++) { + if (pboUi[i] != 0) { + glDeleteBuffers(pboUi[i]); + pboUi[i] = 0; + } + } if (texUi != 0) glDeleteTextures(texUi); @@ -1037,6 +1043,7 @@ public void updateTiledLightingFbo() { if (Arrays.equals(newResolution, tiledLightingResolution) && tiledLightingLayerCount == newLayerCount) return; + renderThread.waitForRenderingCompleted(); destroyTiledLightingFbo(); tiledLightingResolution = newResolution; @@ -1071,7 +1078,7 @@ public void updateTiledLightingFbo() { ARBShaderImageLoadStore.glBindImageTexture( IMAGE_UNIT_TILED_LIGHTING, texTiledLighting, 0, true, 0, GL_WRITE_ONLY, GL_RGBA16UI); - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); checkGLErrors(); @@ -1120,11 +1127,13 @@ public void updateSceneFbo() { if (Arrays.equals(sceneViewport, viewport)) return; + renderThread.waitForRenderingCompleted(); + destroySceneFbo(); sceneViewport = viewport; // Bind default FBO to check whether anti-aliasing is forced - int defaultFramebuffer = awtContext.getFramebuffer(false); + int defaultFramebuffer = awtContextWrapper.getBackBuffer(); glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); final int forcedAASamples = glGetInteger(GL_SAMPLES); msaaSamples = forcedAASamples != 0 ? forcedAASamples : min(config.antiAliasingMode().getSamples(), glGetInteger(GL_MAX_SAMPLES)); @@ -1196,13 +1205,15 @@ public void updateSceneFbo() { } // Reset - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); glBindRenderbuffer(GL_RENDERBUFFER, 0); } private void destroySceneFbo() { sceneViewport = null; + renderer.waitUntilIdle(); + if (fboScene != 0) glDeleteFramebuffers(fboScene); fboScene = 0; @@ -1225,6 +1236,8 @@ private void destroySceneFbo() { } private void initializeShadowMapFbo() { + renderer.waitUntilIdle(); + if (!configShadowsEnabled) { initializeDummyShadowMap(); return; @@ -1271,7 +1284,7 @@ private void initializeShadowMapFbo() { glReadBuffer(GL_NONE); // Reset FBO - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); } private void initializeDummyShadowMap() { @@ -1287,6 +1300,8 @@ private void initializeDummyShadowMap() { } private void destroyShadowMapFbo() { + renderer.waitUntilIdle(); + if (texShadowMap != 0) glDeleteTextures(texShadowMap); texShadowMap = 0; @@ -1303,6 +1318,7 @@ public void initializeShaderHotswapping() { }); } + @SneakyThrows public void prepareInterfaceTexture() { int[] resolution = { max(1, client.getCanvasWidth()), @@ -1312,13 +1328,17 @@ public void prepareInterfaceTexture() { if (resize) { uiResolution = resolution; - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi); - glBufferData(GL_PIXEL_UNPACK_BUFFER, uiResolution[0] * uiResolution[1] * 4L, GL_STREAM_DRAW); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + renderThread.invokeOnRenderThread(() -> { + for(int i = 0; i < pboUi.length; i++) { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi[i]); + glBufferData(GL_PIXEL_UNPACK_BUFFER, uiResolution[0] * uiResolution[1] * 4L, GL_STREAM_DRAW); + } + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - glActiveTexture(TEXTURE_UNIT_UI); - glBindTexture(GL_TEXTURE_2D, texUi); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, uiResolution[0], uiResolution[1], 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + 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); + }); } if (client.isStretchedEnabled()) { @@ -1330,44 +1350,22 @@ public void prepareInterfaceTexture() { } round(actualUiResolution, multiply(vec(actualUiResolution), getDpiScaling())); - if (configAsyncUICopy) { - // Start copying the UI on a different thread, to be uploaded during the next frame - asyncUICopy.prepare(pboUi, texUi); - // If the window was just resized, upload once synchronously so there is something to show - if (resize) - asyncUICopy.complete(); - return; - } - final BufferProvider bufferProvider = client.getBufferProvider(); final int[] pixels = bufferProvider.getPixels(); final int width = bufferProvider.getWidth(); final int height = bufferProvider.getHeight(); - frameTimer.begin(Timer.MAP_UI_BUFFER); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboUi); - ByteBuffer mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); - frameTimer.end(Timer.MAP_UI_BUFFER); - if (mappedBuffer == null) { - log.error("Unable to map interface PBO. Skipping UI..."); - } else if (width > uiResolution[0] || height > uiResolution[1]) { - log.error("UI texture resolution mismatch ({}x{} > {}). Skipping UI...", width, height, uiResolution); - } else { - frameTimer.begin(Timer.COPY_UI); - mappedBuffer.asIntBuffer().put(pixels, 0, width * height); - frameTimer.end(Timer.COPY_UI); - - frameTimer.begin(Timer.UPLOAD_UI); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - glActiveTexture(TEXTURE_UNIT_UI); - glBindTexture(GL_TEXTURE_2D, texUi); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); - frameTimer.end(Timer.UPLOAD_UI); - } - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + uiUploadSync.markInFlight(); + + CommandBuffer cmd = commandBufferPool.acquire(); + cmd.BeginTimer(Timer.UPLOAD_UI); + cmd.UploadPixelData(TEXTURE_UNIT_UI, texUi, pboUi[pboUiIdx], width, height, pixels, uiUploadSync); + cmd.EndTimer(Timer.UPLOAD_UI); + pboUiIdx = (pboUiIdx + 1) % pboUi.length; + renderThread.submit(cmd); } - public void drawUi(int overlayColor) { + public void drawUi(int overlayColor, CommandBuffer cmd) { if (uiResolution == null || developerTools.isHideUiEnabled() && hasLoggedIn) return; @@ -1375,21 +1373,20 @@ public void drawUi(int overlayColor) { if (client.getGameState().getState() < GameState.LOADING.getState()) overlayColor = 0; - frameTimer.begin(Timer.RENDER_UI); - - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + cmd.BeginTimer(Timer.RENDER_UI); + cmd.BindFrameBuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); // Disable alpha writes, just in case the default FBO has an alpha channel - glColorMask(true, true, true, false); + cmd.ColorMask(true, true, true, false); + + cmd.Viewport(0, 0, uiResolution[0], uiResolution[1]); - glViewport(0, 0, actualUiResolution[0], actualUiResolution[1]); + tiledLightingOverlay.render(cmd); - tiledLightingOverlay.render(); + cmd.SetShaderProgram(uiProgram); - uiProgram.use(); - uboUI.sourceDimensions.set(uiResolution); - uboUI.targetDimensions.set(actualUiResolution); - uboUI.alphaOverlay.set(ColorUtils.srgba(overlayColor)); - uboUI.upload(); + cmd.SetUniformProperty(uboUI.sourceDimensions, uiResolution); + cmd.SetUniformProperty(uboUI.targetDimensions, actualUiResolution); + cmd.SetUniformProperty(uboUI.alphaOverlay, ColorUtils.srgba(overlayColor)); // 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. @@ -1401,25 +1398,25 @@ public void drawUi(int overlayColor) { 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); - glDrawArrays(GL_TRIANGLES, 0, 3); + cmd.Enable(GL_BLEND); + cmd.BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + cmd.BindVertexArray(vaoTri); + cmd.DrawArrays(GL_TRIANGLES, 0, 3); - shadowMapOverlay.render(); - gammaCalibrationOverlay.render(); + shadowMapOverlay.render(cmd); + gammaCalibrationOverlay.render(cmd); // Reset - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - glDisable(GL_BLEND); - glColorMask(true, true, true, true); + cmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + cmd.Disable(GL_BLEND); + cmd.ColorMask(true, true, true, true); - frameTimer.end(Timer.RENDER_UI); + cmd.EndTimer(Timer.RENDER_UI); } /** - * Convert the front framebuffer to an Image - */ + * Convert the front framebuffer to an Image + */ public Image screenshot() { if (uiResolution == null) return null; @@ -1429,7 +1426,7 @@ public Image screenshot() { ByteBuffer buffer = BufferUtils.createByteBuffer(width * height * 4); - glReadBuffer(awtContext.getBufferMode()); + glReadBuffer(awtContextWrapper.getBufferMode()); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); @@ -1481,11 +1478,11 @@ private void updateCachedConfigs() { configDynamicLights = config.dynamicLights(); configTiledLighting = config.tiledLighting(); configTiledLightingImageLoadStore = config.tiledLightingImageLoadStore(); + configRenderThread = config.renderThread(); configExpandShadowDraw = config.expandShadowDraw(); configUseFasterModelHashing = config.fasterModelHashing(); configUndoVanillaShading = config.shadingMode() != ShadingMode.VANILLA; configPreserveVanillaNormals = config.preserveVanillaNormals(); - configAsyncUICopy = config.asyncUICopy(); configWindDisplacement = config.windDisplacement(); configCharacterDisplacement = config.characterDisplacement(); configSeasonalTheme = config.seasonalTheme(); @@ -1559,6 +1556,8 @@ public void processPendingConfigChanges() { if (pendingConfigChanges.isEmpty()) return; + renderer.waitUntilIdle(); + try { // Synchronize with scene loading synchronized (this) { @@ -1597,9 +1596,6 @@ public void processPendingConfigChanges() { if (configColorFilter == ColorFilter.CEL_SHADING || configColorFilterPrevious == ColorFilter.CEL_SHADING) reloadScene = true; break; - case KEY_ASYNC_UI_COPY: - asyncUICopy.complete(); - break; } switch (key) { @@ -1682,9 +1678,6 @@ public void processPendingConfigChanges() { } } - if (reloadTexturesAndMaterials || recompilePrograms) - renderer.waitUntilIdle(); - if (reloadTexturesAndMaterials) { materialManager.reload(false); modelOverrideManager.reload(); @@ -1755,7 +1748,7 @@ public void setupSyncMode() { break; } - int actualSwapInterval = awtContext.setSwapInterval(swapInterval); + int actualSwapInterval = awtContextWrapper.setSwapInterval(swapInterval); if (actualSwapInterval != swapInterval) { log.info("unsupported swap interval {}, got {}", swapInterval, actualSwapInterval); } @@ -1789,16 +1782,18 @@ public int getExpandedMapLoadingChunks() { return config.expandedMapLoadingChunks(); } + @SneakyThrows @Subscribe(priority = -1) // Run after the low detail plugin public void onBeforeRender(BeforeRender beforeRender) { SKIP_GL_ERROR_CHECKS = !log.isDebugEnabled() || developerTools.isFrameTimingsOverlayEnabled(); - // Upload the UI which we began copying during the previous frame - if (configAsyncUICopy) - asyncUICopy.complete(); + long start = System.nanoTime(); + uiUploadSync.await(); + frameTimer.add(Timer.COPY_UI, System.nanoTime() - start); if (client.getScene() == null) return; + // The game runs significantly slower with lower planes in Chambers of Xeric client.getScene().setMinLevel(isInChambersOfXeric ? client.getPlane() : client.getScene().getMinLevel()); } @@ -1826,14 +1821,17 @@ public static void clearGLErrors() { // @formatter:on } - public static void checkGLErrors() { + public static boolean checkGLErrors() { return checkGLErrors(null); } + + public static boolean checkGLErrors(String context) { if (SKIP_GL_ERROR_CHECKS) - return; + return false; + boolean hasError = false; while (true) { int err = glGetError(); if (err == GL_NO_ERROR) - return; + return hasError; String errStr; switch (err) { @@ -1860,7 +1858,12 @@ public static void checkGLErrors() { break; } - log.debug("glGetError:", new Exception(errStr)); + if(context != null) { + log.debug("{} - glGetError:", context, new Exception(errStr)); + } else { + log.debug("glGetError:", new Exception(errStr)); + } + hasError = true; } } diff --git a/src/main/java/rs117/hd/HdPluginConfig.java b/src/main/java/rs117/hd/HdPluginConfig.java index 9018a9b231..b04b321735 100644 --- a/src/main/java/rs117/hd/HdPluginConfig.java +++ b/src/main/java/rs117/hd/HdPluginConfig.java @@ -245,7 +245,7 @@ default SyncMode syncMode() ) @Range( min = 0, - max = 999 + max = 2000 ) default int fpsTarget() { @@ -1098,17 +1098,6 @@ default boolean wireframe() { return false; } - String KEY_ASYNC_UI_COPY = "experimentalAsyncUICopy"; - @ConfigItem( - keyName = KEY_ASYNC_UI_COPY, - name = "Perform UI copy asynchronously", - description = "Slightly improves performance by delaying the UI by one frame.", - section = experimentalSettings - ) - default boolean asyncUICopy() { - return false; - } - String KEY_TILED_LIGHTING_IMAGE_STORE = "experimentalTiledLightingImageStore"; @ConfigItem( keyName = KEY_TILED_LIGHTING_IMAGE_STORE, @@ -1120,6 +1109,17 @@ default boolean tiledLightingImageLoadStore() { return true; } + String KEY_RENDER_THREAD = "experimentalRenderThread"; + @ConfigItem( + keyName = KEY_RENDER_THREAD, + name = "Render Thread", + description = "Its a Render Thread...", + section = experimentalSettings + ) + default boolean renderThread() { + return true; + } + /*====== Internal settings ======*/ diff --git a/src/main/java/rs117/hd/opengl/AsyncUICopy.java b/src/main/java/rs117/hd/opengl/AsyncUICopy.java deleted file mode 100644 index 4f8a347789..0000000000 --- a/src/main/java/rs117/hd/opengl/AsyncUICopy.java +++ /dev/null @@ -1,108 +0,0 @@ -package rs117.hd.opengl; - -import java.nio.ByteBuffer; -import java.nio.IntBuffer; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import javax.inject.Inject; -import lombok.extern.slf4j.Slf4j; -import net.runelite.api.*; -import rs117.hd.HdPlugin; -import rs117.hd.overlays.FrameTimer; -import rs117.hd.overlays.Timer; - -import static org.lwjgl.opengl.GL33C.*; - -@Slf4j -public class AsyncUICopy implements Runnable { - @Inject - private Client client; - - @Inject - private HdPlugin plugin; - - @Inject - private FrameTimer timer; - - private final ExecutorService executor = Executors.newSingleThreadExecutor(); - private final Semaphore completionSemaphore = new Semaphore(0); - - private IntBuffer mappedBuffer; - private int[] pixels; - private int interfacePbo; - private int interfaceTexture; - private int width; - private int height; - - @Override - public void run() { - long time = System.nanoTime(); - mappedBuffer.put(pixels, 0, width * height); - time = System.nanoTime() - time; - completionSemaphore.release(); - timer.add(Timer.COPY_UI, time); - } - - public void prepare(int interfacePbo, int interfaceTex) { - // Ensure there isn't already another UI copy in progress - if (mappedBuffer != null) - return; - - timer.begin(Timer.MAP_UI_BUFFER); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, interfacePbo); - ByteBuffer buffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - timer.end(Timer.MAP_UI_BUFFER); - if (buffer == null) { - log.error("Unable to map interface PBO. Skipping UI..."); - return; - } - - this.interfacePbo = interfacePbo; - this.interfaceTexture = interfaceTex; - this.mappedBuffer = buffer.asIntBuffer(); - - var provider = client.getBufferProvider(); - this.pixels = provider.getPixels(); - this.width = provider.getWidth(); - this.height = provider.getHeight(); - - executor.execute(this); - } - - public boolean complete() { - if (mappedBuffer == null) - return false; - - try { - // It shouldn't take this long even in the worst case - boolean acquired = completionSemaphore.tryAcquire(1, 100, TimeUnit.MILLISECONDS); - if (!acquired) - return false; - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - var uiResolution = plugin.getUiResolution(); - if (uiResolution == null || width > uiResolution[0] || height > uiResolution[1]) { - log.error("UI texture resolution mismatch ({}x{} > {}). Skipping UI...", width, height, uiResolution); - return false; - } - - timer.begin(Timer.UPLOAD_UI); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, interfacePbo); - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - glActiveTexture(HdPlugin.TEXTURE_UNIT_UI); - glBindTexture(GL_TEXTURE_2D, interfaceTexture); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); - timer.end(Timer.UPLOAD_UI); - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - - mappedBuffer = null; - pixels = null; - return true; - } -} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java new file mode 100644 index 0000000000..ad29161474 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/AWTContextWrapper.java @@ -0,0 +1,149 @@ +package rs117.hd.opengl.commandbuffer; + +import java.awt.Canvas; +import java.util.concurrent.ConcurrentLinkedQueue; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.*; +import net.runelite.rlawt.AWTContext; +import org.lwjgl.opengl.*; +import rs117.hd.HdPlugin; + +@Slf4j +@Singleton +public final class AWTContextWrapper { + private static final boolean VALIDATE = log.isDebugEnabled(); + + public enum Owner { CLIENT, RENDER_THREAD, NONE } + + private final Object ownershipLock = new Object(); + private volatile Owner currentOwner = Owner.CLIENT; + + private static final int MAX_OWNERSHIP_LOG = 8; + private final ConcurrentLinkedQueue ownershipLog = new ConcurrentLinkedQueue<>(); + + private final StringBuilder sb = new StringBuilder(); + + @Inject + private Client client; + + @Getter + private AWTContext context; + + @Getter + private Canvas canvas; + + public boolean initialize() { + AWTContext.loadNatives(); + canvas = client.getCanvas(); + + synchronized (canvas.getTreeLock()) { + // Delay plugin startup until the client's canvas is valid + if (!canvas.isValid()) + return false; + + context = new AWTContext(canvas); + context.configurePixelFormat(0, 0, 0); + } + + context.createGLContext(); + canvas.setIgnoreRepaint(true); + return true; + } + + public void shutdown() { + if (context != null) + context.destroy(); + context = null; + + // Validate the canvas so it becomes valid without having to manually resize the client + if(canvas != null) + canvas.validate(); + canvas = null; + } + + public int setSwapInterval(int interval) { return context.setSwapInterval(interval); } + + public int getBufferMode() { return context.getBufferMode(); } + + public int getBackBuffer() { return context.getFramebuffer(false); } + + public Owner getOwner() { return currentOwner; } + + public void makeCurrent(Owner newOwner) { + if (currentOwner == newOwner) + return; + + synchronized (ownershipLock) { + while (currentOwner != Owner.NONE) { + try { + ownershipLock.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + + try { + context.makeCurrent(); + if(newOwner == Owner.RENDER_THREAD && HdPlugin.GL_RENDER_THREAD_CAPS == null){ + HdPlugin.GL_RENDER_THREAD_CAPS = GL.createCapabilities(); + } + updateOwner(newOwner, "makeCurrent"); + } catch (Exception e) { + printOwnershipLog(); + log.error("Failed to make OpenGL context current for {}", newOwner, e); + throw new RuntimeException(e); + } + } + } + + public void detachCurrent(String reason) { + try { + context.detachCurrent(); + updateOwner(Owner.NONE, reason); + } catch (Exception e) { + printOwnershipLog(); + log.error("Failed to detach OpenGL context: " + reason, e); + throw new RuntimeException(e); + } + } + + private void updateOwner(Owner newOwner, String action) { + if (currentOwner != newOwner && VALIDATE) { + if (ownershipLog.size() >= MAX_OWNERSHIP_LOG) + ownershipLog.poll(); + + sb.append(currentOwner) + .append(" -> ") + .append(newOwner) + .append(" via ") + .append(action); + + ownershipLog.add(sb.toString()); + + sb.setLength(0); + } + + currentOwner = newOwner; + + synchronized (ownershipLock) { + ownershipLock.notifyAll(); + } + } + + private void printOwnershipLog() { + if (!VALIDATE) return; + + if (ownershipLog.isEmpty()) { + log.error("No recent ownership transitions recorded."); + } else { + log.error("Recent ownership transitions:"); + for (String entry : ownershipLog) { + log.error(" - {}", entry); + } + } + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java new file mode 100644 index 0000000000..73bce74c9d --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/BaseCommand.java @@ -0,0 +1,67 @@ +package rs117.hd.opengl.commandbuffer; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.uniforms.UniformBuffer; + +@Slf4j +public abstract class BaseCommand { + + protected int id; + + @Getter + private final String name; + + @Getter + private final boolean isDrawCall; + + @Getter + private final boolean isGLCommand; + + protected CommandBuffer owner; + + protected BaseCommand(boolean isDrawCall, boolean isGLCommand) { + this.name = getClass().getSimpleName(); + this.isDrawCall = isDrawCall; + this.isGLCommand = isDrawCall || isGLCommand; + } + + protected BaseCommand(boolean isDrawCall) { this(isDrawCall, false); } + + protected BaseCommand() { this(false, false); } + + protected abstract void execute(MemoryStack stack); + protected abstract void print(StringBuilder sb); + + protected final void write() { + write8(id); + doWrite(); + } + + protected abstract void doWrite(); + protected abstract void doRead(MemoryStack stack); + + protected final void markUniformBufferDirty(UniformBuffer buffer) { owner.markUniformBufferDirty(buffer); } + + protected final void write1(int value) { owner.writeBits(value & 0x1L, 1); } + protected final void write8(int value) { owner.writeBits(value & 0xFFL, 8); } + protected final void write16(int value) { owner.writeBits(value & 0xFFFFL, 16); } + protected final void write32(int value) { owner.writeBits(value & 0xFFFFFFFFL, 32); } + protected final void write64(long value) { owner.writeBits(value, 64); } + + protected final int read1() { return (int) owner.readBits(1); } + protected final int read8() { return (int) owner.readBits(8); } + protected final int read16() { return (int) owner.readBits(16); } + protected final int read32() { return (int) owner.readBits(32); } + protected final long read64() { return owner.readBits(64); } + + protected final void writeObject(Object value) {owner.writeObject(value);} + protected final T readObject() { return owner.readObject(); } + + protected final void write32F(float value) { write32(Float.floatToIntBits(value)); } + protected final float read32F() { return Float.intBitsToFloat(read32()); } + + protected final void writeFlag(boolean value) { write1(value ? 1 : 0); } + protected final boolean readFlag() { return read1() != 0; } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java new file mode 100644 index 0000000000..5d1b4d4fd6 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBuffer.java @@ -0,0 +1,570 @@ +package rs117.hd.opengl.commandbuffer; + +import java.util.Arrays; +import lombok.extern.slf4j.Slf4j; +import net.runelite.rlawt.AWTContext; +import org.lwjgl.system.MemoryStack; +import rs117.hd.HdPlugin; +import rs117.hd.opengl.commandbuffer.commands.BindElementsArrayCommand; +import rs117.hd.opengl.commandbuffer.commands.BindFrameBufferCommand; +import rs117.hd.opengl.commandbuffer.commands.BindVertexArrayCommand; +import rs117.hd.opengl.commandbuffer.commands.BlendFuncCommand; +import rs117.hd.opengl.commandbuffer.commands.BlitFrameBufferCommand; +import rs117.hd.opengl.commandbuffer.commands.CallbackCommand; +import rs117.hd.opengl.commandbuffer.commands.ClearCommand; +import rs117.hd.opengl.commandbuffer.commands.ColorMaskCommand; +import rs117.hd.opengl.commandbuffer.commands.DepthFuncCommand; +import rs117.hd.opengl.commandbuffer.commands.DepthMaskCommand; +import rs117.hd.opengl.commandbuffer.commands.DrawArraysCommand; +import rs117.hd.opengl.commandbuffer.commands.DrawBufferCommand; +import rs117.hd.opengl.commandbuffer.commands.DrawElementsCommand; +import rs117.hd.opengl.commandbuffer.commands.ExecuteCommandBufferCommand; +import rs117.hd.opengl.commandbuffer.commands.FrameBufferLayerCommand; +import rs117.hd.opengl.commandbuffer.commands.FrameTimerCommand; +import rs117.hd.opengl.commandbuffer.commands.MultiDrawArraysCommand; +import rs117.hd.opengl.commandbuffer.commands.SetShaderUniformCommand; +import rs117.hd.opengl.commandbuffer.commands.SetUniformBufferPropertyCommand; +import rs117.hd.opengl.commandbuffer.commands.ShaderProgramCommand; +import rs117.hd.opengl.commandbuffer.commands.SignalCommand; +import rs117.hd.opengl.commandbuffer.commands.SwapBuffersCommand; +import rs117.hd.opengl.commandbuffer.commands.ToggleCommand; +import rs117.hd.opengl.commandbuffer.commands.UploadPixelDataCommand; +import rs117.hd.opengl.commandbuffer.commands.UploadUniformBufferCommand; +import rs117.hd.opengl.commandbuffer.commands.ViewportCommand; +import rs117.hd.opengl.shader.ShaderProgram; +import rs117.hd.opengl.uniforms.UniformBuffer; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; + +import static rs117.hd.utils.MathUtils.*; + +@Slf4j +public final class CommandBuffer { + private static final int BITS_PER_WORD = 64; + private static final long[] MASKS = new long[65]; + + static { + MASKS[0] = 0L; + for (int i = 1; i < BITS_PER_WORD; i++) { + MASKS[i] = (1L << i) - 1L; + } + MASKS[BITS_PER_WORD] = ~0L; + } + + private int COMMAND_COUNT = 0; + private final BaseCommand[] REGISTERED_COMMANDS = new BaseCommand[100]; + + private final DrawArraysCommand DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(DrawArraysCommand::new); + private final DrawElementsCommand DRAW_ELEMENTS_COMMAND = REGISTER_COMMAND(DrawElementsCommand::new); + private final MultiDrawArraysCommand MULTI_DRAW_ARRAYS_COMMAND = REGISTER_COMMAND(MultiDrawArraysCommand::new); + private final ToggleCommand TOGGLE_COMMAND = REGISTER_COMMAND(ToggleCommand::new); + private final BindFrameBufferCommand BIND_FRAMEBUFFER_COMMAND = REGISTER_COMMAND(BindFrameBufferCommand::new); + private final ViewportCommand VIEWPORT_COMMAND = REGISTER_COMMAND(ViewportCommand::new); + private final ClearCommand CLEAR_COMMAND = REGISTER_COMMAND(ClearCommand::new); + private final ColorMaskCommand COLOR_MASK_COMMAND = REGISTER_COMMAND(ColorMaskCommand::new); + private final DepthFuncCommand DEPTH_FUNC_COMMAND = REGISTER_COMMAND(DepthFuncCommand::new); + private final DepthMaskCommand DEPTH_MASK_COMMAND = REGISTER_COMMAND(DepthMaskCommand::new); + private final BlendFuncCommand BLENDED_FUNC_COMMAND = REGISTER_COMMAND(BlendFuncCommand::new); + private final BindElementsArrayCommand BIND_ELEMENTS_ARRAY_COMMAND = REGISTER_COMMAND(BindElementsArrayCommand::new); + private final BindVertexArrayCommand BIND_VERTEX_ARRAY_COMMAND = REGISTER_COMMAND(BindVertexArrayCommand::new); + private final BlitFrameBufferCommand BLIT_FRAME_BUFFER_COMMAND = REGISTER_COMMAND(BlitFrameBufferCommand::new); + private final SetUniformBufferPropertyCommand SET_UNIFORM_BUFFER_PROPERTY_COMMAND = REGISTER_COMMAND(SetUniformBufferPropertyCommand::new); + private final SetShaderUniformCommand SET_SHADER_PROPERTY_COMMAND = REGISTER_COMMAND(SetShaderUniformCommand::new); + private final ShaderProgramCommand SHADER_PROGRAM_COMMAND = REGISTER_COMMAND(ShaderProgramCommand::new); + private final UploadPixelDataCommand UPLOAD_PIXEL_DATA_COMMAND = REGISTER_COMMAND(UploadPixelDataCommand::new); + private final ExecuteCommandBufferCommand EXECUTE_COMMAND_BUFFER_COMMAND = REGISTER_COMMAND(ExecuteCommandBufferCommand::new); + private final SwapBuffersCommand SWAP_BUFFER_COMMAND = REGISTER_COMMAND(SwapBuffersCommand::new); + private final FrameTimerCommand FRAME_TIMER_COMMAND = REGISTER_COMMAND(FrameTimerCommand::new); + private final CallbackCommand CALLBACK_COMMAND = REGISTER_COMMAND(CallbackCommand::new); + private final SignalCommand SIGNAL_COMMAND = REGISTER_COMMAND(SignalCommand::new);; + private final UploadUniformBufferCommand UPLOAD_UNIFORM_BUFFER_COMMAND = REGISTER_COMMAND(UploadUniformBufferCommand::new); + private final DrawBufferCommand DRAW_BUFFER_COMMAND = REGISTER_COMMAND(DrawBufferCommand::new); + private final FrameBufferLayerCommand FRAME_BUFFER_LAYER_COMMAND = REGISTER_COMMAND(FrameBufferLayerCommand::new); + + interface ICreateCommand { T construct(); } + + private T REGISTER_COMMAND(ICreateCommand createCommand) { + { + T ExecutorCommand = createCommand.construct(); + ExecutorCommand.id = COMMAND_COUNT; + ExecutorCommand.owner = this; + + REGISTERED_COMMANDS[COMMAND_COUNT] = ExecutorCommand; + } + + { + T WriterCommand = createCommand.construct(); + WriterCommand.id = COMMAND_COUNT; + WriterCommand.owner = this; + COMMAND_COUNT++; + return WriterCommand; + } + } + + private long[] cmd = new long[1000]; + + private final UniformBuffer[] pendingUBOUploads = new UniformBuffer[100]; + private int pendingUBOUploadsCount = 0; + + private Object[] objects = new Object[100]; + private int objectCount = 0; + + private int writeHead = 0; + private int writeBitHead = 0; + + private int readHead = 0; + private int readBitHead = 0; + + protected boolean highPriority; + protected boolean pooled; + + private Timer executionTimer = null; + + public CommandBuffer SetExecutionTimer(Timer timer) { + this.executionTimer = timer; + return this; + } + + public void SetUniformProperty(UniformBuffer.Property property, int... values) { + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.property = property; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.intValues = values; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.isFloat = false; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.write(); + } + + public void SetUniformProperty(UniformBuffer.Property property, float... values) { + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.property = property; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.floatValues = values; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.isFloat = true; + SET_UNIFORM_BUFFER_PROPERTY_COMMAND.write(); + } + + public void SetUniformProperty(ShaderProgram.UniformProperty property, int... values) { + SET_SHADER_PROPERTY_COMMAND.property = property; + SET_SHADER_PROPERTY_COMMAND.intValues = values; + SET_SHADER_PROPERTY_COMMAND.isFloat = false; + SET_SHADER_PROPERTY_COMMAND.write(); + } + + public void SetUniformProperty(ShaderProgram.UniformProperty property, float... values) { + SET_SHADER_PROPERTY_COMMAND.property = property; + SET_SHADER_PROPERTY_COMMAND.floatValues = values; + SET_SHADER_PROPERTY_COMMAND.isFloat = true; + SET_SHADER_PROPERTY_COMMAND.write(); + } + + public void FrameBufferLayer(int attachment, int texId, int level, int layer) { + FRAME_BUFFER_LAYER_COMMAND.attachment = attachment; + FRAME_BUFFER_LAYER_COMMAND.texId = texId; + FRAME_BUFFER_LAYER_COMMAND.level = level; + FRAME_BUFFER_LAYER_COMMAND.layer = layer; + FRAME_BUFFER_LAYER_COMMAND.write(); + } + + public void DrawBuffers(int... drawBuffers) { + DRAW_BUFFER_COMMAND.drawBuffers = drawBuffers; + DRAW_BUFFER_COMMAND.write(); + } + + public void BindVertexArray(int vao) { + BIND_VERTEX_ARRAY_COMMAND.vao = vao; + BIND_VERTEX_ARRAY_COMMAND.write(); + } + + public void BindElementsArray(int ebo) { + BIND_ELEMENTS_ARRAY_COMMAND.ebo = ebo; + BIND_ELEMENTS_ARRAY_COMMAND.write(); + } + + public void SetDepthFunc(int DepthFunc) { + DEPTH_FUNC_COMMAND.mode = DepthFunc; + DEPTH_FUNC_COMMAND.write(); + } + + public void ExecuteCommandBuffer(CommandBuffer cmd) { + assert cmd != this; + EXECUTE_COMMAND_BUFFER_COMMAND.cmd = cmd; + EXECUTE_COMMAND_BUFFER_COMMAND.write(); + } + + public void SetShaderProgram(ShaderProgram program) { + assert program != null; + SHADER_PROGRAM_COMMAND.program = program; + SHADER_PROGRAM_COMMAND.write(); + } + + public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height, int[] data) { + UPLOAD_PIXEL_DATA_COMMAND.texUnit = texUnit; + UPLOAD_PIXEL_DATA_COMMAND.tex = tex; + UPLOAD_PIXEL_DATA_COMMAND.pbo = pbo; + UPLOAD_PIXEL_DATA_COMMAND.width = width; + UPLOAD_PIXEL_DATA_COMMAND.height = height; + UPLOAD_PIXEL_DATA_COMMAND.data = data; + UPLOAD_PIXEL_DATA_COMMAND.frameSync = null; + UPLOAD_PIXEL_DATA_COMMAND.write(); + } + + public void UploadPixelData(int texUnit, int tex, int pbo, int width, int height, int[] data, FrameSync frameSync) { + UPLOAD_PIXEL_DATA_COMMAND.texUnit = texUnit; + UPLOAD_PIXEL_DATA_COMMAND.tex = tex; + UPLOAD_PIXEL_DATA_COMMAND.pbo = pbo; + UPLOAD_PIXEL_DATA_COMMAND.width = width; + UPLOAD_PIXEL_DATA_COMMAND.height = height; + UPLOAD_PIXEL_DATA_COMMAND.data = data; + UPLOAD_PIXEL_DATA_COMMAND.frameSync = frameSync; + UPLOAD_PIXEL_DATA_COMMAND.write(); + } + + public void ClearDepth(float depth) { + CLEAR_COMMAND.clearColor = false; + CLEAR_COMMAND.clearDepth = true; + CLEAR_COMMAND.depth = depth; + CLEAR_COMMAND.write(); + } + + public void ClearColor(float red, float green, float blue, float alpha) { + CLEAR_COMMAND.clearColor = true; + CLEAR_COMMAND.clearDepth = false; + CLEAR_COMMAND.red = red; + CLEAR_COMMAND.green = green; + CLEAR_COMMAND.blue = blue; + CLEAR_COMMAND.alpha = alpha; + CLEAR_COMMAND.write(); + } + + public void ClearColorAndDepth(float red, float green, float blue, float alpha, float depth) { + CLEAR_COMMAND.clearColor = true; + CLEAR_COMMAND.clearDepth = true; + CLEAR_COMMAND.depth = depth; + CLEAR_COMMAND.red = red; + CLEAR_COMMAND.green = green; + CLEAR_COMMAND.blue = blue; + CLEAR_COMMAND.alpha = alpha; + CLEAR_COMMAND.write(); + } + + public void DepthMask(boolean writeDepth) { + DEPTH_MASK_COMMAND.flag = writeDepth; + DEPTH_MASK_COMMAND.write(); + } + + public void BlendFunc(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha) { + BLENDED_FUNC_COMMAND.sfactorRGB = sfactorRGB; + BLENDED_FUNC_COMMAND.dfactorRGB = dfactorRGB; + BLENDED_FUNC_COMMAND.sfactorAlpha = sfactorAlpha; + BLENDED_FUNC_COMMAND.dfactorAlpha = dfactorAlpha; + BLENDED_FUNC_COMMAND.write(); + } + + public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, boolean writeAlpha) { + COLOR_MASK_COMMAND.red = writeRed; + COLOR_MASK_COMMAND.green = writeGreen; + COLOR_MASK_COMMAND.blue = writeBlue; + COLOR_MASK_COMMAND.alpha = writeAlpha; + COLOR_MASK_COMMAND.write(); + } + + public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { + MULTI_DRAW_ARRAYS_COMMAND.mode = mode; + MULTI_DRAW_ARRAYS_COMMAND.offsets = offsets; + MULTI_DRAW_ARRAYS_COMMAND.counts = counts; + MULTI_DRAW_ARRAYS_COMMAND.write(); + } + + public void DrawElements(int mode, int vertexCount, long bytesOffset) { + DRAW_ELEMENTS_COMMAND.mode = mode; + DRAW_ELEMENTS_COMMAND.vertexCount = vertexCount; + DRAW_ELEMENTS_COMMAND.bytesOffset = bytesOffset; + DRAW_ELEMENTS_COMMAND.write(); + } + + public void DrawArrays(int mode, int offset, int vertexCount) { + DRAW_ARRAYS_COMMAND.mode = mode; + DRAW_ARRAYS_COMMAND.offset = offset; + DRAW_ARRAYS_COMMAND.vertexCount = vertexCount; + DRAW_ARRAYS_COMMAND.write(); + } + + public void BindFrameBuffer(int target, int fbo) { + BIND_FRAMEBUFFER_COMMAND.target = target; + BIND_FRAMEBUFFER_COMMAND.fbo = fbo; + BIND_FRAMEBUFFER_COMMAND.write(); + } + + public void Viewport(int x, int y, int width, int height) { + VIEWPORT_COMMAND.x = x; + VIEWPORT_COMMAND.y = y; + VIEWPORT_COMMAND.width = width; + VIEWPORT_COMMAND.height = height; + VIEWPORT_COMMAND.write(); + } + + public void BlitFramebuffer( + int srcFbo, + int resolveFbo, + int dstFbo, + int srcWidth, + int srcHeight, + int dstX, + int dstY, + int dstWidth, + int dstHeight, + int filter + ) { + BLIT_FRAME_BUFFER_COMMAND.srcFbo = srcFbo; + BLIT_FRAME_BUFFER_COMMAND.resolveFbo = resolveFbo; + BLIT_FRAME_BUFFER_COMMAND.dstFbo = dstFbo; + BLIT_FRAME_BUFFER_COMMAND.srcWidth = srcWidth; + BLIT_FRAME_BUFFER_COMMAND.srcHeight = srcHeight; + BLIT_FRAME_BUFFER_COMMAND.dstX = dstX; + BLIT_FRAME_BUFFER_COMMAND.dstY = dstY; + BLIT_FRAME_BUFFER_COMMAND.dstWidth = dstWidth; + BLIT_FRAME_BUFFER_COMMAND.dstHeight = dstHeight; + BLIT_FRAME_BUFFER_COMMAND.filter = filter; + BLIT_FRAME_BUFFER_COMMAND.write(); + } + + public void Callback(CallbackCommand.ICallback callback) { + CALLBACK_COMMAND.callback = callback; + CALLBACK_COMMAND.write(); + } + + public void Signal(FrameSync frameSync) { + SIGNAL_COMMAND.frameSync = frameSync; + SIGNAL_COMMAND.write(); + } + + public void UploadUniformBuffer(UniformBuffer buffer) { + UPLOAD_UNIFORM_BUFFER_COMMAND.buffer = buffer; + UPLOAD_UNIFORM_BUFFER_COMMAND.write(); + } + + public void BeginTimer(Timer timer) { + FRAME_TIMER_COMMAND.timer = timer; + FRAME_TIMER_COMMAND.begin = true; + FRAME_TIMER_COMMAND.write(); + } + + public void EndTimer(Timer timer) { + FRAME_TIMER_COMMAND.timer = timer; + FRAME_TIMER_COMMAND.begin = false; + FRAME_TIMER_COMMAND.write(); + } + + public void SwapBuffers(AWTContext context) { + SWAP_BUFFER_COMMAND.awtContext = context; + SWAP_BUFFER_COMMAND.write(); + } + + public void Enable(int capability) { + Toggle(capability, true); + } + + public void Disable(int capability) { + Toggle(capability, false); + } + + public void Toggle(int capability, boolean enabled) { + TOGGLE_COMMAND.capability = capability; + TOGGLE_COMMAND.state = enabled; + TOGGLE_COMMAND.write(); + } + + public void printCommandBuffer(StringBuilder sb) { + readHead = 0; + readBitHead = 0; + + try (MemoryStack stack = MemoryStack.stackPush()){ + while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { + int type = (int) readBits(8); + assert type < REGISTERED_COMMANDS.length : "Unknown Command Type"; + + BaseCommand command = REGISTERED_COMMANDS[type]; + command.doRead(stack); + command.print(sb); + sb.append('\n'); + } + } + } + + public void execute() { + try(MemoryStack stack = MemoryStack.stackPush()) { + execute(stack); + } + } + + public void execute(MemoryStack stack) { + readHead = 0; + readBitHead = 0; + + long readElapsed = 0; + long readTimestamp = 0; + long executeTimestamp = 0; + + if(executionTimer != null) { + executeTimestamp = System.nanoTime(); + } + + while (readHead < writeHead || (readHead == writeHead && readBitHead < writeBitHead)) { + final int type = (int)readBits(8); + assert type < REGISTERED_COMMANDS.length : "Unknown Command Type"; + + BaseCommand command = REGISTERED_COMMANDS[type]; + + if (command.isDrawCall()) + uploadPendingUniformBuffers(); + + stack.push(); + readTimestamp = System.nanoTime(); + command.doRead(stack); + readElapsed += System.nanoTime() - readTimestamp; + + command.execute(stack); + stack.pop(); + + if(command.isGLCommand() && HdPlugin.checkGLErrors(command.getName())) { + //StringBuilder sb = new StringBuilder(); + //printCommandBuffer(sb); + //log.debug("=== CommandBuffer START ===\n{}\n=== CommandBuffer END ===", sb); + //break; + } + } + + uploadPendingUniformBuffers(); + + if(executionTimer != null) { + FrameTimer.getInstance().add(executionTimer, System.nanoTime() - executeTimestamp); + } + FrameTimer.getInstance().add(Timer.COMMAND_BUFFER_READ, readElapsed); + } + + private void uploadPendingUniformBuffers() { + for(int i = 0; i < pendingUBOUploadsCount; i++) { + if(pendingUBOUploads[i].isDirty()) { + pendingUBOUploads[i].upload(); + pendingUBOUploads[i] = null; + } + } + pendingUBOUploadsCount = 0; + } + + protected void markUniformBufferDirty(UniformBuffer buffer) { + for(int i = 0; i < pendingUBOUploadsCount; i++) { + if(pendingUBOUploads[i] == buffer) { + return; + } + } + pendingUBOUploads[pendingUBOUploadsCount++] = buffer; + } + + protected void writeObject(Object object) { + for(int i = 0; i < objectCount; i++) { + if(objects[i] == object) { + writeBits(i, 8); + return; + } + } + + if (objectCount >= objects.length) { + objects = Arrays.copyOf(objects, objects.length * 2); + } + + assert objectCount < 255 : "Too many objects in command buffer, limit needs increasing: " + objectCount + " > 255"; + writeBits(objectCount, 8); + objects[objectCount++] = object; + } + + protected T readObject() { + int index = (int)readBits(8); + return (T) objects[index]; + } + + protected long readBits(int numBits) { + long word = cmd[readHead]; + if(readBitHead == 0) { + if (numBits == BITS_PER_WORD){ + readHead++; + return word; + } else { + readBitHead += numBits; + return word & MASKS[numBits]; + } + } + + long result = 0; + int shift = 0; + while (numBits > 0) { + final int bitsToRead = min(BITS_PER_WORD - readBitHead, numBits); + final long bits = (word >>> readBitHead) & MASKS[bitsToRead]; + + result |= (bits << shift); + + readBitHead += bitsToRead; + if (readBitHead == BITS_PER_WORD) { + readBitHead = 0; + readHead++; + word = cmd[readHead]; + } + + shift += bitsToRead; + numBits -= bitsToRead; + } + + return result; + } + + protected void writeBits(long value, int numBits) { + if (writeBitHead == 0) { + if (numBits == BITS_PER_WORD) { + cmd[writeHead++] = value; + } else { + writeBitHead += numBits; + cmd[writeHead] = value & MASKS[numBits]; + } + return; + } + + long word = cmd[writeHead]; + while (numBits > 0) { + final int bitsToWrite = min(BITS_PER_WORD - writeBitHead, numBits); + final long bits = value & MASKS[bitsToWrite]; + + word |= bits << writeBitHead; + cmd[writeHead] = word; + + writeBitHead += bitsToWrite; + if (writeBitHead == BITS_PER_WORD) { + writeHead++; + if (writeHead >= cmd.length) + cmd = Arrays.copyOf(cmd, cmd.length * 2); + + writeBitHead = 0; + word = 0; + } + + value >>>= bitsToWrite; + numBits -= bitsToWrite; + } + } + + public void reset() { + highPriority = false; + + writeHead = 0; + writeBitHead = 0; + + readHead = 0; + readBitHead = 0; + + // Objects need to be cleared to avoid holding onto a reference and preventing garbage collection + if(objectCount > 0 ) { + Arrays.fill(objects, 0, objectCount, null); + objectCount = 0; + } + + if(pendingUBOUploadsCount > 0) { + Arrays.fill(pendingUBOUploads, 0, pendingUBOUploadsCount, null); + pendingUBOUploadsCount = 0; + } + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/CommandBufferPool.java b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBufferPool.java new file mode 100644 index 0000000000..6e9a8f49eb --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/CommandBufferPool.java @@ -0,0 +1,34 @@ +package rs117.hd.opengl.commandbuffer; + +import java.util.ArrayDeque; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Singleton +public class CommandBufferPool { + + public static CommandBufferPool INSTANCE; + + private final ArrayDeque pool = new ArrayDeque<>(); + + public CommandBufferPool() { + INSTANCE = this; + } + + public CommandBuffer acquire() { + if (pool.isEmpty()) { + CommandBuffer buffer = new CommandBuffer(); + buffer.pooled = true; + return buffer; + } + + return pool.pop(); + } + + public void release(CommandBuffer buffer) { + assert buffer.pooled; + buffer.reset(); + pool.push(buffer); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java new file mode 100644 index 0000000000..8ca96a2838 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/FrameSync.java @@ -0,0 +1,34 @@ +package rs117.hd.opengl.commandbuffer; + +import lombok.Getter; + +public class FrameSync { + @Getter + private volatile boolean awaiting = false; + private volatile boolean ready = false; + private volatile boolean inFlight = false; + + public boolean await() { + if(inFlight && !awaiting) { + awaiting = true; + while (!ready) { + Thread.onSpinWait(); + } + } + + inFlight = false; + return true; + } + + + public void markInFlight() { + awaiting = false; + ready = false; + inFlight = true; + } + + public void signalReady() { + ready = true; + } +} + diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java new file mode 100644 index 0000000000..b7308df2d7 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/RenderThread.java @@ -0,0 +1,239 @@ +package rs117.hd.opengl.commandbuffer; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.callback.ClientThread; +import org.lwjgl.system.MemoryStack; +import rs117.hd.HdPlugin; +import rs117.hd.opengl.commandbuffer.commands.CallbackCommand; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; + +@Slf4j +@Singleton +public final class RenderThread implements Runnable { + public static final int RENDER_THREAD_STACK_SIZE = 10 * 1024 * 1024; // 10 MB + + private static final RenderTask POISON_PILL = new RenderTask(); + private static final ArrayDeque TASK_BIN = new ArrayDeque<>(); + + private final LinkedBlockingDeque queue = new LinkedBlockingDeque<>(); + private final List completedTasks = new ArrayList<>(); + + private final AtomicInteger pendingCount = new AtomicInteger(0); + private final AtomicBoolean running = new AtomicBoolean(false); + private final FrameSync invokeOnRenderThreadSync = new FrameSync(); + private final FrameSync renderCompleteSync = new FrameSync(); + private Thread thread; + private Thread clientJavaThread; + + @Inject + private HdPlugin plugin; + + @Inject + private FrameTimer frameTimer; + + @Inject + private ClientThread clientThread; + + @Inject + private AWTContextWrapper contextWrapper; + + @Inject + private CommandBufferPool pool; + + public void initialize() { + clientJavaThread = Thread.currentThread(); + + running.set(true); + + thread = new Thread(this, "HD-RenderThread"); + thread.setPriority(Thread.MAX_PRIORITY); + thread.setDaemon(true); + thread.start(); + } + + public void submit(CommandBuffer buffer) { submit(buffer, null); } + + public void submit(CommandBuffer buffer, Runnable onComplete) { + if (buffer == null) + throw new IllegalArgumentException("CommandBuffer cannot be null"); + + if(!plugin.configRenderThread) { + if(isClientThread()) { + buffer.execute(); + if (onComplete != null) onComplete.run(); + if (buffer.pooled) pool.release(buffer); + } else { + clientThread.invoke(() -> { + buffer.execute(); + if (onComplete != null) onComplete.run(); + if (buffer.pooled) pool.release(buffer); + }); + } + return; + } + + pendingCount.incrementAndGet(); + + if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && isClientThread()) { + contextWrapper.detachCurrent("detached for render submit"); + } + + RenderTask newTask = TASK_BIN.poll(); + if (newTask == null) { + newTask = new RenderTask(); + } + + newTask.buffer = buffer; + newTask.callback = onComplete; + + renderCompleteSync.markInFlight(); + + if(buffer.highPriority) { + queue.addFirst(newTask); + } else { + queue.addLast(newTask); + } + } + + private boolean isClientThread() {return Thread.currentThread() == clientJavaThread; } + + public void processCompletedTasks() { + if(isClientThread()) { + synchronized (completedTasks) { + for (RenderTask task : completedTasks) { + if (task.callback != null) { + try { + task.callback.run(); + } catch (Throwable t) { + log.warn("Exception in render completion callback", t); + } + } + + if (task.buffer.pooled) { + pool.release(task.buffer); + } + + task.buffer = null; + task.callback = null; + + TASK_BIN.add(task); + } + completedTasks.clear(); + } + } + } + + @SneakyThrows + public void waitForRenderingCompleted() { + if (pendingCount.get() == 0 && completedTasks.isEmpty() + && contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT) { + return; + } + + while (pendingCount.get() > 0) { + if (contextWrapper.getOwner() == AWTContextWrapper.Owner.CLIENT && isClientThread()) + contextWrapper.detachCurrent("waiting for render completion"); + + renderCompleteSync.await(); + } + + invokeOnRenderThread(() -> contextWrapper.detachCurrent("detached after render completion")); + contextWrapper.makeCurrent(AWTContextWrapper.Owner.CLIENT); + + processCompletedTasks(); + } + + + @SneakyThrows + @Override + public void run() { + log.debug("RenderThread started!"); + + try(MemoryStack stack = MemoryStack.create(RENDER_THREAD_STACK_SIZE)) { + while (running.get()) { + RenderTask task; + try { + task = queue.take(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + + if (task == POISON_PILL) + break; + + if (task.buffer == null) { + taskCompleted(task); + continue; + } + + contextWrapper.makeCurrent(AWTContextWrapper.Owner.RENDER_THREAD); + + try { + long timeStamp = System.nanoTime(); + stack.push(); + task.buffer.execute(stack); + stack.pop(); + frameTimer.add(Timer.COMMAND_BUFFER_EXECUTE, System.nanoTime() - timeStamp); + } catch (Throwable t) { + log.error("Error during CommandBuffer execution", t); + } finally { + taskCompleted(task); + } + } + } + + log.debug("RenderThread stopped!"); + } + + public void invokeOnRenderThread(CallbackCommand.ICallback callback) { + long timeStamp = System.nanoTime(); + CommandBuffer cmd = pool.acquire(); + cmd.highPriority = true; + cmd.Callback(callback); + cmd.Signal(invokeOnRenderThreadSync); + invokeOnRenderThreadSync.markInFlight(); + submit(cmd); + invokeOnRenderThreadSync.await(); + frameTimer.add(Timer.INVOKE_ON_RENDER_THREAD, System.nanoTime() - timeStamp); + } + + private void taskCompleted(RenderTask task) { + synchronized (completedTasks) { + completedTasks.add(task); + } + + int remaining = pendingCount.decrementAndGet(); + if (remaining <= 0) { + pendingCount.set(0); + renderCompleteSync.signalReady(); + } + } + + public void shutdown() { + if (running.getAndSet(false)) { + queue.add(POISON_PILL); + try { + thread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + thread = null; + } + + private static class RenderTask { + public CommandBuffer buffer = null; + public Runnable callback = null; + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java new file mode 100644 index 0000000000..3b618087b7 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindElementsArrayCommand.java @@ -0,0 +1,35 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER; +import static org.lwjgl.opengl.GL15.glBindBuffer; + +public final class BindElementsArrayCommand extends BaseCommand { + public int ebo; + + public BindElementsArrayCommand() { super(false, true); } + + @Override + protected void doWrite() { + write32(ebo); + } + + @Override + protected void doRead(MemoryStack stack) { + ebo = read32(); + } + + @Override + public void execute(MemoryStack stack) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glBindBuffer("); + sb.append(ebo); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java new file mode 100644 index 0000000000..266c75f9fd --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindFrameBufferCommand.java @@ -0,0 +1,37 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL30C.glBindFramebuffer; + +public final class BindFrameBufferCommand extends BaseCommand { + public int target; + public int fbo; + + public BindFrameBufferCommand() { super(false, true); } + + @Override + protected void doWrite() { + write32(target); + write32(fbo); + } + + @Override + protected void doRead(MemoryStack stack) { + target = read32(); + fbo = read32(); + } + + @Override + protected void execute(MemoryStack stack) { glBindFramebuffer(target, fbo); } + + @Override + protected void print(StringBuilder sb) { + sb.append("glBindFramebuffer("); + sb.append(target); + sb.append(", "); + sb.append(fbo); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java new file mode 100644 index 0000000000..277b99b3bc --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BindVertexArrayCommand.java @@ -0,0 +1,34 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL30.glBindVertexArray; + +public final class BindVertexArrayCommand extends BaseCommand { + public int vao; + + public BindVertexArrayCommand() { super(false, true); } + + @Override + protected void doWrite() { + write32(vao); + } + + @Override + protected void doRead(MemoryStack stack) { + vao = read32(); + } + + @Override + public void execute(MemoryStack stack) { + glBindVertexArray(vao); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glBindVertexArray("); + sb.append(vao); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java new file mode 100644 index 0000000000..fb2403290f --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlendFuncCommand.java @@ -0,0 +1,49 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate; + +public final class BlendFuncCommand extends BaseCommand { + public int sfactorRGB; + public int dfactorRGB; + public int sfactorAlpha; + public int dfactorAlpha; + + public BlendFuncCommand() { super(false, true); } + + @Override + protected void doWrite() { + write32(sfactorRGB); + write32(dfactorRGB); + write32(sfactorAlpha); + write32(dfactorAlpha); + } + + @Override + protected void doRead(MemoryStack stack) { + sfactorRGB = read32(); + dfactorRGB = read32(); + sfactorAlpha = read32(); + dfactorAlpha = read32(); + } + + @Override + protected void execute(MemoryStack stack) { + glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); + } + + @Override + protected void print(StringBuilder sb) { + sb.append("glBlendFuncSeparate("); + sb.append(sfactorRGB); + sb.append(", "); + sb.append(dfactorRGB); + sb.append(", "); + sb.append(sfactorAlpha); + sb.append(", "); + sb.append(dfactorAlpha); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java new file mode 100644 index 0000000000..76706e8093 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/BlitFrameBufferCommand.java @@ -0,0 +1,95 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; +import static org.lwjgl.opengl.GL30.GL_DRAW_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30.GL_READ_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30.glBindFramebuffer; +import static org.lwjgl.opengl.GL30.glBlitFramebuffer; + + +public final class BlitFrameBufferCommand extends BaseCommand { + public int srcFbo; + public int resolveFbo; // optional intermediate FBO for MSAA resolve + public int dstFbo; + + public int srcWidth; + public int srcHeight; + + public int dstX; + public int dstY; + public int dstWidth; + public int dstHeight; + + public int filter; // GL_NEAREST or GL_LINEAR + + public BlitFrameBufferCommand() { super(false, true); } + + @Override + protected void execute(MemoryStack stack) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo); + + if (resolveFbo != 0) { + // Optional MSAA resolve step + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFbo); + glBlitFramebuffer( + 0, 0, srcWidth, srcHeight, + 0, 0, srcWidth, srcHeight, + GL_COLOR_BUFFER_BIT, filter + ); + glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFbo); + } + + // Blit from (resolved) source to destination framebuffer + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo); + glBlitFramebuffer( + 0, 0, srcWidth, srcHeight, + dstX, dstY, + dstX + dstWidth, dstY + dstHeight, + GL_COLOR_BUFFER_BIT, filter + ); + } + + @Override + protected void doWrite() { + write32(srcFbo); + write32(resolveFbo); + write32(dstFbo); + write32(srcWidth); + write32(srcHeight); + write32(dstX); + write32(dstY); + write32(dstWidth); + write32(dstHeight); + write32(filter); + } + + @Override + protected void doRead(MemoryStack stack) { + srcFbo = read32(); + resolveFbo = read32(); + dstFbo = read32(); + srcWidth = read32(); + srcHeight = read32(); + dstX = read32(); + dstY = read32(); + dstWidth = read32(); + dstHeight = read32(); + filter = read32(); + } + + @Override + protected void print(StringBuilder sb) { + sb.append("BlitFrameBufferCommand(") + .append("srcFbo=").append(srcFbo).append(", ") + .append("resolveFbo=").append(resolveFbo).append(", ") + .append("dstFbo=").append(dstFbo).append(", ") + .append("srcSize=").append(srcWidth).append("x").append(srcHeight).append(", ") + .append("dstRect=[").append(dstX).append(", ").append(dstY) + .append(", ").append(dstWidth).append(", ").append(dstHeight).append("], ") + .append("filter=").append(filter) + .append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java new file mode 100644 index 0000000000..f0edd2dc77 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/CallbackCommand.java @@ -0,0 +1,32 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +public final class CallbackCommand extends BaseCommand { + public interface ICallback { + void run(); + } + + public ICallback callback; + + @Override + protected void doWrite() { + writeObject(callback); + } + + @Override + protected void doRead(MemoryStack stack) { + callback = readObject(); + } + + @Override + public void execute(MemoryStack stack) { + callback.run(); + } + + @Override + public void print(StringBuilder sb) { + sb.append("CallbackCommand"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java new file mode 100644 index 0000000000..0b08ee33e9 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ClearCommand.java @@ -0,0 +1,93 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; +import static org.lwjgl.opengl.GL11.glClear; +import static org.lwjgl.opengl.GL11.glClearColor; +import static org.lwjgl.opengl.GL11C.GL_DEPTH_BUFFER_BIT; +import static org.lwjgl.opengl.GL11C.glClearDepth; + +public final class ClearCommand extends BaseCommand { + public boolean clearColor; + public boolean clearDepth; + + public float depth; + + public float red; + public float green; + public float blue; + public float alpha; + + public ClearCommand() { super(false, true); } + + @Override + protected void doWrite() { + writeFlag(clearColor); + if(clearColor) { + write32F(red); + write32F(green); + write32F(blue); + write32F(alpha); + } + + writeFlag(clearDepth); + if(clearDepth) { + write32F(depth); + } + } + + @Override + protected void doRead(MemoryStack stack) { + clearColor = readFlag(); + + if(clearColor) { + red = read32F(); + green = read32F(); + blue = read32F(); + alpha = read32F(); + } + + clearDepth = readFlag(); + if(clearDepth) { + depth = read32F(); + } + } + + @Override + protected void execute(MemoryStack stack) { + if(clearColor) { + glClearColor(red, green, blue, alpha); + } + if(clearDepth) { + glClearDepth(depth); + } + glClear((clearColor ? GL_COLOR_BUFFER_BIT : 0) | (clearDepth ? GL_DEPTH_BUFFER_BIT : 0)); + } + + @Override + protected void print(StringBuilder sb) { + if(clearColor) { + sb.append("glClearColor("); + sb.append(red); + sb.append(", "); + sb.append(green); + sb.append(", "); + sb.append(blue); + sb.append(", "); + sb.append(alpha); + sb.append(");"); + } + + if(clearDepth) { + sb.append("glClearDepth("); + sb.append(depth); + sb.append(");"); + } + + sb.append("glClear("); + sb.append((clearColor ? GL_COLOR_BUFFER_BIT : 0) | (clearDepth ? GL_DEPTH_BUFFER_BIT : 0)); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java new file mode 100644 index 0000000000..4341da30e6 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ColorMaskCommand.java @@ -0,0 +1,47 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.glColorMask; + +public final class ColorMaskCommand extends BaseCommand { + public boolean red; + public boolean green; + public boolean blue; + public boolean alpha; + + public ColorMaskCommand() { super(false, true); } + + @Override + protected void doWrite() { + writeFlag(red); + writeFlag(green); + writeFlag(blue); + writeFlag(alpha); + } + + @Override + protected void doRead(MemoryStack stack) { + red = readFlag(); + green = readFlag(); + blue = readFlag(); + alpha = readFlag(); + } + + @Override + public void execute(MemoryStack stack) { glColorMask(red, green, blue, alpha); } + + @Override + public void print(StringBuilder sb) { + sb.append("glColorMask("); + sb.append(red); + sb.append(", "); + sb.append(green); + sb.append(", "); + sb.append(blue); + sb.append(", "); + sb.append(alpha); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java new file mode 100644 index 0000000000..3512c80c1e --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthFuncCommand.java @@ -0,0 +1,29 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11C.glDepthFunc; + +public final class DepthFuncCommand extends BaseCommand { + + public int mode; + + public DepthFuncCommand() { super(false, true); } + + @Override + protected void doWrite() { write32(mode); } + + @Override + protected void doRead(MemoryStack stack) { mode = read32(); } + + @Override + protected void execute(MemoryStack stack) { glDepthFunc(mode); } + + @Override + protected void print(StringBuilder sb) { + sb.append("glDepthFunc("); + sb.append(mode); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java new file mode 100644 index 0000000000..576429a121 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DepthMaskCommand.java @@ -0,0 +1,41 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.glDepthMask; + +public final class DepthMaskCommand extends BaseCommand { + public static boolean SKIP_DEPTH_MASKING; + + public boolean flag; + + public DepthMaskCommand() { super(false, true); } + + @Override + protected void doWrite() { + writeFlag(flag); + } + + @Override + protected void doRead(MemoryStack stack) { + flag = readFlag(); + } + + @Override + public void execute(MemoryStack stack) { + if(SKIP_DEPTH_MASKING) + return; + glDepthMask(flag); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glDepthMask("); + sb.append(flag); + sb.append(");"); + + if(SKIP_DEPTH_MASKING) + sb.append(" - SKIPPED"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java new file mode 100644 index 0000000000..8ac0d4179c --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawArraysCommand.java @@ -0,0 +1,42 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11C.glDrawArrays; + +public final class DrawArraysCommand extends BaseCommand { + public int mode; + public int vertexCount; + public int offset; + + public DrawArraysCommand() { super(true); } + + @Override + public void doWrite() { + write8(mode); + write32(vertexCount); + write32(offset); + } + + @Override + public void doRead(MemoryStack stack) { + mode = read8(); + vertexCount = read32(); + offset = read32(); + } + + @Override + public void execute(MemoryStack stack) { glDrawArrays(mode, offset, vertexCount); } + + @Override + public void print(StringBuilder sb) { + sb.append("glDrawElements("); + sb.append(mode); + sb.append(", "); + sb.append(vertexCount); + sb.append(", "); + sb.append(offset); + sb.append(");"); + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawBufferCommand.java new file mode 100644 index 0000000000..72ead31d38 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawBufferCommand.java @@ -0,0 +1,45 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import java.nio.IntBuffer; +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL20.glDrawBuffers; + +public final class DrawBufferCommand extends BaseCommand { + + public int[] drawBuffers; + + private IntBuffer stagingDrawBuffers; + + public DrawBufferCommand() { super(false, true); } + + @Override + protected void doWrite() { + write32(drawBuffers.length); + for (int drawBuffer : drawBuffers) { + write32(drawBuffer); + } + drawBuffers = null; + } + + @Override + protected void doRead(MemoryStack stack) { + int count = read32(); + stagingDrawBuffers = stack.mallocInt(count); + for(int i = 0; i < count; i++) { + stagingDrawBuffers.put(read32()); + } + stagingDrawBuffers.flip(); + } + + @Override + protected void execute(MemoryStack stack) { + glDrawBuffers(stagingDrawBuffers); + } + + @Override + protected void print(StringBuilder sb) { + + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java new file mode 100644 index 0000000000..d95e30c656 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/DrawElementsCommand.java @@ -0,0 +1,45 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import static org.lwjgl.opengl.GL11.glDrawElements; + +public final class DrawElementsCommand extends BaseCommand { + public int mode; + public int vertexCount; + public long bytesOffset; + + public DrawElementsCommand() { super(true); } + + @Override + public void doWrite() { + write8(mode); + write32(vertexCount); + write64(bytesOffset); + } + + @Override + public void doRead(MemoryStack stack) { + mode = read8(); + vertexCount = read32(); + bytesOffset = read64(); + } + + @Override + public void execute(MemoryStack stack) { + glDrawElements(mode, vertexCount, GL_UNSIGNED_INT, bytesOffset); + } + + @Override + public void print(StringBuilder sb) { + sb.append("glDrawElements("); + sb.append(mode); + sb.append(", "); + sb.append(vertexCount); + sb.append(", GL_UNSIGNED_INT, "); + sb.append(bytesOffset); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java new file mode 100644 index 0000000000..db95e943bf --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ExecuteCommandBufferCommand.java @@ -0,0 +1,31 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.commandbuffer.CommandBuffer; + +public final class ExecuteCommandBufferCommand extends BaseCommand { + + public CommandBuffer cmd; + + @Override + protected void doWrite() { + writeObject(cmd); + } + + @Override + protected void doRead(MemoryStack stack) { + cmd = readObject(); + } + + @Override + protected void execute(MemoryStack stack) { + cmd.execute(stack); + } + + @Override + protected void print(StringBuilder sb) { + sb.append("ExecuteCommandBufferCommand"); + cmd.printCommandBuffer(sb); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameBufferLayerCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameBufferLayerCommand.java new file mode 100644 index 0000000000..e1df478f8f --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameBufferLayerCommand.java @@ -0,0 +1,51 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL30C.GL_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30C.glFramebufferTextureLayer; + +public final class FrameBufferLayerCommand extends BaseCommand { + + public int attachment; + public int texId; + public int level; + public int layer; + + public FrameBufferLayerCommand() {super(false, true);} + + @Override + protected void doWrite() { + write32(attachment); + write32(texId); + write32(level); + write32(layer); + } + + @Override + protected void doRead(MemoryStack stack) { + attachment = read32(); + texId = read32(); + level = read32(); + layer = read32(); + } + + @Override + protected void execute(MemoryStack stack) { + glFramebufferTextureLayer(GL_FRAMEBUFFER, attachment, texId, level, layer); + } + + @Override + protected void print(StringBuilder sb) { + sb.append("glFramebufferTextureLayer(GL_FRAMEBUFFER, "); + sb.append(attachment); + sb.append(", "); + sb.append(texId); + sb.append(", "); + sb.append(level); + sb.append(", "); + sb.append(layer); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java new file mode 100644 index 0000000000..e0a0caf6c2 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/FrameTimerCommand.java @@ -0,0 +1,37 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; + +public final class FrameTimerCommand extends BaseCommand { + public Timer timer; + public boolean begin; + + @Override + protected void doWrite() { + write32(timer.ordinal()); + writeFlag(begin); + } + + @Override + protected void doRead(MemoryStack stack) { + timer = Timer.TIMERS[read32()]; + begin = readFlag(); + } + + @Override + protected void execute(MemoryStack stack) { + if(begin) { + FrameTimer.getInstance().begin(timer); + } else { + FrameTimer.getInstance().end(timer); + } + } + + @Override + protected void print(StringBuilder sb) { + // EEEEERM + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java new file mode 100644 index 0000000000..ccdfb399d5 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/MultiDrawArraysCommand.java @@ -0,0 +1,74 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import java.nio.IntBuffer; +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL14.glMultiDrawArrays; + +public final class MultiDrawArraysCommand extends BaseCommand { + public int mode; + public int[] offsets; + public int[] counts; + + private IntBuffer offsetsBuffer; + private IntBuffer countsBuffer; + + public MultiDrawArraysCommand() { super(true); } + + @Override + public void doWrite() { + assert offsets.length == counts.length; + + write8(mode); + write32(offsets.length); + for(int i = 0; i < offsets.length; i++) { + write32(offsets[i]); + write32(counts[i]); + } + + offsets = null; + counts = null; + } + + @Override + public void doRead(MemoryStack stack) { + mode = read8(); + + int length = read32(); + offsetsBuffer = stack.callocInt(length); + countsBuffer = stack.callocInt(length); + + for(int i = 0; i < length; i++) { + offsetsBuffer.put(read32()); + countsBuffer.put(read32()); + } + + offsetsBuffer.flip(); + countsBuffer.flip(); + } + + @Override + public void execute(MemoryStack stack) { + glMultiDrawArrays(mode, offsetsBuffer, countsBuffer); + offsetsBuffer = null; // TODO: Kinda need a clear function? + countsBuffer = null; + } + + @Override + public void print(StringBuilder sb) { + sb.append("glMultiDrawArrays("); + sb.append(mode); + sb.append(", ["); + for(int i = 0; i < offsetsBuffer.limit(); i++) { + if(i > 0) sb.append(", "); + sb.append(offsetsBuffer.get(i)); + } + sb.append("], ["); + for(int i = 0; i < countsBuffer.limit(); i++) { + if(i > 0) sb.append(", "); + sb.append(countsBuffer.get(i)); + } + sb.append("]);"); + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java new file mode 100644 index 0000000000..2d97720c8f --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetShaderUniformCommand.java @@ -0,0 +1,102 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.shader.ShaderProgram; + +public final class SetShaderUniformCommand extends BaseCommand { + + public ShaderProgram.UniformProperty property; + public int[] intValues; + public float[] floatValues; + public boolean isFloat; + + private int stagingSize; + private int[] stagingIntValues; + private float[] stagingFloatValues; + + @Override + protected void doWrite() { + writeObject(property); + writeFlag(isFloat); + if(isFloat) { + write32(floatValues.length); + for(float f : floatValues) { + write32F(f); + } + } else { + write32(intValues.length); + for(int i : intValues) { + write32(i); + } + } + floatValues = null; + intValues = null; + } + + @Override + protected void doRead(MemoryStack stack) { + property = readObject(); + isFloat = readFlag(); + stagingSize = read32(); + if(isFloat) { + if(stagingFloatValues == null || stagingFloatValues.length < stagingSize) { + stagingFloatValues = new float[stagingSize]; + } + for(int i = 0; i < stagingSize; i++) { + stagingFloatValues[i] = read32F(); + } + } else { + if(stagingIntValues == null || stagingIntValues.length < stagingSize) { + stagingIntValues = new int[stagingSize]; + } + for(int i = 0; i < stagingSize; i++) { + stagingIntValues[i] = read32(); + } + } + } + + @Override + protected void execute(MemoryStack stack) { + if(isFloat) { + if (property instanceof ShaderProgram.Uniform1f) { + ((ShaderProgram.Uniform1f) property).set(stagingFloatValues[0]); + } else if (property instanceof ShaderProgram.Uniform2f) { + ((ShaderProgram.Uniform2f) property).set(stagingFloatValues[0], stagingFloatValues[1]); + } else if (property instanceof ShaderProgram.Uniform3f) { + ((ShaderProgram.Uniform3f) property).set(stagingFloatValues[0], stagingFloatValues[1], stagingFloatValues[2]); + } else if (property instanceof ShaderProgram.Uniform4f) { + ((ShaderProgram.Uniform4f) property).set( + stagingFloatValues[0], + stagingFloatValues[1], + stagingFloatValues[2], + stagingFloatValues[3] + ); + } + } else { + if (property instanceof ShaderProgram.UniformBool) { + ((ShaderProgram.UniformBool) property).set(stagingIntValues[0] == 1); + } else if (property instanceof ShaderProgram.UniformTexture) { + ((ShaderProgram.UniformTexture) property).set(stagingIntValues[0]); + } else if (property instanceof ShaderProgram.Uniform1i) { + ((ShaderProgram.Uniform1i) property).set(stagingIntValues[0]); + } else if (property instanceof ShaderProgram.Uniform2i) { + ((ShaderProgram.Uniform2i) property).set(stagingIntValues[0], stagingIntValues[1]); + } else if (property instanceof ShaderProgram.Uniform3i) { + ((ShaderProgram.Uniform3i) property).set(stagingIntValues[0], stagingIntValues[1], stagingIntValues[2]); + } else if (property instanceof ShaderProgram.Uniform4i) { + ((ShaderProgram.Uniform4i) property).set( + stagingIntValues[0], + stagingIntValues[1], + stagingIntValues[2], + stagingIntValues[3] + ); + } + } + } + + @Override + protected void print(StringBuilder sb) { + // TODO: Something useful + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java new file mode 100644 index 0000000000..31420f0c0d --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SetUniformBufferPropertyCommand.java @@ -0,0 +1,107 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.uniforms.UniformBuffer; + +public final class SetUniformBufferPropertyCommand extends BaseCommand { + + public UniformBuffer.Property property; + public int[] intValues; + public float[] floatValues; + public boolean isFloat; + + private int stagingSize; + private int[] stagingIntValues; + private float[] stagingFloatValues; + + public SetUniformBufferPropertyCommand() { super(false, true); } + + @Override + protected void doWrite() { + writeObject(property); + writeFlag(isFloat); + if(isFloat) { + write32(floatValues.length); + for(float f : floatValues) { + write32F(f); + } + } else { + write32(intValues.length); + for(int i : intValues) { + write32(i); + } + } + floatValues = null; + intValues = null; + } + + @Override + protected void doRead(MemoryStack stack) { + property = readObject(); + isFloat = readFlag(); + stagingSize = read32(); + if(isFloat) { + if(stagingFloatValues == null || stagingFloatValues.length < stagingSize) { + stagingFloatValues = new float[stagingSize]; + } + for(int i = 0; i < stagingSize; i++) { + stagingFloatValues[i] = read32F(); + } + } else { + if(stagingIntValues == null || stagingIntValues.length < stagingSize) { + stagingIntValues = new int[stagingSize]; + } + for(int i = 0; i < stagingSize; i++) { + stagingIntValues[i] = read32(); + } + } + } + + @Override + protected void execute(MemoryStack stack) { + if(isFloat) { + switch (stagingSize) { + case 1: + property.set( stagingFloatValues[0] ); + break; + case 2: + property.set( stagingFloatValues[0], stagingFloatValues[1] ); + break; + case 3: + property.set( stagingFloatValues[0], stagingFloatValues[1], stagingFloatValues[2] ); + break; + case 4: + property.set( stagingFloatValues[0], stagingFloatValues[1], stagingFloatValues[2], stagingFloatValues[3] ); + break; + default: + property.set( stagingFloatValues ); + break; + } + } else { + switch (stagingSize) { + case 1: + property.set( stagingIntValues[0] ); + break; + case 2: + property.set( stagingIntValues[0], stagingIntValues[1] ); + break; + case 3: + property.set( stagingIntValues[0], stagingIntValues[1], stagingIntValues[2] ); + break; + case 4: + property.set( stagingIntValues[0], stagingIntValues[1], stagingIntValues[2], stagingIntValues[3] ); + break; + default: + property.set( stagingIntValues ); + break; + } + } + markUniformBufferDirty(property.getOwner()); + } + + @Override + protected void print(StringBuilder sb) { + // TODO: Something useful + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java new file mode 100644 index 0000000000..05bf4be90c --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ShaderProgramCommand.java @@ -0,0 +1,32 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.shader.ShaderProgram; + +public final class ShaderProgramCommand extends BaseCommand { + public ShaderProgram program; + + public ShaderProgramCommand() { super(false, true); } + + @Override + protected void doWrite() { + writeObject(program); + } + + @Override + protected void doRead(MemoryStack stack) { + program = readObject(); + } + + @Override + protected void execute(MemoryStack stack) { + program.use(); + } + + @Override + protected void print(StringBuilder sb) { + sb.append(program.getClass().getSimpleName()); + sb.append(".use();"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java new file mode 100644 index 0000000000..a444a2344e --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SignalCommand.java @@ -0,0 +1,28 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.commandbuffer.FrameSync; + +public final class SignalCommand extends BaseCommand { + + public FrameSync frameSync; + + @Override + protected void doWrite() { + writeObject(frameSync); + } + + @Override + protected void doRead(MemoryStack stack) { + frameSync = readObject(); + } + + @Override + protected void execute(MemoryStack stack) { + frameSync.signalReady(); + } + + @Override + protected void print(StringBuilder sb) {} +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java new file mode 100644 index 0000000000..7b87c828ea --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/SwapBuffersCommand.java @@ -0,0 +1,30 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import net.runelite.rlawt.AWTContext; +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +public final class SwapBuffersCommand extends BaseCommand { + + public AWTContext awtContext; + + @Override + protected void doWrite() { + writeObject(awtContext); + } + + @Override + protected void doRead(MemoryStack stack) { + awtContext = readObject(); + } + + @Override + protected void execute(MemoryStack stack) { + awtContext.swapBuffers(); + } + + @Override + protected void print(StringBuilder sb) { + sb.append("awtContext.swapBuffers();"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java new file mode 100644 index 0000000000..0c158138ce --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ToggleCommand.java @@ -0,0 +1,48 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11.glDisable; +import static org.lwjgl.opengl.GL11.glEnable; + +public final class ToggleCommand extends BaseCommand { + public int capability; + public boolean state; + + public ToggleCommand() { super(false, true); } + + @Override + public void doWrite() { + write32(capability); + writeFlag(state); + } + + @Override + public void doRead(MemoryStack stack) { + capability = read32(); + state = readFlag(); + } + + @Override + public void execute(MemoryStack stack) { + if (state) { + glEnable(capability); + } else { + glDisable(capability); + } + } + + @Override + public void print(StringBuilder sb) { + if (state) { + sb.append("glEnable("); + sb.append(capability); + sb.append(");"); + } else { + sb.append("glDisable("); + sb.append(capability); + sb.append(");"); + } + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java new file mode 100644 index 0000000000..a3de74b46e --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadPixelDataCommand.java @@ -0,0 +1,116 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.commandbuffer.FrameSync; + +import static org.lwjgl.opengl.GL11C.GL_TEXTURE_2D; +import static org.lwjgl.opengl.GL11C.glBindTexture; +import static org.lwjgl.opengl.GL11C.glTexSubImage2D; +import static org.lwjgl.opengl.GL12C.GL_BGRA; +import static org.lwjgl.opengl.GL12C.GL_UNSIGNED_INT_8_8_8_8_REV; +import static org.lwjgl.opengl.GL13C.glActiveTexture; +import static org.lwjgl.opengl.GL15C.glBindBuffer; +import static org.lwjgl.opengl.GL15C.glUnmapBuffer; +import static org.lwjgl.opengl.GL21C.GL_PIXEL_UNPACK_BUFFER; +import static org.lwjgl.opengl.GL30.GL_MAP_UNSYNCHRONIZED_BIT; +import static org.lwjgl.opengl.GL30.GL_MAP_WRITE_BIT; +import static org.lwjgl.opengl.GL30.glMapBufferRange; + +public final class UploadPixelDataCommand extends BaseCommand { + public int texUnit; + public int tex; + public int pbo; + public int width; + public int height; + public int[] data; + public FrameSync frameSync; + + private IntBuffer stagingData = null; + private ByteBuffer mappedBuffer = null; + + @Override + protected void doWrite() { + write32(texUnit); + write32(tex); + write32(pbo); + write32(width); + write32(height); + writeObject(data); + if(frameSync != null) { + writeFlag(true); + writeObject(frameSync); + } else { + writeFlag(false); + } + data = null; + } + + @Override + protected void doRead(MemoryStack stack) { + texUnit = read32(); + tex = read32(); + pbo = read32(); + width = read32(); + height = read32(); + data = readObject(); + if(readFlag()) { + frameSync = readObject(); + } else { + frameSync = null; + } + } + + @Override + protected void execute(MemoryStack stack) { + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); + + mappedBuffer = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, + 0, ((long) width * height) * Integer.BYTES, + GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT, mappedBuffer); + + if (mappedBuffer != null) { + IntBuffer mappedIntBuffer = mappedBuffer.asIntBuffer(); + + if(frameSync != null && (stagingData == null || stagingData.capacity() < data.length)) { + stagingData = MemoryUtil.memAllocInt(data.length); + } + + int chunks = 8; + int remaining = width * height; + int chunkSize = remaining / chunks; + int offset = 0; + for(int i = 0; i < chunks; i++) { + if (frameSync != null && frameSync.isAwaiting()) { + stagingData.put(data, offset, remaining); + frameSync.signalReady(); + + stagingData.flip(); + mappedIntBuffer.put(stagingData); + stagingData.clear(); + break; + } else { + mappedIntBuffer.put(data, offset, chunkSize); + offset += chunkSize; + remaining -= chunkSize; + } + } + + if (frameSync != null) { + frameSync.signalReady(); + } + + glActiveTexture(texUnit); + glBindTexture(GL_TEXTURE_2D, tex); + glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); + } + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + + @Override + protected void print(StringBuilder sb) {} +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadUniformBufferCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadUniformBufferCommand.java new file mode 100644 index 0000000000..7ca40e51dd --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/UploadUniformBufferCommand.java @@ -0,0 +1,33 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; +import rs117.hd.opengl.uniforms.UniformBuffer; + +public final class UploadUniformBufferCommand extends BaseCommand { + + public UniformBuffer buffer; + + public UploadUniformBufferCommand() { super(false, true); } + + @Override + protected void doWrite() { + writeObject(buffer); + } + + @Override + protected void doRead(MemoryStack stack) { + buffer = readObject(); + } + + @Override + protected void execute(MemoryStack stack) { + markUniformBufferDirty(buffer); + } + + @Override + protected void print(StringBuilder sb) { + sb.append(buffer.getClass().getSimpleName()); + sb.append(".upload()"); + } +} diff --git a/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java new file mode 100644 index 0000000000..365514a333 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/commandbuffer/commands/ViewportCommand.java @@ -0,0 +1,47 @@ +package rs117.hd.opengl.commandbuffer.commands; + +import org.lwjgl.system.MemoryStack; +import rs117.hd.opengl.commandbuffer.BaseCommand; + +import static org.lwjgl.opengl.GL11C.glViewport; + +public final class ViewportCommand extends BaseCommand { + public int x; + public int y; + public int width; + public int height; + + public ViewportCommand() { super(false, true); } + + @Override + protected void doWrite() { + write32(x); + write32(y); + write32(width); + write32(height); + } + + @Override + protected void doRead(MemoryStack stack) { + x = read32(); + y = read32(); + width = read32(); + height = read32(); + } + + @Override + protected void execute(MemoryStack stack) { glViewport(x, y, width, height); } + + @Override + protected void print(StringBuilder sb) { + sb.append("glViewport("); + sb.append(x); + sb.append(", "); + sb.append(y); + sb.append(", "); + sb.append(width); + sb.append(", "); + sb.append(height); + sb.append(");"); + } +} diff --git a/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java index fbfba55fbf..1a4fbfdc4a 100644 --- a/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/ShaderProgram.java @@ -111,7 +111,7 @@ public void destroy() { uniformBlockMappings.clear(); } - private static class UniformProperty { + public static class UniformProperty { ShaderProgram program; String uniformName; int uniformIndex; diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOWorldViews.java b/src/main/java/rs117/hd/opengl/uniforms/UBOWorldViews.java index 3df77d3e71..0c7f857ad5 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOWorldViews.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOWorldViews.java @@ -69,8 +69,6 @@ public void update(Client client) { ); } } - - upload(); } public int getIndex(@Nullable Scene scene) { diff --git a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java index c5c11964c6..ca4c90a0c2 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java @@ -42,6 +42,7 @@ protected enum PropertyType { @AllArgsConstructor @RequiredArgsConstructor public static class Property { + @Getter private UniformBuffer owner; private int position; private int offset = -1; diff --git a/src/main/java/rs117/hd/overlays/FrameTimer.java b/src/main/java/rs117/hd/overlays/FrameTimer.java index ca0091a8ba..e8324b79c2 100644 --- a/src/main/java/rs117/hd/overlays/FrameTimer.java +++ b/src/main/java/rs117/hd/overlays/FrameTimer.java @@ -9,15 +9,22 @@ import net.runelite.client.callback.ClientThread; import org.lwjgl.opengl.*; import rs117.hd.HdPlugin; +import rs117.hd.opengl.commandbuffer.RenderThread; import static org.lwjgl.opengl.GL33C.*; @Slf4j @Singleton public class FrameTimer { + @Getter + private static FrameTimer instance; + @Inject private ClientThread clientThread; + @Inject + private RenderThread renderThread; + @Inject private HdPlugin plugin; @@ -37,8 +44,12 @@ public class FrameTimer { public long cumulativeError; public long errorCompensation; + public FrameTimer() { + instance = this; + } + private void initialize() { - clientThread.invoke(() -> { + renderThread.invokeOnRenderThread(() -> { int[] queryNames = new int[NUM_GPU_TIMERS * 2]; glGenQueries(queryNames); int queryIndex = 0; @@ -68,7 +79,7 @@ private void initialize() { } private void destroy() { - clientThread.invoke(() -> { + renderThread.invokeOnRenderThread(() -> { if (!isActive) return; @@ -124,8 +135,10 @@ public void begin(Timer timer) { return; if (timer.isGpuTimer) { - if (activeTimers[timer.ordinal()]) - throw new UnsupportedOperationException("Cumulative GPU timing isn't supported"); + if (activeTimers[timer.ordinal()]) { + log.warn("Cumulative GPU timing isn't supported"); + return; + } glQueryCounter(gpuQueries[timer.ordinal() * 2], GL_TIMESTAMP); } else if (!activeTimers[timer.ordinal()]) { cumulativeError += errorCompensation + 1 >> 1; @@ -191,7 +204,7 @@ public void endFrameAndReset() { } else { if (activeTimers[i]) { // End the CPU timer automatically, but warn about it - log.warn("Timer {} was never ended", timer); + //log.warn("Timer {} was never ended", timer); timings[i] += frameEndNanos; } } diff --git a/src/main/java/rs117/hd/overlays/GammaCalibrationOverlay.java b/src/main/java/rs117/hd/overlays/GammaCalibrationOverlay.java index a2bafe33be..5ae81c2be5 100644 --- a/src/main/java/rs117/hd/overlays/GammaCalibrationOverlay.java +++ b/src/main/java/rs117/hd/overlays/GammaCalibrationOverlay.java @@ -7,6 +7,7 @@ import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; import rs117.hd.HdPluginConfig; +import rs117.hd.opengl.commandbuffer.CommandBuffer; import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPluginConfig.*; @@ -47,8 +48,8 @@ public boolean isHidden() { } @Override - protected void updateUniforms() { - shader.uniCalibrationTimer.set(getTimeout()); + protected void updateUniforms(CommandBuffer cmd) { + cmd.SetUniformProperty(shader.uniCalibrationTimer, getTimeout()); } @Subscribe diff --git a/src/main/java/rs117/hd/overlays/ShaderOverlay.java b/src/main/java/rs117/hd/overlays/ShaderOverlay.java index f01799b15b..f907a66ce0 100644 --- a/src/main/java/rs117/hd/overlays/ShaderOverlay.java +++ b/src/main/java/rs117/hd/overlays/ShaderOverlay.java @@ -47,6 +47,7 @@ import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.ui.overlay.OverlayPosition; import rs117.hd.HdPlugin; +import rs117.hd.opengl.commandbuffer.CommandBuffer; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderProgram; import rs117.hd.opengl.shader.ShaderTemplate; @@ -372,10 +373,9 @@ public boolean isManageable() { return isMovable() || isResizable(); } - private void updateTransform() { - assert shader.isActive(); + private void updateTransform(CommandBuffer cmd) { if (isFullscreen()) { - shader.uniTransform.set(0, 0, 1, 1); + cmd.SetUniformProperty(shader.uniTransform, 0.0f, 0.0f, 1.0f, 1.0f); } else { int[] resolution = plugin.getUiResolution(); if (resolution == null) @@ -391,31 +391,31 @@ private void updateTransform() { rect[i] -= 1; } rect[1] *= -1; - shader.uniTransform.set(rect); + cmd.SetUniformProperty(shader.uniTransform, rect); } } - public void render() { + public void render(CommandBuffer cmd) { if (isHidden()) return; - shader.use(); - updateTransform(); - updateUniforms(); - renderShader(); + cmd.SetShaderProgram( shader); + updateTransform(cmd); + updateUniforms(cmd); + renderShader(cmd); } - protected void updateUniforms() {} + protected void updateUniforms(CommandBuffer cmd) {} - protected void renderShader() { - glEnable(GL_BLEND); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); + protected void renderShader(CommandBuffer cmd) { + cmd.Enable(GL_BLEND); + cmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); if (fullscreen) { - glBindVertexArray(plugin.vaoTri); - glDrawArrays(GL_TRIANGLES, 0, 3); + cmd.BindVertexArray(plugin.vaoTri); + cmd.DrawArrays(GL_TRIANGLES, 0, 3); } else { - glBindVertexArray(plugin.vaoQuad); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + cmd.BindVertexArray(plugin.vaoQuad); + cmd.DrawArrays(GL_TRIANGLE_FAN, 0, 4); } } diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 0decbc6535..081ad55311 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -17,6 +17,10 @@ public enum Timer { MODEL_PUSHING_VERTEX, MODEL_PUSHING_NORMAL, MODEL_PUSHING_UV(false, "Model pushing UV"), + COMMAND_BUFFER_EXECUTE, + INVOKE_ON_RENDER_THREAD, + FRAME_SYNC, + COMMAND_BUFFER_READ, UPDATE_ENVIRONMENT, UPDATE_LIGHTS, IMPOSTOR_TRACKING, diff --git a/src/main/java/rs117/hd/renderer/Renderer.java b/src/main/java/rs117/hd/renderer/Renderer.java index abbf0730a0..b67cce1334 100644 --- a/src/main/java/rs117/hd/renderer/Renderer.java +++ b/src/main/java/rs117/hd/renderer/Renderer.java @@ -28,6 +28,7 @@ default boolean isLoadingScene() { } default void reloadScene() {} default void clearCaches() {} + default void onBeforeRender() {} @Nullable default SceneContext getSceneContext() { return null; diff --git a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java index c0b58dd72f..da10ad4c99 100644 --- a/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java +++ b/src/main/java/rs117/hd/renderer/legacy/LegacyRenderer.java @@ -31,6 +31,9 @@ import rs117.hd.config.DynamicLights; import rs117.hd.model.ModelHasher; import rs117.hd.model.ModelOffsets; +import rs117.hd.opengl.commandbuffer.AWTContextWrapper; +import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.commandbuffer.CommandBufferPool; import rs117.hd.opengl.compute.ComputeMode; import rs117.hd.opengl.compute.OpenCLManager; import rs117.hd.opengl.shader.ModelPassthroughComputeProgram; @@ -93,6 +96,12 @@ public class LegacyRenderer implements Renderer { @Inject private ClientThread clientThread; + + @Inject + private AWTContextWrapper awtContextWrapper; + + @Inject + private CommandBufferPool commandBufferPool; @Inject private EventBus eventBus; @@ -243,7 +252,7 @@ public void initialize() { int maxComputeThreadCount; if (computeMode == ComputeMode.OPENCL) { - clManager.startUp(this, plugin.awtContext); + clManager.startUp(this, awtContextWrapper.getContext()); maxComputeThreadCount = clManager.getMaxWorkGroupSize(); } else { maxComputeThreadCount = glGetInteger(GL43C.GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS); @@ -1273,7 +1282,7 @@ public void draw(int overlayColor) { } // Blit from the resolved FBO to the default FBO - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); glBlitFramebuffer( 0, 0, @@ -1291,16 +1300,19 @@ public void draw(int overlayColor) { glClear(GL_COLOR_BUFFER_BIT); } - plugin.drawUi(overlayColor); + CommandBuffer cmd = commandBufferPool.acquire(); + plugin.drawUi(overlayColor, cmd); + cmd.execute(); + commandBufferPool.release(cmd); try { frameTimer.begin(Timer.SWAP_BUFFERS); - plugin.awtContext.swapBuffers(); + awtContextWrapper.getContext().swapBuffers(); frameTimer.end(Timer.SWAP_BUFFERS); drawManager.processDrawComplete(plugin::screenshot); } catch (RuntimeException ex) { // this is always fatal - if (!plugin.canvas.isValid()) { + if (!awtContextWrapper.getCanvas().isValid()) { // this might be AWT shutting down on VM shutdown, ignore it return; } @@ -1308,7 +1320,7 @@ public void draw(int overlayColor) { log.error("Unable to swap buffers:", ex); } - glBindFramebuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); frameTimer.end(Timer.DRAW_FRAME); frameTimer.end(Timer.RENDER_FRAME); diff --git a/src/main/java/rs117/hd/renderer/zone/VAO.java b/src/main/java/rs117/hd/renderer/zone/VAO.java index 3164a5656c..601f28c714 100644 --- a/src/main/java/rs117/hd/renderer/zone/VAO.java +++ b/src/main/java/rs117/hd/renderer/zone/VAO.java @@ -4,9 +4,11 @@ import java.util.Arrays; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; -import rs117.hd.utils.CommandBuffer; +import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.commandbuffer.RenderThread; import static org.lwjgl.opengl.GL33C.*; @@ -76,8 +78,6 @@ void destroy() { int off = 0; void addRange(Scene scene) { - assert vbo.mapped; - if (off > 0 && lengths[off - 1] == vbo.vb.position()) { return; } @@ -94,8 +94,6 @@ void addRange(Scene scene) { } void draw(ZoneRenderer renderer, CommandBuffer cmd) { - assert !vbo.mapped; - int start = 0; for (int i = 0; i < off; ++i) { int end = lengths[i]; @@ -103,7 +101,7 @@ void draw(ZoneRenderer renderer, CommandBuffer cmd) { int count = end - start; - cmd.SetWorldViewIndex(renderer.uboWorldViews.getIndex(scene)); + cmd.SetUniformProperty(renderer.uboCommandBuffer.worldViewIndex, renderer.uboWorldViews.getIndex(scene)); cmd.BindVertexArray(vao); cmd.DrawArrays(GL_TRIANGLES, start / (VERT_SIZE / 4), count / (VAO.VERT_SIZE / 4)); @@ -127,14 +125,14 @@ static class VAOList { private final List vaos = new ArrayList<>(); private final int eboAlpha; - VAO get(int size) { + @SneakyThrows + VAO get(RenderThread renderThread, int size) { assert size <= VAO_SIZE; while (curIdx < vaos.size()) { VAO vao = vaos.get(curIdx); - if (!vao.vbo.mapped) { - vao.vbo.map(); - } + if (!vao.vbo.mapped) + renderThread.invokeOnRenderThread(vao.vbo::map); int rem = vao.vbo.vb.remaining() * Integer.BYTES; if (size <= rem) { @@ -144,24 +142,33 @@ VAO get(int size) { curIdx++; } - VAO vao = new VAO(VAO_SIZE); - vao.initialize(eboAlpha); - vao.vbo.map(); - vaos.add(vao); - log.debug("Allocated VAO {} request {}", vao.vao, size); + final VAO vao = new VAO(VAO_SIZE); + renderThread.invokeOnRenderThread(() -> { + vao.initialize(eboAlpha); + vao.vbo.map(); + vaos.add(vao); + log.debug("Allocated VAO {} request {}", vao.vao, size); + }); return vao; } + void map() { + for (VAO vao : vaos) { + if (!vao.vbo.mapped) + vao.vbo.map(); + } + } + + void setDrawCount() { + drawCount = curIdx + 1; + } + void unmap() { - int sz = 0; for (VAO vao : vaos) { - if (vao.vbo.mapped) { - ++sz; + if (vao.vbo.mapped) vao.vbo.unmap(); - } } curIdx = 0; - drawCount = sz; } void free() { diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 35fec691d5..0e0e8be690 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -11,10 +11,11 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; +import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.uniforms.UBOCommandBuffer; import rs117.hd.scene.SceneContext; import rs117.hd.scene.materials.Material; import rs117.hd.utils.Camera; -import rs117.hd.utils.CommandBuffer; import static net.runelite.api.Perspective.*; import static org.lwjgl.opengl.GL33C.*; @@ -191,7 +192,7 @@ private void convertForDraw(int vertSize) { glDrawLength = Arrays.copyOfRange(drawEnd, 0, drawIdx); } - void renderOpaque(CommandBuffer cmd, int zx, int zz, int minLevel, int currentLevel, int maxLevel, Set hiddenRoofIds) { + void renderOpaque(UBOCommandBuffer ubo, CommandBuffer cmd, int zx, int zz, int minLevel, int currentLevel, int maxLevel, Set hiddenRoofIds) { drawIdx = 0; for (int level = minLevel; level <= maxLevel; ++level) { @@ -236,12 +237,16 @@ void renderOpaque(CommandBuffer cmd, int zx, int zz, int minLevel, int currentLe convertForDraw(VERT_SIZE); - cmd.SetBaseOffset(zx << 10, 0, zz << 10); + cmd.SetUniformProperty(ubo.sceneBase, zx << 10, 0, zz << 10); cmd.BindVertexArray(glVao); - cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + if(glDrawOffset.length > 1) { + cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + } else { + cmd.DrawArrays(GL_TRIANGLES, glDrawOffset[0], glDrawLength[0]); + } } - void renderOpaqueLevel(CommandBuffer cmd, int zx, int zz, int level) { + void renderOpaqueLevel(UBOCommandBuffer ubo, CommandBuffer cmd, int zx, int zz, int level) { drawIdx = 0; // draw the specific level @@ -252,9 +257,13 @@ void renderOpaqueLevel(CommandBuffer cmd, int zx, int zz, int level) { convertForDraw(VERT_SIZE); - cmd.SetBaseOffset(zx << 10, 0, zz << 10); + cmd.SetUniformProperty(ubo.sceneBase, zx << 10, 0, zz << 10); cmd.BindVertexArray(glVao); - cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + if(glDrawOffset.length > 1) { + cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + } else { + cmd.DrawArrays(GL_TRIANGLES, glDrawOffset[0], glDrawLength[0]); + } } private static void pushRange(int start, int end) { @@ -480,6 +489,7 @@ void alphaSort(int zx, int zz, Camera camera) { } void renderAlpha( + UBOCommandBuffer ubo, CommandBuffer cmd, int zx, int zz, @@ -487,13 +497,16 @@ void renderAlpha( int currentLevel, int maxLevel, int level, + boolean isShadow, Camera camera, Set hiddenRoofIds ) { if (alphaModels.isEmpty()) return; - cmd.DepthMask(false); + if(!isShadow) { + cmd.DepthMask(false); + } drawIdx = 0; lastDrawMode = lastVao = 0; @@ -512,7 +525,7 @@ void renderAlpha( continue; if (lastVao != m.vao || lastzx != (zx - m.zofx) || lastzz != (zz - m.zofz)) - flush(cmd); + flush(ubo, cmd); lastVao = m.vao; lastzx = zx - m.zofx; @@ -603,15 +616,17 @@ void renderAlpha( } } - flush(cmd); - cmd.DepthMask(true); + flush(ubo, cmd); + if(!isShadow) { + cmd.DepthMask(true); + } } - private void flush(CommandBuffer cmd) { + private void flush(UBOCommandBuffer ubo, CommandBuffer cmd) { if (lastDrawMode == TEMP) { - cmd.SetBaseOffset(0, 0, 0); + cmd.SetUniformProperty(ubo.sceneBase, 0, 0, 0); } else { - cmd.SetBaseOffset(lastzx << 10, 0, lastzz << 10); + cmd.SetUniformProperty(ubo.sceneBase, lastzx << 10, 0, lastzz << 10); } if (lastDrawMode == STATIC) { @@ -625,7 +640,11 @@ private void flush(CommandBuffer cmd) { } else if (drawIdx != 0) { convertForDraw(VAO.VERT_SIZE); cmd.BindVertexArray(lastVao); - cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + if(glDrawOffset.length > 1) { + cmd.MultiDrawArrays(GL_TRIANGLES, glDrawOffset, glDrawLength); + } else { + cmd.DrawArrays(GL_TRIANGLES, glDrawOffset[0], glDrawLength[0]); + } drawIdx = 0; } } diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 311a05228b..a59e34c95a 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -32,7 +32,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.inject.Inject; @@ -48,6 +47,11 @@ import rs117.hd.HdPluginConfig; import rs117.hd.config.ColorFilter; import rs117.hd.config.DynamicLights; +import rs117.hd.opengl.commandbuffer.AWTContextWrapper; +import rs117.hd.opengl.commandbuffer.CommandBuffer; +import rs117.hd.opengl.commandbuffer.CommandBufferPool; +import rs117.hd.opengl.commandbuffer.FrameSync; +import rs117.hd.opengl.commandbuffer.RenderThread; import rs117.hd.opengl.shader.SceneShaderProgram; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; @@ -69,7 +73,6 @@ import rs117.hd.scene.model_overrides.ModelOverride; import rs117.hd.utils.Camera; import rs117.hd.utils.ColorUtils; -import rs117.hd.utils.CommandBuffer; import rs117.hd.utils.HDUtils; import rs117.hd.utils.Mat4; import rs117.hd.utils.ModelHash; @@ -103,7 +106,16 @@ public class ZoneRenderer implements Renderer { @Inject private ClientThread clientThread; + + @Inject + private RenderThread renderThread; + @Inject + private AWTContextWrapper awtContextWrapper; + + @Inject + private CommandBufferPool commandBufferPool; + @Inject private DrawManager drawManager; @@ -151,14 +163,36 @@ public class ZoneRenderer implements Renderer { private int minLevel, level, maxLevel; private Set hideRoofIds; + private int frameNo; + + private final CommandBuffer zoneDrawCallBuffer = new CommandBuffer(); + private final CommandBuffer zoneShadowDrawCallBuffer = new CommandBuffer(); + + private final FrameSync mainFrameSync = new FrameSync(); + + // TODO: Can we rename these to something more human readable? + class DrawContext { + public VAO.VAOList vaoO; + public VAO.VAOList vaoA; + public VAO.VAOList vaoPO; + public VAO.VAOList vaoPOShadow; - private final CommandBuffer sceneCmd = new CommandBuffer(); - private final CommandBuffer directionalCmd = new CommandBuffer(); + public void init(int eboAlpha) { + vaoO = new VAO.VAOList(eboAlpha); + vaoA = new VAO.VAOList(eboAlpha); + vaoPO = new VAO.VAOList(eboAlpha); + vaoPOShadow = new VAO.VAOList(eboAlpha); + } - private VAO.VAOList vaoO; - private VAO.VAOList vaoA; - private VAO.VAOList vaoPO; - private VAO.VAOList vaoPOShadow; + public void free() { + vaoO.free(); + vaoA.free(); + vaoPO.free(); + vaoPOShadow.free(); + vaoO = vaoA = vaoPO = vaoPOShadow = null; + } + } + private DrawContext vaos = new DrawContext(), vaos_prev = new DrawContext(); public static int eboAlpha; public static GpuIntBuffer eboAlphaStaging; @@ -210,7 +244,7 @@ WorldViewContext context(int worldViewId) { private boolean sceneFboValid; - private final UBOCommandBuffer uboCommandBuffer = new UBOCommandBuffer(); + public final UBOCommandBuffer uboCommandBuffer = new UBOCommandBuffer(); public final UBOWorldViews uboWorldViews = new UBOWorldViews(MAX_WORLDVIEWS); private final WorldViewContext root = new WorldViewContext(null, NUM_ZONES, NUM_ZONES); @@ -243,9 +277,6 @@ public void initialize() { uboCommandBuffer.initialize(UNIFORM_BLOCK_COMMAND_BUFFER); uboWorldViews.initialize(UNIFORM_BLOCK_WORLD_VIEWS); - - sceneCmd.setUboCommandBuffer(uboCommandBuffer); - directionalCmd.setUboCommandBuffer(uboCommandBuffer); } @Override @@ -271,6 +302,7 @@ public void destroy() { @Override public void waitUntilIdle() { + renderThread.waitForRenderingCompleted(); glFinish(); } @@ -297,18 +329,13 @@ private void initializeBuffers() { eboAlpha = glGenBuffers(); eboAlphaStaging = new GpuIntBuffer(); - vaoO = new VAO.VAOList(eboAlpha); - vaoA = new VAO.VAOList(eboAlpha); - vaoPO = new VAO.VAOList(eboAlpha); - vaoPOShadow = new VAO.VAOList(eboAlpha); + vaos.init(eboAlpha); + vaos_prev.init(eboAlpha); } private void destroyBuffers() { - vaoO.free(); - vaoA.free(); - vaoPO.free(); - vaoPOShadow.free(); - vaoO = vaoA = vaoPO = vaoPOShadow = null; + vaos.free(); + vaos_prev.free(); if (eboAlpha != 0) glDeleteBuffers(eboAlpha); @@ -348,11 +375,11 @@ public void preSceneDraw( preSceneDrawTopLevel(scene, cameraX, cameraY, cameraZ, cameraPitch, cameraYaw); } else { Scene topLevel = client.getScene(); - vaoO.addRange(topLevel); - vaoPO.addRange(topLevel); - vaoPOShadow.addRange(topLevel); - sceneCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); - directionalCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + vaos.vaoO.addRange(topLevel); + vaos.vaoPO.addRange(topLevel); + vaos.vaoPOShadow.addRange(topLevel); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); } } @@ -366,6 +393,8 @@ private void preSceneDrawTopLevel( if (root.sceneContext == null || plugin.sceneViewport == null) return; + CommandBuffer cmd = commandBufferPool.acquire(); + frameTimer.begin(Timer.DRAW_FRAME); frameTimer.begin(Timer.DRAW_SCENE); @@ -556,7 +585,7 @@ private void preSceneDrawTopLevel( plugin.uboGlobal.projectionMatrix.set(plugin.viewProjMatrix); plugin.uboGlobal.invProjectionMatrix.set(plugin.invViewProjMatrix); plugin.uboGlobal.pointLightsCount.set(root.sceneContext.numVisibleLights); - plugin.uboGlobal.upload(); + cmd.UploadUniformBuffer(plugin.uboGlobal); } } @@ -590,39 +619,39 @@ private void preSceneDrawTopLevel( plugin.uboLightsCulling.setLight(i, lightPosition, lightColor); } } - - plugin.uboLights.upload(); - plugin.uboLightsCulling.upload(); frameTimer.end(Timer.UPDATE_LIGHTS); + cmd.UploadUniformBuffer(plugin.uboLights); + cmd.UploadUniformBuffer(plugin.uboLightsCulling); + // Perform tiled lighting culling before the compute memory barrier, so it's performed asynchronously if (plugin.configTiledLighting) { plugin.updateTiledLightingFbo(); assert plugin.fboTiledLighting != 0; frameTimer.begin(Timer.DRAW_TILED_LIGHTING); - frameTimer.begin(Timer.RENDER_TILED_LIGHTING); + cmd.BeginTimer(Timer.RENDER_TILED_LIGHTING); - glViewport(0, 0, plugin.tiledLightingResolution[0], plugin.tiledLightingResolution[1]); - glBindFramebuffer(GL_FRAMEBUFFER, plugin.fboTiledLighting); + cmd.Viewport(0, 0, plugin.tiledLightingResolution[0], plugin.tiledLightingResolution[1]); + cmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.fboTiledLighting); - glBindVertexArray(plugin.vaoTri); + cmd.BindVertexArray(plugin.vaoTri); if (plugin.tiledLightingImageStoreProgram.isValid()) { - plugin.tiledLightingImageStoreProgram.use(); - glDrawBuffer(GL_NONE); - glDrawArrays(GL_TRIANGLES, 0, 3); + cmd.SetShaderProgram(plugin.tiledLightingImageStoreProgram); + cmd.DrawBuffers(GL_NONE); + cmd.DrawArrays(GL_TRIANGLES, 0, 3); } else { - glDrawBuffer(GL_COLOR_ATTACHMENT0); + cmd.DrawBuffers(GL_COLOR_ATTACHMENT0); int layerCount = plugin.configDynamicLights.getTiledLightingLayers(); for (int layer = 0; layer < layerCount; layer++) { - plugin.tiledLightingShaderPrograms.get(layer).use(); - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, plugin.texTiledLighting, 0, layer); - glDrawArrays(GL_TRIANGLES, 0, 3); + cmd.SetShaderProgram(plugin.tiledLightingShaderPrograms.get(layer)); + cmd.FrameBufferLayer(GL_COLOR_ATTACHMENT0, plugin.texTiledLighting, 0, layer); + cmd.DrawArrays(GL_TRIANGLES, 0, 3); } } - frameTimer.end(Timer.RENDER_TILED_LIGHTING); + cmd.EndTimer(Timer.RENDER_TILED_LIGHTING); frameTimer.end(Timer.DRAW_TILED_LIGHTING); } } @@ -735,18 +764,22 @@ private void preSceneDrawTopLevel( plugin.uboGlobal.colorFilterFade.set(clamp(timeSinceChange / COLOR_FILTER_FADE_DURATION, 0, 1)); } - plugin.uboGlobal.upload(); uboWorldViews.update(client); + cmd.UploadUniformBuffer(plugin.uboGlobal); + cmd.UploadUniformBuffer(uboWorldViews); + + renderThread.submit(cmd); + // Reset buffers for the next frame eboAlphaStaging.clear(); - sceneCmd.reset(); - directionalCmd.reset(); - sceneCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); - directionalCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); + // Reset Command Buffers + zoneDrawCallBuffer.reset(); + zoneShadowDrawCallBuffer.reset(); - checkGLErrors(); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); } @Override @@ -755,8 +788,8 @@ public void postSceneDraw(Scene scene) { if (scene.getWorldViewId() == WorldView.TOPLEVEL) { postDrawTopLevel(); } else { - sceneCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); - directionalCmd.SetWorldViewIndex(uboWorldViews.getIndex(null)); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(null)); } } @@ -766,55 +799,66 @@ private void postDrawTopLevel() { sceneFboValid = true; - vaoA.unmap(); + CommandBuffer cmd = commandBufferPool.acquire(); // Scene draw state to apply before all recorded commands if (eboAlphaStaging.position() > 0) { - eboAlphaStaging.flip(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboAlpha); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboAlphaStaging.getBuffer(), GL_STREAM_DRAW); + cmd.Callback(() -> { + vaos.vaoO.unmap(); + vaos.vaoPO.unmap(); + vaos.vaoPOShadow.unmap(); + vaos.vaoA.unmap(); + + eboAlphaStaging.flip(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboAlpha); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboAlphaStaging.getBuffer(), GL_STREAM_DRAW); + + // Prepare for next frame + DrawContext temp = vaos; + vaos = vaos_prev; + vaos_prev = temp; + + vaos.vaoO.map(); + vaos.vaoA.map(); + vaos.vaoPO.map(); + vaos.vaoPOShadow.map(); + + checkGLErrors(); + }); } if (plugin.configShadowsEnabled && plugin.fboShadowMap != 0 && 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); - glClearDepth(1); - glClear(GL_DEPTH_BUFFER_BIT); - - plugin.shadowProgram.use(); + cmd.BeginTimer(Timer.RENDER_FRAME); + cmd.BeginTimer(Timer.RENDER_SHADOWS); + cmd.Viewport(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); + cmd.BindFrameBuffer(GL_FRAMEBUFFER, plugin.fboShadowMap); + cmd.ClearDepth(1.0f); - // TODO: Depth test will get changed by the command buffer, but we'll be adding a shadowCmd anyway - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LEQUAL); - glDisable(GL_CULL_FACE); + cmd.Enable(GL_DEPTH_TEST); + cmd.Disable(GL_CULL_FACE); + cmd.SetDepthFunc(GL_LEQUAL); - CommandBuffer.SKIP_DEPTH_MASKING = true; - directionalCmd.execute(); - CommandBuffer.SKIP_DEPTH_MASKING = false; + cmd.SetShaderProgram(plugin.shadowProgram); - glDisable(GL_DEPTH_TEST); + cmd.ExecuteCommandBuffer(zoneShadowDrawCallBuffer); - frameTimer.end(Timer.RENDER_SHADOWS); - } - - sceneProgram.use(); + cmd.Disable(GL_DEPTH_TEST); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.fboScene); - if (plugin.msaaSamples > 1) { - glEnable(GL_MULTISAMPLE); + cmd.EndTimer(Timer.RENDER_SHADOWS); } else { - glDisable(GL_MULTISAMPLE); + cmd.BeginTimer(Timer.RENDER_FRAME); } - glViewport(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); - // Clear scene - frameTimer.begin(Timer.CLEAR_SCENE); + cmd.BeginTimer(Timer.RENDER_SCENE); + cmd.SetShaderProgram(sceneProgram); + + cmd.BindFrameBuffer(GL_DRAW_FRAMEBUFFER, plugin.fboScene); + cmd.Toggle(GL_MULTISAMPLE, plugin.msaaSamples > 1); + cmd.Viewport(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); float[] fogColor = ColorUtils.linearToSrgb(environmentManager.currentFogColor); float[] gammaCorrectedFogColor = pow(fogColor, plugin.getGammaCorrection()); @@ -824,45 +868,29 @@ private void postDrawTopLevel() { gammaCorrectedFogColor[2], 1f ); - glClearDepth(0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - frameTimer.end(Timer.CLEAR_SCENE); - frameTimer.begin(Timer.RENDER_SCENE); + cmd.BeginTimer(Timer.CLEAR_SCENE); + cmd.ClearColorAndDepth(gammaCorrectedFogColor[0], gammaCorrectedFogColor[1], gammaCorrectedFogColor[2], 1f, 0.0f); + cmd.EndTimer(Timer.CLEAR_SCENE); - glEnable(GL_BLEND); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - glEnable(GL_CULL_FACE); - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_GREATER); + cmd.Enable(GL_CULL_FACE); + cmd.Enable(GL_DEPTH_TEST); + cmd.SetDepthFunc(GL_GREATER); - // Render the scene - sceneCmd.execute(); + cmd.Enable(GL_BLEND); + cmd.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - // TODO: Filler tiles + cmd.ExecuteCommandBuffer(zoneDrawCallBuffer); - frameTimer.end(Timer.DRAW_SCENE); - frameTimer.end(Timer.RENDER_SCENE); - frameTimer.begin(Timer.RENDER_FRAME); - - // Done rendering the scene - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - - // The client only updates animations once per client tick, so we can skip updating geometry buffers, - // but the compute shaders should still be executed in case the camera angle has changed. - // Technically we could skip compute shaders as well when the camera is unchanged, - // but it would only lead to micro stuttering when rotating the camera, compared to no rotation. -// if (!plugin.redrawPreviousFrame) { -// updateSceneVao(hRenderBufferVertices, hRenderBufferUvs, hRenderBufferNormals); -// } + cmd.Disable(GL_CULL_FACE); + cmd.Disable(GL_DEPTH_TEST); + cmd.Disable(GL_BLEND); -// frameTimer.begin(Timer.COMPUTE); -// plugin.uboCompute.upload(); -// frameTimer.end(Timer.COMPUTE); + cmd.EndTimer(Timer.RENDER_SCENE); - checkGLErrors(); + renderThread.submit(cmd); + + frameTimer.end(Timer.DRAW_SCENE); } @Override @@ -901,14 +929,15 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int int offset = ctx.sceneContext.sceneOffset >> 3; if (ctx != root || z.inSceneFrustum) { - sceneCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); - z.renderOpaque(sceneCmd, zx - offset, zz - offset, minLevel, level, maxLevel, hideRoofIds); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); + z.renderOpaque(uboCommandBuffer, zoneDrawCallBuffer, zx - offset, zz - offset, minLevel, level, maxLevel, hideRoofIds); } if (ctx != root || z.inShadowFrustum) { - directionalCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); z.renderOpaque( - directionalCmd, + uboCommandBuffer, + zoneShadowDrawCallBuffer, zx - offset, zz - offset, minLevel, @@ -917,8 +946,6 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int plugin.configRoofShadows ? Collections.emptySet() : hideRoofIds ); } - - checkGLErrors(); } @Override @@ -937,11 +964,11 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i boolean renderWater = z.inSceneFrustum && level == 0 && z.hasWater; if (renderWater || hasAlpha) - sceneCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); int offset = ctx.sceneContext.sceneOffset >> 3; if (renderWater) - z.renderOpaqueLevel(sceneCmd, zx - offset, zz - offset, Zone.LEVEL_WATER_SURFACE); + z.renderOpaqueLevel(uboCommandBuffer, zoneDrawCallBuffer, zx - offset, zz - offset, Zone.LEVEL_WATER_SURFACE); if (!hasAlpha) return; @@ -953,34 +980,36 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i if (ctx != root || z.inSceneFrustum) { z.renderAlpha( - sceneCmd, + uboCommandBuffer, + zoneDrawCallBuffer, zx - offset, zz - offset, minLevel, this.level, maxLevel, level, + false, sceneCamera, hideRoofIds ); } if (ctx != root || z.inShadowFrustum) { - directionalCmd.SetWorldViewIndex(uboWorldViews.getIndex(scene)); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.worldViewIndex, uboWorldViews.getIndex(scene)); z.renderAlpha( - directionalCmd, + uboCommandBuffer, + zoneShadowDrawCallBuffer, zx - offset, zz - offset, minLevel, this.level, plugin.configRoofShadows ? 3 : maxLevel, level, + true, sceneCamera, plugin.configRoofShadows ? Collections.emptySet() : hideRoofIds ); } - - checkGLErrors(); } @Override @@ -992,38 +1021,38 @@ public void drawPass(Projection projection, Scene scene, int pass) { switch (pass) { case DrawCallbacks.PASS_OPAQUE: - vaoO.addRange(scene); - vaoPO.addRange(scene); - vaoPOShadow.addRange(scene); + vaos.vaoO.addRange(scene); + vaos.vaoPO.addRange(scene); + vaos.vaoPOShadow.addRange(scene); if (scene.getWorldViewId() == -1) { - sceneCmd.SetBaseOffset(0, 0, 0); - directionalCmd.SetBaseOffset(0, 0, 0); + zoneDrawCallBuffer.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); + zoneShadowDrawCallBuffer.SetUniformProperty(uboCommandBuffer.sceneBase, 0, 0, 0); - // Draw opaque - vaoO.unmap(); - vaoO.drawAll(this, sceneCmd); - vaoO.drawAll(this, directionalCmd); - vaoO.resetAll(); + vaos.vaoO.setDrawCount(); + vaos.vaoPO.setDrawCount(); + vaos.vaoPOShadow.setDrawCount(); - vaoPO.unmap(); + // Draw opaque + vaos.vaoO.drawAll(this, zoneDrawCallBuffer); + vaos.vaoO.drawAll(this, zoneShadowDrawCallBuffer); + vaos.vaoO.resetAll(); // Draw player shadows - vaoPOShadow.unmap(); - vaoPOShadow.drawAll(this, directionalCmd); - vaoPOShadow.resetAll(); + vaos.vaoPOShadow.drawAll(this, zoneShadowDrawCallBuffer); + vaos.vaoPOShadow.resetAll(); // Draw players opaque, without depth writes - sceneCmd.DepthMask(false); - vaoPO.drawAll(this, sceneCmd); - sceneCmd.DepthMask(true); + zoneDrawCallBuffer.DepthMask(false); + vaos.vaoPO.drawAll(this, zoneDrawCallBuffer); + zoneDrawCallBuffer.DepthMask(true); // Draw players opaque, writing only depth - sceneCmd.ColorMask(false, false, false, false); - vaoPO.drawAll(this, sceneCmd); - sceneCmd.ColorMask(true, true, true, true); + zoneDrawCallBuffer.ColorMask(false, false, false, false); + vaos.vaoPO.drawAll(this, zoneDrawCallBuffer); + zoneDrawCallBuffer.ColorMask(true, true, true, true); - vaoPO.resetAll(); + vaos.vaoPO.resetAll(); } break; case DrawCallbacks.PASS_ALPHA: @@ -1032,7 +1061,6 @@ public void drawPass(Projection projection, Scene scene, int pass) { ctx.zones[x][z].removeTemp(); break; } - checkGLErrors(); } @Override @@ -1080,11 +1108,11 @@ public void drawDynamic( int size = m.getFaceCount() * 3 * VAO.VERT_SIZE; if (!hasAlpha) { - VAO o = vaoO.get(size); + VAO o = vaos.vaoO.get(renderThread, size); sceneUploader.uploadTempModel(m, modelOverride, preOrientation, orient, x, y, z, o.vbo.vb); } else { m.calculateBoundsCylinder(); - VAO o = vaoO.get(size), a = vaoA.get(size); + VAO o = vaos.vaoO.get(renderThread, size), a = vaos.vaoA.get(renderThread, size); int start = a.vbo.vb.position(); facePrioritySorter.uploadSortedModel(worldProjection, m, modelOverride, preOrientation, orient, x, y, z, o.vbo.vb, a.vbo.vb); int end = a.vbo.vb.position(); @@ -1127,8 +1155,8 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj // opaque player faces have their own vao and are drawn in a separate pass from normal opaque faces // because they are not depth tested. transparent player faces don't need their own vao because normal // transparent faces are already not depth tested - VAO o = renderable instanceof Player ? vaoPO.get(size) : vaoO.get(size); - VAO a = vaoA.get(size); + VAO o = renderable instanceof Player ? vaos.vaoPO.get(renderThread, size) : vaos.vaoO.get(renderThread, size); + VAO a = vaos.vaoA.get(renderThread, size); int start = a.vbo.vb.position(); m.calculateBoundsCylinder(); @@ -1166,7 +1194,7 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj if (zone.inShadowFrustum) { // Since priority sorting of models includes back-face culling, // we need to upload the entire model again for shadows - VAO o = vaoPOShadow.get(size); + VAO o = vaos.vaoPOShadow.get(renderThread, size); sceneUploader.uploadTempModel( m, modelOverride, @@ -1179,7 +1207,7 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj ); } } else { - VAO o = vaoO.get(size); + VAO o = vaos.vaoO.get(renderThread, size); sceneUploader.uploadTempModel( m, modelOverride, @@ -1220,55 +1248,69 @@ private void rebuild(WorldView wv) { if (ctx == null) return; + + // Check if any zones need to be invalidated before invoking on the RenderThread + boolean needsRebuild = false; for (int x = 0; x < ctx.sizeX; ++x) { for (int z = 0; z < ctx.sizeZ; ++z) { Zone zone = ctx.zones[x][z]; - if (!zone.invalidate) - continue; - - assert zone.initialized; - zone.free(); - zone = ctx.zones[x][z] = new Zone(); - - SceneUploader sceneUploader = injector.getInstance(SceneUploader.class); - sceneUploader.zoneSize(ctx.sceneContext, zone, x, z); - - VBO o = null, a = null; - int sz = zone.sizeO * Zone.VERT_SIZE * 3; - if (sz > 0) { - o = new VBO(sz); - o.initialize(GL_STATIC_DRAW); - o.map(); + if (zone.invalidate) { + needsRebuild = true; + break; } + } + } - sz = zone.sizeA * Zone.VERT_SIZE * 3; - if (sz > 0) { - a = new VBO(sz); - a.initialize(GL_STATIC_DRAW); - a.map(); - } + if(!needsRebuild) + return; + + renderThread.invokeOnRenderThread(() -> { + for (int x = 0; x < ctx.sizeX; ++x) { + for (int z = 0; z < ctx.sizeZ; ++z) { + Zone zone = ctx.zones[x][z]; + if (!zone.invalidate) + continue; + + assert zone.initialized; + zone.free(); + zone = ctx.zones[x][z] = new Zone(); + + SceneUploader sceneUploader = injector.getInstance(SceneUploader.class); + sceneUploader.zoneSize(ctx.sceneContext, zone, x, z); - zone.initialize(o, a, eboAlpha); + VBO o = null, a = null; + int sz = zone.sizeO * Zone.VERT_SIZE * 3; + if (sz > 0) { + o = new VBO(sz); + o.initialize(GL_STATIC_DRAW); + o.map(); + } + + sz = zone.sizeA * Zone.VERT_SIZE * 3; + if (sz > 0) { + a = new VBO(sz); + a.initialize(GL_STATIC_DRAW); + a.map(); + } + + zone.initialize(o, a, eboAlpha); - sceneUploader.uploadZone(ctx.sceneContext, zone, x, z); + sceneUploader.uploadZone(ctx.sceneContext, zone, x, z); - zone.unmap(); - zone.initialized = true; - zone.dirty = true; + zone.unmap(); + zone.initialized = true; + zone.dirty = true; - log.trace("Rebuilt zone wv={} x={} z={}", wv.getId(), x, z); + log.trace("Rebuilt zone wv={} x={} z={}", wv.getId(), x, z); + } } - } + }); } @Override public void draw(int overlayColor) { log.trace("draw(overlaySrgba={})", overlayColor); - final GameState gameState = client.getGameState(); - if (gameState == GameState.STARTING) { - frameTimer.end(Timer.DRAW_FRAME); - return; - } + frameTimer.end(Timer.DRAW_FRAME); try { plugin.prepareInterfaceTexture(); @@ -1280,63 +1322,30 @@ public void draw(int overlayColor) { return; } + CommandBuffer cmd = commandBufferPool.acquire(); 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], - 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 - ); + cmd.BlitFramebuffer( + plugin.fboScene, plugin.fboSceneResolve, awtContextWrapper.getBackBuffer(), + plugin.sceneResolution[0], plugin.sceneResolution[1], + plugin.sceneViewport[0], plugin.sceneViewport[1], + plugin.sceneViewport[2], plugin.sceneViewport[3], + config.sceneScalingMode().glFilter); } else { - glBindFramebuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); + cmd.BindFrameBuffer(GL_FRAMEBUFFER, awtContextWrapper.getBackBuffer()); + cmd.ClearColor(0, 0, 0, 1); } - plugin.drawUi(overlayColor); - - try { - frameTimer.begin(Timer.SWAP_BUFFERS); - plugin.awtContext.swapBuffers(); - frameTimer.end(Timer.SWAP_BUFFERS); - drawManager.processDrawComplete(plugin::screenshot); - } catch (RuntimeException ex) { - // this is always fatal - if (!plugin.canvas.isValid()) { - // this might be AWT shutting down on VM shutdown, ignore it - return; - } - - log.error("Unable to swap buffers:", ex); - } + plugin.drawUi(overlayColor, cmd); - glBindFramebuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); + cmd.BeginTimer(Timer.SWAP_BUFFERS); + cmd.SwapBuffers(awtContextWrapper.getContext()); + cmd.EndTimer(Timer.SWAP_BUFFERS); + cmd.EndTimer(Timer.RENDER_FRAME); + cmd.Callback(frameTimer::endFrameAndReset); + cmd.Signal(mainFrameSync); - frameTimer.end(Timer.DRAW_FRAME); - frameTimer.end(Timer.RENDER_FRAME); - frameTimer.endFrameAndReset(); -// frameModelInfoMap.clear(); - checkGLErrors(); + mainFrameSync.markInFlight(); + renderThread.submit(cmd); } @Subscribe @@ -1525,9 +1534,7 @@ public void loadScene(WorldView worldView, Scene scene) { ); // allocate buffers for zones which require upload - CountDownLatch latch = new CountDownLatch(1); - clientThread.invoke(() -> - { + renderThread.invokeOnRenderThread(() -> { for (int x = 0; x < EXTENDED_SCENE_SIZE >> 3; ++x) { for (int z = 0; z < EXTENDED_SCENE_SIZE >> 3; ++z) { Zone zone = newZones[x][z]; @@ -1554,14 +1561,7 @@ public void loadScene(WorldView worldView, Scene scene) { zone.initialize(o, a, eboAlpha); } } - - latch.countDown(); }); - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } // upload zones sw = Stopwatch.createStarted(); @@ -1665,9 +1665,7 @@ private void loadSubScene(WorldView worldView, Scene scene) { sceneUploader.zoneSize(sceneContext, ctx.zones[x][z], x, z); // allocate buffers for zones which require upload - CountDownLatch latch = new CountDownLatch(1); - clientThread.invoke(() -> - { + renderThread.invokeOnRenderThread(() -> { for (int x = 0; x < ctx.sizeX; ++x) { for (int z = 0; z < ctx.sizeZ; ++z) { Zone zone = ctx.zones[x][z]; @@ -1690,14 +1688,7 @@ private void loadSubScene(WorldView worldView, Scene scene) { zone.initialize(o, a, eboAlpha); } } - - latch.countDown(); }); - try { - latch.await(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } for (int x = 0; x < ctx.sizeX; ++x) for (int z = 0; z < ctx.sizeZ; ++z) @@ -1721,6 +1712,9 @@ public void despawnWorldView(WorldView worldView) { @Override public void swapScene(Scene scene) { log.trace("swapScene({})", scene); + + renderThread.waitForRenderingCompleted(); + if (scene.getWorldViewId() > -1) { swapSub(scene); return; @@ -1739,6 +1733,7 @@ public void swapScene(Scene scene) { return; // Return early if scene loading failed } + lightManager.loadSceneLights(nextSceneContext, root.sceneContext); fishingSpotReplacer.despawnRuneLiteObjects(); npcDisplacementCache.clear(); diff --git a/src/main/java/rs117/hd/utils/CommandBuffer.java b/src/main/java/rs117/hd/utils/CommandBuffer.java deleted file mode 100644 index 03af06a3d7..0000000000 --- a/src/main/java/rs117/hd/utils/CommandBuffer.java +++ /dev/null @@ -1,240 +0,0 @@ -package rs117.hd.utils; - -import java.nio.IntBuffer; -import java.util.Arrays; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.lwjgl.system.MemoryStack; -import rs117.hd.opengl.uniforms.UBOCommandBuffer; - -import static org.lwjgl.opengl.GL33C.*; - -@Slf4j -public class CommandBuffer { - public static boolean SKIP_DEPTH_MASKING; - - private static final int GL_BIND_VERTEX_ARRAY_TYPE = 0; - private static final int GL_BIND_ELEMENTS_ARRAY_TYPE = 1; - private static final int GL_MULTI_DRAW_ARRAYS_TYPE = 2; - private static final int GL_DRAW_ARRAYS_TYPE = 3; - private static final int GL_DRAW_ELEMENTS_TYPE = 4; - - private static final int GL_DEPTH_MASK_TYPE = 5; - private static final int GL_COLOR_MASK_TYPE = 6; - - private static final int UNIFORM_BASE_OFFSET = 7; - private static final int UNIFORM_WORLD_VIEW_ID = 8; - - private static final int GL_TOGGLE_TYPE = 9; // Combined glEnable & glDisable - - private static final long INT_MASK = 0xFFFF_FFFFL; - - @Setter - private UBOCommandBuffer uboCommandBuffer; - - private long[] cmd = new long[1 << 20]; // ~1 million calls - private int writeHead = 0; - private int readHead = 0; - - private void ensureCapacity(int numLongs) { - if (writeHead + numLongs >= cmd.length) - cmd = Arrays.copyOf(cmd, cmd.length * 2); - } - - public void SetBaseOffset(int x, int y, int z) { - ensureCapacity(4); - cmd[writeHead++] = UNIFORM_BASE_OFFSET; - cmd[writeHead++] = x; - cmd[writeHead++] = y; - cmd[writeHead++] = z; - } - - public void SetWorldViewIndex(int index) { - ensureCapacity(2); - cmd[writeHead++] = UNIFORM_WORLD_VIEW_ID; - cmd[writeHead++] = index; - } - - public void BindVertexArray(int vao) { - ensureCapacity(2); - cmd[writeHead++] = GL_BIND_VERTEX_ARRAY_TYPE; - cmd[writeHead++] = vao; - } - - public void BindElementsArray(int ebo) { - ensureCapacity(2); - cmd[writeHead++] = GL_BIND_ELEMENTS_ARRAY_TYPE; - cmd[writeHead++] = ebo; - } - - public void DepthMask(boolean writeDepth) { - ensureCapacity(2); - cmd[writeHead++] = GL_DEPTH_MASK_TYPE; - cmd[writeHead++] = writeDepth ? 1 : 0; - } - - public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, boolean writeAlpha) { - ensureCapacity(5); - cmd[writeHead++] = GL_COLOR_MASK_TYPE; - cmd[writeHead++] = writeRed ? 1 : 0; - cmd[writeHead++] = writeGreen ? 1 : 0; - cmd[writeHead++] = writeBlue ? 1 : 0; - cmd[writeHead++] = writeAlpha ? 1 : 0; - } - - public void MultiDrawArrays(int mode, int[] offsets, int[] counts) { - assert offsets.length == counts.length; - - ensureCapacity(3 + (offsets.length * 2)); - cmd[writeHead++] = GL_MULTI_DRAW_ARRAYS_TYPE; - cmd[writeHead++] = mode; - cmd[writeHead++] = offsets.length; - for (int i = 0; i < offsets.length; i++) { - cmd[writeHead++] = offsets[i]; - cmd[writeHead++] = counts[i]; - } - } - - public void DrawElements(int mode, int vertexCount, long offset) { - ensureCapacity(4); - cmd[writeHead++] = GL_DRAW_ELEMENTS_TYPE; - cmd[writeHead++] = mode; - cmd[writeHead++] = vertexCount; - cmd[writeHead++] = offset; - } - - public void DrawArrays(int mode, int offset, int vertexCount) { - ensureCapacity(4); - cmd[writeHead++] = GL_DRAW_ARRAYS_TYPE; - cmd[writeHead++] = mode; - cmd[writeHead++] = offset; - cmd[writeHead++] = vertexCount; - } - - public void Enable(int capability) { - Toggle(capability, true); - } - - public void Disable(int capability) { - Toggle(capability, false); - } - - public void Toggle(int capability, boolean enabled) { - ensureCapacity(2); - cmd[writeHead++] = GL_TOGGLE_TYPE; - cmd[writeHead++] = (enabled ? 1L : 0) << 32 | capability & INT_MASK; - } - - public void execute() { - try (MemoryStack stack = MemoryStack.stackPush()) { - IntBuffer offsets = null, counts = null; - readHead = 0; - while (readHead < writeHead) { - int type = (int) cmd[readHead++]; - switch (type) { - case UNIFORM_BASE_OFFSET: { - int x = (int) cmd[readHead++]; - int y = (int) cmd[readHead++]; - int z = (int) cmd[readHead++]; - if (uboCommandBuffer != null) - uboCommandBuffer.sceneBase.set(x, y, z); - break; - } - case UNIFORM_WORLD_VIEW_ID: { - int id = (int) cmd[readHead++]; - if (uboCommandBuffer != null) - uboCommandBuffer.worldViewIndex.set(id); - break; - } - case GL_DEPTH_MASK_TYPE: { - int state = (int) cmd[readHead++]; - if (SKIP_DEPTH_MASKING) - continue; - glDepthMask(state == 1); - break; - } - case GL_COLOR_MASK_TYPE: { - int red = (int) cmd[readHead++]; - int green = (int) cmd[readHead++]; - int blue = (int) cmd[readHead++]; - int alpha = (int) cmd[readHead++]; - glColorMask(red == 1, green == 1, blue == 1, alpha == 1); - break; - } - case GL_BIND_VERTEX_ARRAY_TYPE: { - glBindVertexArray((int) cmd[readHead++]); - break; - } - case GL_BIND_ELEMENTS_ARRAY_TYPE: { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (int) cmd[readHead++]); - break; - } - case GL_DRAW_ARRAYS_TYPE: { - int mode = (int) cmd[readHead++]; - int offset = (int) cmd[readHead++]; - int count = (int) cmd[readHead++]; - - if (uboCommandBuffer != null && uboCommandBuffer.isDirty()) - uboCommandBuffer.upload(); - - glDrawArrays(mode, offset, count); - break; - } - case GL_DRAW_ELEMENTS_TYPE: { - int mode = (int) cmd[readHead++]; - int vertexCount = (int) cmd[readHead++]; - long byteOffset = cmd[readHead++]; - - if (uboCommandBuffer != null && uboCommandBuffer.isDirty()) - uboCommandBuffer.upload(); - - glDrawElements(mode, vertexCount, GL_UNSIGNED_INT, byteOffset); - break; - } - case GL_MULTI_DRAW_ARRAYS_TYPE: { - int mode = (int) cmd[readHead++]; - int drawCount = (int) cmd[readHead++]; - - if (offsets == null || offsets.capacity() < drawCount) { - offsets = stack.callocInt(drawCount); - counts = stack.callocInt(drawCount); - } - - for (int i = 0; i < drawCount; i++) { - offsets.put((int) cmd[readHead++]); - counts.put((int) cmd[readHead++]); - } - - offsets.flip(); - counts.flip(); - - if (uboCommandBuffer != null && uboCommandBuffer.isDirty()) - uboCommandBuffer.upload(); - - glMultiDrawArrays(mode, offsets, counts); - - offsets.clear(); - counts.clear(); - break; - } - case GL_TOGGLE_TYPE: { - long packed = cmd[readHead++]; - int capability = (int) (packed & INT_MASK); - if ((packed >> 32) != 0) { - glEnable(capability); - } else { - glDisable(capability); - } - break; - } - default: - throw new IllegalArgumentException("Encountered an unknown DrawCall type: " + type); - } - } - } - } - - public void reset() { - writeHead = 0; - } -}