1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-07-08 13:02:54 +00:00

Cleanup and optimise terminal rendering (#1057)

- Remove the POSITION_COLOR render type. Instead we just render a
   background terminal quad as the pocket computer light - it's a little
   (lot?) more cheaty, but saves having to create a render type.

 - Use the existing position_color_tex shader instead of our copy. I
   looked at using RenderType.text, but had a bunch of problems with GUI
   terminals. Its possible we can fix it, but didn't want to spend too
   much time on it.

 - Remove some methods from FixedWidthFontRenderer, inlining them into
   the call site.

 - Switch back to using GL_QUADS rather than GL_TRIANGLES. I know Lig
   will shout at me for this, but the rest of MC uses QUADS, so I don't
   think best practice really matters here.

 - Fix the TBO backend monitor not rendering monitors with fog.
 
   Unfortunately we can't easily do this to the VBO one without writing
   a custom shader (which defeats the whole point of the VBO backend!),
   as the distance calculation of most render types expect an
   already-transformed position (camera-relative I think!) while we pass
   a world-relative one.

 - When rendering to a VBO we push vertices to a ByteBuffer directly,
   rather than going through MC's VertexConsumer system. This removes
   the overhead which comes with VertexConsumer, significantly improving
   performance.

 - Pre-convert palette colours to bytes, storing both the coloured and
   greyscale versions as a byte array. This allows us to remove the
   multiple casts and conversions (double -> float -> (greyscale) ->
   byte), offering noticeable performance improvements (multiple ms per
   frame).

   We're using a byte[] here rather than a record of three bytes as
   notionally it provides better performance when writing to a
   ByteBuffer directly compared to calling .put() four times. [^1]

 - Memorize getRenderBoundingBox. This was taking about 5% of the total
   time on the render thread[^2], so worth doing.

   I don't actually think the allocation is the heavy thing here -
   VisualVM says it's toWorldPos being slow. I'm not sure why - possibly
   just all the block property lookups? [^2]

Note that none of these changes improve compatibility with Optifine.
Right now there's some serious issues where monitors are writing _over_
blocks in front of them. To fix this, we probably need to remove the
depth blocker and just render characters with a z offset. Will do that
in a separate commit, as I need to evaluate how well that change will
work first.

The main advantage of this commit is the improved performance. In my 
stress test with 120 monitors updating every tick, I'm getting 10-20fps
[^3] (still much worse than TBOs, which manages a solid 60-100).

In practice, we'll actually be much better than this. Our network
bandwidth limits means only 40 change in a single tick - and so FPS is
much more reasonable (+60fps).

[^1]: In general, put(byte[]) is faster than put(byte) multiple times.
Just not clear if this is true when dealing with a small (and loop
unrolled) number of bytes.

[^2]: To be clear, this is with 120 monitors and no other block entities
with custom renderers. so not really representative.

[^3]: I wish I could provide a narrower range, but it varies so much
between me restarting the game. Makes it impossible to benchmark
anything!
This commit is contained in:
Jonathan Coates 2022-04-02 10:54:03 +01:00 committed by GitHub
parent ba7598c689
commit 41fa95bce4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 412 additions and 246 deletions

View File

@ -5,7 +5,7 @@
*/ */
package dan200.computercraft.client.gui; package dan200.computercraft.client.gui;
import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Matrix4f; import com.mojang.math.Matrix4f;
import dan200.computercraft.client.FrameInfo; import dan200.computercraft.client.FrameInfo;
@ -14,11 +14,11 @@ import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.util.Colour; import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette; import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP; import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
@ -44,15 +44,22 @@ public final class FixedWidthFontRenderer
public static final int FONT_HEIGHT = 9; public static final int FONT_HEIGHT = 9;
public static final int FONT_WIDTH = 6; public static final int FONT_WIDTH = 6;
public static final float WIDTH = 256.0f; private static final float WIDTH = 256.0f;
public static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH; private static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH;
public static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH; private 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 };
private FixedWidthFontRenderer() private FixedWidthFontRenderer()
{ {
} }
private static byte byteColour( float c )
{
return (byte) (int) (c * 255);
}
public static float toGreyscale( double[] rgb ) public static float toGreyscale( double[] rgb )
{ {
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3); return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
@ -63,7 +70,7 @@ public final class FixedWidthFontRenderer
return 15 - Terminal.getColour( c, def ); return 15 - Terminal.getColour( c, def );
} }
private static void drawChar( Matrix4f transform, VertexConsumer buffer, float x, float y, int index, float r, float g, float b, int light ) private static void drawChar( VertexEmitter emitter, float x, float y, int index, byte[] colour, int light )
{ {
// Short circuit to avoid the common case - the texture should be blank here after all. // Short circuit to avoid the common case - the texture should be blank here after all.
if( index == '\0' || index == ' ' ) return; if( index == '\0' || index == ' ' ) return;
@ -74,56 +81,40 @@ public final class FixedWidthFontRenderer
int xStart = 1 + column * (FONT_WIDTH + 2); int xStart = 1 + column * (FONT_WIDTH + 2);
int yStart = 1 + row * (FONT_HEIGHT + 2); int yStart = 1 + row * (FONT_HEIGHT + 2);
buffer.vertex( transform, x, y, 0f ).color( r, g, b, 1.0f ).uv( xStart / WIDTH, yStart / WIDTH ).uv2( light ).endVertex(); emitter.vertex( x, y, (float) 0, colour, xStart / WIDTH, yStart / WIDTH, light );
buffer.vertex( transform, x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).uv( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).uv2( light ).endVertex(); emitter.vertex( x, y + FONT_HEIGHT, (float) 0, colour, xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH, light );
buffer.vertex( transform, x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).uv( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).uv2( light ).endVertex(); emitter.vertex( x + FONT_WIDTH, y + FONT_HEIGHT, (float) 0, colour, (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH, light );
buffer.vertex( transform, x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).uv( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).uv2( light ).endVertex(); emitter.vertex( x + FONT_WIDTH, y, (float) 0, colour, (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH, light );
buffer.vertex( transform, x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).uv( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).uv2( light ).endVertex();
buffer.vertex( transform, x + FONT_WIDTH, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).uv( (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).uv2( light ).endVertex();
} }
private static void drawQuad( Matrix4f transform, VertexConsumer buffer, float x, float y, float width, float height, float r, float g, float b ) public static void drawQuad( VertexEmitter emitter, float x, float y, float z, float width, float height, byte[] colour, int light )
{ {
buffer.vertex( transform, x, y, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_START, BACKGROUND_START ).endVertex(); emitter.vertex( x, y, z, colour, BACKGROUND_START, BACKGROUND_START, light );
buffer.vertex( transform, x, y + height, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_START, BACKGROUND_END ).endVertex(); emitter.vertex( x, y + height, z, colour, BACKGROUND_START, BACKGROUND_END, light );
buffer.vertex( transform, x + width, y, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_END, BACKGROUND_START ).endVertex(); emitter.vertex( x + width, y + height, z, colour, BACKGROUND_END, BACKGROUND_END, light );
buffer.vertex( transform, x + width, y, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_END, BACKGROUND_START ).endVertex(); emitter.vertex( x + width, y, z, colour, BACKGROUND_END, BACKGROUND_START, light );
buffer.vertex( transform, x, y + height, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_START, BACKGROUND_END ).endVertex();
buffer.vertex( transform, x + width, y + height, 0 ).color( r, g, b, 1.0f ).uv( BACKGROUND_END, BACKGROUND_END ).endVertex();
} }
private static void drawQuad( Matrix4f transform, VertexConsumer buffer, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex ) private static void drawQuad( VertexEmitter emitter, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex, int light )
{ {
double[] colour = palette.getColour( getColour( colourIndex, Colour.BLACK ) ); var colour = palette.getByteColour( getColour( colourIndex, Colour.BLACK ), greyscale );
float r, g, b; drawQuad( emitter, x, y, 0, width, height, colour, light );
if( greyscale )
{
r = g = b = toGreyscale( colour );
}
else
{
r = (float) colour[0];
g = (float) colour[1];
b = (float) colour[2];
}
drawQuad( transform, buffer, x, y, width, height, r, g, b );
} }
private static void drawBackground( private static void drawBackground(
@Nonnull Matrix4f transform, @Nonnull VertexConsumer renderer, float x, float y, @Nonnull VertexEmitter emitter, float x, float y,
@Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale, @Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale,
float leftMarginSize, float rightMarginSize, float height float leftMarginSize, float rightMarginSize, float height, int light
) )
{ {
if( leftMarginSize > 0 ) if( leftMarginSize > 0 )
{ {
drawQuad( transform, renderer, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ) ); drawQuad( emitter, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ), light );
} }
if( rightMarginSize > 0 ) if( rightMarginSize > 0 )
{ {
drawQuad( transform, renderer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, greyscale, backgroundColour.charAt( backgroundColour.length() - 1 ) ); drawQuad( emitter, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, greyscale, backgroundColour.charAt( backgroundColour.length() - 1 ), light );
} }
// Batch together runs of identical background cells. // Batch together runs of identical background cells.
@ -136,7 +127,7 @@ public final class FixedWidthFontRenderer
if( blockColour != '\0' ) if( blockColour != '\0' )
{ {
drawQuad( transform, renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour ); drawQuad( emitter, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour, light );
} }
blockColour = colourIndex; blockColour = colourIndex;
@ -145,46 +136,35 @@ public final class FixedWidthFontRenderer
if( blockColour != '\0' ) if( blockColour != '\0' )
{ {
drawQuad( transform, renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour ); drawQuad( emitter, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour, light );
} }
} }
public static void drawString( public static void drawString(
@Nonnull Matrix4f transform, @Nonnull VertexConsumer renderer, float x, float y, @Nonnull VertexEmitter emitter, float x, float y,
@Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nullable TextBuffer backgroundColour, @Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nullable TextBuffer backgroundColour,
@Nonnull Palette palette, boolean greyscale, float leftMarginSize, float rightMarginSize, int light @Nonnull Palette palette, boolean greyscale, float leftMarginSize, float rightMarginSize, int light
) )
{ {
if( backgroundColour != null ) if( backgroundColour != null )
{ {
drawBackground( transform, renderer, x, y, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize, FONT_HEIGHT ); drawBackground( emitter, x, y, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize, FONT_HEIGHT, light );
} }
for( int i = 0; i < text.length(); i++ ) for( int i = 0; i < text.length(); i++ )
{ {
double[] colour = palette.getColour( getColour( textColour.charAt( i ), Colour.BLACK ) ); var colour = palette.getByteColour( getColour( textColour.charAt( i ), Colour.BLACK ), greyscale );
float r, g, b;
if( greyscale )
{
r = g = b = toGreyscale( colour );
}
else
{
r = (float) colour[0];
g = (float) colour[1];
b = (float) colour[2];
}
// Draw char // Draw char
int index = text.charAt( i ); int index = text.charAt( i );
if( index > 255 ) index = '?'; if( index > 255 ) index = '?';
drawChar( transform, renderer, x + i * FONT_WIDTH, y, index, r, g, b, light ); drawChar( emitter, x + i * FONT_WIDTH, y, index, colour, light );
} }
} }
public static void drawTerminalWithoutCursor( public static void drawTerminalWithoutCursor(
@Nonnull Matrix4f transform, @Nonnull VertexConsumer buffer, float x, float y, @Nonnull VertexEmitter emitter, float x, float y,
@Nonnull Terminal terminal, boolean greyscale, @Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
) )
@ -194,32 +174,29 @@ public final class FixedWidthFontRenderer
// Top and bottom margins // Top and bottom margins
drawBackground( drawBackground(
transform, buffer, x, y - topMarginSize, emitter, x, y - topMarginSize,
terminal.getBackgroundColourLine( 0 ), palette, greyscale, terminal.getBackgroundColourLine( 0 ), palette, greyscale,
leftMarginSize, rightMarginSize, topMarginSize leftMarginSize, rightMarginSize, topMarginSize, FULL_BRIGHT_LIGHTMAP
); );
drawBackground( drawBackground(
transform, buffer, x, y + height * FONT_HEIGHT, emitter, x, y + height * FONT_HEIGHT,
terminal.getBackgroundColourLine( height - 1 ), palette, greyscale, terminal.getBackgroundColourLine( height - 1 ), palette, greyscale,
leftMarginSize, rightMarginSize, bottomMarginSize leftMarginSize, rightMarginSize, bottomMarginSize, FULL_BRIGHT_LIGHTMAP
); );
// The main text // The main text
for( int i = 0; i < height; i++ ) for( int i = 0; i < height; i++ )
{ {
drawString( drawString(
transform, buffer, x, y + FixedWidthFontRenderer.FONT_HEIGHT * i, emitter, x, y + FixedWidthFontRenderer.FONT_HEIGHT * i,
terminal.getLine( i ), terminal.getTextColourLine( i ), terminal.getBackgroundColourLine( i ), terminal.getLine( i ), terminal.getTextColourLine( i ), terminal.getBackgroundColourLine( i ),
palette, greyscale, leftMarginSize, rightMarginSize, FULL_BRIGHT_LIGHTMAP palette, greyscale, leftMarginSize, rightMarginSize, FULL_BRIGHT_LIGHTMAP
); );
} }
} }
public static void drawCursor( public static void drawCursor( @Nonnull VertexEmitter emitter, float x, float y, @Nonnull Terminal terminal, boolean greyscale )
@Nonnull Matrix4f transform, @Nonnull VertexConsumer buffer, float x, float y,
@Nonnull Terminal terminal, boolean greyscale
)
{ {
Palette palette = terminal.getPalette(); Palette palette = terminal.getPalette();
int width = terminal.getWidth(); int width = terminal.getWidth();
@ -229,60 +206,100 @@ public final class FixedWidthFontRenderer
int cursorY = terminal.getCursorY(); int cursorY = terminal.getCursorY();
if( terminal.getCursorBlink() && cursorX >= 0 && cursorX < width && cursorY >= 0 && cursorY < height && FrameInfo.getGlobalCursorBlink() ) if( terminal.getCursorBlink() && cursorX >= 0 && cursorX < width && cursorY >= 0 && cursorY < height && FrameInfo.getGlobalCursorBlink() )
{ {
double[] colour = palette.getColour( 15 - terminal.getTextColour() ); var colour = palette.getByteColour( 15 - terminal.getTextColour(), greyscale );
float r, g, b; drawChar( emitter, x + cursorX * FONT_WIDTH, y + cursorY * FONT_HEIGHT, '_', colour, FULL_BRIGHT_LIGHTMAP );
if( greyscale )
{
r = g = b = toGreyscale( colour );
}
else
{
r = (float) colour[0];
g = (float) colour[1];
b = (float) colour[2];
}
drawChar( transform, buffer, x + cursorX * FONT_WIDTH, y + cursorY * FONT_HEIGHT, '_', r, g, b, FULL_BRIGHT_LIGHTMAP );
} }
} }
public static void drawTerminal( public static void drawTerminal(
@Nonnull Matrix4f transform, @Nonnull VertexConsumer buffer, float x, float y, @Nonnull VertexEmitter buffer, float x, float y,
@Nonnull Terminal terminal, boolean greyscale, @Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
) )
{ {
drawTerminalWithoutCursor( transform, buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize ); drawTerminalWithoutCursor( buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
drawCursor( transform, buffer, x, y, terminal, greyscale ); drawCursor( buffer, x, y, terminal, greyscale );
} }
public static void drawTerminal( public static void drawEmptyTerminal( @Nonnull VertexEmitter emitter, float x, float y, float width, float height )
@Nonnull Matrix4f transform, float x, float y, @Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{ {
MultiBufferSource.BufferSource renderer = MultiBufferSource.immediate( Tesselator.getInstance().getBuilder() ); drawQuad( emitter, x, y, 0, width, height, BLACK, FULL_BRIGHT_LIGHTMAP );
VertexConsumer buffer = renderer.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH );
drawTerminal( transform, buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
renderer.endBatch();
} }
public static void drawEmptyTerminal( @Nonnull Matrix4f transform, @Nonnull MultiBufferSource renderer, float x, float y, float width, float height ) public static void drawBlocker( @Nonnull VertexEmitter emitter, float x, float y, float width, float height )
{ {
Colour colour = Colour.BLACK; drawQuad( emitter, x, y, 0, width, height, BLACK, FULL_BRIGHT_LIGHTMAP );
drawQuad( transform, renderer.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH ), x, y, width, height, colour.getR(), colour.getG(), colour.getB() );
} }
public static void drawEmptyTerminal( @Nonnull Matrix4f transform, float x, float y, float width, float height ) public static int getVertexCount( Terminal terminal )
{ {
MultiBufferSource.BufferSource renderer = MultiBufferSource.immediate( Tesselator.getInstance().getBuilder() ); int height = terminal.getHeight();
drawEmptyTerminal( transform, renderer, x, y, width, height ); int count = 0;
renderer.endBatch();
for( int y = 0; y < height; y++ )
{
// We compress runs of adjacent characters, so we need to do that calculation here too :/.
int background = 2;
TextBuffer backgroundColour = terminal.getBackgroundColourLine( y );
char blockColour = '\0';
for( int x = 0; x < backgroundColour.length(); x++ )
{
char colourIndex = backgroundColour.charAt( x );
if( colourIndex == blockColour ) continue;
if( blockColour != '\0' ) background++;
blockColour = colourIndex;
}
if( blockColour != '\0' ) background++;
count += background;
if( y == 0 ) count += background;
if( y == height - 1 ) count += background;
// Thankfully the normal characters are much easier!
TextBuffer foreground = terminal.getLine( y );
for( int x = 0; x < foreground.length(); x++ )
{
char c = foreground.charAt( x );
if( c != '\0' && c != ' ' ) count++;
}
}
return count * 4;
} }
public static void drawBlocker( @Nonnull Matrix4f transform, @Nonnull MultiBufferSource renderer, float x, float y, float width, float height ) /**
* Emit a single vertex to some buffer.
*
* @see #toVertexConsumer(Matrix4f, VertexConsumer) Emits to a {@link VertexConsumer}.
* @see #toByteBuffer(ByteBuffer) Emits to a {@link ByteBuffer}.
*/
@FunctionalInterface
public interface VertexEmitter
{ {
Colour colour = Colour.BLACK; void vertex( float x, float y, float z, byte[] rgba, float u, float v, int light );
drawQuad( transform, renderer.getBuffer( RenderTypes.TERMINAL_BLOCKER ), x, y, width, height, colour.getR(), colour.getG(), colour.getB() ); }
public static VertexEmitter toVertexConsumer( Matrix4f matrix, VertexConsumer consumer )
{
return ( float x, float y, float z, byte[] rgba, float u, float v, int light ) ->
consumer.vertex( matrix, x, y, z ).color( rgba[0], rgba[1], rgba[2], rgba[3] ).uv( u, v ).uv2( light ).endVertex();
}
/**
* An optimised vertex emitter which bypasses {@link VertexConsumer}. This allows us to emit vertices very quickly,
* when using the VBO renderer with some limitations:
* <ul>
* <li>No transformation matrix (not needed for VBOs).</li>
* <li>Only works with {@link DefaultVertexFormat#POSITION_COLOR_TEX_LIGHTMAP}.</li>
* </ul>
*
* @param buffer The buffer to emit to. This must have space for at least {@link #getVertexCount(Terminal)} vertices.
* @return The emitter, ot be passed to the rendering functions.
*/
public static VertexEmitter toByteBuffer( ByteBuffer buffer )
{
return ( float x, float y, float z, byte[] rgba, float u, float v, int light ) -> buffer
.putFloat( x ).putFloat( y ).putFloat( z ).put( rgba ).putFloat( u ).putFloat( v );
} }
} }

View File

@ -6,14 +6,17 @@
package dan200.computercraft.client.gui.widgets; package dan200.computercraft.client.gui.widgets;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.math.Matrix4f; import com.mojang.math.Matrix4f;
import dan200.computercraft.client.gui.FixedWidthFontRenderer; import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.computer.core.ClientComputer; import dan200.computercraft.shared.computer.core.ClientComputer;
import net.minecraft.SharedConstants; import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.narration.NarrationElementOutput; import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.network.chat.TextComponent; import net.minecraft.network.chat.TextComponent;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
@ -315,14 +318,24 @@ public class WidgetTerminal extends AbstractWidget
if( !visible ) return; if( !visible ) return;
Matrix4f matrix = transform.last().pose(); Matrix4f matrix = transform.last().pose();
Terminal terminal = computer.getTerminal(); Terminal terminal = computer.getTerminal();
var bufferSource = MultiBufferSource.immediate( Tesselator.getInstance().getBuilder() );
var emitter = FixedWidthFontRenderer.toVertexConsumer( matrix, bufferSource.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH ) );
if( terminal != null ) if( terminal != null )
{ {
FixedWidthFontRenderer.drawTerminal( matrix, innerX, innerY, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN ); boolean greyscale = !computer.isColour();
FixedWidthFontRenderer.drawTerminal(
emitter,
(float) innerX, (float) innerY, terminal, greyscale, (float) MARGIN, (float) MARGIN, (float) MARGIN, (float) MARGIN
);
} }
else else
{ {
FixedWidthFontRenderer.drawEmptyTerminal( matrix, x, y, width, height ); FixedWidthFontRenderer.drawEmptyTerminal( emitter, (float) x, (float) y, (float) width, (float) height );
} }
bufferSource.endBatch();
} }
@Override @Override

View File

@ -54,7 +54,7 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
} }
@Override @Override
protected void renderItem( PoseStack transform, MultiBufferSource renderer, ItemStack stack, int light ) protected void renderItem( PoseStack transform, MultiBufferSource bufferSource, ItemStack stack, int light )
{ {
ClientComputer computer = ItemPocketComputer.createClientComputer( stack ); ClientComputer computer = ItemPocketComputer.createClientComputer( stack );
Terminal terminal = computer == null ? null : computer.getTerminal(); Terminal terminal = computer == null ? null : computer.getTerminal();
@ -91,24 +91,30 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
int frameColour = item.getColour( stack ); int frameColour = item.getColour( stack );
Matrix4f matrix = transform.last().pose(); Matrix4f matrix = transform.last().pose();
renderFrame( matrix, renderer, family, frameColour, light, width, height ); renderFrame( matrix, bufferSource, family, frameColour, light, width, height );
// Render the light // Render the light
int lightColour = ItemPocketComputer.getLightState( stack ); int lightColour = ItemPocketComputer.getLightState( stack );
if( lightColour == -1 ) lightColour = Colour.BLACK.getHex(); if( lightColour == -1 ) lightColour = Colour.BLACK.getHex();
renderLight( matrix, renderer, lightColour, width, height ); renderLight( matrix, bufferSource, lightColour, width, height );
if( computer != null && terminal != null ) if( computer != null && terminal != null )
{ {
FixedWidthFontRenderer.drawTerminal( FixedWidthFontRenderer.drawTerminal(
matrix, renderer.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH ), FixedWidthFontRenderer.toVertexConsumer( matrix, bufferSource.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH ) ),
MARGIN, MARGIN, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN MARGIN, MARGIN, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN
); );
FixedWidthFontRenderer.drawBlocker( transform.last().pose(), renderer, 0, 0, width, height ); FixedWidthFontRenderer.drawBlocker(
FixedWidthFontRenderer.toVertexConsumer( matrix, bufferSource.getBuffer( RenderTypes.TERMINAL_BLOCKER ) ),
0, 0, width, height
);
} }
else else
{ {
FixedWidthFontRenderer.drawEmptyTerminal( matrix, renderer, 0, 0, width, height ); FixedWidthFontRenderer.drawEmptyTerminal(
FixedWidthFontRenderer.toVertexConsumer( matrix, bufferSource.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH ) ),
0, 0, width, height
);
} }
transform.popPose(); transform.popPose();
@ -127,15 +133,16 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
private static void renderLight( Matrix4f transform, MultiBufferSource render, int colour, int width, int height ) private static void renderLight( Matrix4f transform, MultiBufferSource render, int colour, int width, int height )
{ {
float r = ((colour >>> 16) & 0xFF) / 255.0f; byte r = (byte) ((colour >>> 16) & 0xFF);
float g = ((colour >>> 8) & 0xFF) / 255.0f; byte g = (byte) ((colour >>> 8) & 0xFF);
float b = (colour & 0xFF) / 255.0f; byte b = (byte) (colour & 0xFF);
float z = 0.001f; var c = new byte[] { r, g, b, (byte) 255 };
VertexConsumer buffer = render.getBuffer( RenderTypes.POSITION_COLOR ); VertexConsumer buffer = render.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH );
buffer.vertex( transform, width - LIGHT_HEIGHT * 2, height + LIGHT_HEIGHT + BORDER / 2.0f, z ).color( r, g, b, 1.0f ).endVertex(); FixedWidthFontRenderer.drawQuad(
buffer.vertex( transform, width, height + LIGHT_HEIGHT + BORDER / 2.0f, z ).color( r, g, b, 1.0f ).endVertex(); FixedWidthFontRenderer.toVertexConsumer( transform, buffer ),
buffer.vertex( transform, width, height + BORDER / 2.0f, z ).color( r, g, b, 1.0f ).endVertex(); width - LIGHT_HEIGHT * 2, height + BORDER / 2.0f, 0.001f, LIGHT_HEIGHT * 2, LIGHT_HEIGHT,
buffer.vertex( transform, width - LIGHT_HEIGHT * 2, height + BORDER / 2.0f, z ).color( r, g, b, 1.0f ).endVertex(); c, RenderTypes.FULL_BRIGHT_LIGHTMAP
);
} }
} }

View File

@ -54,12 +54,13 @@ public final class PrintoutRenderer
private PrintoutRenderer() {} private PrintoutRenderer() {}
public static void drawText( Matrix4f transform, MultiBufferSource renderer, int x, int y, int start, int light, TextBuffer[] text, TextBuffer[] colours ) public static void drawText( Matrix4f transform, MultiBufferSource bufferSource, int x, int y, int start, int light, TextBuffer[] text, TextBuffer[] colours )
{ {
VertexConsumer buffer = renderer.getBuffer( RenderTypes.PRINTOUT_TEXT ); var buffer = bufferSource.getBuffer( RenderTypes.PRINTOUT_TEXT );
var emitter = FixedWidthFontRenderer.toVertexConsumer( transform, buffer );
for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ ) for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ )
{ {
FixedWidthFontRenderer.drawString( transform, buffer, FixedWidthFontRenderer.drawString( emitter,
x, y + line * FONT_HEIGHT, text[start + line], colours[start + line], null, Palette.DEFAULT, x, y + line * FONT_HEIGHT, text[start + line], colours[start + line], null, Palette.DEFAULT,
false, 0, 0, false, 0, 0,
light light
@ -67,12 +68,13 @@ public final class PrintoutRenderer
} }
} }
public static void drawText( Matrix4f transform, MultiBufferSource renderer, int x, int y, int start, int light, String[] text, String[] colours ) public static void drawText( Matrix4f transform, MultiBufferSource bufferSource, int x, int y, int start, int light, String[] text, String[] colours )
{ {
VertexConsumer buffer = renderer.getBuffer( RenderTypes.PRINTOUT_TEXT ); var buffer = bufferSource.getBuffer( RenderTypes.PRINTOUT_TEXT );
var emitter = FixedWidthFontRenderer.toVertexConsumer( transform, buffer );
for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ ) for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ )
{ {
FixedWidthFontRenderer.drawString( transform, buffer, FixedWidthFontRenderer.drawString( emitter,
x, y + line * FONT_HEIGHT, x, y + line * FONT_HEIGHT,
new TextBuffer( text[start + line] ), new TextBuffer( colours[start + line] ), new TextBuffer( text[start + line] ), new TextBuffer( colours[start + line] ),
null, Palette.DEFAULT, false, 0, 0, null, Palette.DEFAULT, false, 0, 0,
@ -81,12 +83,12 @@ public final class PrintoutRenderer
} }
} }
public static void drawBorder( Matrix4f transform, MultiBufferSource renderer, float x, float y, float z, int page, int pages, boolean isBook, int light ) public static void drawBorder( Matrix4f transform, MultiBufferSource bufferSource, float x, float y, float z, int page, int pages, boolean isBook, int light )
{ {
int leftPages = page; int leftPages = page;
int rightPages = pages - page - 1; int rightPages = pages - page - 1;
VertexConsumer buffer = renderer.getBuffer( RenderTypes.PRINTOUT_BACKGROUND ); VertexConsumer buffer = bufferSource.getBuffer( RenderTypes.PRINTOUT_BACKGROUND );
if( isBook ) if( isBook )
{ {

View File

@ -9,6 +9,7 @@ import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.blaze3d.vertex.VertexFormat;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.FixedWidthFontRenderer; import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.RenderStateShard; import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.ShaderInstance; import net.minecraft.client.renderer.ShaderInstance;
@ -27,24 +28,45 @@ public class RenderTypes
public static final int FULL_BRIGHT_LIGHTMAP = (0xF << 4) | (0xF << 20); public static final int FULL_BRIGHT_LIGHTMAP = (0xF << 4) | (0xF << 20);
private static MonitorTextureBufferShader monitorTboShader; private static MonitorTextureBufferShader monitorTboShader;
private static ShaderInstance terminalShader;
public static final RenderType TERMINAL_WITHOUT_DEPTH = Types.TERMINAL_WITHOUT_DEPTH;
public static final RenderType TERMINAL_BLOCKER = Types.TERMINAL_BLOCKER;
public static final RenderType TERMINAL_WITH_DEPTH = Types.TERMINAL_WITH_DEPTH;
public static final RenderType MONITOR_TBO = Types.MONITOR_TBO;
public static final RenderType PRINTOUT_TEXT = Types.PRINTOUT_TEXT;
/** /**
* This looks wrong (it should be POSITION_COLOR_TEX_LIGHTMAP surely!) but the fragment/vertex shader for that * Renders a fullbright terminal without writing to the depth layer. This is used in combination with
* appear to entirely ignore the lightmap. * {@link #TERMINAL_BLOCKER} to ensure we can render a terminal without z-fighting.
*/
public static final RenderType TERMINAL_WITHOUT_DEPTH = Types.TERMINAL_WITHOUT_DEPTH;
/**
* A transparent texture which only writes to the depth layer.
*/
public static final RenderType TERMINAL_BLOCKER = Types.TERMINAL_BLOCKER;
/**
* Renders a fullbright terminal which also writes to the depth layer. This is used when z-fighting isn't an issue -
* for instance rendering an empty terminal or inside a GUI.
* *
* Note that vanilla maps do the same, so this isn't unreasonable. * This is identical to <em>vanilla's</em> {@link RenderType#text}. Forge overrides one with a definition which sets
* sortOnUpload to true, which is entirely broken!
*/
public static final RenderType TERMINAL_WITH_DEPTH = Types.TERMINAL_WITH_DEPTH;
/**
* Renders a monitor with the TBO shader.
*
* @see MonitorTextureBufferShader
*/
public static final RenderType MONITOR_TBO = Types.MONITOR_TBO;
/**
* A variant of {@link #TERMINAL_WITH_DEPTH} which uses the lightmap rather than rendering fullbright.
*/
public static final RenderType PRINTOUT_TEXT = RenderType.text( FixedWidthFontRenderer.FONT );
/**
* Printout's background texture. {@link RenderType#text(ResourceLocation)} is a <em>little</em> questionable, but
* it is what maps use, so should behave the same as vanilla in both item frames and in-hand.
*/ */
public static final RenderType PRINTOUT_BACKGROUND = RenderType.text( new ResourceLocation( "computercraft", "textures/gui/printout.png" ) ); public static final RenderType PRINTOUT_BACKGROUND = RenderType.text( new ResourceLocation( "computercraft", "textures/gui/printout.png" ) );
public static final RenderType POSITION_COLOR = Types.POSITION_COLOR;
@Nonnull @Nonnull
static MonitorTextureBufferShader getMonitorTextureBufferShader() static MonitorTextureBufferShader getMonitorTextureBufferShader()
{ {
@ -55,8 +77,7 @@ public class RenderTypes
@Nonnull @Nonnull
static ShaderInstance getTerminalShader() static ShaderInstance getTerminalShader()
{ {
if( terminalShader == null ) throw new NullPointerException( "MonitorTboShader has not been registered" ); return GameRenderer.getPositionColorTexShader();
return terminalShader;
} }
@SubscribeEvent @SubscribeEvent
@ -70,15 +91,6 @@ public class RenderTypes
), ),
x -> monitorTboShader = (MonitorTextureBufferShader) x x -> monitorTboShader = (MonitorTextureBufferShader) x
); );
event.registerShader(
new ShaderInstance(
event.getResourceManager(),
new ResourceLocation( ComputerCraft.MOD_ID, "terminal" ),
TERMINAL_WITHOUT_DEPTH.format()
),
x -> terminalShader = x
);
} }
private static final class Types extends RenderStateShard private static final class Types extends RenderStateShard
@ -88,7 +100,6 @@ public class RenderTypes
false, false // blur, minimap false, false // blur, minimap
); );
private static final VertexFormat TERM_FORMAT = DefaultVertexFormat.POSITION_COLOR_TEX; private static final VertexFormat TERM_FORMAT = DefaultVertexFormat.POSITION_COLOR_TEX;
private static final VertexFormat.Mode TERM_MODE = VertexFormat.Mode.TRIANGLES;
private static final ShaderStateShard TERM_SHADER = new ShaderStateShard( RenderTypes::getTerminalShader ); private static final ShaderStateShard TERM_SHADER = new ShaderStateShard( RenderTypes::getTerminalShader );
static final RenderType MONITOR_TBO = RenderType.create( static final RenderType MONITOR_TBO = RenderType.create(
@ -102,52 +113,32 @@ public class RenderTypes
); );
static final RenderType TERMINAL_WITHOUT_DEPTH = RenderType.create( static final RenderType TERMINAL_WITHOUT_DEPTH = RenderType.create(
"terminal_without_depth", TERM_FORMAT, TERM_MODE, 1024, "terminal_without_depth", TERM_FORMAT, VertexFormat.Mode.QUADS, 1024,
false, false, // useDelegate, needsSorting false, false, // useDelegate, needsSorting
RenderType.CompositeState.builder() RenderType.CompositeState.builder()
.setTextureState( TERM_FONT_TEXTURE ) .setTextureState( TERM_FONT_TEXTURE )
.setShaderState( TERM_SHADER ) .setShaderState( TERM_SHADER )
.setLightmapState( LIGHTMAP )
.setWriteMaskState( COLOR_WRITE ) .setWriteMaskState( COLOR_WRITE )
.createCompositeState( false ) .createCompositeState( false )
); );
static final RenderType TERMINAL_BLOCKER = RenderType.create( static final RenderType TERMINAL_BLOCKER = RenderType.create(
"terminal_blocker", TERM_FORMAT, TERM_MODE, 256, "terminal_blocker", DefaultVertexFormat.POSITION, VertexFormat.Mode.QUADS, 256,
false, false, // useDelegate, needsSorting false, false, // useDelegate, needsSorting
RenderType.CompositeState.builder() RenderType.CompositeState.builder()
.setTextureState( TERM_FONT_TEXTURE ) .setShaderState( POSITION_SHADER )
.setShaderState( TERM_SHADER )
.setWriteMaskState( DEPTH_WRITE ) .setWriteMaskState( DEPTH_WRITE )
.createCompositeState( false ) .createCompositeState( false )
); );
static final RenderType TERMINAL_WITH_DEPTH = RenderType.create( static final RenderType TERMINAL_WITH_DEPTH = RenderType.create(
"terminal_with_depth", TERM_FORMAT, TERM_MODE, 1024, "terminal_with_depth", TERM_FORMAT, VertexFormat.Mode.QUADS, 1024,
false, false, // useDelegate, needsSorting false, false, // useDelegate, needsSorting
RenderType.CompositeState.builder() RenderType.CompositeState.builder()
.setTextureState( TERM_FONT_TEXTURE ) .setTextureState( TERM_FONT_TEXTURE )
.setShaderState( TERM_SHADER ) .setShaderState( TERM_SHADER )
.createCompositeState( false ) .setLightmapState( LIGHTMAP )
);
/**
* A variant of {@link #TERMINAL_WITH_DEPTH} which uses the lightmap rather than rendering fullbright.
*/
static final RenderType PRINTOUT_TEXT = RenderType.create(
"printout_text", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, TERM_MODE, 1024,
false, false, // useDelegate, needsSorting
RenderType.CompositeState.builder()
.setTextureState( TERM_FONT_TEXTURE )
.setShaderState( RenderStateShard.RENDERTYPE_TEXT_SHADER )
.setLightmapState( RenderStateShard.LIGHTMAP )
.createCompositeState( false )
);
static final RenderType POSITION_COLOR = RenderType.create(
"position_color", DefaultVertexFormat.POSITION_COLOR, VertexFormat.Mode.QUADS, 128,
false, false, // useDelegate, needsSorting
RenderType.CompositeState.builder()
.setShaderState( POSITION_COLOR_SHADER )
.createCompositeState( false ) .createCompositeState( false )
); );

View File

@ -8,9 +8,9 @@ package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.platform.MemoryTracker; import com.mojang.blaze3d.platform.MemoryTracker;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Matrix4f; import com.mojang.math.Matrix4f;
import com.mojang.math.Transformation;
import com.mojang.math.Vector3f; import com.mojang.math.Vector3f;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.FrameInfo; import dan200.computercraft.client.FrameInfo;
@ -44,16 +44,14 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
* the monitor frame and contents. * the monitor frame and contents.
*/ */
private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1); private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1);
private static ByteBuffer tboContents; private static ByteBuffer backingBuffer;
private static final Matrix4f IDENTITY = Transformation.identity().getMatrix();
public TileEntityMonitorRenderer( BlockEntityRendererProvider.Context context ) public TileEntityMonitorRenderer( BlockEntityRendererProvider.Context context )
{ {
} }
@Override @Override
public void render( @Nonnull TileMonitor monitor, float partialTicks, @Nonnull PoseStack transform, @Nonnull MultiBufferSource renderer, int lightmapCoord, int overlayLight ) public void render( @Nonnull TileMonitor monitor, float partialTicks, @Nonnull PoseStack transform, @Nonnull MultiBufferSource bufferSource, int lightmapCoord, int overlayLight )
{ {
// Render from the origin monitor // Render from the origin monitor
ClientMonitor originTerminal = monitor.getClientMonitor(); ClientMonitor originTerminal = monitor.getClientMonitor();
@ -114,31 +112,31 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
Matrix4f matrix = transform.last().pose(); Matrix4f matrix = transform.last().pose();
renderTerminal( renderer, matrix, originTerminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale) ); renderTerminal( bufferSource, matrix, originTerminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale) );
// We don't draw the cursor with the VBO, as it's dynamic and so we'll end up refreshing far more than is // We don't draw the cursor with the VBO/TBO, as it's dynamic and so we'll end up refreshing far more than
// reasonable. // is reasonable.
FixedWidthFontRenderer.drawCursor( FixedWidthFontRenderer.drawCursor(
matrix, renderer.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH ), FixedWidthFontRenderer.toVertexConsumer( matrix, bufferSource.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH ) ),
0, 0, terminal, !originTerminal.isColour() 0, 0, terminal, !originTerminal.isColour()
); );
transform.popPose(); transform.popPose();
FixedWidthFontRenderer.drawBlocker( FixedWidthFontRenderer.drawBlocker(
transform.last().pose(), renderer, FixedWidthFontRenderer.toVertexConsumer( transform.last().pose(), bufferSource.getBuffer( RenderTypes.TERMINAL_BLOCKER ) ),
-MARGIN, MARGIN, -MARGIN, MARGIN,
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2) (float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
); );
// Force a flush of the blocker. WorldRenderer.updateCameraAndRender will "finish" all the built-in // Force a flush of the blocker. WorldRenderer.updateCameraAndRender will "finish" all the built-in
// buffers before calling renderer.finish, which means the blocker isn't actually rendered at that point! // buffers before calling renderer.finish, which means the blocker isn't actually rendered at that point!
renderer.getBuffer( RenderType.solid() ); bufferSource.getBuffer( RenderType.solid() );
} }
else else
{ {
FixedWidthFontRenderer.drawEmptyTerminal( FixedWidthFontRenderer.drawEmptyTerminal(
transform.last().pose(), renderer, FixedWidthFontRenderer.toVertexConsumer( transform.last().pose(), bufferSource.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH ) ),
-MARGIN, MARGIN, -MARGIN, MARGIN,
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2) (float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
); );
@ -147,7 +145,7 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
transform.popPose(); transform.popPose();
} }
private static void renderTerminal( @Nonnull MultiBufferSource renderer, Matrix4f matrix, ClientMonitor monitor, float xMargin, float yMargin ) private static void renderTerminal( @Nonnull MultiBufferSource bufferSource, Matrix4f matrix, ClientMonitor monitor, float xMargin, float yMargin )
{ {
Terminal terminal = monitor.getTerminal(); Terminal terminal = monitor.getTerminal();
@ -164,14 +162,7 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT; int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
if( redraw ) if( redraw )
{ {
int size = width * height * 3; ByteBuffer monitorBuffer = getBuffer( width * height * 3 );
if( tboContents == null || tboContents.capacity() < size )
{
tboContents = MemoryTracker.create( size );
}
ByteBuffer monitorBuffer = tboContents;
monitorBuffer.clear();
for( int y = 0; y < height; y++ ) for( int y = 0; y < height; y++ )
{ {
TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y ); TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y );
@ -198,7 +189,7 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
MonitorTextureBufferShader shader = RenderTypes.getMonitorTextureBufferShader(); MonitorTextureBufferShader shader = RenderTypes.getMonitorTextureBufferShader();
shader.setupUniform( width, height, terminal.getPalette(), !monitor.isColour() ); shader.setupUniform( width, height, terminal.getPalette(), !monitor.isColour() );
VertexConsumer buffer = renderer.getBuffer( RenderTypes.MONITOR_TBO ); VertexConsumer buffer = bufferSource.getBuffer( RenderTypes.MONITOR_TBO );
tboVertex( buffer, matrix, -xMargin, -yMargin ); tboVertex( buffer, matrix, -xMargin, -yMargin );
tboVertex( buffer, matrix, -xMargin, pixelHeight + yMargin ); tboVertex( buffer, matrix, -xMargin, pixelHeight + yMargin );
tboVertex( buffer, matrix, pixelWidth + xMargin, -yMargin ); tboVertex( buffer, matrix, pixelWidth + xMargin, -yMargin );
@ -206,28 +197,27 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
// And force things to flush. We strictly speaking do this later on anyway for the cursor, but nice to // And force things to flush. We strictly speaking do this later on anyway for the cursor, but nice to
// be consistent. // be consistent.
renderer.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH ); bufferSource.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH );
break; break;
} }
case VBO: case VBO:
{ {
VertexBuffer vbo = monitor.buffer; var vbo = monitor.buffer;
if( redraw ) if( redraw )
{ {
Tesselator tessellator = Tesselator.getInstance(); int vertexCount = FixedWidthFontRenderer.getVertexCount( terminal );
BufferBuilder builder = tessellator.getBuilder(); ByteBuffer buffer = getBuffer( vertexCount * RenderTypes.TERMINAL_WITHOUT_DEPTH.format().getVertexSize() );
builder.begin( RenderTypes.TERMINAL_WITHOUT_DEPTH.mode(), RenderTypes.TERMINAL_WITHOUT_DEPTH.format() );
FixedWidthFontRenderer.drawTerminalWithoutCursor( FixedWidthFontRenderer.drawTerminalWithoutCursor(
IDENTITY, builder, 0, 0, FixedWidthFontRenderer.toByteBuffer( buffer ), 0, 0,
terminal, !monitor.isColour(), yMargin, yMargin, xMargin, xMargin terminal, !monitor.isColour(), yMargin, yMargin, xMargin, xMargin
); );
buffer.flip();
builder.end(); vbo.upload( vertexCount, RenderTypes.TERMINAL_WITHOUT_DEPTH.mode(), RenderTypes.TERMINAL_WITHOUT_DEPTH.format(), buffer );
vbo.upload( builder );
} }
renderer.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH ); bufferSource.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH );
RenderTypes.TERMINAL_WITHOUT_DEPTH.setupRenderState(); RenderTypes.TERMINAL_WITHOUT_DEPTH.setupRenderState();
vbo.drawWithShader( matrix, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader() ); vbo.drawWithShader( matrix, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader() );
break; break;
@ -241,6 +231,20 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
builder.vertex( matrix, x, y, 0 ).uv( x, y ).endVertex(); builder.vertex( matrix, x, y, 0 ).uv( x, y ).endVertex();
} }
@Nonnull
private static ByteBuffer getBuffer( int capacity )
{
ByteBuffer buffer = backingBuffer;
if( buffer == null || buffer.capacity() < capacity )
{
buffer = backingBuffer = MemoryTracker.create( capacity );
}
buffer.clear();
return buffer;
}
@Override @Override
public int getViewDistance() public int getViewDistance()
{ {

View File

@ -0,0 +1,64 @@
/*
* 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 com.mojang.blaze3d.vertex.BufferUploader;
import com.mojang.blaze3d.vertex.VertexBuffer;
import com.mojang.blaze3d.vertex.VertexFormat;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL45C;
import java.nio.ByteBuffer;
/**
* A version of {@link VertexBuffer} which allows uploading {@link ByteBuffer}s directly.
*
* This should probably be its own class (rather than subclassing), but I need access to {@link VertexBuffer#drawWithShader}.
*/
public class DirectVertexBuffer extends VertexBuffer
{
private static final boolean HAS_DSA;
static
{
var capabilities = GL.getCapabilities();
HAS_DSA = capabilities.OpenGL45 || capabilities.GL_ARB_direct_state_access;
}
public DirectVertexBuffer()
{
if( HAS_DSA )
{
RenderSystem.glDeleteBuffers( vertextBufferId );
vertextBufferId = GL45C.glCreateBuffers();
}
}
public void upload( int vertexCount, VertexFormat.Mode mode, VertexFormat format, ByteBuffer buffer )
{
RenderSystem.assertOnRenderThread();
if( HAS_DSA )
{
GL45C.glNamedBufferData( vertextBufferId, buffer, GL15.GL_STATIC_DRAW );
}
else
{
BufferUploader.reset();
bind();
RenderSystem.glBufferData( GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW );
unbind();
}
this.format = format;
this.mode = mode;
indexCount = mode.indexCount( vertexCount );
indexType = VertexFormat.IndexType.SHORT;
sequentialIndices = true;
}
}

View File

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

View File

@ -70,6 +70,11 @@ public class TileMonitor extends TileGeneric
private int xIndex = 0; private int xIndex = 0;
private int yIndex = 0; private int yIndex = 0;
private BlockPos bbPos;
private BlockState bbState;
private int bbX, bbY, bbWidth, bbHeight;
private AABB boundingBox;
public TileMonitor( BlockEntityType<? extends TileMonitor> type, BlockPos pos, BlockState state, boolean advanced ) public TileMonitor( BlockEntityType<? extends TileMonitor> type, BlockPos pos, BlockState state, boolean advanced )
{ {
super( type, pos, state ); super( type, pos, state );
@ -620,9 +625,25 @@ public class TileMonitor extends TileGeneric
@Override @Override
public AABB getRenderBoundingBox() public AABB getRenderBoundingBox()
{ {
// We attempt to cache the bounding box to save having to do property lookups (and allocations!) on every frame.
// Unfortunately the AABB does depend on quite a lot of state, so we need to add a bunch of extra fields -
// ideally these'd be a single object, but I don't think worth doing until Java has value types.
if( boundingBox != null && getBlockState().equals( bbState ) && getBlockPos().equals( bbPos ) &&
xIndex == bbX && yIndex == bbY && width == bbWidth && height == bbHeight )
{
return boundingBox;
}
bbState = getBlockState();
bbPos = getBlockPos();
bbX = xIndex;
bbY = yIndex;
bbWidth = width;
bbHeight = height;
BlockPos startPos = toWorldPos( 0, 0 ); BlockPos startPos = toWorldPos( 0, 0 );
BlockPos endPos = toWorldPos( width, height ); BlockPos endPos = toWorldPos( width, height );
return new AABB( return boundingBox = new AABB(
Math.min( startPos.getX(), endPos.getX() ), Math.min( startPos.getX(), endPos.getX() ),
Math.min( startPos.getY(), endPos.getY() ), Math.min( startPos.getY(), endPos.getY() ),
Math.min( startPos.getZ(), endPos.getZ() ), Math.min( startPos.getZ(), endPos.getZ() ),

View File

@ -5,13 +5,18 @@
*/ */
package dan200.computercraft.shared.util; package dan200.computercraft.shared.util;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import javax.annotation.Nonnull;
public class Palette public class Palette
{ {
private static final int PALETTE_SIZE = 16; private static final int PALETTE_SIZE = 16;
private final double[][] colours = new double[PALETTE_SIZE][3]; private final double[][] colours = new double[PALETTE_SIZE][3];
private final byte[][] byteColours = new byte[PALETTE_SIZE][4];
private final byte[][] greyByteColours = new byte[PALETTE_SIZE][4];
public static final Palette DEFAULT = new Palette(); public static final Palette DEFAULT = new Palette();
@ -19,16 +24,23 @@ public class Palette
{ {
// Get the default palette // Get the default palette
resetColours(); resetColours();
for( int i = 0; i < PALETTE_SIZE; i++ ) byteColours[i][3] = greyByteColours[i][3] = (byte) 255;
} }
public void setColour( int i, double r, double g, double b ) public void setColour( int i, double r, double g, double b )
{ {
if( i >= 0 && i < colours.length ) if( i < 0 || i >= colours.length ) return;
{ colours[i][0] = r;
colours[i][0] = r; colours[i][1] = g;
colours[i][1] = g; colours[i][2] = b;
colours[i][2] = b;
} byteColours[i][0] = (byte) (int) (r * 255);
byteColours[i][1] = (byte) (int) (g * 255);
byteColours[i][2] = (byte) (int) (b * 255);
byte grey = (byte) (int) ((r + g + b) / 3 * 255);
greyByteColours[i][0] = greyByteColours[i][1] = greyByteColours[i][2] = grey;
} }
public void setColour( int i, Colour colour ) public void setColour( int i, Colour colour )
@ -38,19 +50,29 @@ public class Palette
public double[] getColour( int i ) public double[] getColour( int i )
{ {
if( i >= 0 && i < colours.length ) return i >= 0 && i < colours.length ? colours[i] : null;
{ }
return colours[i];
} /**
return null; * Get the colour as a set of bytes rather than floats. This is called frequently by {@link FixedWidthFontRenderer},
* as our vertex format uses bytes.
*
* This allows us to do the conversion once (when setting the colour) rather than for every vertex, at the cost of
* some memory overhead.
*
* @param i The colour index.
* @param greyscale Whether this number should be converted to greyscale.
* @return The number as a tuple of bytes.
*/
@Nonnull
public byte[] getByteColour( int i, boolean greyscale )
{
return greyscale ? greyByteColours[i] : byteColours[i];
} }
public void resetColour( int i ) public void resetColour( int i )
{ {
if( i >= 0 && i < colours.length ) if( i >= 0 && i < colours.length ) setColour( i, Colour.VALUES[i] );
{
setColour( i, Colour.VALUES[i] );
}
} }
public void resetColours() public void resetColours()
@ -89,9 +111,12 @@ public class Palette
public void read( FriendlyByteBuf buffer ) public void read( FriendlyByteBuf buffer )
{ {
for( double[] colour : colours ) for( int i = 0; i < PALETTE_SIZE; i++ )
{ {
for( int i = 0; i < colour.length; i++ ) colour[i] = (buffer.readByte() & 0xFF) / 255.0; double r = (buffer.readByte() & 0xFF) / 255.0;
double g = (buffer.readByte() & 0xFF) / 255.0;
double b = (buffer.readByte() & 0xFF) / 255.0;
setColour( i, r, g, b );
} }
} }
@ -117,7 +142,8 @@ public class Palette
for( int i = 0; i < colours.length; i++ ) for( int i = 0; i < colours.length; i++ )
{ {
colours[i] = decodeRGB8( rgb8[i] ); var colours = decodeRGB8( rgb8[i] );
setColour( i, colours[0], colours[1], colours[2] );
} }
} }
} }

View File

@ -11,3 +11,11 @@ public net.minecraft.client.Minecraft f_91080_ # screen
# SpeakerInstance/SpeakerManager # SpeakerInstance/SpeakerManager
public com.mojang.blaze3d.audio.Channel m_83652_(I)V # pumpBuffers public com.mojang.blaze3d.audio.Channel m_83652_(I)V # pumpBuffers
public net.minecraft.client.sounds.SoundEngine f_120223_ # executor public net.minecraft.client.sounds.SoundEngine f_120223_ # executor
# DirectVertexBuffer
protected com.mojang.blaze3d.vertex.VertexBuffer f_166859_ # vertextBufferId
protected com.mojang.blaze3d.vertex.VertexBuffer f_166861_ # indexType
protected com.mojang.blaze3d.vertex.VertexBuffer f_166863_ # indexCount
protected com.mojang.blaze3d.vertex.VertexBuffer f_166864_ # mode
protected com.mojang.blaze3d.vertex.VertexBuffer f_166865_ # sequentialIndices
protected com.mojang.blaze3d.vertex.VertexBuffer f_85917_ # format

View File

@ -1,5 +1,7 @@
#version 150 #version 150
#moj_import <fog.glsl>
#define FONT_WIDTH 6.0 #define FONT_WIDTH 6.0
#define FONT_HEIGHT 9.0 #define FONT_HEIGHT 9.0
@ -9,9 +11,15 @@ uniform int Height;
uniform usamplerBuffer Tbo; uniform usamplerBuffer Tbo;
uniform vec3 Palette[16]; uniform vec3 Palette[16];
in vec2 f_pos; uniform vec4 ColorModulator;
uniform float FogStart;
uniform float FogEnd;
uniform vec4 FogColor;
out vec4 colour; in float vertexDistance;
in vec2 fontPos;
out vec4 fragColor;
vec2 texture_corner(int index) { vec2 texture_corner(int index) {
float x = 1.0 + float(index % 16) * (FONT_WIDTH + 2.0); float x = 1.0 + float(index % 16) * (FONT_WIDTH + 2.0);
@ -20,7 +28,7 @@ vec2 texture_corner(int index) {
} }
void main() { void main() {
vec2 term_pos = vec2(f_pos.x / FONT_WIDTH, f_pos.y / FONT_HEIGHT); vec2 term_pos = vec2(fontPos.x / FONT_WIDTH, fontPos.y / FONT_HEIGHT);
vec2 corner = floor(term_pos); vec2 corner = floor(term_pos);
ivec2 cell = ivec2(corner); ivec2 cell = ivec2(corner);
@ -36,5 +44,7 @@ void main() {
vec2 pos = (term_pos - corner) * vec2(FONT_WIDTH, FONT_HEIGHT); vec2 pos = (term_pos - corner) * vec2(FONT_WIDTH, FONT_HEIGHT);
vec4 img = texture(Sampler0, (texture_corner(character) + pos) / 256.0); vec4 img = texture(Sampler0, (texture_corner(character) + pos) / 256.0);
colour = vec4(mix(Palette[bg], img.rgb * Palette[fg], img.a * mult), 1.0); vec4 colour = vec4(mix(Palette[bg], img.rgb * Palette[fg], img.a * mult), 1.0) * ColorModulator;
fragColor = linear_fog(colour, vertexDistance, FogStart, FogEnd, FogColor);
} }

View File

@ -6,6 +6,13 @@
"uniforms": [ "uniforms": [
{ "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
{ "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
{ "name": "IViewRotMat", "type": "matrix3x3", "count": 9, "values": [ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ] },
{ "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] },
{ "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] },
{ "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] },
{ "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] },
{ "name": "FogShape", "type": "int", "count": 1, "values": [ 0 ] },
{ "name": "Width", "type": "int", "count": 1, "values": [ 1 ] }, { "name": "Width", "type": "int", "count": 1, "values": [ 1 ] },
{ "name": "Height", "type": "int", "count": 1, "values": [ 1 ] }, { "name": "Height", "type": "int", "count": 1, "values": [ 1 ] },
{ "name": "Tbo", "type": "int", "count": 1, "values": [ 3 ] } { "name": "Tbo", "type": "int", "count": 1, "values": [ 3 ] }

View File

@ -1,14 +1,21 @@
#version 150 #version 150
#moj_import <fog.glsl>
in vec3 Position; in vec3 Position;
in vec2 UV0; in vec2 UV0;
uniform mat4 ModelViewMat; uniform mat4 ModelViewMat;
uniform mat4 ProjMat; uniform mat4 ProjMat;
uniform mat3 IViewRotMat;
uniform int FogShape;
out vec2 f_pos; out float vertexDistance;
out vec2 fontPos;
void main() { void main() {
gl_Position = ProjMat * ModelViewMat * vec4(Position, 1); gl_Position = ProjMat * ModelViewMat * vec4(Position, 1);
f_pos = UV0;
vertexDistance = fog_distance(ModelViewMat, IViewRotMat * Position, FogShape);
fontPos = UV0;
} }

View File

@ -1,11 +0,0 @@
{
"vertex": "minecraft:position_color_tex",
"fragment": "minecraft:position_color_tex",
"attributes": [ "Position", "Color", "UV0" ],
"samplers": [ { "name": "Sampler0" } ],
"uniforms": [
{ "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
{ "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
{ "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }
]
}