1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-13 19:50:31 +00:00

Use Ű̶̹̚n̵̦̂́s̷̭̲͐a̶̞͔̔f̸̠́̀e̵͔̋̀ to upload the monitor's contents

The VBO renderer needs to generate a buffer with two quads for each
cell, and then transfer it to the GPU. For large monitors, generating
this buffer can get quite slow. Most of the issues come from
IVertexBuilder (VertexConsumer under MojMap) having a lot of overhead.

By emitting a ByteBuffer directly (and doing so with Unsafe to avoid
bounds checks), we can improve performance 10 fold, going from
3fps/300ms for 120 monitors to 111fps/9ms.

See 41fa95bce4 and #1065 for some more
context and other exploratory work. The key thing to note is we _need_ a
separate version of FWFR for emitting to a ByteBuffer, as introducing
polymorphism to it comes with a significant performance hit.
This commit is contained in:
Jonathan Coates 2022-04-26 18:25:49 +01:00
parent 48c4f397f9
commit 1196568a7c
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
6 changed files with 441 additions and 36 deletions

View File

@ -9,7 +9,10 @@ import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.client.util.DirectBuffers;
import dan200.computercraft.client.util.DirectVertexBuffer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
@ -17,15 +20,16 @@ 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.renderer.*;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.util.math.vector.TransformationMatrix;
import net.minecraft.util.math.vector.Vector3f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
@ -44,9 +48,7 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
* the monitor frame and contents.
*/
private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1);
private static ByteBuffer tboContents;
private static final Matrix4f IDENTITY = TransformationMatrix.identity().getMatrix();
private static ByteBuffer backingBuffer;
public TileEntityMonitorRenderer( TileEntityRendererDispatcher rendererDispatcher )
{
@ -170,14 +172,7 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
if( redraw )
{
int size = width * height * 3;
if( tboContents == null || tboContents.capacity() < size )
{
tboContents = GLAllocation.createByteBuffer( size );
}
ByteBuffer monitorBuffer = tboContents;
monitorBuffer.clear();
ByteBuffer monitorBuffer = getBuffer( width * height * 3 );
for( int y = 0; y < height; y++ )
{
TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y );
@ -217,28 +212,42 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
case VBO:
{
VertexBuffer vbo = monitor.buffer;
DirectVertexBuffer vbo = monitor.buffer;
if( redraw )
{
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder builder = tessellator.getBuilder();
builder.begin( RenderTypes.TERMINAL_WITHOUT_DEPTH.mode(), RenderTypes.TERMINAL_WITHOUT_DEPTH.format() );
FixedWidthFontRenderer.drawTerminalWithoutCursor(
IDENTITY, builder, 0, 0,
terminal, !monitor.isColour(), yMargin, yMargin, xMargin, xMargin
);
int vertexSize = RenderTypes.TERMINAL_WITHOUT_DEPTH.format().getVertexSize();
ByteBuffer buffer = getBuffer( DirectFixedWidthFontRenderer.getVertexCount( terminal ) * vertexSize );
builder.end();
vbo.upload( builder );
// Draw the main terminal and store how many vertices it has.
DirectFixedWidthFontRenderer.drawTerminalWithoutCursor(
buffer, 0, 0, terminal, !monitor.isColour(), yMargin, yMargin, xMargin, xMargin
);
int termIndexes = buffer.position() / vertexSize;
buffer.flip();
vbo.upload( termIndexes, RenderTypes.TERMINAL_WITHOUT_DEPTH.format(), buffer );
}
vbo.bind();
RenderTypes.TERMINAL_WITHOUT_DEPTH.format().setupBufferState( 0L );
vbo.draw( matrix, RenderTypes.TERMINAL_WITHOUT_DEPTH.mode() );
VertexBuffer.unbind();
vbo.draw( matrix, vbo.getIndexCount() );
RenderTypes.TERMINAL_WITHOUT_DEPTH.format().clearBufferState();
break;
}
}
}
@Nonnull
private static ByteBuffer getBuffer( int capacity )
{
ByteBuffer buffer = backingBuffer;
if( buffer == null || buffer.capacity() < capacity )
{
buffer = backingBuffer = buffer == null ? DirectBuffers.createByteBuffer( capacity ) : DirectBuffers.resizeByteBuffer( buffer, capacity );
}
buffer.clear();
return buffer;
}
}

View File

@ -0,0 +1,231 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render.text;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import dan200.computercraft.client.util.DirectBuffers;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import org.lwjgl.system.MemoryUtil;
import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.*;
import static org.lwjgl.system.MemoryUtil.memPutByte;
import static org.lwjgl.system.MemoryUtil.memPutFloat;
/**
* An optimised copy of {@link FixedWidthFontRenderer} emitter emits directly to a {@link ByteBuffer} rather than
* emitting to {@link IVertexBuilder}. This allows us to emit vertices very quickly, when using the VBO renderer.
*
* There are some limitations here:
* <ul>
* <li>No transformation matrix (not needed for VBOs).</li>
* <li>Only works with {@link DefaultVertexFormats#POSITION_COLOR_TEX}.</li>
* <li>The buffer <strong>MUST</strong> be allocated with {@link DirectBuffers}, and not through any other means.</li>
* </ul>
*
* Note this is almost an exact copy of {@link FixedWidthFontRenderer}. While the code duplication is unfortunate,
* it is measurably faster than introducing polymorphism into {@link FixedWidthFontRenderer}.
*
* <strong>IMPORTANT: </strong> When making changes to this class, please check if you need to make the same changes to
* {@link FixedWidthFontRenderer}.
*/
public final class DirectFixedWidthFontRenderer
{
private DirectFixedWidthFontRenderer()
{
}
private static void drawChar( ByteBuffer buffer, float x, float y, int index, byte[] colour )
{
// Short circuit to avoid the common case - the texture should be blank here after all.
if( index == '\0' || index == ' ' ) return;
int column = index % 16;
int row = index / 16;
int xStart = 1 + column * (FONT_WIDTH + 2);
int yStart = 1 + row * (FONT_HEIGHT + 2);
quad(
buffer, x, y, x + FONT_WIDTH, y + FONT_HEIGHT, colour,
xStart / WIDTH, yStart / WIDTH, (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH
);
}
private static void drawQuad( ByteBuffer emitter, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex )
{
byte[] colour = palette.getByteColour( getColour( colourIndex, Colour.BLACK ), greyscale );
quad( emitter, x, y, x + width, y + height, colour, BACKGROUND_START, BACKGROUND_START, BACKGROUND_END, BACKGROUND_END );
}
private static void drawBackground(
@Nonnull ByteBuffer buffer, float x, float y, @Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale,
float leftMarginSize, float rightMarginSize, float height
)
{
if( leftMarginSize > 0 )
{
drawQuad( buffer, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ) );
}
if( rightMarginSize > 0 )
{
drawQuad( buffer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, greyscale, backgroundColour.charAt( backgroundColour.length() - 1 ) );
}
// Batch together runs of identical background cells.
int blockStart = 0;
char blockColour = '\0';
for( int i = 0; i < backgroundColour.length(); i++ )
{
char colourIndex = backgroundColour.charAt( i );
if( colourIndex == blockColour ) continue;
if( blockColour != '\0' )
{
drawQuad( buffer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour );
}
blockColour = colourIndex;
blockStart = i;
}
if( blockColour != '\0' )
{
drawQuad( buffer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour );
}
}
private static void drawString( @Nonnull ByteBuffer buffer, float x, float y, @Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nonnull Palette palette, boolean greyscale )
{
for( int i = 0; i < text.length(); i++ )
{
byte[] colour = palette.getByteColour( getColour( textColour.charAt( i ), Colour.BLACK ), greyscale );
int index = text.charAt( i );
if( index > 255 ) index = '?';
drawChar( buffer, x + i * FONT_WIDTH, y, index, colour );
}
}
public static void drawTerminalWithoutCursor(
@Nonnull ByteBuffer buffer, float x, float y, @Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{
Palette palette = terminal.getPalette();
int height = terminal.getHeight();
// Top and bottom margins
drawBackground(
buffer, x, y - topMarginSize, terminal.getBackgroundColourLine( 0 ), palette, greyscale,
leftMarginSize, rightMarginSize, topMarginSize
);
drawBackground(
buffer, x, y + height * FONT_HEIGHT, terminal.getBackgroundColourLine( height - 1 ), palette, greyscale,
leftMarginSize, rightMarginSize, bottomMarginSize
);
// The main text
for( int i = 0; i < height; i++ )
{
float rowY = y + FONT_HEIGHT * i;
drawBackground(
buffer, x, rowY, terminal.getBackgroundColourLine( i ), palette, greyscale,
leftMarginSize, rightMarginSize, FONT_HEIGHT
);
drawString(
buffer, x, rowY, terminal.getLine( i ), terminal.getTextColourLine( i ),
palette, greyscale
);
}
}
public static void drawCursor( @Nonnull ByteBuffer buffer, float x, float y, @Nonnull Terminal terminal, boolean greyscale )
{
if( isCursorVisible( terminal ) )
{
byte[] colour = terminal.getPalette().getByteColour( 15 - terminal.getTextColour(), greyscale );
drawChar( buffer, x + terminal.getCursorX() * FONT_WIDTH, y + terminal.getCursorY() * FONT_HEIGHT, '_', colour );
}
}
public static int getVertexCount( Terminal terminal )
{
return (1 + (terminal.getHeight() + 2) * terminal.getWidth() * 2) * 4;
}
private static void quad( ByteBuffer buffer, float x1, float y1, float x2, float y2, byte[] rgba, float u1, float v1, float u2, float v2 )
{
// Emit a single quad to our buffer. This uses Unsafe (well, LWJGL's MemoryUtil) to directly blit bytes to the
// underlying buffer. This allows us to have a single bounds check up-front, rather than one for every write.
// This provides significant performance gains, at the cost of well, using Unsafe.
// Each vertex is 24 bytes, giving 96 bytes in total. Vertices are of the form (xyz:FFF)(rgba:BBBB)(uv:FF),
// which matches the POSITION_COLOR_TEX vertex format.
int position = buffer.position();
long addr = MemoryUtil.memAddress( buffer );
// We're doing terrible unsafe hacks below, so let's be really sure that what we're doing is reasonable.
if( position < 0 || 96 > buffer.limit() - position ) throw new IndexOutOfBoundsException();
// Require the pointer to be aligned to a 32-bit boundary.
if( (addr & 3) != 0 ) throw new IllegalStateException( "Memory is not aligned" );
// Also assert the length of the array. This appears to help elide bounds checks on the array in some circumstances.
if( rgba.length != 4 ) throw new IllegalStateException();
memPutFloat( addr + 0, x1 );
memPutFloat( addr + 4, y1 );
memPutFloat( addr + 8, 0 );
memPutByte( addr + 12, rgba[0] );
memPutByte( addr + 13, rgba[1] );
memPutByte( addr + 14, rgba[2] );
memPutByte( addr + 15, (byte) 255 );
memPutFloat( addr + 16, u1 );
memPutFloat( addr + 20, v1 );
memPutFloat( addr + 24, x1 );
memPutFloat( addr + 28, y2 );
memPutFloat( addr + 32, 0 );
memPutByte( addr + 36, rgba[0] );
memPutByte( addr + 37, rgba[1] );
memPutByte( addr + 38, rgba[2] );
memPutByte( addr + 39, (byte) 255 );
memPutFloat( addr + 40, u1 );
memPutFloat( addr + 44, v2 );
memPutFloat( addr + 48, x2 );
memPutFloat( addr + 52, y2 );
memPutFloat( addr + 56, 0 );
memPutByte( addr + 60, rgba[0] );
memPutByte( addr + 61, rgba[1] );
memPutByte( addr + 62, rgba[2] );
memPutByte( addr + 63, (byte) 255 );
memPutFloat( addr + 64, u2 );
memPutFloat( addr + 68, v2 );
memPutFloat( addr + 72, x2 );
memPutFloat( addr + 76, y1 );
memPutFloat( addr + 80, 0 );
memPutByte( addr + 84, rgba[0] );
memPutByte( addr + 85, rgba[1] );
memPutByte( addr + 86, rgba[2] );
memPutByte( addr + 87, (byte) 255 );
memPutFloat( addr + 88, u2 );
memPutFloat( addr + 92, v1 );
// Finally increment the position.
buffer.position( position + 96 );
// Well done for getting to the end of this method. I recommend you take a break and go look at cute puppies.
}
}

View File

@ -7,6 +7,7 @@ package dan200.computercraft.client.render.text;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.util.Colour;
@ -30,9 +31,12 @@ import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMA
* </li>
* <li>{@link #drawTerminal}: Draw a terminal with a cursor. This is used by the various computer GUIs to render the
* whole term.</li>
* <li>{@link #drawBlocker}: When rendering a terminal using {@link dan200.computercraft.client.render.RenderTypes#TERMINAL_WITHOUT_DEPTH} you need to
* <li>{@link #drawBlocker}: When rendering a terminal using {@link RenderTypes#TERMINAL_WITHOUT_DEPTH} you need to
* render an additional "depth blocker" on top of the monitor.</li>
* </ul>
*
* <strong>IMPORTANT: </strong> When making changes to this class, please check if you need to make the same changes to
* {@link DirectFixedWidthFontRenderer}.
*/
public final class FixedWidthFontRenderer
{
@ -40,10 +44,10 @@ public final class FixedWidthFontRenderer
public static final int FONT_HEIGHT = 9;
public static final int FONT_WIDTH = 6;
public static final float WIDTH = 256.0f;
static final float WIDTH = 256.0f;
private static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH;
private static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH;
static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH;
static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH;
private static final byte[] BLACK = new byte[] { byteColour( Colour.BLACK.getR() ), byteColour( Colour.BLACK.getR() ), byteColour( Colour.BLACK.getR() ), (byte) 255 };

View File

@ -0,0 +1,83 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.util;
import com.mojang.blaze3d.platform.GlStateManager;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL15C;
import org.lwjgl.opengl.GL45C;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
/**
* Provides utilities to interact with OpenGL's buffer objects, either using direct state access or binding/unbinding
* it.
*/
public class DirectBuffers
{
public static final boolean HAS_DSA;
static
{
GLCapabilities capabilities = GL.getCapabilities();
HAS_DSA = capabilities.OpenGL45 || capabilities.GL_ARB_direct_state_access;
}
public static int createBuffer()
{
return HAS_DSA ? GL45C.glCreateBuffers() : GL15C.glGenBuffers();
}
public static void setBufferData( int type, int id, ByteBuffer buffer, int flags )
{
if( HAS_DSA )
{
GL45C.glNamedBufferData( id, buffer, flags );
}
else
{
GlStateManager._glBindBuffer( type, id );
GlStateManager._glBufferData( type, buffer, GL15C.GL_STATIC_DRAW );
GlStateManager._glBindBuffer( type, 0 );
}
}
public static void setEmptyBufferData( int type, int id, int flags )
{
if( HAS_DSA )
{
GL45C.glNamedBufferData( id, 0, flags );
}
else
{
GlStateManager._glBindBuffer( type, id );
GL45C.glBufferData( type, 0, GL15C.GL_STATIC_DRAW );
GlStateManager._glBindBuffer( type, 0 );
}
}
private static final MemoryUtil.MemoryAllocator ALLOCATOR = MemoryUtil.getAllocator( false );
public static ByteBuffer createByteBuffer( int size )
{
long i = ALLOCATOR.malloc( size );
if( i == 0L ) throw new OutOfMemoryError( "Failed to allocate " + size + " bytes" );
return MemoryUtil.memByteBuffer( i, size );
}
public static ByteBuffer resizeByteBuffer( ByteBuffer buffer, int size )
{
long i = ALLOCATOR.realloc( MemoryUtil.memAddress0( buffer ), size );
if( i == 0L )
{
throw new OutOfMemoryError( "Failed to resize buffer from " + buffer.capacity() + " bytes to " + size + " bytes" );
}
return MemoryUtil.memByteBuffer( i, size );
}
}

View File

@ -0,0 +1,79 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.util;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.math.vector.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
/**
* A version of {@link VertexBuffer} which allows uploading {@link ByteBuffer}s directly.
*/
public class DirectVertexBuffer implements AutoCloseable
{
private int vertextBufferId;
private int indexCount;
private VertexFormat format;
public DirectVertexBuffer()
{
vertextBufferId = DirectBuffers.createBuffer();
}
public void upload( int vertexCount, VertexFormat format, ByteBuffer buffer )
{
RenderSystem.assertThread( RenderSystem::isOnGameThread );
DirectBuffers.setBufferData( GL15.GL_ARRAY_BUFFER, vertextBufferId, buffer, GL15.GL_STATIC_DRAW );
this.format = format;
indexCount = vertexCount;
}
public void draw( Matrix4f matrix, int indexCount )
{
bind();
format.setupBufferState( 0 );
RenderSystem.pushMatrix();
RenderSystem.loadIdentity();
RenderSystem.multMatrix( matrix );
RenderSystem.drawArrays( GL11.GL_QUADS, 0, indexCount );
RenderSystem.popMatrix();
unbind();
}
public int getIndexCount()
{
return indexCount;
}
@Override
public void close()
{
if( vertextBufferId >= 0 )
{
RenderSystem.glDeleteBuffers( vertextBufferId );
vertextBufferId = -1;
}
}
private void bind()
{
RenderSystem.glBindBuffer( GL15.GL_ARRAY_BUFFER, () -> vertextBufferId );
}
private static void unbind()
{
RenderSystem.glBindBuffer( GL15.GL_ARRAY_BUFFER, () -> 0 );
}
}

View File

@ -7,9 +7,8 @@ package dan200.computercraft.shared.peripheral.monitor;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.util.DirectVertexBuffer;
import dan200.computercraft.shared.common.ClientTerminal;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@ -33,7 +32,7 @@ public final class ClientMonitor extends ClientTerminal
public int tboBuffer;
public int tboTexture;
public VertexBuffer buffer;
public DirectVertexBuffer buffer;
public ClientMonitor( boolean colour, TileMonitor origin )
{
@ -82,7 +81,7 @@ public final class ClientMonitor extends ClientTerminal
if( buffer != null ) return false;
deleteBuffers();
buffer = new VertexBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH.format() );
buffer = new DirectVertexBuffer();
addMonitor();
return true;