rewrote the render side of texture bakery, is very significantly better
This commit is contained in:
@@ -0,0 +1,72 @@
|
|||||||
|
package me.cortex.voxy.client.core.gl;
|
||||||
|
|
||||||
|
import me.cortex.voxy.common.util.TrackedObject;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.GL45C.*;
|
||||||
|
|
||||||
|
public class GlVertexArray extends TrackedObject {
|
||||||
|
public final int id;
|
||||||
|
private int[] indices = new int[0];
|
||||||
|
private int stride;
|
||||||
|
public GlVertexArray() {
|
||||||
|
this.id = glCreateVertexArrays();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void free() {
|
||||||
|
this.free0();
|
||||||
|
glDeleteVertexArrays(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind() {
|
||||||
|
glBindVertexArray(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlVertexArray bindBuffer(int buffer) {
|
||||||
|
//TODO: optimization, use glVertexArrayVertexBuffers
|
||||||
|
for (int index : this.indices) {
|
||||||
|
glVertexArrayVertexBuffer(this.id, index, buffer, 0, this.stride);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlVertexArray bindElementBuffer(int buffer) {
|
||||||
|
glVertexArrayElementBuffer(this.id, buffer);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlVertexArray setStride(int stride) {
|
||||||
|
this.stride = stride;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlVertexArray setI(int index, int type, int count, int offset) {
|
||||||
|
this.addIndex(index);
|
||||||
|
glEnableVertexArrayAttrib(this.id, index);
|
||||||
|
glVertexArrayAttribIFormat(this.id, index, count, type, offset);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlVertexArray setF(int index, int type, int count, int offset) {
|
||||||
|
return this.setF(index, type, count, false, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlVertexArray setF(int index, int type, int count, boolean normalize, int offset) {
|
||||||
|
this.addIndex(index);
|
||||||
|
glEnableVertexArrayAttrib(this.id, index);
|
||||||
|
glVertexArrayAttribFormat(this.id, index, count, type, normalize, offset);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addIndex(int index) {
|
||||||
|
for (int i : this.indices) {
|
||||||
|
if (i == index) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.indices = Arrays.copyOf(this.indices, this.indices.length+1);
|
||||||
|
this.indices[this.indices.length-1] = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -123,7 +123,7 @@ public class BakedBlockEntityModel {
|
|||||||
layer.putInto(bb);
|
layer.putInto(bb);
|
||||||
var mesh = bb.endNullable();
|
var mesh = bb.endNullable();
|
||||||
if (mesh!=null)
|
if (mesh!=null)
|
||||||
BudgetBufferRenderer.draw(mesh, texture, matrix);
|
BudgetBufferRenderer.drawFast(mesh, texture, matrix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,54 +8,103 @@ import com.mojang.blaze3d.systems.RenderPass;
|
|||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
import com.mojang.blaze3d.textures.GpuTexture;
|
import com.mojang.blaze3d.textures.GpuTexture;
|
||||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||||
|
import me.cortex.voxy.client.core.gl.GlBuffer;
|
||||||
|
import me.cortex.voxy.client.core.gl.GlVertexArray;
|
||||||
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.SharedIndexBuffer;
|
||||||
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
|
import me.cortex.voxy.common.util.UnsafeUtil;
|
||||||
import net.minecraft.client.gl.*;
|
import net.minecraft.client.gl.*;
|
||||||
import net.minecraft.client.render.BuiltBuffer;
|
import net.minecraft.client.render.BuiltBuffer;
|
||||||
import net.minecraft.client.render.VertexFormats;
|
import net.minecraft.client.render.VertexFormats;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.ARBVertexArrayObject.glBindVertexArray;
|
||||||
|
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
|
||||||
|
import static org.lwjgl.opengl.GL15.glBindBuffer;
|
||||||
|
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
|
||||||
|
import static org.lwjgl.opengl.GL33.glBindSampler;
|
||||||
|
import static org.lwjgl.opengl.GL43.glBindVertexBuffer;
|
||||||
|
import static org.lwjgl.opengl.GL45.*;
|
||||||
|
|
||||||
public class BudgetBufferRenderer {
|
public class BudgetBufferRenderer {
|
||||||
public static final RenderPipeline RENDERER_THING = RenderPipeline.builder()
|
private static final Shader bakeryShader = Shader.make()
|
||||||
.withLocation(Identifier.of("voxy","bakery/position_tex"))
|
.add(ShaderType.VERTEX, "voxy:bakery/position_tex.vsh")
|
||||||
.withVertexShader(Identifier.of("voxy","bakery/position_tex"))
|
.add(ShaderType.FRAGMENT, "voxy:bakery/position_tex.fsh")
|
||||||
.withFragmentShader(Identifier.of("voxy","bakery/position_tex"))
|
.compile();
|
||||||
.withUniform("transform", UniformType.MATRIX4X4)
|
|
||||||
.withSampler("tex")
|
|
||||||
.withDepthTestFunction(DepthTestFunction.LEQUAL_DEPTH_TEST)
|
private static final GlBuffer indexBuffer;
|
||||||
.withVertexFormat(VertexFormats.POSITION_TEXTURE_COLOR, VertexFormat.DrawMode.QUADS)
|
static {
|
||||||
.build();
|
var i = RenderSystem.getSequentialBuffer(VertexFormat.DrawMode.QUADS);
|
||||||
|
int id = ((GlGpuBuffer) i.getIndexBuffer(4096*3*2)).id;
|
||||||
|
if (i.getIndexType() != VertexFormat.IndexType.SHORT) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
indexBuffer = new GlBuffer(3*2*2*4096);
|
||||||
|
glCopyNamedBufferSubData(id, indexBuffer.id, 0, 0, 3*2*2*4096);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int STRIDE = 24;
|
||||||
|
private static final GlVertexArray VA = new GlVertexArray()
|
||||||
|
.setStride(STRIDE)
|
||||||
|
.setF(0, GL_FLOAT, 3, 0)//pos
|
||||||
|
.setI(1, GL_INT, 1, 4 * 3)//metadata
|
||||||
|
.setF(2, GL_FLOAT, 2, 4 * 4)//UV
|
||||||
|
.bindElementBuffer(indexBuffer.id);
|
||||||
|
|
||||||
|
private static GlBuffer immediateBuffer;
|
||||||
|
private static int quadCount;
|
||||||
|
public static void drawFast(BuiltBuffer buffer, GpuTexture tex, Matrix4f matrix) {
|
||||||
|
if (buffer.getDrawParameters().mode() != VertexFormat.DrawMode.QUADS) {
|
||||||
|
throw new IllegalStateException("Fast only supports quads");
|
||||||
|
}
|
||||||
|
|
||||||
|
var buff = buffer.getBuffer();
|
||||||
|
int size = buff.remaining();
|
||||||
|
if (size%STRIDE != 0) throw new IllegalStateException();
|
||||||
|
size /= STRIDE;
|
||||||
|
if (size%4 != 0) throw new IllegalStateException();
|
||||||
|
size /= 4;
|
||||||
|
setup(MemoryUtil.memAddress(buff), size, ((net.minecraft.client.texture.GlTexture)tex).getGlId());
|
||||||
|
buffer.close();
|
||||||
|
|
||||||
|
render(matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setup(long dataPtr, int quads, int texId) {
|
||||||
|
if (quads == 0) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
public static void draw(BuiltBuffer buffer, GpuTexture tex, Matrix4f matrix) {
|
|
||||||
//Fuz the gpu sampler state
|
|
||||||
GlStateManager._activeTexture(GlConst.GL_TEXTURE0);
|
GlStateManager._activeTexture(GlConst.GL_TEXTURE0);
|
||||||
GlStateManager._bindTexture(0);
|
GlStateManager._bindTexture(0);
|
||||||
GlStateManager._activeTexture(GlConst.GL_TEXTURE1);
|
|
||||||
GlStateManager._bindTexture(0);
|
|
||||||
GlStateManager._activeTexture(GlConst.GL_TEXTURE2);
|
|
||||||
GlStateManager._bindTexture(0);
|
|
||||||
GlStateManager._activeTexture(GlConst.GL_TEXTURE2+1);
|
|
||||||
GlStateManager._bindTexture(0);
|
|
||||||
|
|
||||||
RenderSystem.ShapeIndexBuffer shapeIndexBuffer = RenderSystem.getSequentialBuffer(buffer.getDrawParameters().mode());
|
quadCount = quads;
|
||||||
GpuBuffer gpuBuffer = buffer.getDrawParameters().format().uploadImmediateVertexBuffer(buffer.getBuffer());
|
|
||||||
|
|
||||||
var res = (GlResourceManager)RenderSystem.getDevice()
|
long size = quads * 4L * STRIDE;
|
||||||
.createCommandEncoder();
|
if (immediateBuffer == null || immediateBuffer.size()<size) {
|
||||||
res.currentProgram = null;
|
if (immediateBuffer != null) {
|
||||||
res.currentPipeline = null;
|
immediateBuffer.free();
|
||||||
try (RenderPass renderPass = new RenderPassImpl(res, false)) {
|
}
|
||||||
renderPass.setPipeline(RENDERER_THING);
|
immediateBuffer = new GlBuffer(size*2L);//This also accounts for when immediateBuffer == null
|
||||||
renderPass.setVertexBuffer(0, gpuBuffer);
|
VA.bindBuffer(immediateBuffer.id);
|
||||||
|
|
||||||
renderPass.bindSampler("tex", tex);
|
|
||||||
renderPass.setUniform("transform", matrix);
|
|
||||||
|
|
||||||
renderPass.setIndexBuffer(shapeIndexBuffer.getIndexBuffer(buffer.getDrawParameters().indexCount()), shapeIndexBuffer.getIndexType());
|
|
||||||
renderPass.drawIndexed(0, buffer.getDrawParameters().indexCount());
|
|
||||||
}
|
}
|
||||||
//gpuBuffer.close();
|
long ptr = UploadStream.INSTANCE.upload(immediateBuffer, 0, size);
|
||||||
buffer.close();
|
MemoryUtil.memCopy(dataPtr, ptr, size);
|
||||||
res.currentProgram = null;
|
UploadStream.INSTANCE.commit();
|
||||||
res.currentPipeline = null;
|
|
||||||
|
bakeryShader.bind();
|
||||||
|
VA.bind();
|
||||||
|
glBindSampler(0, 0);
|
||||||
|
glBindTextureUnit(0, texId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void render(Matrix4f matrix) {
|
||||||
|
glUniformMatrix4fv(1, false, matrix.get(new float[16]));
|
||||||
|
glDrawElements(GL_TRIANGLES, quadCount * 2 * 3, GL_UNSIGNED_SHORT, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,12 @@ import java.util.concurrent.locks.StampedLock;
|
|||||||
|
|
||||||
import static org.lwjgl.opengl.ARBFramebufferObject.GL_COLOR_ATTACHMENT0;
|
import static org.lwjgl.opengl.ARBFramebufferObject.GL_COLOR_ATTACHMENT0;
|
||||||
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
|
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
|
||||||
|
import static org.lwjgl.opengl.GL11.glGetInteger;
|
||||||
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
|
import static org.lwjgl.opengl.GL11C.GL_NEAREST;
|
||||||
|
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER;
|
||||||
|
import static org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_BINDING;
|
||||||
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
|
import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING;
|
||||||
|
import static org.lwjgl.opengl.GL30C.glBindFramebuffer;
|
||||||
import static org.lwjgl.opengl.GL45.glBlitNamedFramebuffer;
|
import static org.lwjgl.opengl.GL45.glBlitNamedFramebuffer;
|
||||||
|
|
||||||
public class ModelBakerySubsystem {
|
public class ModelBakerySubsystem {
|
||||||
@@ -79,6 +83,8 @@ public class ModelBakerySubsystem {
|
|||||||
Integer i = this.blockIdQueue.poll();
|
Integer i = this.blockIdQueue.poll();
|
||||||
int j = 0;
|
int j = 0;
|
||||||
if (i != null) {
|
if (i != null) {
|
||||||
|
int fbBinding = glGetInteger(GL_FRAMEBUFFER_BINDING);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
this.factory.addEntry(i);
|
this.factory.addEntry(i);
|
||||||
j++;
|
j++;
|
||||||
@@ -86,6 +92,8 @@ public class ModelBakerySubsystem {
|
|||||||
break;
|
break;
|
||||||
i = this.blockIdQueue.poll();
|
i = this.blockIdQueue.poll();
|
||||||
} while (i != null);
|
} while (i != null);
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fbBinding);//This is done here as stops needing to set then unset the fb in the thing 1000x
|
||||||
}
|
}
|
||||||
this.blockIdCount.addAndGet(-j);
|
this.blockIdCount.addAndGet(-j);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
|
|||||||
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
import it.unimi.dsi.fastutil.objects.ObjectSet;
|
||||||
import me.cortex.voxy.client.core.gl.Capabilities;
|
import me.cortex.voxy.client.core.gl.Capabilities;
|
||||||
import me.cortex.voxy.client.core.model.bakery.ModelTextureBakery;
|
import me.cortex.voxy.client.core.model.bakery.ModelTextureBakery;
|
||||||
|
import me.cortex.voxy.client.core.model.bakery.ModelTextureBakery2;
|
||||||
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
|
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
|
||||||
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
import me.cortex.voxy.client.core.rendering.util.UploadStream;
|
||||||
import me.cortex.voxy.common.Logger;
|
import me.cortex.voxy.common.Logger;
|
||||||
@@ -67,7 +68,7 @@ public class ModelFactory {
|
|||||||
|
|
||||||
private final Biome DEFAULT_BIOME = MinecraftClient.getInstance().world.getRegistryManager().getOrThrow(RegistryKeys.BIOME).get(BiomeKeys.PLAINS);
|
private final Biome DEFAULT_BIOME = MinecraftClient.getInstance().world.getRegistryManager().getOrThrow(RegistryKeys.BIOME).get(BiomeKeys.PLAINS);
|
||||||
|
|
||||||
public final ModelTextureBakery bakery;
|
public final ModelTextureBakery2 bakery;
|
||||||
|
|
||||||
|
|
||||||
//Model data might also contain a constant colour if the colour resolver produces a constant colour, this saves space in the
|
//Model data might also contain a constant colour if the colour resolver produces a constant colour, this saves space in the
|
||||||
@@ -125,7 +126,7 @@ public class ModelFactory {
|
|||||||
public ModelFactory(Mapper mapper, ModelStore storage) {
|
public ModelFactory(Mapper mapper, ModelStore storage) {
|
||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.bakery = new ModelTextureBakery(MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
|
this.bakery = new ModelTextureBakery2(MODEL_TEXTURE_SIZE, MODEL_TEXTURE_SIZE);
|
||||||
|
|
||||||
this.metadataCache = new long[1<<16];
|
this.metadataCache = new long[1<<16];
|
||||||
this.fluidStateLUT = new int[1<<16];
|
this.fluidStateLUT = new int[1<<16];
|
||||||
@@ -194,7 +195,7 @@ public class ModelFactory {
|
|||||||
}
|
}
|
||||||
this.resultJobs.add(()->processTextureBakeResult(blockId, blockState, textureData));
|
this.resultJobs.add(()->processTextureBakeResult(blockId, blockState, textureData));
|
||||||
});
|
});
|
||||||
this.bakery.renderFacesToStream(blockState, 123456, isFluid, this.downstream.getBufferId(), allocation);
|
this.bakery.renderToStream(blockState, this.downstream.getBufferId(), allocation);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import me.cortex.voxy.client.core.gl.GlTexture;
|
|||||||
import me.cortex.voxy.client.core.gl.shader.Shader;
|
import me.cortex.voxy.client.core.gl.shader.Shader;
|
||||||
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
import me.cortex.voxy.client.core.gl.shader.ShaderType;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.ARBDirectStateAccess.glClearNamedFramebufferfv;
|
||||||
import static org.lwjgl.opengl.ARBDirectStateAccess.glTextureParameteri;
|
import static org.lwjgl.opengl.ARBDirectStateAccess.glTextureParameteri;
|
||||||
import static org.lwjgl.opengl.GL11.*;
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
import static org.lwjgl.opengl.GL11.GL_STENCIL_INDEX;
|
import static org.lwjgl.opengl.GL11.GL_STENCIL_INDEX;
|
||||||
import static org.lwjgl.opengl.GL30.*;
|
import static org.lwjgl.opengl.GL30.*;
|
||||||
import static org.lwjgl.opengl.GL43.*;
|
import static org.lwjgl.opengl.GL43.*;
|
||||||
|
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfi;
|
||||||
|
|
||||||
public class GlViewCapture {
|
public class GlViewCapture {
|
||||||
private final int width;
|
private final int width;
|
||||||
@@ -57,6 +59,11 @@ public class GlViewCapture {
|
|||||||
glDispatchCompute(3, 2, 1);
|
glDispatchCompute(3, 2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
glClearNamedFramebufferfv(this.framebuffer.id, GL_COLOR, 0, new float[]{0,0,0,0});
|
||||||
|
glClearNamedFramebufferfi(this.framebuffer.id, GL_DEPTH_STENCIL, 0, 1.0f, 0);
|
||||||
|
}
|
||||||
|
|
||||||
public void free() {
|
public void free() {
|
||||||
this.framebuffer.free();
|
this.framebuffer.free();
|
||||||
this.colourTex.free();
|
this.colourTex.free();
|
||||||
|
|||||||
@@ -219,6 +219,8 @@ public class ModelTextureBakery {
|
|||||||
if (!renderFluid) {
|
if (!renderFluid) {
|
||||||
//TODO: need to do 2 variants for quads, one which have coloured, ones that dont, might be able to pull a spare bit
|
//TODO: need to do 2 variants for quads, one which have coloured, ones that dont, might be able to pull a spare bit
|
||||||
// at the end whether or not a pixel should be mixed with texture
|
// at the end whether or not a pixel should be mixed with texture
|
||||||
|
|
||||||
|
//TODO: CACHE THE BUILT MODEL AND REUSE AND JUST RENDER FROM DIFFERENT VIEWS
|
||||||
renderQuads(bb, model, new MatrixStack(), randomValue);
|
renderQuads(bb, model, new MatrixStack(), randomValue);
|
||||||
} else {
|
} else {
|
||||||
MinecraftClient.getInstance().getBlockRenderManager().renderFluid(BlockPos.ORIGIN, new BlockRenderView() {
|
MinecraftClient.getInstance().getBlockRenderManager().renderFluid(BlockPos.ORIGIN, new BlockRenderView() {
|
||||||
@@ -291,7 +293,7 @@ public class ModelTextureBakery {
|
|||||||
|
|
||||||
var mesh = bb.endNullable();
|
var mesh = bb.endNullable();
|
||||||
if (mesh != null)
|
if (mesh != null)
|
||||||
BudgetBufferRenderer.draw(mesh, texture, transform);
|
BudgetBufferRenderer.drawFast(mesh, texture, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void renderQuads(BufferBuilder builder, BlockStateModel model, MatrixStack stack, long randomValue) {
|
private static void renderQuads(BufferBuilder builder, BlockStateModel model, MatrixStack stack, long randomValue) {
|
||||||
|
|||||||
@@ -0,0 +1,375 @@
|
|||||||
|
package me.cortex.voxy.client.core.model.bakery;
|
||||||
|
|
||||||
|
import me.cortex.voxy.client.core.model.BudgetBufferRenderer;
|
||||||
|
import me.cortex.voxy.common.util.MemoryBuffer;
|
||||||
|
import net.minecraft.block.BlockState;
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
|
import net.minecraft.block.FluidBlock;
|
||||||
|
import net.minecraft.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.client.MinecraftClient;
|
||||||
|
import net.minecraft.client.render.RenderLayer;
|
||||||
|
import net.minecraft.client.render.RenderLayers;
|
||||||
|
import net.minecraft.client.render.VertexConsumer;
|
||||||
|
import net.minecraft.client.render.model.BakedQuad;
|
||||||
|
import net.minecraft.client.render.model.BlockStateModel;
|
||||||
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
|
import net.minecraft.fluid.FluidState;
|
||||||
|
import net.minecraft.util.Identifier;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.Direction;
|
||||||
|
import net.minecraft.util.math.RotationAxis;
|
||||||
|
import net.minecraft.util.math.random.LocalRandom;
|
||||||
|
import net.minecraft.world.BlockRenderView;
|
||||||
|
import net.minecraft.world.LightType;
|
||||||
|
import net.minecraft.world.biome.ColorResolver;
|
||||||
|
import net.minecraft.world.chunk.light.LightingProvider;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
|
import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BIT;
|
||||||
|
import static org.lwjgl.opengl.ARBShaderImageLoadStore.glMemoryBarrier;
|
||||||
|
import static org.lwjgl.opengl.GL11.*;
|
||||||
|
import static org.lwjgl.opengl.GL11C.GL_DEPTH;
|
||||||
|
import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate;
|
||||||
|
import static org.lwjgl.opengl.GL30.*;
|
||||||
|
import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfv;
|
||||||
|
|
||||||
|
public class ModelTextureBakery2 {
|
||||||
|
//Note: the first bit of metadata is if alpha discard is enabled
|
||||||
|
private static final Matrix4f[] VIEWS = new Matrix4f[6];
|
||||||
|
|
||||||
|
private final GlViewCapture capture;
|
||||||
|
private final ReuseVertexConsumer vc = new ReuseVertexConsumer();
|
||||||
|
private static final int FORMAT_STRIDE = 24;
|
||||||
|
private static final class ReuseVertexConsumer implements VertexConsumer {
|
||||||
|
private MemoryBuffer buffer = new MemoryBuffer(8192);
|
||||||
|
private long ptr;
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
public ReuseVertexConsumer() {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReuseVertexConsumer vertex(float x, float y, float z) {
|
||||||
|
this.ensureCanPut();
|
||||||
|
this.ptr += FORMAT_STRIDE; this.count++; //Goto next vertex
|
||||||
|
MemoryUtil.memPutFloat(this.ptr, x);
|
||||||
|
MemoryUtil.memPutFloat(this.ptr + 4, y);
|
||||||
|
MemoryUtil.memPutFloat(this.ptr + 8, z);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReuseVertexConsumer meta(int metadata) {
|
||||||
|
MemoryUtil.memPutInt(this.ptr + 12, metadata);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReuseVertexConsumer color(int red, int green, int blue, int alpha) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReuseVertexConsumer texture(float u, float v) {
|
||||||
|
MemoryUtil.memPutFloat(this.ptr + 16, u);
|
||||||
|
MemoryUtil.memPutFloat(this.ptr + 20, v);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReuseVertexConsumer overlay(int u, int v) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReuseVertexConsumer light(int u, int v) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReuseVertexConsumer normal(float x, float y, float z) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReuseVertexConsumer quad(BakedQuad quad, int metadata) {
|
||||||
|
int[] data = quad.vertexData();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
float x = Float.intBitsToFloat(data[i * 8]);
|
||||||
|
float y = Float.intBitsToFloat(data[i * 8 + 1]);
|
||||||
|
float z = Float.intBitsToFloat(data[i * 8 + 2]);
|
||||||
|
this.vertex(x,y,z);
|
||||||
|
float u = Float.intBitsToFloat(data[i * 8 + 4]);
|
||||||
|
float v = Float.intBitsToFloat(data[i * 8 + 5]);
|
||||||
|
this.texture(u,v);
|
||||||
|
|
||||||
|
this.meta(metadata);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureCanPut() {
|
||||||
|
if ((long) (this.count + 1) * FORMAT_STRIDE < this.buffer.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long offset = this.buffer.address-this.ptr;
|
||||||
|
//1.5x the size
|
||||||
|
var newBuffer = new MemoryBuffer((((int)(this.buffer.size*1.5)+FORMAT_STRIDE-1)/FORMAT_STRIDE)*FORMAT_STRIDE);
|
||||||
|
this.buffer.cpyTo(newBuffer.address);
|
||||||
|
this.buffer.free();
|
||||||
|
this.buffer = newBuffer;
|
||||||
|
this.ptr = offset + newBuffer.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReuseVertexConsumer reset() {
|
||||||
|
this.count = 0;
|
||||||
|
this.ptr = this.buffer.address - FORMAT_STRIDE;//the thing is first time this gets incremented by FORMAT_STRIDE
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void free() {
|
||||||
|
this.buffer.free();
|
||||||
|
this.buffer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int width;
|
||||||
|
private final int height;
|
||||||
|
public ModelTextureBakery2(int width, int height) {
|
||||||
|
this.capture = new GlViewCapture(width, height);
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bakeBlockModel(BlockState state, RenderLayer layer) {
|
||||||
|
var model = MinecraftClient.getInstance()
|
||||||
|
.getBakedModelManager()
|
||||||
|
.getBlockModels()
|
||||||
|
.getModel(state);
|
||||||
|
|
||||||
|
boolean hasDiscard = layer == RenderLayer.getCutout() ||
|
||||||
|
layer == RenderLayer.getCutoutMipped() ||
|
||||||
|
layer == RenderLayer.getTripwire();
|
||||||
|
|
||||||
|
for (Direction direction : new Direction[]{Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST, null}) {
|
||||||
|
for (var part : model.getParts(new LocalRandom(42L))) {
|
||||||
|
var quads = part.getQuads(direction);
|
||||||
|
for (var quad : quads) {
|
||||||
|
//TODO: add meta specifiying quad has a tint
|
||||||
|
|
||||||
|
int meta = hasDiscard?1:0;
|
||||||
|
this.vc.quad(quad, meta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void bakeFluidState(BlockState state, int face) {
|
||||||
|
MinecraftClient.getInstance().getBlockRenderManager().renderFluid(BlockPos.ORIGIN, new BlockRenderView() {
|
||||||
|
@Override
|
||||||
|
public float getBrightness(Direction direction, boolean shaded) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LightingProvider getLightingProvider() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLightLevel(LightType type, BlockPos pos) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getColor(BlockPos pos, ColorResolver colorResolver) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public BlockEntity getBlockEntity(BlockPos pos) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockState getBlockState(BlockPos pos) {
|
||||||
|
if (shouldReturnAirForFluid(pos, face)) {
|
||||||
|
return Blocks.AIR.getDefaultState();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fixme:
|
||||||
|
// This makes it so that the top face of water is always air, if this is commented out
|
||||||
|
// the up block will be a liquid state which makes the sides full
|
||||||
|
// if this is uncommented, that issue is fixed but e.g. stacking water layers ontop of eachother
|
||||||
|
// doesnt fill the side of the block
|
||||||
|
|
||||||
|
//if (pos.getY() == 1) {
|
||||||
|
// return Blocks.AIR.getDefaultState();
|
||||||
|
//}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FluidState getFluidState(BlockPos pos) {
|
||||||
|
if (shouldReturnAirForFluid(pos, face)) {
|
||||||
|
return Blocks.AIR.getDefaultState().getFluidState();
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.getFluidState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeight() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBottomY() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}, this.vc, state, state.getFluidState());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean shouldReturnAirForFluid(BlockPos pos, int face) {
|
||||||
|
var fv = Direction.byIndex(face).getVector();
|
||||||
|
int dot = fv.getX()*pos.getX() + fv.getY()*pos.getY() + fv.getZ()*pos.getZ();
|
||||||
|
return dot >= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void free() {
|
||||||
|
this.capture.free();
|
||||||
|
this.vc.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void renderToStream(BlockState state, int streamBuffer, int streamOffset) {
|
||||||
|
this.capture.clear();
|
||||||
|
boolean isBlock = true;
|
||||||
|
RenderLayer layer;
|
||||||
|
if (state.getBlock() instanceof FluidBlock) {
|
||||||
|
layer = RenderLayers.getFluidLayer(state.getFluidState());
|
||||||
|
isBlock = false;
|
||||||
|
} else {
|
||||||
|
layer = RenderLayers.getBlockLayer(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: support block model entities
|
||||||
|
//state.hasBlockEntity()
|
||||||
|
|
||||||
|
//Setup GL state
|
||||||
|
int[] viewdat = new int[4];
|
||||||
|
int blockTextureId;
|
||||||
|
|
||||||
|
{
|
||||||
|
glEnable(GL_STENCIL_TEST);
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
if (layer == RenderLayer.getTranslucent()) {
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFuncSeparate(GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
} else {
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
|
||||||
|
glStencilFunc(GL_ALWAYS, 1, 0xFF);
|
||||||
|
glStencilMask(0xFF);
|
||||||
|
|
||||||
|
glGetIntegerv(GL_VIEWPORT, viewdat);//TODO: faster way todo this, or just use main framebuffer resolution
|
||||||
|
|
||||||
|
//Bind the capture framebuffer
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
|
||||||
|
|
||||||
|
var tex = MinecraftClient.getInstance().getTextureManager().getTexture(Identifier.of("minecraft", "textures/atlas/blocks.png")).getGlTexture();
|
||||||
|
blockTextureId = ((net.minecraft.client.texture.GlTexture)tex).getGlId();
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: fastpath for blocks
|
||||||
|
if (isBlock) {
|
||||||
|
this.vc.reset();
|
||||||
|
this.bakeBlockModel(state, layer);
|
||||||
|
if (this.vc.count != 0) {//only render if there... is shit to render
|
||||||
|
if (this.vc.count % 4 != 0) throw new IllegalStateException();
|
||||||
|
|
||||||
|
//Setup for continual emission
|
||||||
|
BudgetBufferRenderer.setup(this.vc.buffer.address, this.vc.count / 4, blockTextureId);//note: this.vc.buffer.address NOT this.vc.ptr
|
||||||
|
|
||||||
|
var mat = new Matrix4f();
|
||||||
|
for (int i = 0; i < VIEWS.length; i++) {
|
||||||
|
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
|
||||||
|
|
||||||
|
//The projection matrix
|
||||||
|
mat.set(2, 0, 0, 0,
|
||||||
|
0, 2, 0, 0,
|
||||||
|
0, 0, -1f, 0,
|
||||||
|
-1, -1, 0, 1);
|
||||||
|
|
||||||
|
BudgetBufferRenderer.render(mat.mul(VIEWS[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glBindVertexArray(0);
|
||||||
|
} else {//Is fluid, slow path :(
|
||||||
|
if (!(state.getBlock() instanceof FluidBlock)) throw new IllegalStateException();
|
||||||
|
|
||||||
|
var mat = new Matrix4f();
|
||||||
|
for (int i = 0; i < VIEWS.length; i++) {
|
||||||
|
this.vc.reset();
|
||||||
|
this.bakeFluidState(state, i);
|
||||||
|
if (this.vc.count == 0) continue;
|
||||||
|
BudgetBufferRenderer.setup(this.vc.buffer.address, this.vc.count / 4, blockTextureId);
|
||||||
|
|
||||||
|
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
|
||||||
|
|
||||||
|
//The projection matrix
|
||||||
|
mat.set(2, 0, 0, 0,
|
||||||
|
0, 2, 0, 0,
|
||||||
|
0, 0, -1f, 0,
|
||||||
|
-1, -1, 0, 1);
|
||||||
|
|
||||||
|
BudgetBufferRenderer.render(mat.mul(VIEWS[i]));
|
||||||
|
}
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//"Restore" gl state
|
||||||
|
glViewport(viewdat[0], viewdat[1], viewdat[2], viewdat[3]);
|
||||||
|
glDisable(GL_STENCIL_TEST);
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
|
||||||
|
//Finish and download
|
||||||
|
glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);
|
||||||
|
this.capture.emitToStream(streamBuffer, streamOffset);
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id);
|
||||||
|
glClearDepth(1);
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static {
|
||||||
|
//TODO: FIXME: need to bake in the correct orientation, HOWEVER some orientations require a flipped winding order!!!!
|
||||||
|
|
||||||
|
addView(0, -90,0, 0, false);//Direction.DOWN
|
||||||
|
addView(1, 90,0, 0, false);//Direction.UP
|
||||||
|
addView(2, 0,180, 0, true);//Direction.NORTH
|
||||||
|
addView(3, 0,0, 0, false);//Direction.SOUTH
|
||||||
|
//TODO: check these arnt the wrong way round
|
||||||
|
addView(4, 0,90, 270, false);//Direction.EAST
|
||||||
|
addView(5, 0,270, 270, false);//Direction.WEST
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addView(int i, float pitch, float yaw, float rotation, boolean flipX) {
|
||||||
|
var stack = new MatrixStack();
|
||||||
|
stack.translate(0.5f,0.5f,0.5f);
|
||||||
|
stack.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(rotation));
|
||||||
|
stack.multiply(RotationAxis.POSITIVE_X.rotationDegrees(pitch));
|
||||||
|
stack.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw));
|
||||||
|
stack.translate(-0.5f,-0.5f,-0.5f);
|
||||||
|
VIEWS[i] = new Matrix4f(stack.peek().getPositionMatrix());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,7 +80,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
|||||||
this.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport);
|
this.viewportSelector = new ViewportSelector<>(this.sectionRenderer::createViewport);
|
||||||
this.renderGen = new RenderGenerationService(world, this.modelService, serviceThreadPool,
|
this.renderGen = new RenderGenerationService(world, this.modelService, serviceThreadPool,
|
||||||
this.geometryUpdateQueue::push, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets,
|
this.geometryUpdateQueue::push, this.sectionRenderer.getGeometryManager() instanceof IUsesMeshlets,
|
||||||
()->this.geometryUpdateQueue.count()<1000);
|
()->this.geometryUpdateQueue.count()<7000);
|
||||||
|
|
||||||
router.setCallbacks(this.renderGen::enqueueTask, section -> {
|
router.setCallbacks(this.renderGen::enqueueTask, section -> {
|
||||||
section.acquire();
|
section.acquire();
|
||||||
@@ -142,7 +142,7 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
|||||||
|
|
||||||
//if (this.modelService.getProcessingCount() < 750)
|
//if (this.modelService.getProcessingCount() < 750)
|
||||||
{//Very bad hack to try control things
|
{//Very bad hack to try control things
|
||||||
this.geometryUpdateQueue.consumeNano(1_000_000 - (System.nanoTime() - frameStart));
|
this.geometryUpdateQueue.consumeNano(1_500_000 - (System.nanoTime() - frameStart));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
|
this.nodeCleaner.tick(this.traversal.getNodeBuffer());//Probably do this here??
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ public class ServiceThreadPool {
|
|||||||
if (service == null) {
|
if (service == null) {
|
||||||
Logger.warn("No available jobs, sleeping releasing returning");
|
Logger.warn("No available jobs, sleeping releasing returning");
|
||||||
try {
|
try {
|
||||||
Thread.sleep(500);
|
Thread.sleep((long) (200*Math.random()+5));
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,21 +173,26 @@ public class ActiveSectionTracker {
|
|||||||
sec = section;
|
sec = section;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorldSection aa = null;
|
||||||
|
if (sec != null) {
|
||||||
|
long stamp2 = this.lruLock.writeLock();
|
||||||
|
WorldSection a = this.lruSecondaryCache.put(section.key, section);
|
||||||
|
if (a != null) {
|
||||||
|
throw new IllegalStateException("duplicate sections in cache is impossible");
|
||||||
|
}
|
||||||
|
//If cache is bigger than its ment to be, remove the least recently used and free it
|
||||||
|
if (this.lruSize < this.lruSecondaryCache.size()) {
|
||||||
|
aa = this.lruSecondaryCache.removeFirst();
|
||||||
|
}
|
||||||
|
this.lruLock.unlockWrite(stamp2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
lock.unlockWrite(stamp);
|
lock.unlockWrite(stamp);
|
||||||
|
|
||||||
if (sec != null) {
|
if (aa != null) {
|
||||||
stamp = this.lruLock.writeLock();
|
aa._releaseArray();
|
||||||
|
|
||||||
WorldSection a = this.lruSecondaryCache.put(section.key, section);
|
|
||||||
//If cache is bigger than its ment to be, remove the least recently used and free it
|
|
||||||
if (a == null && this.lruSize < this.lruSecondaryCache.size()) {
|
|
||||||
a = this.lruSecondaryCache.removeFirst();
|
|
||||||
}
|
|
||||||
this.lruLock.unlockWrite(stamp);
|
|
||||||
|
|
||||||
if (a != null) {
|
|
||||||
a._releaseArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
#version 430
|
#version 430
|
||||||
|
|
||||||
layout(location=0) uniform sampler2D tex;
|
layout(location=0) uniform sampler2D tex;
|
||||||
in vec2 texCoord;
|
|
||||||
in flat uint metadata;
|
in flat uint metadata;
|
||||||
|
in vec2 texCoord;
|
||||||
out vec4 colour;
|
out vec4 colour;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
colour = texture(tex, texCoord)*(metadata&1);
|
colour = texture(tex, texCoord);
|
||||||
if (colour.a <0.0001f) {
|
if (colour.a < 0.0001f && ((metadata&1u)!=0)) {
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
#version 430
|
#version 430
|
||||||
|
|
||||||
layout(location=0) in vec3 pos;
|
layout(location=0) in vec3 pos;
|
||||||
layout(location=1) in vec2 uv;
|
layout(location=1) in uint _metadata;
|
||||||
layout(location=2) in vec4 _metadata;
|
layout(location=2) in vec2 uv;
|
||||||
|
|
||||||
uniform mat4 transform;
|
layout(location=1) uniform mat4 transform;
|
||||||
out vec2 texCoord;
|
out vec2 texCoord;
|
||||||
out flat uint metadata;
|
out flat uint metadata;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
uvec4 meta = uvec4(_metadata*255);
|
metadata = _metadata;
|
||||||
metadata = (meta.r<<16)|(meta.g<<8)|(meta.b);
|
|
||||||
|
|
||||||
gl_Position = transform * vec4(pos, 1.0);
|
gl_Position = transform * vec4(pos, 1.0);
|
||||||
texCoord = uv;
|
texCoord = uv;
|
||||||
|
|||||||
Reference in New Issue
Block a user