From 70b457ed185a1629a015292d1e2c0c16f4fae1d7 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Tue, 5 May 2020 13:05:23 +0100 Subject: [PATCH] Add a monitor renderer using TBOs (#443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This uses the system described in #409, to render monitors in a more efficient manner. Each monitor is backed by a texture buffer object (TBO) which contains a relatively compact encoding of the terminal state. This is then rendered using a shader, which consumes the TBO and uses it to index into main font texture. As we're transmitting significantly less data to the GPU (only 3 bytes per character), this effectively reduces any update lag to 0. FPS appears to be up by a small fraction (10-15fps on my machine, to ~110), possibly as we're now only drawing a single quad (though doing much more work in the shader). On my laptop, with its Intel integrated graphics card, I'm able to draw 120 full-sized monitors (with an effective resolution of 3972 x 2330) at a consistent 60fps. Updates still cause a slight spike, but we always remain above 30fps - a significant improvement over VBOs, where updates would go off the chart. Many thanks to @Lignum and @Lemmmy for devising this scheme, and helping test and review it! ♥ --- .../client/gui/FixedWidthFontRenderer.java | 4 +- .../render/MonitorTextureBufferShader.java | 176 ++++++++++++++++++ .../render/TileEntityMonitorRenderer.java | 63 ++++++- .../peripheral/monitor/ClientMonitor.java | 46 ++++- .../peripheral/monitor/MonitorRenderer.java | 36 +++- .../assets/computercraft/lang/en_us.lang | 1 + .../assets/computercraft/shaders/monitor.frag | 40 ++++ .../assets/computercraft/shaders/monitor.vert | 13 ++ 8 files changed, 367 insertions(+), 12 deletions(-) create mode 100644 src/main/java/dan200/computercraft/client/render/MonitorTextureBufferShader.java create mode 100644 src/main/resources/assets/computercraft/shaders/monitor.frag create mode 100644 src/main/resources/assets/computercraft/shaders/monitor.vert diff --git a/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java b/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java index 9e6d80268..7cdeeb4d3 100644 --- a/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java +++ b/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java @@ -50,12 +50,12 @@ public final class FixedWidthFontRenderer { } - private static float toGreyscale( double[] rgb ) + public static float toGreyscale( double[] rgb ) { return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3); } - private static int getColour( char c, Colour def ) + public static int getColour( char c, Colour def ) { return 15 - Terminal.getColour( c, def ); } diff --git a/src/main/java/dan200/computercraft/client/render/MonitorTextureBufferShader.java b/src/main/java/dan200/computercraft/client/render/MonitorTextureBufferShader.java new file mode 100644 index 000000000..5d4a43043 --- /dev/null +++ b/src/main/java/dan200/computercraft/client/render/MonitorTextureBufferShader.java @@ -0,0 +1,176 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ + +package dan200.computercraft.client.render; + +import com.google.common.base.Strings; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.client.gui.FixedWidthFontRenderer; +import dan200.computercraft.shared.util.Palette; +import net.minecraft.client.renderer.OpenGlHelper; +import org.apache.commons.io.IOUtils; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL13; +import org.lwjgl.opengl.GL20; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +class MonitorTextureBufferShader +{ + static final int TEXTURE_INDEX = GL13.GL_TEXTURE3; + + private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer( 16 ); + private static final FloatBuffer PALETTE_BUFFER = BufferUtils.createFloatBuffer( 16 * 3 ); + + private static int uniformMv; + private static int uniformP; + + private static int uniformFont; + private static int uniformWidth; + private static int uniformHeight; + private static int uniformTbo; + private static int uniformPalette; + + private static boolean initialised; + private static boolean ok; + private static int program; + + static void setupUniform( int width, int height, Palette palette, boolean greyscale ) + { + MATRIX_BUFFER.rewind(); + GL11.glGetFloat( GL11.GL_MODELVIEW_MATRIX, MATRIX_BUFFER ); + MATRIX_BUFFER.rewind(); + OpenGlHelper.glUniformMatrix4( uniformMv, false, MATRIX_BUFFER ); + + MATRIX_BUFFER.rewind(); + GL11.glGetFloat( GL11.GL_PROJECTION_MATRIX, MATRIX_BUFFER ); + MATRIX_BUFFER.rewind(); + OpenGlHelper.glUniformMatrix4( uniformP, false, MATRIX_BUFFER ); + + OpenGlHelper.glUniform1i( uniformWidth, width ); + OpenGlHelper.glUniform1i( uniformHeight, height ); + + PALETTE_BUFFER.rewind(); + for( int i = 0; i < 16; i++ ) + { + double[] colour = palette.getColour( i ); + if( greyscale ) + { + float f = FixedWidthFontRenderer.toGreyscale( colour ); + PALETTE_BUFFER.put( f ).put( f ).put( f ); + } + else + { + PALETTE_BUFFER.put( (float) colour[0] ).put( (float) colour[1] ).put( (float) colour[2] ); + } + } + PALETTE_BUFFER.flip(); + OpenGlHelper.glUniform3( uniformPalette, PALETTE_BUFFER ); + } + + static boolean use() + { + if( initialised ) + { + if( ok ) OpenGlHelper.glUseProgram( program ); + return ok; + } + + if( ok = load() ) + { + GL20.glUseProgram( program ); + OpenGlHelper.glUniform1i( uniformFont, 0 ); + OpenGlHelper.glUniform1i( uniformTbo, TEXTURE_INDEX - GL13.GL_TEXTURE0 ); + } + + return ok; + } + + private static boolean load() + { + initialised = true; + + try + { + int vertexShader = loadShader( GL20.GL_VERTEX_SHADER, "assets/computercraft/shaders/monitor.vert" ); + int fragmentShader = loadShader( GL20.GL_FRAGMENT_SHADER, "assets/computercraft/shaders/monitor.frag" ); + + program = OpenGlHelper.glCreateProgram(); + OpenGlHelper.glAttachShader( program, vertexShader ); + OpenGlHelper.glAttachShader( program, fragmentShader ); + GL20.glBindAttribLocation( program, 0, "v_pos" ); + + OpenGlHelper.glLinkProgram( program ); + boolean ok = OpenGlHelper.glGetProgrami( program, GL20.GL_LINK_STATUS ) != 0; + String log = OpenGlHelper.glGetProgramInfoLog( program, Short.MAX_VALUE ).trim(); + if( !Strings.isNullOrEmpty( log ) ) + { + ComputerCraft.log.warn( "Problems when linking monitor shader: {}", log ); + } + + GL20.glDetachShader( program, vertexShader ); + GL20.glDetachShader( program, fragmentShader ); + OpenGlHelper.glDeleteShader( vertexShader ); + OpenGlHelper.glDeleteShader( fragmentShader ); + + if( !ok ) return false; + + uniformMv = getUniformLocation( program, "u_mv" ); + uniformP = getUniformLocation( program, "u_p" ); + + uniformFont = getUniformLocation( program, "u_font" ); + uniformWidth = getUniformLocation( program, "u_width" ); + uniformHeight = getUniformLocation( program, "u_height" ); + uniformTbo = getUniformLocation( program, "u_tbo" ); + uniformPalette = getUniformLocation( program, "u_palette" ); + + ComputerCraft.log.info( "Loaded monitor shader." ); + return true; + } + catch( Exception e ) + { + ComputerCraft.log.error( "Cannot load monitor shaders", e ); + return false; + } + } + + private static int loadShader( int kind, String path ) throws IOException + { + InputStream stream = TileEntityMonitorRenderer.class.getClassLoader().getResourceAsStream( path ); + if( stream == null ) throw new IllegalArgumentException( "Cannot find " + path ); + byte[] contents = IOUtils.toByteArray( new BufferedInputStream( stream ) ); + ByteBuffer buffer = BufferUtils.createByteBuffer( contents.length ); + buffer.put( contents ); + buffer.position( 0 ); + + int shader = OpenGlHelper.glCreateShader( kind ); + + OpenGlHelper.glShaderSource( shader, buffer ); + OpenGlHelper.glCompileShader( shader ); + + boolean ok = OpenGlHelper.glGetShaderi( shader, GL20.GL_COMPILE_STATUS ) != 0; + String log = OpenGlHelper.glGetShaderInfoLog( shader, Short.MAX_VALUE ).trim(); + if( !Strings.isNullOrEmpty( log ) ) + { + ComputerCraft.log.warn( "Problems when loading monitor shader {}: {}", path, log ); + } + + if( !ok ) throw new IllegalStateException( "Cannot compile shader " + path ); + return shader; + } + + private static int getUniformLocation( int program, String name ) + { + int uniform = OpenGlHelper.glGetUniformLocation( program, name ); + if( uniform == -1 ) throw new IllegalStateException( "Cannot find uniform " + name ); + return uniform; + } +} diff --git a/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java b/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java index db7f7b785..405a36a29 100644 --- a/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java @@ -8,23 +8,28 @@ package dan200.computercraft.client.render; import dan200.computercraft.client.FrameInfo; import dan200.computercraft.client.gui.FixedWidthFontRenderer; import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.shared.peripheral.monitor.ClientMonitor; import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer; import dan200.computercraft.shared.peripheral.monitor.TileMonitor; +import dan200.computercraft.shared.util.Colour; import dan200.computercraft.shared.util.DirectionUtil; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.BufferBuilder; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.OpenGlHelper; -import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.*; import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.VertexBuffer; import net.minecraft.util.EnumFacing; import net.minecraft.util.math.BlockPos; import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL13; +import org.lwjgl.opengl.GL15; +import org.lwjgl.opengl.GL31; import javax.annotation.Nonnull; +import java.nio.ByteBuffer; +import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*; import static dan200.computercraft.shared.peripheral.monitor.TileMonitor.RENDER_MARGIN; public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer @@ -97,8 +102,8 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer