1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-02-15 02:20:05 +00:00

Merge branch 'mc-1.16.x' into mc-1.18.x

I was right: I did not enjoy this.
This commit is contained in:
Jonathan Coates 2022-04-26 22:17:17 +01:00
commit 59e3608d2a
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
30 changed files with 777 additions and 516 deletions

View File

@ -1,305 +0,0 @@
/*
* 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.gui;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Matrix4f;
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;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
/**
* Handles rendering fixed width text and computer terminals.
*
* This class has several modes of usage:
* <ul>
* <li>{@link #drawString}: Drawing basic text without a terminal (such as for printouts). Unlike the other methods,
* this accepts a lightmap coordinate as, unlike terminals, printed pages render fullbright.</li>
* <li>{@link #drawTerminalWithoutCursor}/{@link #drawCursor}: Draw a terminal without a cursor and then draw the cursor
* separately. This is used by the monitor renderer to render the terminal to a VBO and draw the cursor dynamically.
* </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 RenderTypes#TERMINAL_WITHOUT_DEPTH} you need to
* render an additional "depth blocker" on top of the monitor.</li>
* </ul>
*/
public final class FixedWidthFontRenderer
{
public static final ResourceLocation FONT = new ResourceLocation( "computercraft", "textures/gui/term_font.png" );
public static final int FONT_HEIGHT = 9;
public static final int FONT_WIDTH = 6;
private 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;
private static final byte[] BLACK = new byte[] { byteColour( Colour.BLACK.getR() ), byteColour( Colour.BLACK.getR() ), byteColour( Colour.BLACK.getR() ), (byte) 255 };
private FixedWidthFontRenderer()
{
}
private static byte byteColour( float c )
{
return (byte) (int) (c * 255);
}
public static float toGreyscale( double[] rgb )
{
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
}
public static int getColour( char c, Colour def )
{
return 15 - Terminal.getColour( c, def );
}
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.
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);
emitter.vertex( x, y, (float) 0, colour, xStart / WIDTH, yStart / WIDTH, light );
emitter.vertex( x, y + FONT_HEIGHT, (float) 0, colour, xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH, light );
emitter.vertex( x + FONT_WIDTH, y + FONT_HEIGHT, (float) 0, colour, (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH, light );
emitter.vertex( x + FONT_WIDTH, y, (float) 0, colour, (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH, light );
}
public static void drawQuad( VertexEmitter emitter, float x, float y, float z, float width, float height, byte[] colour, int light )
{
emitter.vertex( x, y, z, colour, BACKGROUND_START, BACKGROUND_START, light );
emitter.vertex( x, y + height, z, colour, BACKGROUND_START, BACKGROUND_END, light );
emitter.vertex( x + width, y + height, z, colour, BACKGROUND_END, BACKGROUND_END, light );
emitter.vertex( x + width, y, z, colour, BACKGROUND_END, BACKGROUND_START, light );
}
private static void drawQuad( VertexEmitter emitter, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex, int light )
{
var colour = palette.getByteColour( getColour( colourIndex, Colour.BLACK ), greyscale );
drawQuad( emitter, x, y, 0, width, height, colour, light );
}
private static void drawBackground(
@Nonnull VertexEmitter emitter, float x, float y,
@Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale,
float leftMarginSize, float rightMarginSize, float height, int light
)
{
if( leftMarginSize > 0 )
{
drawQuad( emitter, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ), light );
}
if( rightMarginSize > 0 )
{
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.
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( emitter, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour, light );
}
blockColour = colourIndex;
blockStart = i;
}
if( blockColour != '\0' )
{
drawQuad( emitter, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour, light );
}
}
public static void drawString(
@Nonnull VertexEmitter emitter, float x, float y,
@Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nullable TextBuffer backgroundColour,
@Nonnull Palette palette, boolean greyscale, float leftMarginSize, float rightMarginSize, int light
)
{
if( backgroundColour != null )
{
drawBackground( emitter, x, y, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize, FONT_HEIGHT, light );
}
for( int i = 0; i < text.length(); i++ )
{
var colour = palette.getByteColour( getColour( textColour.charAt( i ), Colour.BLACK ), greyscale );
// Draw char
int index = text.charAt( i );
if( index > 255 ) index = '?';
drawChar( emitter, x + i * FONT_WIDTH, y, index, colour, light );
}
}
public static void drawTerminalWithoutCursor(
@Nonnull VertexEmitter emitter, 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(
emitter, x, y - topMarginSize,
terminal.getBackgroundColourLine( 0 ), palette, greyscale,
leftMarginSize, rightMarginSize, topMarginSize, FULL_BRIGHT_LIGHTMAP
);
drawBackground(
emitter, x, y + height * FONT_HEIGHT,
terminal.getBackgroundColourLine( height - 1 ), palette, greyscale,
leftMarginSize, rightMarginSize, bottomMarginSize, FULL_BRIGHT_LIGHTMAP
);
// The main text
for( int i = 0; i < height; i++ )
{
drawString(
emitter, x, y + FixedWidthFontRenderer.FONT_HEIGHT * i,
terminal.getLine( i ), terminal.getTextColourLine( i ), terminal.getBackgroundColourLine( i ),
palette, greyscale, leftMarginSize, rightMarginSize, FULL_BRIGHT_LIGHTMAP
);
}
}
public static void drawCursor( @Nonnull VertexEmitter emitter, float x, float y, @Nonnull Terminal terminal, boolean greyscale )
{
Palette palette = terminal.getPalette();
int width = terminal.getWidth();
int height = terminal.getHeight();
int cursorX = terminal.getCursorX();
int cursorY = terminal.getCursorY();
if( terminal.getCursorBlink() && cursorX >= 0 && cursorX < width && cursorY >= 0 && cursorY < height && FrameInfo.getGlobalCursorBlink() )
{
var colour = palette.getByteColour( 15 - terminal.getTextColour(), greyscale );
drawChar( emitter, x + cursorX * FONT_WIDTH, y + cursorY * FONT_HEIGHT, '_', colour, FULL_BRIGHT_LIGHTMAP );
}
}
public static void drawTerminal(
@Nonnull VertexEmitter buffer, float x, float y,
@Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{
drawTerminalWithoutCursor( buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
drawCursor( buffer, x, y, terminal, greyscale );
}
public static void drawEmptyTerminal( @Nonnull VertexEmitter emitter, float x, float y, float width, float height )
{
drawQuad( emitter, x, y, 0, width, height, BLACK, FULL_BRIGHT_LIGHTMAP );
}
public static void drawBlocker( @Nonnull VertexEmitter emitter, float x, float y, float width, float height )
{
drawQuad( emitter, x, y, 0, width, height, BLACK, FULL_BRIGHT_LIGHTMAP );
}
public static int getVertexCount( Terminal terminal )
{
int height = terminal.getHeight();
int count = 0;
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;
}
/**
* 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
{
void vertex( float x, float y, float z, byte[] rgba, float u, float v, int light );
}
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

@ -10,7 +10,6 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
import dan200.computercraft.client.render.ComputerBorderRenderer;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.shared.computer.inventory.ContainerComputerBase;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import net.minecraft.network.chat.Component;
@ -19,6 +18,7 @@ import net.minecraft.world.entity.player.Inventory;
import javax.annotation.Nonnull;
import static dan200.computercraft.client.render.ComputerBorderRenderer.BORDER;
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
public final class GuiComputer<T extends ContainerComputerBase> extends ComputerScreenBase<T>
{
@ -78,7 +78,7 @@ public final class GuiComputer<T extends ContainerComputerBase> extends Computer
// Draw a border around the terminal
ComputerBorderRenderer.render(
ComputerBorderRenderer.getTexture( family ), terminal.x, terminal.y, getBlitOffset(),
RenderTypes.FULL_BRIGHT_LIGHTMAP, terminal.getWidth(), terminal.getHeight()
FULL_BRIGHT_LIGHTMAP, terminal.getWidth(), terminal.getHeight()
);
ComputerSidebar.renderBackground( stack, leftPos, topPos + sidebarYOffset );
}

View File

@ -8,8 +8,8 @@ package dan200.computercraft.client.gui.widgets;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.math.Matrix4f;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.computer.core.ClientComputer;
import net.minecraft.SharedConstants;
@ -23,9 +23,9 @@ import org.lwjgl.glfw.GLFW;
import javax.annotation.Nonnull;
import java.util.BitSet;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH;
public class WidgetTerminal extends AbstractWidget
{

View File

@ -70,7 +70,6 @@ public class ComputerBorderRenderer
this.b = b;
}
@Nonnull
public static ResourceLocation getTexture( @Nonnull ComputerFamily family )
{

View File

@ -10,7 +10,7 @@ import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Matrix4f;
import com.mojang.math.Vector3f;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
@ -24,9 +24,9 @@ import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
import static dan200.computercraft.client.render.ComputerBorderRenderer.*;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH;
/**
* Emulates map rendering for pocket computers.
@ -136,7 +136,7 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
byte r = (byte) ((colour >>> 16) & 0xFF);
byte g = (byte) ((colour >>> 8) & 0xFF);
byte b = (byte) (colour & 0xFF);
var c = new byte[] { r, g, b, (byte) 255 };
byte[] c = new byte[] { r, g, b, (byte) 255 };
VertexConsumer buffer = render.getBuffer( RenderTypes.TERMINAL_WITH_DEPTH );
FixedWidthFontRenderer.drawQuad(

View File

@ -19,8 +19,8 @@ import net.minecraftforge.client.event.RenderItemInFrameEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH;
import static dan200.computercraft.client.render.PrintoutRenderer.*;
import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAGE;
import static dan200.computercraft.shared.media.items.ItemPrintout.LINE_MAX_LENGTH;

View File

@ -7,95 +7,63 @@ package dan200.computercraft.client.render;
import com.mojang.blaze3d.shaders.Uniform;
import com.mojang.blaze3d.vertex.VertexFormat;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.shared.util.Palette;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.util.Colour;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceProvider;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL31;
import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.nio.ByteBuffer;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.getColour;
public class MonitorTextureBufferShader extends ShaderInstance
{
public static final int UNIFORM_SIZE = 4 * 4 * 16 + 4 + 4 + 2 * 4 + 4;
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
private static final Logger LOGGER = LogManager.getLogger();
private final Uniform palette;
private final Uniform width;
private final Uniform height;
private final int monitorData;
private int uniformBuffer = 0;
private final Uniform cursorBlink;
public MonitorTextureBufferShader( ResourceProvider provider, ResourceLocation location, VertexFormat format ) throws IOException
{
super( provider, location, format );
monitorData = GL31.glGetUniformBlockIndex( getId(), "MonitorData" );
if( monitorData == -1 ) throw new IllegalStateException( "Could not find MonitorData uniform." );
width = getUniformChecked( "Width" );
height = getUniformChecked( "Height" );
palette = new Uniform( "Palette", Uniform.UT_FLOAT3, 16 * 3, this );
updateUniformLocation( palette );
cursorBlink = getUniformChecked( "CursorBlink" );
Uniform tbo = getUniformChecked( "Tbo" );
if( tbo != null ) tbo.set( TEXTURE_INDEX - GL13.GL_TEXTURE0 );
}
void setupUniform( int width, int height, Palette palette, boolean greyscale )
public void setupUniform( int buffer )
{
if( this.width != null ) this.width.set( width );
if( this.height != null ) this.height.set( height );
setupPalette( palette, greyscale );
}
uniformBuffer = buffer;
private void setupPalette( Palette palette, boolean greyscale )
{
if( this.palette == null ) return;
FloatBuffer paletteBuffer = this.palette.getFloatBuffer();
paletteBuffer.rewind();
for( int i = 0; i < 16; i++ )
{
double[] colour = palette.getColour( i );
if( greyscale )
{
float f = FixedWidthFontRenderer.toGreyscale( colour );
paletteBuffer.put( f ).put( f ).put( f );
}
else
{
paletteBuffer.put( (float) colour[0] ).put( (float) colour[1] ).put( (float) colour[2] );
}
}
int cursorAlpha = FrameInfo.getGlobalCursorBlink() ? 1 : 0;
if( cursorBlink != null && cursorBlink.getIntBuffer().get( 0 ) != cursorAlpha ) cursorBlink.set( cursorAlpha );
}
@Override
public void apply()
{
super.apply();
palette.upload();
}
@Override
public void close()
{
palette.close();
super.close();
}
private void updateUniformLocation( Uniform uniform )
{
int id = Uniform.glGetUniformLocation( getId(), uniform.getName() );
if( id == -1 )
{
LOGGER.warn( "Shader {} could not find uniform named {} in the specified shader program.", getName(), uniform.getName() );
}
else
{
uniform.setLocation( id );
}
GL31.glBindBufferBase( GL31.GL_UNIFORM_BUFFER, monitorData, uniformBuffer );
}
@Nullable
@ -109,4 +77,56 @@ public class MonitorTextureBufferShader extends ShaderInstance
return uniform;
}
public static void setTerminalData( ByteBuffer buffer, Terminal terminal )
{
int width = terminal.getWidth(), height = terminal.getHeight();
int pos = 0;
for( int y = 0; y < height; y++ )
{
TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y );
for( int x = 0; x < width; x++ )
{
buffer.put( pos, (byte) (text.charAt( x ) & 0xFF) );
buffer.put( pos + 1, (byte) getColour( textColour.charAt( x ), Colour.WHITE ) );
buffer.put( pos + 2, (byte) getColour( background.charAt( x ), Colour.BLACK ) );
pos += 3;
}
}
buffer.limit( pos );
}
public static void setUniformData( ByteBuffer buffer, Terminal terminal, boolean greyscale )
{
int pos = 0;
var palette = terminal.getPalette();
for( int i = 0; i < 16; i++ )
{
{
double[] colour = palette.getColour( i );
if( greyscale )
{
float f = FixedWidthFontRenderer.toGreyscale( colour );
buffer.putFloat( pos, f ).putFloat( pos + 4, f ).putFloat( pos + 8, f );
}
else
{
buffer.putFloat( pos, (float) colour[0] ).putFloat( pos + 4, (float) colour[1] ).putFloat( pos + 8, (float) colour[2] );
}
}
pos += 4 * 4; // std140 requires these are 4-wide
}
boolean showCursor = FixedWidthFontRenderer.isCursorVisible( terminal );
buffer
.putInt( pos, terminal.getWidth() ).putInt( pos + 4, terminal.getHeight() )
.putInt( pos + 8, showCursor ? terminal.getCursorX() : -2 )
.putInt( pos + 12, showCursor ? terminal.getCursorY() : -2 )
.putInt( pos + 16, 15 - terminal.getTextColour() );
buffer.limit( UNIFORM_SIZE );
}
}

View File

@ -7,12 +7,12 @@ package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Matrix4f;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.renderer.MultiBufferSource;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAGE;
public final class PrintoutRenderer
@ -61,9 +61,8 @@ public final class PrintoutRenderer
for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ )
{
FixedWidthFontRenderer.drawString( emitter,
x, y + line * FONT_HEIGHT, text[start + line], colours[start + line], null, Palette.DEFAULT,
false, 0, 0,
light
x, y + line * FONT_HEIGHT, text[start + line], colours[start + line],
Palette.DEFAULT, false, light
);
}
}
@ -77,8 +76,7 @@ public final class PrintoutRenderer
FixedWidthFontRenderer.drawString( emitter,
x, y + line * FONT_HEIGHT,
new TextBuffer( text[start + line] ), new TextBuffer( colours[start + line] ),
null, Palette.DEFAULT, false, 0, 0,
light
Palette.DEFAULT, false, light
);
}
}

View File

@ -8,7 +8,7 @@ package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderType;
@ -108,7 +108,6 @@ public class RenderTypes
RenderType.CompositeState.builder()
.setTextureState( TERM_FONT_TEXTURE )
.setShaderState( new ShaderStateShard( RenderTypes::getMonitorTextureBufferShader ) )
.setWriteMaskState( COLOR_WRITE )
.createCompositeState( false )
);

View File

@ -14,13 +14,13 @@ import com.mojang.math.Matrix4f;
import com.mojang.math.Vector3f;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.client.util.DirectBuffers;
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.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
@ -35,7 +35,8 @@ import org.lwjgl.opengl.GL31;
import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_WIDTH;
public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonitor>
{
@ -114,24 +115,7 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
renderTerminal( bufferSource, matrix, originTerminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale) );
// We don't draw the cursor with the VBO/TBO, as it's dynamic and so we'll end up refreshing far more than
// is reasonable.
FixedWidthFontRenderer.drawCursor(
FixedWidthFontRenderer.toVertexConsumer( matrix, bufferSource.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH ) ),
0, 0, terminal, !originTerminal.isColour()
);
transform.popPose();
FixedWidthFontRenderer.drawBlocker(
FixedWidthFontRenderer.toVertexConsumer( transform.last().pose(), bufferSource.getBuffer( RenderTypes.TERMINAL_BLOCKER ) ),
-MARGIN, MARGIN,
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
);
// 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!
bufferSource.getBuffer( RenderType.solid() );
}
else
{
@ -148,6 +132,8 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
private static void renderTerminal( @Nonnull MultiBufferSource bufferSource, Matrix4f matrix, ClientMonitor monitor, float xMargin, float yMargin )
{
Terminal terminal = monitor.getTerminal();
int width = terminal.getWidth(), height = terminal.getHeight();
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
MonitorRenderer renderType = MonitorRenderer.current();
boolean redraw = monitor.pollTerminalChanged();
@ -157,27 +143,15 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
{
case TBO:
{
int width = terminal.getWidth(), height = terminal.getHeight();
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
if( redraw )
{
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 );
for( int x = 0; x < width; x++ )
{
monitorBuffer.put( (byte) (text.charAt( x ) & 0xFF) );
monitorBuffer.put( (byte) getColour( textColour.charAt( x ), Colour.WHITE ) );
monitorBuffer.put( (byte) getColour( background.charAt( x ), Colour.BLACK ) );
}
}
monitorBuffer.flip();
var terminalBuffer = getBuffer( width * height * 3 );
MonitorTextureBufferShader.setTerminalData( terminalBuffer, terminal );
DirectBuffers.setBufferData( GL31.GL_TEXTURE_BUFFER, monitor.tboBuffer, terminalBuffer, GL20.GL_STATIC_DRAW );
GlStateManager._glBindBuffer( GL31.GL_TEXTURE_BUFFER, monitor.tboBuffer );
GlStateManager._glBufferData( GL31.GL_TEXTURE_BUFFER, monitorBuffer, GL20.GL_STATIC_DRAW );
GlStateManager._glBindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
var uniformBuffer = getBuffer( MonitorTextureBufferShader.UNIFORM_SIZE );
MonitorTextureBufferShader.setUniformData( uniformBuffer, terminal, !monitor.isColour() );
DirectBuffers.setBufferData( GL31.GL_UNIFORM_BUFFER, monitor.tboUniform, uniformBuffer, GL20.GL_STATIC_DRAW );
}
// Nobody knows what they're doing!
@ -187,17 +161,15 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
RenderSystem.activeTexture( active );
MonitorTextureBufferShader shader = RenderTypes.getMonitorTextureBufferShader();
shader.setupUniform( width, height, terminal.getPalette(), !monitor.isColour() );
shader.setupUniform( monitor.tboUniform );
// TODO: Switch to using a VBO here? Something to avoid having to do the
VertexConsumer buffer = bufferSource.getBuffer( RenderTypes.MONITOR_TBO );
tboVertex( buffer, matrix, -xMargin, -yMargin );
tboVertex( buffer, matrix, -xMargin, pixelHeight + yMargin );
tboVertex( buffer, matrix, pixelWidth + xMargin, -yMargin );
tboVertex( buffer, matrix, pixelWidth + xMargin, pixelHeight + yMargin );
// And force things to flush. We strictly speaking do this later on anyway for the cursor, but nice to
// be consistent.
bufferSource.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH );
break;
}
@ -206,23 +178,45 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
var vbo = monitor.buffer;
if( redraw )
{
int vertexCount = FixedWidthFontRenderer.getVertexCount( terminal );
ByteBuffer buffer = getBuffer( vertexCount * RenderTypes.TERMINAL_WITHOUT_DEPTH.format().getVertexSize() );
FixedWidthFontRenderer.drawTerminalWithoutCursor(
FixedWidthFontRenderer.toByteBuffer( buffer ), 0, 0,
terminal, !monitor.isColour(), yMargin, yMargin, xMargin, xMargin
int vertexSize = RenderTypes.TERMINAL_WITHOUT_DEPTH.format().getVertexSize();
ByteBuffer buffer = getBuffer( DirectFixedWidthFontRenderer.getVertexCount( terminal ) * vertexSize );
// 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;
// If the cursor is visible, we append it to the end of our buffer. When rendering, we can either
// render n or n+1 quads and so toggle the cursor on and off.
DirectFixedWidthFontRenderer.drawCursor( buffer, 0, 0, terminal, !monitor.isColour() );
buffer.flip();
vbo.upload( vertexCount, RenderTypes.TERMINAL_WITHOUT_DEPTH.mode(), RenderTypes.TERMINAL_WITHOUT_DEPTH.format(), buffer );
vbo.upload( termIndexes, RenderTypes.TERMINAL_WITHOUT_DEPTH.mode(), RenderTypes.TERMINAL_WITHOUT_DEPTH.format(), buffer );
}
bufferSource.getBuffer( RenderTypes.TERMINAL_WITHOUT_DEPTH );
RenderTypes.TERMINAL_WITHOUT_DEPTH.setupRenderState();
vbo.drawWithShader( matrix, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader() );
vbo.drawWithShader(
matrix, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader(),
// As mentioned in the above comment, render the extra cursor quad if it is visible this frame. Each
// // quad has an index count of 6.
FixedWidthFontRenderer.isCursorVisible( terminal ) && FrameInfo.getGlobalCursorBlink() ? vbo.getIndexCount() + 6 : vbo.getIndexCount()
);
FixedWidthFontRenderer.drawBlocker(
FixedWidthFontRenderer.toVertexConsumer( matrix, bufferSource.getBuffer( RenderTypes.TERMINAL_BLOCKER ) ),
-xMargin, -yMargin, pixelWidth + xMargin, pixelHeight + yMargin
);
break;
}
}
// Force a flush of the buffer. WorldRenderer.updateCameraAndRender will "finish" all the built-in buffers
// before calling renderer.finish, which means our TBO quad or depth blocker won't be rendered yet!
bufferSource.getBuffer( RenderType.solid() );
}
private static void tboVertex( VertexConsumer builder, Matrix4f matrix, float x, float y )
@ -238,7 +232,7 @@ public class TileEntityMonitorRenderer implements BlockEntityRenderer<TileMonito
ByteBuffer buffer = backingBuffer;
if( buffer == null || buffer.capacity() < capacity )
{
buffer = backingBuffer = MemoryTracker.create( capacity );
buffer = backingBuffer = buffer == null ? MemoryTracker.create( capacity ) : MemoryTracker.resize( buffer, capacity );
}
buffer.clear();

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.platform.MemoryTracker;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexConsumer;
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 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 VertexConsumer}. 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 DefaultVertexFormat#POSITION_COLOR_TEX}.</li>
* <li>The buffer <strong>MUST</strong> be allocated with {@link MemoryTracker}, 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

@ -0,0 +1,243 @@
/*
* 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.VertexConsumer;
import com.mojang.math.Matrix4f;
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;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nonnull;
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
/**
* Handles rendering fixed width text and computer terminals.
*
* This class has several modes of usage:
* <ul>
* <li>{@link #drawString}: Drawing basic text without a terminal (such as for printouts). Unlike the other methods,
* this accepts a lightmap coordinate as, unlike terminals, printed pages render fullbright.</li>
* <li>{@link #drawTerminalWithoutCursor}/{@link #drawCursor}: Draw a terminal without a cursor and then draw the cursor
* separately. This is used by the monitor renderer to render the terminal to a VBO and draw the cursor dynamically.
* </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 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
{
public static final ResourceLocation FONT = new ResourceLocation( "computercraft", "textures/gui/term_font.png" );
public static final int FONT_HEIGHT = 9;
public static final int FONT_WIDTH = 6;
static final float WIDTH = 256.0f;
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 };
private FixedWidthFontRenderer()
{
}
private static byte byteColour( float c )
{
return (byte) (int) (c * 255);
}
public static float toGreyscale( double[] rgb )
{
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
}
public static int getColour( char c, Colour def )
{
return 15 - Terminal.getColour( c, def );
}
private static void drawChar( QuadEmitter 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.
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(
emitter, x, y, x + FONT_WIDTH, y + FONT_HEIGHT, 0, colour,
xStart / WIDTH, yStart / WIDTH, (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH, light
);
}
public static void drawQuad( QuadEmitter emitter, float x, float y, float z, float width, float height, byte[] colour, int light )
{
quad( emitter, x, y, x + width, y + height, z, colour, BACKGROUND_START, BACKGROUND_START, BACKGROUND_END, BACKGROUND_END, light );
}
private static void drawQuad( QuadEmitter emitter, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex, int light )
{
byte[] colour = palette.getByteColour( getColour( colourIndex, Colour.BLACK ), greyscale );
drawQuad( emitter, x, y, 0, width, height, colour, light );
}
private static void drawBackground(
@Nonnull QuadEmitter emitter, float x, float y, @Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale,
float leftMarginSize, float rightMarginSize, float height, int light
)
{
if( leftMarginSize > 0 )
{
drawQuad( emitter, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ), light );
}
if( rightMarginSize > 0 )
{
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.
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( emitter, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour, light );
}
blockColour = colourIndex;
blockStart = i;
}
if( blockColour != '\0' )
{
drawQuad( emitter, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour, light );
}
}
public static void drawString( @Nonnull QuadEmitter emitter, float x, float y, @Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nonnull Palette palette, boolean greyscale, int light )
{
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( emitter, x + i * FONT_WIDTH, y, index, colour, light );
}
}
public static void drawTerminalWithoutCursor(
@Nonnull QuadEmitter emitter, 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(
emitter, x, y - topMarginSize, terminal.getBackgroundColourLine( 0 ), palette, greyscale,
leftMarginSize, rightMarginSize, topMarginSize, FULL_BRIGHT_LIGHTMAP
);
drawBackground(
emitter, x, y + height * FONT_HEIGHT, terminal.getBackgroundColourLine( height - 1 ), palette, greyscale,
leftMarginSize, rightMarginSize, bottomMarginSize, FULL_BRIGHT_LIGHTMAP
);
// The main text
for( int i = 0; i < height; i++ )
{
float rowY = y + FixedWidthFontRenderer.FONT_HEIGHT * i;
drawBackground(
emitter, x, rowY, terminal.getBackgroundColourLine( i ), palette, greyscale,
leftMarginSize, rightMarginSize, FONT_HEIGHT, FULL_BRIGHT_LIGHTMAP
);
drawString(
emitter, x, rowY, terminal.getLine( i ), terminal.getTextColourLine( i ),
palette, greyscale, FULL_BRIGHT_LIGHTMAP
);
}
}
public static boolean isCursorVisible( Terminal terminal )
{
if( !terminal.getCursorBlink() ) return false;
int cursorX = terminal.getCursorX();
int cursorY = terminal.getCursorY();
return cursorX >= 0 && cursorX < terminal.getWidth() && cursorY >= 0 && cursorY < terminal.getHeight();
}
public static void drawCursor( @Nonnull QuadEmitter emitter, float x, float y, @Nonnull Terminal terminal, boolean greyscale )
{
if( isCursorVisible( terminal ) && FrameInfo.getGlobalCursorBlink() )
{
byte[] colour = terminal.getPalette().getByteColour( 15 - terminal.getTextColour(), greyscale );
drawChar( emitter, x + terminal.getCursorX() * FONT_WIDTH, y + terminal.getCursorY() * FONT_HEIGHT, '_', colour, FULL_BRIGHT_LIGHTMAP );
}
}
public static void drawTerminal(
@Nonnull QuadEmitter emitter, float x, float y,
@Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{
drawTerminalWithoutCursor( emitter, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
drawCursor( emitter, x, y, terminal, greyscale );
}
public static void drawEmptyTerminal( @Nonnull QuadEmitter emitter, float x, float y, float width, float height )
{
drawQuad( emitter, x, y, 0, width, height, BLACK, FULL_BRIGHT_LIGHTMAP );
}
public static void drawBlocker( @Nonnull QuadEmitter emitter, float x, float y, float width, float height )
{
drawQuad( emitter, x, y, 0, width, height, BLACK, FULL_BRIGHT_LIGHTMAP );
}
public record QuadEmitter(Matrix4f matrix4f, VertexConsumer consumer) {}
public static QuadEmitter toVertexConsumer( Matrix4f matrix, VertexConsumer consumer )
{
return new QuadEmitter( matrix, consumer );
}
private static void quad( QuadEmitter c, float x1, float y1, float x2, float y2, float z, byte[] rgba, float u1, float v1, float u2, float v2, int light )
{
var matrix = c.matrix4f();
var consumer = c.consumer();
byte r = rgba[0], g = rgba[1], b = rgba[2], a = rgba[3];
consumer.vertex( matrix, x1, y1, z ).color( r, g, b, a ).uv( u1, v1 ).uv2( light ).endVertex();
consumer.vertex( matrix, x1, y2, z ).color( r, g, b, a ).uv( u1, v2 ).uv2( light ).endVertex();
consumer.vertex( matrix, x2, y2, z ).color( r, g, b, a ).uv( u2, v2 ).uv2( light ).endVertex();
consumer.vertex( matrix, x2, y1, z ).color( r, g, b, a ).uv( u2, v1 ).uv2( light ).endVertex();
}
}

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.platform.GlStateManager;
import com.mojang.blaze3d.vertex.BufferUploader;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL15C;
import org.lwjgl.opengl.GL45C;
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
{
var 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
{
if( type == GL45C.GL_ARRAY_BUFFER ) BufferUploader.reset();
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
{
if( type == GL45C.GL_ARRAY_BUFFER ) BufferUploader.reset();
GlStateManager._glBindBuffer( type, id );
GlStateManager._glBufferData( type, 0, GL15C.GL_STATIC_DRAW );
GlStateManager._glBindBuffer( type, 0 );
}
}
}

View File

@ -6,10 +6,10 @@
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 com.mojang.math.Matrix4f;
import net.minecraft.client.renderer.ShaderInstance;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL45C;
@ -22,17 +22,11 @@ import java.nio.ByteBuffer;
*/
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;
}
private int actualIndexCount;
public DirectVertexBuffer()
{
if( HAS_DSA )
if( DirectBuffers.HAS_DSA )
{
RenderSystem.glDeleteBuffers( vertextBufferId );
vertextBufferId = GL45C.glCreateBuffers();
@ -43,22 +37,24 @@ public class DirectVertexBuffer extends VertexBuffer
{
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();
}
DirectBuffers.setBufferData( GL15.GL_ARRAY_BUFFER, vertextBufferId, buffer, GL15.GL_STATIC_DRAW );
this.format = format;
this.mode = mode;
indexCount = mode.indexCount( vertexCount );
actualIndexCount = indexCount = mode.indexCount( vertexCount );
indexType = VertexFormat.IndexType.SHORT;
sequentialIndices = true;
}
public void drawWithShader( Matrix4f modelView, Matrix4f projection, ShaderInstance shader, int indexCount )
{
this.indexCount = indexCount;
drawWithShader( modelView, projection, shader );
this.indexCount = actualIndexCount;
}
public int getIndexCount()
{
return actualIndexCount;
}
}

View File

@ -57,7 +57,8 @@ public final class ComputerMBean implements DynamicMBean, Tracker
{
ManagementFactory.getPlatformMBeanServer().registerMBean( instance = new ComputerMBean(), new ObjectName( "dan200.computercraft:type=Computers" ) );
}
catch( InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException | MalformedObjectNameException e )
catch( InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException |
MalformedObjectNameException e )
{
ComputerCraft.log.warn( "Failed to register JMX bean", e );
}

View File

@ -99,7 +99,7 @@ public class TileCommandComputer extends TileComputer
}
return new CommandSourceStack( receiver,
new Vec3( worldPosition.getX() + 0.5, worldPosition.getY() + 0.5, worldPosition.getZ() + 0.5 ), Vec2.ZERO,
Vec3.atCenterOf( worldPosition ), Vec2.ZERO,
(ServerLevel) getLevel(), 2,
name, new TextComponent( name ),
getLevel().getServer(), null

View File

@ -45,7 +45,7 @@ public class GenericPeripheralProvider
for( Capability<?> capability : capabilities )
{
LazyOptional<?> wrapper = tile.getCapability( capability );
LazyOptional<?> wrapper = CapabilityUtil.getCapability( tile, capability, side );
wrapper.ifPresent( contents -> {
List<NamedMethod<PeripheralMethod>> capabilityMethods = PeripheralMethod.GENERATOR.getMethods( contents.getClass() );
if( capabilityMethods.isEmpty() ) return;

View File

@ -60,8 +60,7 @@ public class TileCable extends TileGeneric
@Override
public Vec3 getPosition()
{
BlockPos pos = getBlockPos();
return new Vec3( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 );
return Vec3.atCenterOf( getBlockPos() );
}
@Override
@ -104,8 +103,7 @@ public class TileCable extends TileGeneric
@Override
public Vec3 getPosition()
{
BlockPos pos = getBlockPos().relative( getDirection() );
return new Vec3( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 );
return Vec3.atCenterOf( getBlockPos().relative( getDirection() ) );
}
@Nonnull

View File

@ -87,8 +87,7 @@ public class TileWiredModemFull extends TileGeneric
@Override
public Vec3 getPosition()
{
BlockPos pos = entity.getBlockPos();
return new Vec3( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 );
return Vec3.atCenterOf( entity.getBlockPos() );
}
}
@ -417,8 +416,7 @@ public class TileWiredModemFull extends TileGeneric
@Override
public Vec3 getPosition()
{
BlockPos pos = getBlockPos().relative( side );
return new Vec3( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 );
return Vec3.atCenterOf( getBlockPos().relative( side ) );
}
@Nonnull

View File

@ -48,8 +48,7 @@ public class TileWirelessModem extends TileGeneric
@Override
public Vec3 getPosition()
{
BlockPos pos = entity.getBlockPos().relative( entity.getDirection() );
return new Vec3( pos.getX(), pos.getY(), pos.getZ() );
return Vec3.atLowerCornerOf( entity.getBlockPos().relative( entity.getDirection() ) );
}
@Override

View File

@ -7,6 +7,7 @@ package dan200.computercraft.shared.peripheral.monitor;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import dan200.computercraft.client.util.DirectBuffers;
import dan200.computercraft.client.util.DirectVertexBuffer;
import dan200.computercraft.shared.common.ClientTerminal;
import net.minecraft.core.BlockPos;
@ -32,6 +33,7 @@ public final class ClientMonitor extends ClientTerminal
public int tboBuffer;
public int tboTexture;
public int tboUniform;
public DirectVertexBuffer buffer;
public ClientMonitor( boolean colour, TileMonitor origin )
@ -63,15 +65,15 @@ public final class ClientMonitor extends ClientTerminal
deleteBuffers();
tboBuffer = GlStateManager._glGenBuffers();
GlStateManager._glBindBuffer( GL31.GL_TEXTURE_BUFFER, tboBuffer );
GL15.glBufferData( GL31.GL_TEXTURE_BUFFER, 0, GL15.GL_STATIC_DRAW );
tboBuffer = DirectBuffers.createBuffer();
DirectBuffers.setEmptyBufferData( GL31.GL_TEXTURE_BUFFER, tboBuffer, GL15.GL_STATIC_DRAW );
tboTexture = GlStateManager._genTexture();
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, tboTexture );
GL31.glTexBuffer( GL31.GL_TEXTURE_BUFFER, GL30.GL_R8UI, tboBuffer );
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, 0 );
GlStateManager._glBindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
tboUniform = DirectBuffers.createBuffer();
DirectBuffers.setEmptyBufferData( GL31.GL_UNIFORM_BUFFER, tboUniform, GL15.GL_STATIC_DRAW );
addMonitor();
return true;
@ -113,6 +115,12 @@ public final class ClientMonitor extends ClientTerminal
tboTexture = 0;
}
if( tboUniform != 0 )
{
RenderSystem.glDeleteBuffers( tboUniform );
tboUniform = 0;
}
if( buffer != null )
{
buffer.close();

View File

@ -7,6 +7,7 @@ package dan200.computercraft.shared.peripheral.monitor;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import net.minecraftforge.fml.ModList;
import org.lwjgl.opengl.GL;
import javax.annotation.Nonnull;
@ -47,39 +48,24 @@ public enum MonitorRenderer
public static MonitorRenderer current()
{
MonitorRenderer current = ComputerCraft.monitorRenderer;
switch( current )
{
case BEST:
return best();
case TBO:
checkCapabilities();
if( !textureBuffer )
{
ComputerCraft.log.warn( "Texture buffers are not supported on your graphics card. Falling back to default." );
ComputerCraft.monitorRenderer = BEST;
return best();
}
return TBO;
default:
return current;
}
if( current == BEST ) current = ComputerCraft.monitorRenderer = best();
return current;
}
private static MonitorRenderer best()
{
checkCapabilities();
return textureBuffer ? TBO : VBO;
}
if( !GL.getCapabilities().OpenGL31 )
{
ComputerCraft.log.warn( "Texture buffers are not supported on your graphics card. Falling back to VBO monitor renderer." );
return VBO;
}
private static boolean initialised = false;
private static boolean textureBuffer = false;
if( ModList.get().isLoaded( "optifine" ) )
{
ComputerCraft.log.warn( "Optifine is loaded, assuming shaders are being used. Falling back to VBO monitor renderer." );
return VBO;
}
private static void checkCapabilities()
{
if( initialised ) return;
textureBuffer = GL.getCapabilities().OpenGL31;
initialised = true;
return TBO;
}
}

View File

@ -89,8 +89,7 @@ public class TileSpeaker extends TileGeneric
@Override
public Vec3 getPosition()
{
BlockPos pos = speaker.getBlockPos();
return new Vec3( pos.getX(), pos.getY(), pos.getZ() );
return Vec3.atCenterOf( speaker.getBlockPos() );
}
@Override

View File

@ -66,6 +66,7 @@ import java.util.Optional;
* accessible by accessing the `"left"` or `"right"` peripheral.
*
* [Turtle Graphics]: https://en.wikipedia.org/wiki/Turtle_graphics "Turtle graphics"
*
* @cc.module turtle
* @cc.since 1.3
*/

View File

@ -13,7 +13,6 @@ import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleUpgradeType;
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
@ -47,8 +46,7 @@ public class TurtleSpeaker extends AbstractTurtleUpgrade
@Override
public Vec3 getPosition()
{
BlockPos pos = turtle.getPosition();
return new Vec3( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 );
return Vec3.atCenterOf( turtle.getPosition() );
}
@Override

View File

@ -5,9 +5,13 @@
*/
package dan200.computercraft.shared.util;
import net.minecraft.core.Direction;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.common.util.NonNullConsumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public final class CapabilityUtil
@ -57,4 +61,21 @@ public final class CapabilityUtil
{
return !p.isPresent() ? null : p.orElseThrow( NullPointerException::new );
}
/**
* Find a capability, preferring the internal/null side but falling back to a given side if a mod doesn't support
* the internal one.
*
* @param provider The capability provider to get the capability from.
* @param capability The capability to get.
* @param side The side we'll fall back to.
* @param <T> The type of the underlying capability.
* @return The extracted capability, if present.
*/
@Nonnull
public static <T> LazyOptional<T> getCapability( ICapabilityProvider provider, Capability<T> capability, Direction side )
{
LazyOptional<T> cap = provider.getCapability( capability );
return cap.isPresent() ? cap : provider.getCapability( capability, side );
}
}

View File

@ -5,7 +5,7 @@
*/
package dan200.computercraft.shared.util;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
@ -142,7 +142,7 @@ public class Palette
for( int i = 0; i < colours.length; i++ )
{
var colours = decodeRGB8( rgb8[i] );
double[] colours = decodeRGB8( rgb8[i] );
setColour( i, colours[0], colours[1], colours[2] );
}
}

View File

@ -6,10 +6,16 @@
#define FONT_HEIGHT 9.0
uniform sampler2D Sampler0; // Font
uniform int Width;
uniform int Height;
uniform usamplerBuffer Tbo;
uniform vec3 Palette[16];
layout(std140) uniform MonitorData {
vec3 Palette[16];
int Width;
int Height;
ivec2 CursorPos;
int CursorColour;
};
uniform int CursorBlink;
uniform vec4 ColorModulator;
uniform float FogStart;
@ -27,6 +33,10 @@ vec2 texture_corner(int index) {
return vec2(x, y);
}
vec4 recolour(vec4 texture, int colour) {
return vec4(texture.rgb * Palette[colour], texture.rgba);
}
void main() {
vec2 term_pos = vec2(fontPos.x / FONT_WIDTH, fontPos.y / FONT_HEIGHT);
vec2 corner = floor(term_pos);
@ -43,8 +53,14 @@ void main() {
int bg = int(texelFetch(Tbo, index + 2).r);
vec2 pos = (term_pos - corner) * vec2(FONT_WIDTH, FONT_HEIGHT);
vec4 img = texture(Sampler0, (texture_corner(character) + pos) / 256.0);
vec4 colour = vec4(mix(Palette[bg], img.rgb * Palette[fg], img.a * mult), 1.0) * ColorModulator;
vec4 charTex = recolour(texture(Sampler0, (texture_corner(character) + pos) / 256.0), fg);
// Applies the cursor on top of the current character if we're blinking and in the current cursor's cell. We do it
// this funky way to avoid branches.
vec4 cursorTex = recolour(texture(Sampler0, (texture_corner(95) + pos) / 256.0), CursorColour); // 95 = '_'
vec4 img = mix(charTex, cursorTex, cursorTex.a * float(CursorBlink) * (CursorPos == cell ? 1.0 : 0.0));
vec4 colour = vec4(mix(Palette[bg], img.rgb, img.a * mult), 1.0) * ColorModulator;
fragColor = linear_fog(colour, vertexDistance, FogStart, FogEnd, FogColor);
}

View File

@ -13,8 +13,7 @@
{ "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": "Height", "type": "int", "count": 1, "values": [ 1 ] },
{ "name": "Tbo", "type": "int", "count": 1, "values": [ 3 ] }
{ "name": "Tbo", "type": "int", "count": 1, "values": [ 3 ] },
{ "name": "CursorBlink", "type": "int", "count": 1, "values": [ 0 ] }
]
}

View File

@ -23,8 +23,6 @@ table.pretty-table th {
pre.highlight.highlight-lua {
position: relative;
background: var(--background-2);
padding: 2px;
}
.example-run {