Putting everything in context

- Begin context refactor.
- Rename (old) Context -> ContextShader and remove gl program method.
- Add (new) Context which does not need to be registered.
- Contexts are responsible for binding textures given a material.
- Instancers are created in a specific context.
- Add Shader interface for attaching textures.
- Add Textures interface for fetching textures to pass to a Shader.
- Remove texture related code from MaterialRenderState.
- Key culling groups by context.
- Key DrawSets by context.
This commit is contained in:
Jozufozu 2024-02-11 17:49:32 -08:00
parent e7d7602941
commit bab0448724
40 changed files with 419 additions and 185 deletions

View File

@ -19,7 +19,7 @@ import com.jozufozu.flywheel.impl.BackendManagerImpl;
import com.jozufozu.flywheel.impl.registry.IdRegistryImpl;
import com.jozufozu.flywheel.impl.registry.RegistryImpl;
import com.jozufozu.flywheel.impl.visualization.VisualizationEventHandler;
import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.context.ContextShaders;
import com.jozufozu.flywheel.lib.instance.InstanceTypes;
import com.jozufozu.flywheel.lib.material.CutoutShaders;
import com.jozufozu.flywheel.lib.material.FogShaders;
@ -128,7 +128,7 @@ public class Flywheel {
CutoutShaders.init();
FogShaders.init();
StandardMaterialShaders.init();
Contexts.init();
ContextShaders.init();
ShaderIndices.init();

View File

@ -3,6 +3,7 @@ package com.jozufozu.flywheel.api.backend;
import java.util.List;
import com.jozufozu.flywheel.api.BackendImplemented;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
@ -31,7 +32,7 @@ public interface Engine {
* @return An instancer for the given instance type, model, and render stage.
* @see InstancerProvider
*/
<I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, RenderStage stage);
<I extends Instance> Instancer<I> instancer(InstanceType<I> type, Context context, Model model, RenderStage stage);
/**
* Create a plan that will be executed every frame.

View File

@ -1,17 +1,16 @@
package com.jozufozu.flywheel.api.context;
import com.jozufozu.flywheel.api.internal.InternalFlywheelApi;
import com.jozufozu.flywheel.api.registry.Registry;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.resources.ResourceLocation;
import com.jozufozu.flywheel.api.material.Material;
public interface Context {
static Registry<Context> REGISTRY = InternalFlywheelApi.INSTANCE.createRegistry();
ContextShader contextShader();
void onProgramLink(GlProgram program);
ResourceLocation vertexShader();
ResourceLocation fragmentShader();
/**
* Prepare the shader for rendering with the given material and textures.
*
* @param material The material about to be rendered.
* @param shader The shader to prepare.
* @param textures Source of the textures to use.
*/
void prepare(Material material, Shader shader, Textures textures);
}

View File

@ -0,0 +1,14 @@
package com.jozufozu.flywheel.api.context;
import com.jozufozu.flywheel.api.internal.InternalFlywheelApi;
import com.jozufozu.flywheel.api.registry.Registry;
import net.minecraft.resources.ResourceLocation;
public interface ContextShader {
static Registry<ContextShader> REGISTRY = InternalFlywheelApi.INSTANCE.createRegistry();
ResourceLocation vertexShader();
ResourceLocation fragmentShader();
}

View File

@ -0,0 +1,8 @@
package com.jozufozu.flywheel.api.context;
import com.jozufozu.flywheel.api.BackendImplemented;
@BackendImplemented
public interface Shader {
void setTexture(String glslName, Texture texture);
}

View File

@ -0,0 +1,8 @@
package com.jozufozu.flywheel.api.context;
import com.jozufozu.flywheel.api.BackendImplemented;
@BackendImplemented
public interface Texture {
void filter(boolean blur, boolean mipmap);
}

View File

@ -0,0 +1,32 @@
package com.jozufozu.flywheel.api.context;
import com.jozufozu.flywheel.api.BackendImplemented;
import net.minecraft.resources.ResourceLocation;
@BackendImplemented
public interface Textures {
/**
* Get a built-in texture by its resource location.
*
* @param texture The texture's resource location.
* @return The texture.
*/
Texture byName(ResourceLocation texture);
/**
* Get the overlay texture.
*
* @return The overlay texture.
*/
Texture overlay();
/**
* Get the light texture.
*
* @return The light texture.
*/
Texture light();
// TODO: Allow creating dynamic textures.
}

View File

@ -7,7 +7,7 @@ import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.context.ContextShader;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.backend.ShaderIndices;
import com.jozufozu.flywheel.backend.compile.component.UberShaderComponent;
@ -46,9 +46,9 @@ public final class FlwPrograms {
private static ImmutableList<PipelineProgramKey> createPipelineKeys() {
ImmutableList.Builder<PipelineProgramKey> builder = ImmutableList.builder();
for (Context context : Context.REGISTRY) {
for (ContextShader contextShader : ContextShader.REGISTRY) {
for (InstanceType<?> instanceType : InstanceType.REGISTRY) {
builder.add(new PipelineProgramKey(instanceType, context));
builder.add(new PipelineProgramKey(instanceType, contextShader));
}
}
return builder.build();

View File

@ -7,7 +7,7 @@ import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.context.ContextShader;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.backend.compile.component.IndirectComponent;
import com.jozufozu.flywheel.backend.compile.core.CompilationHarness;
@ -121,7 +121,7 @@ public class IndirectPrograms extends AbstractPrograms {
return instance != null;
}
public GlProgram getIndirectProgram(InstanceType<?> instanceType, Context contextShader) {
public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader) {
return pipeline.get(new PipelineProgramKey(instanceType, contextShader));
}

View File

@ -6,7 +6,7 @@ import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.context.ContextShader;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.glsl.ShaderSources;
@ -61,7 +61,7 @@ public class InstancingPrograms extends AbstractPrograms {
return instance != null;
}
public GlProgram get(InstanceType<?> instanceType, Context contextShader) {
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader) {
return pipeline.get(new PipelineProgramKey(instanceType, contextShader));
}

View File

@ -50,8 +50,6 @@ public class PipelineCompiler {
.fragmentShader())
.withResource(pipeline.fragmentMain()))
.then((key, program) -> {
key.contextShader()
.onProgramLink(program);
program.setUniformBlockBinding("_FlwFrameUniforms", 0);
program.setUniformBlockBinding("_FlwFogUniforms", 1);
})

View File

@ -1,6 +1,6 @@
package com.jozufozu.flywheel.backend.compile;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.context.ContextShader;
import com.jozufozu.flywheel.api.instance.InstanceType;
/**
@ -9,5 +9,5 @@ import com.jozufozu.flywheel.api.instance.InstanceType;
* @param instanceType The instance shader to use.
* @param contextShader The context shader to use.
*/
public record PipelineProgramKey(InstanceType<?> instanceType, Context contextShader) {
public record PipelineProgramKey(InstanceType<?> instanceType, ContextShader contextShader) {
}

View File

@ -1,6 +1,7 @@
package com.jozufozu.flywheel.backend.engine;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
@ -21,8 +22,8 @@ public abstract class AbstractEngine implements Engine {
}
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, RenderStage stage) {
return getStorage().getInstancer(type, model, stage);
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Context context, Model model, RenderStage stage) {
return getStorage().getInstancer(type, context, model, stage);
}
@Override

View File

@ -2,6 +2,7 @@ package com.jozufozu.flywheel.backend.engine;
import java.util.ArrayList;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
@ -9,6 +10,7 @@ import com.jozufozu.flywheel.lib.util.AtomicBitset;
public abstract class AbstractInstancer<I extends Instance> implements Instancer<I> {
public final InstanceType<I> type;
public final Context context;
// Lock for all instances, only needs to be used in methods that may run on the TaskExecutor.
protected final Object lock = new Object();
@ -18,8 +20,9 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
protected final AtomicBitset changed = new AtomicBitset();
protected final AtomicBitset deleted = new AtomicBitset();
protected AbstractInstancer(InstanceType<I> type) {
protected AbstractInstancer(InstanceType<I> type, Context context) {
this.type = type;
this.context = context;
}
@Override

View File

@ -1,9 +1,10 @@
package com.jozufozu.flywheel.backend.engine;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.model.Model;
public record InstancerKey<I extends Instance>(InstanceType<I> type, Model model, RenderStage stage) {
public record InstancerKey<I extends Instance>(InstanceType<I> type, Context context, Model model, RenderStage stage) {
}

View File

@ -6,6 +6,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
@ -27,8 +28,8 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
protected final List<UninitializedInstancer<N, ?>> initializationQueue = new ArrayList<>();
@SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> getInstancer(InstanceType<I> type, Model model, RenderStage stage) {
return (Instancer<I>) instancers.computeIfAbsent(new InstancerKey<>(type, model, stage), this::createAndDeferInit);
public <I extends Instance> Instancer<I> getInstancer(InstanceType<I> type, Context context, Model model, RenderStage stage) {
return (Instancer<I>) instancers.computeIfAbsent(new InstancerKey<>(type, context, model, stage), this::createAndDeferInit);
}
public void delete() {
@ -50,12 +51,12 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
.forEach(AbstractInstancer::clear);
}
protected abstract <I extends Instance> N create(InstanceType<I> type);
protected abstract <I extends Instance> N create(InstancerKey<I> type);
protected abstract <I extends Instance> void initialize(InstancerKey<I> key, N instancer);
private N createAndDeferInit(InstancerKey<?> key) {
var out = create(key.type());
var out = create(key);
// Only queue the instancer for initialization if it has anything to render.
if (key.model()

View File

@ -8,13 +8,9 @@ import com.jozufozu.flywheel.api.material.DepthTest;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.material.Transparency;
import com.jozufozu.flywheel.api.material.WriteMask;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.AbstractTexture;
public final class MaterialRenderState {
public static final Comparator<Material> COMPARATOR = Comparator.comparing(Material::texture)
.thenComparing(Material::blur)
@ -29,7 +25,6 @@ public final class MaterialRenderState {
}
public static void setup(Material material) {
setupTexture(material);
setupBackfaceCulling(material.backfaceCulling());
setupPolygonOffset(material.polygonOffset());
setupDepthTest(material.depthTest());
@ -37,17 +32,6 @@ public final class MaterialRenderState {
setupWriteMask(material.writeMask());
}
private static void setupTexture(Material material) {
GlTextureUnit.T0.makeActive();
AbstractTexture texture = Minecraft.getInstance()
.getTextureManager()
.getTexture(material.texture());
texture.setFilter(material.blur(), material.mipmap());
var textureId = texture.getId();
RenderSystem.setShaderTexture(0, textureId);
RenderSystem.bindTexture(textureId);
}
private static void setupBackfaceCulling(boolean backfaceCulling) {
if (backfaceCulling) {
RenderSystem.enableCull();
@ -141,7 +125,6 @@ public final class MaterialRenderState {
}
public static void reset() {
resetTexture();
resetBackfaceCulling();
resetPolygonOffset();
resetDepthTest();
@ -149,11 +132,6 @@ public final class MaterialRenderState {
resetWriteMask();
}
private static void resetTexture() {
GlTextureUnit.T0.makeActive();
RenderSystem.setShaderTexture(0, 0);
}
private static void resetBackfaceCulling() {
RenderSystem.enableCull();
}

View File

@ -16,6 +16,7 @@ import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
@ -24,11 +25,13 @@ import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.backend.compile.IndirectPrograms;
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
import com.jozufozu.flywheel.backend.engine.textures.TextureBinder;
import com.jozufozu.flywheel.backend.engine.textures.TexturesImpl;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.Driver;
import com.jozufozu.flywheel.backend.gl.GlCompat;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.context.SimpleContextShader;
public class IndirectCullingGroup<I extends Instance> {
private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::stage)
@ -37,6 +40,7 @@ public class IndirectCullingGroup<I extends Instance> {
private static final int DRAW_BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT;
private final InstanceType<I> instanceType;
private final Context context;
private final long objectStride;
private final IndirectBuffers buffers;
private final IndirectMeshPool meshPool;
@ -53,17 +57,19 @@ public class IndirectCullingGroup<I extends Instance> {
private boolean hasNewDraws;
private int instanceCountThisFrame;
IndirectCullingGroup(InstanceType<I> instanceType, IndirectPrograms programs) {
IndirectCullingGroup(InstanceType<I> instanceType, Context context, IndirectPrograms programs) {
this.instanceType = instanceType;
this.context = context;
objectStride = instanceType.layout()
.byteSize() + IndirectBuffers.INT_SIZE;
buffers = new IndirectBuffers(objectStride);
meshPool = new IndirectMeshPool();
this.programs = programs;
// TODO: Culling programs need to be context aware.
cullProgram = programs.getCullingProgram(instanceType);
applyProgram = programs.getApplyProgram();
drawProgram = programs.getIndirectProgram(instanceType, Contexts.DEFAULT);
drawProgram = programs.getIndirectProgram(instanceType, context.contextShader());
}
public void flush(StagingBuffer stagingBuffer) {
@ -179,7 +185,7 @@ public class IndirectCullingGroup<I extends Instance> {
hasNewDraws = true;
}
public void submit(RenderStage stage) {
public void submit(RenderStage stage, TexturesImpl textures) {
if (nothingToDo(stage)) {
return;
}
@ -195,12 +201,17 @@ public class IndirectCullingGroup<I extends Instance> {
for (var multiDraw : multiDraws.get(stage)) {
glUniform1ui(flwBaseDraw, multiDraw.start);
context.prepare(multiDraw.material, drawProgram, textures);
MaterialRenderState.setup(multiDraw.material);
multiDraw.submit();
TextureBinder.resetTextureBindings();
}
}
public void bindForCrumbling() {
var program = programs.getIndirectProgram(instanceType, Contexts.CRUMBLING);
public void bindWithContextShader(SimpleContextShader override) {
var program = programs.getIndirectProgram(instanceType, override);
program.bind();
@ -265,16 +276,14 @@ public class IndirectCullingGroup<I extends Instance> {
}
private record MultiDraw(Material material, int start, int end) {
void submit() {
MaterialRenderState.setup(material);
private void submit() {
if (GlCompat.DRIVER == Driver.INTEL) {
// Intel renders garbage with MDI, but Consecutive Normal Draws works fine.
for (int i = start; i < end; i++) {
for (int i = this.start; i < this.end; i++) {
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, i * IndirectBuffers.DRAW_COMMAND_STRIDE);
}
} else {
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, start * IndirectBuffers.DRAW_COMMAND_STRIDE, end - start, (int) IndirectBuffers.DRAW_COMMAND_STRIDE);
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, this.start * IndirectBuffers.DRAW_COMMAND_STRIDE, this.end - this.start, (int) IndirectBuffers.DRAW_COMMAND_STRIDE);
}
}
}

View File

@ -12,6 +12,7 @@ import java.util.List;
import java.util.Map;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
@ -21,8 +22,10 @@ import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
import com.jozufozu.flywheel.backend.engine.textures.TexturesImpl;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.lib.context.ContextShaders;
import com.jozufozu.flywheel.lib.material.SimpleMaterial;
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import com.jozufozu.flywheel.lib.util.Pair;
@ -34,7 +37,8 @@ import net.minecraft.client.resources.model.ModelBakery;
public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>> {
private final IndirectPrograms programs;
private final StagingBuffer stagingBuffer;
private final Map<InstanceType<?>, IndirectCullingGroup<?>> cullingGroups = new HashMap<>();
private final TexturesImpl textures = new TexturesImpl();
private final Map<GroupKey<?>, IndirectCullingGroup<?>> cullingGroups = new HashMap<>();
private final GlBuffer crumblingDrawBuffer = new GlBuffer();
public IndirectDrawManager(IndirectPrograms programs) {
@ -43,14 +47,15 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
}
@Override
protected <I extends Instance> IndirectInstancer<?> create(InstanceType<I> type) {
return new IndirectInstancer<>(type);
protected <I extends Instance> IndirectInstancer<?> create(InstancerKey<I> key) {
return new IndirectInstancer<>(key.type(), key.context());
}
@SuppressWarnings("unchecked")
@Override
protected <I extends Instance> void initialize(InstancerKey<I> key, IndirectInstancer<?> instancer) {
var group = (IndirectCullingGroup<I>) cullingGroups.computeIfAbsent(key.type(), t -> new IndirectCullingGroup<>(t, programs));
var groupKey = new GroupKey<>(key.type(), key.context());
var group = (IndirectCullingGroup<I>) cullingGroups.computeIfAbsent(groupKey, t -> new IndirectCullingGroup<>(t.type, t.context, programs));
group.add((IndirectInstancer<I>) instancer, key.model(), key.stage());
}
@ -65,7 +70,7 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
public void renderStage(RenderStage stage) {
for (var group : cullingGroups.values()) {
group.submit(stage);
group.submit(stage, textures);
}
}
@ -123,7 +128,7 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
// Set up the crumbling program buffers. Nothing changes here between draws.
cullingGroups.get(instanceTypeEntry.getKey())
.bindForCrumbling();
.bindWithContextShader(ContextShaders.CRUMBLING);
for (var progressEntry : byProgress.int2ObjectEntrySet()) {
for (var instanceHandlePair : progressEntry.getValue()) {
@ -156,8 +161,8 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
block.free();
}
private static Map<InstanceType<?>, Int2ObjectMap<List<Pair<IndirectInstancer<?>, InstanceHandleImpl>>>> doCrumblingSort(List<Engine.CrumblingBlock> crumblingBlocks) {
Map<InstanceType<?>, Int2ObjectMap<List<Pair<IndirectInstancer<?>, InstanceHandleImpl>>>> byType = new HashMap<>();
private static Map<GroupKey<?>, Int2ObjectMap<List<Pair<IndirectInstancer<?>, InstanceHandleImpl>>>> doCrumblingSort(List<Engine.CrumblingBlock> crumblingBlocks) {
Map<GroupKey<?>, Int2ObjectMap<List<Pair<IndirectInstancer<?>, InstanceHandleImpl>>>> byType = new HashMap<>();
for (Engine.CrumblingBlock block : crumblingBlocks) {
int progress = block.progress();
@ -176,11 +181,14 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
continue;
}
byType.computeIfAbsent(instancer.type, $ -> new Int2ObjectArrayMap<>())
byType.computeIfAbsent(new GroupKey<>(instancer.type, instancer.context), $ -> new Int2ObjectArrayMap<>())
.computeIfAbsent(progress, $ -> new ArrayList<>())
.add(Pair.of(instancer, impl));
}
}
return byType;
}
public record GroupKey<I extends Instance>(InstanceType<I> type, Context context) {
}
}

View File

@ -13,15 +13,9 @@ import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.jozufozu.flywheel.lib.task.Flag;
import com.jozufozu.flywheel.lib.task.NamedFlag;
import com.jozufozu.flywheel.lib.task.SyncedPlan;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
public class IndirectEngine extends AbstractEngine {
private final IndirectPrograms programs;
@ -60,23 +54,9 @@ public class IndirectEngine extends AbstractEngine {
}
try (var restoreState = GlStateTracker.getRestoreState()) {
GameRenderer gameRenderer = Minecraft.getInstance().gameRenderer;
int prevActiveTexture = GlStateManager._getActiveTexture();
gameRenderer.overlayTexture().setupOverlayColor();
gameRenderer.lightTexture().turnOnLightLayer();
GlTextureUnit.T1.makeActive();
RenderSystem.bindTexture(RenderSystem.getShaderTexture(1));
GlTextureUnit.T2.makeActive();
RenderSystem.bindTexture(RenderSystem.getShaderTexture(2));
drawManager.renderStage(stage);
MaterialRenderState.reset();
gameRenderer.overlayTexture().teardownOverlayColor();
gameRenderer.lightTexture().turnOffLightLayer();
GlStateManager._activeTexture(prevActiveTexture);
}
}

View File

@ -5,6 +5,7 @@ import java.util.List;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.InstanceWriter;
@ -18,8 +19,8 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
private long lastStartPos = -1;
public IndirectInstancer(InstanceType<I> type) {
super(type);
public IndirectInstancer(InstanceType<I> type, Context context) {
super(type, context);
this.objectStride = type.layout()
.byteSize() + IndirectBuffers.INT_SIZE;
writer = this.type.writer();

View File

@ -1,5 +1,7 @@
package com.jozufozu.flywheel.backend.engine.instancing;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.backend.InternalVertex;
import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
import com.jozufozu.flywheel.backend.gl.array.GlVertexArray;
@ -9,7 +11,9 @@ public class DrawCall {
private final InstancedInstancer<?> instancer;
private final InstancedMeshPool.BufferedMesh mesh;
@Nullable
private GlVertexArray vao;
@Nullable
private GlVertexArray vaoScratch;
public DrawCall(InstancedInstancer<?> instancer, InstancedMeshPool.BufferedMesh mesh, ShaderState shaderState) {

View File

@ -13,7 +13,7 @@ import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.context.ContextShaders;
import com.jozufozu.flywheel.lib.material.SimpleMaterial;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
@ -46,7 +46,7 @@ public class InstancedCrumbling {
CommonCrumbling.applyCrumblingProperties(crumblingMaterial, baseMaterial);
var program = programs.get(shader.instanceType(), Contexts.CRUMBLING);
var program = programs.get(shader.instanceType(), ContextShaders.CRUMBLING);
program.bind();
Uniforms.bindForDraw();

View File

@ -10,7 +10,6 @@ import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.model.Mesh;
import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
@ -56,8 +55,8 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
}
@Override
protected <I extends Instance> InstancedInstancer<I> create(InstanceType<I> type) {
return new InstancedInstancer<>(type);
protected <I extends Instance> InstancedInstancer<I> create(InstancerKey<I> key) {
return new InstancedInstancer<>(key.type(), key.context());
}
@Override
@ -71,7 +70,7 @@ public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>
for (var entry : meshes.entrySet()) {
var mesh = alloc(entry.getValue());
ShaderState shaderState = new ShaderState(entry.getKey(), instancer.type);
ShaderState shaderState = new ShaderState(entry.getKey(), key.type(), key.context());
DrawCall drawCall = new DrawCall(instancer, mesh, shaderState);
drawSet.put(shaderState, drawCall);

View File

@ -5,7 +5,10 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.InstanceWriter;
@ -23,12 +26,13 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
private final Set<GlVertexArray> boundTo = new HashSet<>();
private final InstanceWriter<I> writer;
@Nullable
private GlBuffer vbo;
private final List<DrawCall> drawCalls = new ArrayList<>();
public InstancedInstancer(InstanceType<I> type) {
super(type);
public InstancedInstancer(InstanceType<I> type, Context context) {
super(type, context);
var layout = type.layout();
instanceAttributes = LayoutAttributes.attributes(layout);
instanceStride = layout.byteSize();
@ -68,7 +72,7 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
}
private void updateBuffer() {
if (changed.isEmpty()) {
if (changed.isEmpty() || vbo == null) {
return;
}
@ -120,6 +124,9 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
}
public void delete() {
if (vbo == null) {
return;
}
vbo.delete();
vbo = null;
}

View File

@ -16,22 +16,18 @@ import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.backend.engine.MaterialEncoder;
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
import com.jozufozu.flywheel.backend.engine.textures.TextureBinder;
import com.jozufozu.flywheel.backend.engine.textures.TexturesImpl;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.lib.context.Contexts;
import com.jozufozu.flywheel.lib.task.Flag;
import com.jozufozu.flywheel.lib.task.NamedFlag;
import com.jozufozu.flywheel.lib.task.SyncedPlan;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
public class InstancingEngine extends AbstractEngine {
private final InstancingPrograms programs;
private final TexturesImpl textures = new TexturesImpl();
private final InstancedDrawManager drawManager = new InstancedDrawManager();
private final Flag flushFlag = new NamedFlag("flushed");
@ -68,21 +64,7 @@ public class InstancingEngine extends AbstractEngine {
}
try (var state = GlStateTracker.getRestoreState()) {
GameRenderer gameRenderer = Minecraft.getInstance().gameRenderer;
int prevActiveTexture = GlStateManager._getActiveTexture();
gameRenderer.overlayTexture().setupOverlayColor();
gameRenderer.lightTexture().turnOnLightLayer();
GlTextureUnit.T1.makeActive();
RenderSystem.bindTexture(RenderSystem.getShaderTexture(1));
GlTextureUnit.T2.makeActive();
RenderSystem.bindTexture(RenderSystem.getShaderTexture(2));
render(drawSet);
gameRenderer.overlayTexture().teardownOverlayColor();
gameRenderer.lightTexture().turnOffLightLayer();
GlStateManager._activeTexture(prevActiveTexture);
}
}
@ -116,17 +98,21 @@ public class InstancingEngine extends AbstractEngine {
continue;
}
var program = programs.get(shader.instanceType(), Contexts.DEFAULT);
var program = programs.get(shader.instanceType(), shader.context()
.contextShader());
program.bind();
Uniforms.bindForDraw();
uploadMaterialUniform(program, shader.material());
shader.context()
.prepare(shader.material(), program, textures);
MaterialRenderState.setup(shader.material());
for (var drawCall : drawCalls) {
drawCall.render();
}
TextureBinder.resetTextureBindings();
}
MaterialRenderState.reset();

View File

@ -1,7 +1,8 @@
package com.jozufozu.flywheel.backend.engine.instancing;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.material.Material;
public record ShaderState(Material material, InstanceType<?> instanceType) {
public record ShaderState(Material material, InstanceType<?> instanceType, Context context) {
}

View File

@ -0,0 +1,13 @@
package com.jozufozu.flywheel.backend.engine.textures;
import com.jozufozu.flywheel.api.context.Texture;
/**
* Internal base interface that {@link com.jozufozu.flywheel.backend.gl.shader.GlProgram GlProgram} expects.
*/
public interface IdentifiedTexture extends Texture {
/**
* @return The GL texture id of this texture.
*/
int id();
}

View File

@ -0,0 +1,41 @@
package com.jozufozu.flywheel.backend.engine.textures;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import com.mojang.blaze3d.systems.RenderSystem;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
public class TextureBinder {
// TODO: some kind of cache eviction when the program changes
// so we don't always reset and bind the light and overlay textures?
private static final Int2IntMap texturesToSamplerUnits = new Int2IntArrayMap();
private static int nextSamplerUnit = 0;
/**
* Binds the given texture to the next available texture unit, returning the unit it was bound to.
*
* @param id The id of the texture to bind.
* @return The texture unit the texture was bound to.
*/
public static int bindTexture(int id) {
return texturesToSamplerUnits.computeIfAbsent(id, i -> {
int unit = nextSamplerUnit++;
RenderSystem.activeTexture(GL_TEXTURE0 + unit);
RenderSystem.bindTexture(i);
return unit;
});
}
public static void resetTextureBindings() {
nextSamplerUnit = 0;
for (Int2IntMap.Entry entry : texturesToSamplerUnits.int2IntEntrySet()) {
RenderSystem.activeTexture(GL_TEXTURE0 + entry.getIntValue());
RenderSystem.bindTexture(0);
}
texturesToSamplerUnits.clear();
}
}

View File

@ -0,0 +1,66 @@
package com.jozufozu.flywheel.backend.engine.textures;
import java.util.HashMap;
import java.util.Map;
import com.jozufozu.flywheel.api.context.Texture;
import com.jozufozu.flywheel.api.context.Textures;
import com.jozufozu.flywheel.backend.mixin.LightTextureAccessor;
import com.jozufozu.flywheel.backend.mixin.OverlayTextureAccessor;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.AbstractTexture;
import net.minecraft.resources.ResourceLocation;
public class TexturesImpl implements Textures {
private final DirectTexture lightTexture;
private final DirectTexture overlayTexture;
private final Map<ResourceLocation, WrappedTexture> wrappers = new HashMap<>();
public TexturesImpl() {
var gameRenderer = Minecraft.getInstance().gameRenderer;
this.lightTexture = new DirectTexture(((LightTextureAccessor) gameRenderer.lightTexture()).flywheel$texture()
.getId());
this.overlayTexture = new DirectTexture(((OverlayTextureAccessor) gameRenderer.overlayTexture()).flywheel$texture()
.getId());
}
@Override
public Texture byName(ResourceLocation texture) {
return wrappers.computeIfAbsent(texture, key -> new WrappedTexture(Minecraft.getInstance()
.getTextureManager()
.getTexture(key)));
}
@Override
public Texture overlay() {
return overlayTexture;
}
@Override
public Texture light() {
return lightTexture;
}
public record WrappedTexture(AbstractTexture texture) implements IdentifiedTexture {
@Override
public void filter(boolean blur, boolean mipmap) {
texture.setFilter(blur, mipmap);
}
@Override
public int id() {
return texture.getId();
}
}
public record DirectTexture(int id) implements IdentifiedTexture {
@Override
public void filter(boolean blur, boolean mipmap) {
// no-op
}
}
}

View File

@ -36,7 +36,7 @@ public class GlStateTracker {
}
public static State getRestoreState() {
return new State(BUFFERS.clone(), vao, program);
return new State(BUFFERS.clone(), vao, program, GlStateManager._getActiveTexture());
}
public static void bindVao(int vao) {
@ -51,7 +51,7 @@ public class GlStateTracker {
}
}
public record State(int[] buffers, int vao, int program) implements AutoCloseable {
public record State(int[] buffers, int vao, int program, int activeTexture) implements AutoCloseable {
public void restore() {
if (vao != GlStateTracker.vao) {
GlStateManager._glBindVertexArray(vao);
@ -68,6 +68,8 @@ public class GlStateTracker {
if (program != GlStateTracker.program) {
GlStateManager._glUseProgram(program);
}
GlStateManager._activeTexture(activeTexture);
}
@Override

View File

@ -9,6 +9,10 @@ import static org.lwjgl.opengl.GL31.glUniformBlockBinding;
import org.slf4j.Logger;
import com.jozufozu.flywheel.api.context.Shader;
import com.jozufozu.flywheel.api.context.Texture;
import com.jozufozu.flywheel.backend.engine.textures.IdentifiedTexture;
import com.jozufozu.flywheel.backend.engine.textures.TextureBinder;
import com.jozufozu.flywheel.backend.gl.GlObject;
import com.mojang.blaze3d.shaders.ProgramManager;
import com.mojang.logging.LogUtils;
@ -16,7 +20,7 @@ import com.mojang.logging.LogUtils;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
public class GlProgram extends GlObject {
public class GlProgram extends GlObject implements Shader {
private static final Logger LOGGER = LogUtils.getLogger();
private final Object2IntMap<String> uniformLocationCache = new Object2IntOpenHashMap<>();
@ -33,6 +37,24 @@ public class GlProgram extends GlObject {
ProgramManager.glUseProgram(0);
}
@Override
public void setTexture(String glslName, Texture texture) {
if (!(texture instanceof IdentifiedTexture identified)) {
return;
}
int uniform = getUniformLocation(glslName);
if (uniform < 0) {
return;
}
int id = identified.id();
int binding = TextureBinder.bindTexture(id);
glUniform1i(uniform, binding);
}
/**
* Retrieves the index of the uniform with the given name.
*

View File

@ -0,0 +1,13 @@
package com.jozufozu.flywheel.backend.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.texture.DynamicTexture;
@Mixin(LightTexture.class)
public interface LightTextureAccessor {
@Accessor("lightTexture")
DynamicTexture flywheel$texture();
}

View File

@ -0,0 +1,13 @@
package com.jozufozu.flywheel.backend.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.OverlayTexture;
@Mixin(OverlayTexture.class)
public interface OverlayTextureAccessor {
@Accessor("texture")
DynamicTexture flywheel$texture();
}

View File

@ -10,12 +10,13 @@ import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.instance.InstancerProvider;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.lib.context.Contexts;
public record InstancerProviderImpl(Engine engine,
RenderStage renderStage) implements InstancerProvider, Supplier<VisualizationContext> {
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model) {
return engine.instancer(type, model, renderStage);
return engine.instancer(type, Contexts.DEFAULT, model, renderStage);
}
@Override

View File

@ -0,0 +1,18 @@
package com.jozufozu.flywheel.lib.context;
import org.jetbrains.annotations.ApiStatus;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.context.ContextShader;
public class ContextShaders {
public static final SimpleContextShader DEFAULT = ContextShader.REGISTRY.registerAndGet(new SimpleContextShader(Flywheel.rl("context/default.vert"), Flywheel.rl("context/default.frag")));
public static final SimpleContextShader CRUMBLING = ContextShader.REGISTRY.registerAndGet(new SimpleContextShader(Flywheel.rl("context/crumbling.vert"), Flywheel.rl("context/crumbling.frag")));
private ContextShaders() {
}
@ApiStatus.Internal
public static void init() {
}
}

View File

@ -1,37 +1,48 @@
package com.jozufozu.flywheel.lib.context;
import org.jetbrains.annotations.ApiStatus;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.api.context.ContextShader;
import com.jozufozu.flywheel.api.context.Shader;
import com.jozufozu.flywheel.api.context.Textures;
import com.jozufozu.flywheel.api.material.Material;
import net.minecraft.client.resources.model.ModelBakery;
public final class Contexts {
public static final SimpleContext DEFAULT = Context.REGISTRY.registerAndGet(new SimpleContext(
Flywheel.rl("context/default.vert"),
Flywheel.rl("context/default.frag"),
program -> {
program.bind();
program.setSamplerBinding("_flw_diffuseTex", 0);
program.setSamplerBinding("_flw_overlayTex", 1);
program.setSamplerBinding("_flw_lightTex", 2);
GlProgram.unbind();
}));
public static final Context DEFAULT = new Context() {
@Override
public ContextShader contextShader() {
return ContextShaders.DEFAULT;
}
public static final SimpleContext CRUMBLING = Context.REGISTRY.registerAndGet(new SimpleContext(
Flywheel.rl("context/crumbling.vert"),
Flywheel.rl("context/crumbling.frag"),
program -> {
program.bind();
program.setSamplerBinding("_flw_crumblingTex", 0);
program.setSamplerBinding("_flw_diffuseTex", 1);
GlProgram.unbind();
}));
@Override
public void prepare(Material material, Shader shader, Textures textures) {
var texture = textures.byName(material.texture());
texture.filter(material.blur(), material.mipmap());
shader.setTexture("_flw_diffuseTex", texture);
shader.setTexture("_flw_overlayTex", textures.overlay());
shader.setTexture("_flw_lightTex", textures.light());
}
};
public static final Context CRUMBLING = new Context() {
@Override
public ContextShader contextShader() {
return ContextShaders.CRUMBLING;
}
@Override
public void prepare(Material material, Shader shader, Textures textures) {
var texture = textures.byName(material.texture());
texture.filter(material.blur(), material.mipmap());
shader.setTexture("_flw_diffuseTex", texture);
var crumblingTexture = textures.byName(ModelBakery.BREAKING_LOCATIONS.get(0));
shader.setTexture("_flw_crumblingTex", crumblingTexture);
}
};
private Contexts() {
}
@ApiStatus.Internal
public static void init() {
}
}

View File

@ -1,25 +0,0 @@
package com.jozufozu.flywheel.lib.context;
import java.util.function.Consumer;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.resources.ResourceLocation;
public record SimpleContext(ResourceLocation vertexShader, ResourceLocation fragmentShader, Consumer<GlProgram> onLink) implements Context {
@Override
public void onProgramLink(GlProgram program) {
onLink.accept(program);
}
@Override
public ResourceLocation vertexShader() {
return vertexShader;
}
@Override
public ResourceLocation fragmentShader() {
return fragmentShader;
}
}

View File

@ -0,0 +1,18 @@
package com.jozufozu.flywheel.lib.context;
import com.jozufozu.flywheel.api.context.ContextShader;
import net.minecraft.resources.ResourceLocation;
public record SimpleContextShader(ResourceLocation vertexShader,
ResourceLocation fragmentShader) implements ContextShader {
@Override
public ResourceLocation vertexShader() {
return vertexShader;
}
@Override
public ResourceLocation fragmentShader() {
return fragmentShader;
}
}

View File

@ -6,6 +6,8 @@
"refmap": "flywheel.refmap.json",
"client": [
"GlStateManagerMixin",
"LightTextureAccessor",
"OverlayTextureAccessor",
"RenderSystemMixin"
],
"injectors": {