mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-02-03 16:54:57 +01:00
Reject eagerness return to lazy
- Eagerly load ALL shaders in ShaderSources, resolving imports there - Compile and cache programs on-demand - Move gl state try blocks to EngineImpl - EngineImpl catches shader exceptions and triggers a fallback
This commit is contained in:
parent
cb58f6075e
commit
27e5b609af
13 changed files with 293 additions and 260 deletions
|
@ -6,11 +6,7 @@ import org.jetbrains.annotations.Nullable;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import dev.engine_room.flywheel.api.Flywheel;
|
||||
import dev.engine_room.flywheel.api.instance.InstanceType;
|
||||
import dev.engine_room.flywheel.api.material.LightShader;
|
||||
import dev.engine_room.flywheel.backend.MaterialShaderIndices;
|
||||
import dev.engine_room.flywheel.backend.compile.component.UberShaderComponent;
|
||||
import dev.engine_room.flywheel.backend.compile.core.CompilerStats;
|
||||
|
@ -43,9 +39,11 @@ public final class FlwPrograms {
|
|||
var vertexComponentsHeader = loader.find(COMPONENTS_HEADER_VERT);
|
||||
var fragmentComponentsHeader = loader.find(COMPONENTS_HEADER_FRAG);
|
||||
|
||||
// TODO: de-uber material shaders
|
||||
var vertexMaterialComponent = createVertexMaterialComponent(loader);
|
||||
var fragmentMaterialComponent = createFragmentMaterialComponent(loader);
|
||||
var fogComponent = createFogComponent(loader);
|
||||
// TODO: separate compilation for cutout OFF, but keep the rest uber'd
|
||||
var cutoutComponent = createCutoutComponent(loader);
|
||||
|
||||
if (stats.errored() || vertexComponentsHeader == null || fragmentComponentsHeader == null || vertexMaterialComponent == null || fragmentMaterialComponent == null || fogComponent == null || cutoutComponent == null) {
|
||||
|
@ -57,21 +55,8 @@ public final class FlwPrograms {
|
|||
List<SourceComponent> vertexComponents = List.of(vertexComponentsHeader, vertexMaterialComponent);
|
||||
List<SourceComponent> fragmentComponents = List.of(fragmentComponentsHeader, fragmentMaterialComponent, fogComponent, cutoutComponent);
|
||||
|
||||
var pipelineKeys = createPipelineKeys();
|
||||
InstancingPrograms.reload(sources, pipelineKeys, vertexComponents, fragmentComponents);
|
||||
IndirectPrograms.reload(sources, pipelineKeys, vertexComponents, fragmentComponents);
|
||||
}
|
||||
|
||||
private static ImmutableList<PipelineProgramKey> createPipelineKeys() {
|
||||
ImmutableList.Builder<PipelineProgramKey> builder = ImmutableList.builder();
|
||||
for (ContextShader contextShader : ContextShader.values()) {
|
||||
for (InstanceType<?> instanceType : InstanceType.REGISTRY) {
|
||||
for (LightShader light : LightShader.REGISTRY.getAll()) {
|
||||
builder.add(new PipelineProgramKey(instanceType, contextShader, light));
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
InstancingPrograms.reload(sources, vertexComponents, fragmentComponents);
|
||||
IndirectPrograms.reload(sources, vertexComponents, fragmentComponents);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package dev.engine_room.flywheel.backend.compile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
@ -43,11 +42,11 @@ public class IndirectPrograms extends AtomicReferenceCounted {
|
|||
@Nullable
|
||||
private static IndirectPrograms instance;
|
||||
|
||||
private final Map<PipelineProgramKey, GlProgram> pipeline;
|
||||
private final Map<InstanceType<?>, GlProgram> culling;
|
||||
private final Map<ResourceLocation, GlProgram> utils;
|
||||
private final CompilationHarness<PipelineProgramKey> pipeline;
|
||||
private final CompilationHarness<InstanceType<?>> culling;
|
||||
private final CompilationHarness<ResourceLocation> utils;
|
||||
|
||||
private IndirectPrograms(Map<PipelineProgramKey, GlProgram> pipeline, Map<InstanceType<?>, GlProgram> culling, Map<ResourceLocation, GlProgram> utils) {
|
||||
private IndirectPrograms(CompilationHarness<PipelineProgramKey> pipeline, CompilationHarness<InstanceType<?>> culling, CompilationHarness<ResourceLocation> utils) {
|
||||
this.pipeline = pipeline;
|
||||
this.culling = culling;
|
||||
this.utils = utils;
|
||||
|
@ -81,32 +80,16 @@ public class IndirectPrograms extends AtomicReferenceCounted {
|
|||
return extensions.build();
|
||||
}
|
||||
|
||||
static void reload(ShaderSources sources, ImmutableList<PipelineProgramKey> pipelineKeys, List<SourceComponent> vertexComponents, List<SourceComponent> fragmentComponents) {
|
||||
static void reload(ShaderSources sources, List<SourceComponent> vertexComponents, List<SourceComponent> fragmentComponents) {
|
||||
if (!GlCompat.SUPPORTS_INDIRECT) {
|
||||
return;
|
||||
}
|
||||
|
||||
IndirectPrograms newInstance = null;
|
||||
|
||||
var pipelineCompiler = PipelineCompiler.create(sources, Pipelines.INDIRECT, vertexComponents, fragmentComponents, EXTENSIONS);
|
||||
var cullingCompiler = createCullingCompiler(sources);
|
||||
var utilCompiler = createUtilCompiler(sources);
|
||||
|
||||
try {
|
||||
var pipelineResult = pipelineCompiler.compileAndReportErrors(pipelineKeys);
|
||||
var cullingResult = cullingCompiler.compileAndReportErrors(createCullingKeys());
|
||||
var utils = utilCompiler.compileAndReportErrors(UTIL_SHADERS);
|
||||
|
||||
if (pipelineResult != null && cullingResult != null && utils != null) {
|
||||
newInstance = new IndirectPrograms(pipelineResult, cullingResult, utils);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
FlwPrograms.LOGGER.error("Failed to compile indirect programs", t);
|
||||
}
|
||||
|
||||
pipelineCompiler.delete();
|
||||
cullingCompiler.delete();
|
||||
utilCompiler.delete();
|
||||
IndirectPrograms newInstance = new IndirectPrograms(pipelineCompiler, cullingCompiler, utilCompiler);
|
||||
|
||||
setInstance(newInstance);
|
||||
}
|
||||
|
@ -142,14 +125,6 @@ public class IndirectPrograms extends AtomicReferenceCounted {
|
|||
.harness("utilities", sources);
|
||||
}
|
||||
|
||||
private static ImmutableList<InstanceType<?>> createCullingKeys() {
|
||||
ImmutableList.Builder<InstanceType<?>> builder = ImmutableList.builder();
|
||||
for (InstanceType<?> instanceType : InstanceType.REGISTRY) {
|
||||
builder.add(instanceType);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
static void setInstance(@Nullable IndirectPrograms newInstance) {
|
||||
if (instance != null) {
|
||||
instance.release();
|
||||
|
@ -169,6 +144,10 @@ public class IndirectPrograms extends AtomicReferenceCounted {
|
|||
return instance != null;
|
||||
}
|
||||
|
||||
public static void kill() {
|
||||
setInstance(null);
|
||||
}
|
||||
|
||||
public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader, LightShader light) {
|
||||
return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light));
|
||||
}
|
||||
|
@ -195,11 +174,8 @@ public class IndirectPrograms extends AtomicReferenceCounted {
|
|||
|
||||
@Override
|
||||
protected void _delete() {
|
||||
pipeline.values()
|
||||
.forEach(GlProgram::delete);
|
||||
culling.values()
|
||||
.forEach(GlProgram::delete);
|
||||
utils.values()
|
||||
.forEach(GlProgram::delete);
|
||||
pipeline.delete();
|
||||
culling.delete();
|
||||
utils.delete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package dev.engine_room.flywheel.backend.compile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
@ -9,6 +8,7 @@ import com.google.common.collect.ImmutableList;
|
|||
|
||||
import dev.engine_room.flywheel.api.instance.InstanceType;
|
||||
import dev.engine_room.flywheel.api.material.LightShader;
|
||||
import dev.engine_room.flywheel.backend.compile.core.CompilationHarness;
|
||||
import dev.engine_room.flywheel.backend.gl.GlCompat;
|
||||
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
|
||||
import dev.engine_room.flywheel.backend.glsl.GlslVersion;
|
||||
|
@ -22,9 +22,9 @@ public class InstancingPrograms extends AtomicReferenceCounted {
|
|||
@Nullable
|
||||
private static InstancingPrograms instance;
|
||||
|
||||
private final Map<PipelineProgramKey, GlProgram> pipeline;
|
||||
private final CompilationHarness<PipelineProgramKey> pipeline;
|
||||
|
||||
private InstancingPrograms(Map<PipelineProgramKey, GlProgram> pipeline) {
|
||||
private InstancingPrograms(CompilationHarness<PipelineProgramKey> pipeline) {
|
||||
this.pipeline = pipeline;
|
||||
}
|
||||
|
||||
|
@ -36,26 +36,14 @@ public class InstancingPrograms extends AtomicReferenceCounted {
|
|||
return extensions.build();
|
||||
}
|
||||
|
||||
static void reload(ShaderSources sources, ImmutableList<PipelineProgramKey> pipelineKeys, List<SourceComponent> vertexComponents, List<SourceComponent> fragmentComponents) {
|
||||
static void reload(ShaderSources sources, List<SourceComponent> vertexComponents, List<SourceComponent> fragmentComponents) {
|
||||
if (!GlCompat.SUPPORTS_INSTANCING) {
|
||||
return;
|
||||
}
|
||||
|
||||
InstancingPrograms newInstance = null;
|
||||
|
||||
var pipelineCompiler = PipelineCompiler.create(sources, Pipelines.INSTANCING, vertexComponents, fragmentComponents, EXTENSIONS);
|
||||
|
||||
try {
|
||||
var pipelineResult = pipelineCompiler.compileAndReportErrors(pipelineKeys);
|
||||
|
||||
if (pipelineResult != null) {
|
||||
newInstance = new InstancingPrograms(pipelineResult);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
FlwPrograms.LOGGER.error("Failed to compile instancing programs", t);
|
||||
}
|
||||
|
||||
pipelineCompiler.delete();
|
||||
InstancingPrograms newInstance = new InstancingPrograms(pipelineCompiler);
|
||||
|
||||
setInstance(newInstance);
|
||||
}
|
||||
|
@ -79,13 +67,16 @@ public class InstancingPrograms extends AtomicReferenceCounted {
|
|||
return instance != null;
|
||||
}
|
||||
|
||||
public static void kill() {
|
||||
setInstance(null);
|
||||
}
|
||||
|
||||
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, LightShader light) {
|
||||
return pipeline.get(new PipelineProgramKey(instanceType, contextShader, light));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _delete() {
|
||||
pipeline.values()
|
||||
.forEach(GlProgram::delete);
|
||||
pipeline.delete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package dev.engine_room.flywheel.backend.compile.core;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -16,6 +15,8 @@ public class CompilationHarness<K> {
|
|||
private final ProgramLinker programLinker;
|
||||
private final CompilerStats stats;
|
||||
|
||||
private final Map<K, GlProgram> programs = new HashMap<>();
|
||||
|
||||
public CompilationHarness(String marker, ShaderSources sources, KeyCompiler<K> compiler) {
|
||||
this.compiler = compiler;
|
||||
stats = new CompilerStats(marker);
|
||||
|
@ -24,23 +25,16 @@ public class CompilationHarness<K> {
|
|||
programLinker = new ProgramLinker(stats);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Map<K, GlProgram> compileAndReportErrors(Collection<K> keys) {
|
||||
stats.start();
|
||||
Map<K, GlProgram> out = new HashMap<>();
|
||||
for (var key : keys) {
|
||||
GlProgram glProgram = compiler.compile(key, sourceLoader, shaderCache, programLinker);
|
||||
if (out != null && glProgram != null) {
|
||||
out.put(key, glProgram);
|
||||
} else {
|
||||
out = null; // Return null when a preloading error occurs.
|
||||
}
|
||||
}
|
||||
stats.finish();
|
||||
public GlProgram get(K key) {
|
||||
return programs.computeIfAbsent(key, this::compile);
|
||||
}
|
||||
|
||||
if (stats.errored()) {
|
||||
stats.emitErrorLog();
|
||||
return null;
|
||||
private GlProgram compile(K key) {
|
||||
var out = compiler.compile(key, sourceLoader, shaderCache, programLinker);
|
||||
|
||||
if (out == null) {
|
||||
// TODO: populate exception with error details
|
||||
throw new ShaderException();
|
||||
}
|
||||
|
||||
return out;
|
||||
|
@ -48,6 +42,10 @@ public class CompilationHarness<K> {
|
|||
|
||||
public void delete() {
|
||||
shaderCache.delete();
|
||||
|
||||
for (var program : programs.values()) {
|
||||
program.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public interface KeyCompiler<K> {
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
package dev.engine_room.flywheel.backend.compile.core;
|
||||
|
||||
public class ShaderException extends RuntimeException {
|
||||
}
|
|
@ -135,6 +135,8 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
|
|||
initializationQueue.clear();
|
||||
}
|
||||
|
||||
public abstract void triggerFallback();
|
||||
|
||||
protected record UninitializedInstancer<N, I extends Instance>(InstancerKey<I> key, N instancer) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import dev.engine_room.flywheel.api.task.Plan;
|
|||
import dev.engine_room.flywheel.api.visualization.VisualEmbedding;
|
||||
import dev.engine_room.flywheel.api.visualization.VisualType;
|
||||
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
|
||||
import dev.engine_room.flywheel.backend.compile.core.ShaderException;
|
||||
import dev.engine_room.flywheel.backend.engine.embed.EmbeddedEnvironment;
|
||||
import dev.engine_room.flywheel.backend.engine.embed.Environment;
|
||||
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
|
||||
|
@ -90,17 +91,27 @@ public class EngineImpl implements Engine {
|
|||
Uniforms.update(context);
|
||||
environmentStorage.flush();
|
||||
drawManager.flush(lightStorage, environmentStorage);
|
||||
} catch (ShaderException e) {
|
||||
triggerFallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(RenderContext context, VisualType visualType) {
|
||||
drawManager.render(visualType);
|
||||
try (var state = GlStateTracker.getRestoreState()) {
|
||||
drawManager.render(visualType);
|
||||
} catch (ShaderException e) {
|
||||
triggerFallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderCrumbling(RenderContext context, List<CrumblingBlock> crumblingBlocks) {
|
||||
drawManager.renderCrumbling(crumblingBlocks);
|
||||
try (var state = GlStateTracker.getRestoreState()) {
|
||||
drawManager.renderCrumbling(crumblingBlocks);
|
||||
} catch (ShaderException e) {
|
||||
triggerFallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -110,6 +121,10 @@ public class EngineImpl implements Engine {
|
|||
environmentStorage.delete();
|
||||
}
|
||||
|
||||
private void triggerFallback() {
|
||||
drawManager.triggerFallback();
|
||||
}
|
||||
|
||||
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) {
|
||||
return drawManager.getInstancer(environment, type, model, visualType, bias);
|
||||
}
|
||||
|
|
|
@ -29,12 +29,12 @@ import dev.engine_room.flywheel.backend.engine.MeshPool;
|
|||
import dev.engine_room.flywheel.backend.engine.TextureBinder;
|
||||
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
|
||||
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
|
||||
import dev.engine_room.flywheel.backend.gl.GlStateTracker;
|
||||
import dev.engine_room.flywheel.backend.gl.array.GlVertexArray;
|
||||
import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer;
|
||||
import dev.engine_room.flywheel.backend.gl.buffer.GlBufferType;
|
||||
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
|
||||
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
|
||||
public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
|
||||
|
@ -91,26 +91,24 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
|
|||
return;
|
||||
}
|
||||
|
||||
try (var state = GlStateTracker.getRestoreState()) {
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
|
||||
vertexArray.bindForDraw();
|
||||
lightBuffers.bind();
|
||||
matrixBuffer.bind();
|
||||
Uniforms.bindAll();
|
||||
vertexArray.bindForDraw();
|
||||
lightBuffers.bind();
|
||||
matrixBuffer.bind();
|
||||
Uniforms.bindAll();
|
||||
|
||||
if (needsBarrier) {
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
needsBarrier = false;
|
||||
}
|
||||
|
||||
for (var group : cullingGroups.values()) {
|
||||
group.submit(visualType);
|
||||
}
|
||||
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
if (needsBarrier) {
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
needsBarrier = false;
|
||||
}
|
||||
|
||||
for (var group : cullingGroups.values()) {
|
||||
group.submit(visualType);
|
||||
}
|
||||
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -194,63 +192,67 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
|
|||
return;
|
||||
}
|
||||
|
||||
try (var state = GlStateTracker.getRestoreState()) {
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
|
||||
vertexArray.bindForDraw();
|
||||
Uniforms.bindAll();
|
||||
vertexArray.bindForDraw();
|
||||
Uniforms.bindAll();
|
||||
|
||||
var crumblingMaterial = SimpleMaterial.builder();
|
||||
var crumblingMaterial = SimpleMaterial.builder();
|
||||
|
||||
// Scratch memory for writing draw commands.
|
||||
var block = MemoryBlock.malloc(IndirectBuffers.DRAW_COMMAND_STRIDE);
|
||||
// Scratch memory for writing draw commands.
|
||||
var block = MemoryBlock.malloc(IndirectBuffers.DRAW_COMMAND_STRIDE);
|
||||
|
||||
// Set up the crumbling program buffers. Nothing changes here between draws.
|
||||
GlBufferType.DRAW_INDIRECT_BUFFER.bind(crumblingDrawBuffer.handle());
|
||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.DRAW, crumblingDrawBuffer.handle(), 0, IndirectBuffers.DRAW_COMMAND_STRIDE);
|
||||
// Set up the crumbling program buffers. Nothing changes here between draws.
|
||||
GlBufferType.DRAW_INDIRECT_BUFFER.bind(crumblingDrawBuffer.handle());
|
||||
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.DRAW, crumblingDrawBuffer.handle(), 0, IndirectBuffers.DRAW_COMMAND_STRIDE);
|
||||
|
||||
for (var groupEntry : byType.entrySet()) {
|
||||
var byProgress = groupEntry.getValue();
|
||||
for (var groupEntry : byType.entrySet()) {
|
||||
var byProgress = groupEntry.getValue();
|
||||
|
||||
GroupKey<?> groupKey = groupEntry.getKey();
|
||||
IndirectCullingGroup<?> cullingGroup = cullingGroups.get(groupKey.instanceType());
|
||||
GroupKey<?> groupKey = groupEntry.getKey();
|
||||
IndirectCullingGroup<?> cullingGroup = cullingGroups.get(groupKey.instanceType());
|
||||
|
||||
if (cullingGroup == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cullingGroup.bindWithContextShader(ContextShader.CRUMBLING);
|
||||
|
||||
for (var progressEntry : byProgress.int2ObjectEntrySet()) {
|
||||
Samplers.CRUMBLING.makeActive();
|
||||
TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey()));
|
||||
|
||||
for (var instanceHandlePair : progressEntry.getValue()) {
|
||||
IndirectInstancer<?> instancer = instanceHandlePair.getFirst();
|
||||
int instanceIndex = instanceHandlePair.getSecond().index;
|
||||
|
||||
for (IndirectDraw draw : instancer.draws()) {
|
||||
// Transform the material to be suited for crumbling.
|
||||
CommonCrumbling.applyCrumblingProperties(crumblingMaterial, draw.material());
|
||||
|
||||
MaterialRenderState.setup(crumblingMaterial);
|
||||
|
||||
// Upload the draw command.
|
||||
draw.writeWithOverrides(block.ptr(), instanceIndex, crumblingMaterial);
|
||||
crumblingDrawBuffer.upload(block);
|
||||
|
||||
// Submit! Everything is already bound by here.
|
||||
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (cullingGroup == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
cullingGroup.bindWithContextShader(ContextShader.CRUMBLING);
|
||||
|
||||
block.free();
|
||||
for (var progressEntry : byProgress.int2ObjectEntrySet()) {
|
||||
Samplers.CRUMBLING.makeActive();
|
||||
TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey()));
|
||||
|
||||
for (var instanceHandlePair : progressEntry.getValue()) {
|
||||
IndirectInstancer<?> instancer = instanceHandlePair.getFirst();
|
||||
int instanceIndex = instanceHandlePair.getSecond().index;
|
||||
|
||||
for (IndirectDraw draw : instancer.draws()) {
|
||||
// Transform the material to be suited for crumbling.
|
||||
CommonCrumbling.applyCrumblingProperties(crumblingMaterial, draw.material());
|
||||
|
||||
MaterialRenderState.setup(crumblingMaterial);
|
||||
|
||||
// Upload the draw command.
|
||||
draw.writeWithOverrides(block.ptr(), instanceIndex, crumblingMaterial);
|
||||
crumblingDrawBuffer.upload(block);
|
||||
|
||||
// Submit! Everything is already bound by here.
|
||||
glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, 0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
|
||||
block.free();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerFallback() {
|
||||
IndirectPrograms.kill();
|
||||
Minecraft.getInstance().levelRenderer.allChanged();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,12 +25,12 @@ import dev.engine_room.flywheel.backend.engine.MeshPool;
|
|||
import dev.engine_room.flywheel.backend.engine.TextureBinder;
|
||||
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
|
||||
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
|
||||
import dev.engine_room.flywheel.backend.gl.GlStateTracker;
|
||||
import dev.engine_room.flywheel.backend.gl.TextureBuffer;
|
||||
import dev.engine_room.flywheel.backend.gl.array.GlVertexArray;
|
||||
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
|
||||
import dev.engine_room.flywheel.lib.material.LightShaders;
|
||||
import dev.engine_room.flywheel.lib.material.SimpleMaterial;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.model.ModelBakery;
|
||||
|
||||
public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
|
||||
|
@ -94,17 +94,15 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
|
|||
return;
|
||||
}
|
||||
|
||||
try (var state = GlStateTracker.getRestoreState()) {
|
||||
Uniforms.bindAll();
|
||||
vao.bindForDraw();
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
light.bind();
|
||||
Uniforms.bindAll();
|
||||
vao.bindForDraw();
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
light.bind();
|
||||
|
||||
stage.draw(instanceTexture, programs);
|
||||
stage.draw(instanceTexture, programs);
|
||||
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
}
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -162,46 +160,50 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
|
|||
|
||||
var crumblingMaterial = SimpleMaterial.builder();
|
||||
|
||||
try (var state = GlStateTracker.getRestoreState()) {
|
||||
Uniforms.bindAll();
|
||||
vao.bindForDraw();
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
Uniforms.bindAll();
|
||||
vao.bindForDraw();
|
||||
TextureBinder.bindLightAndOverlay();
|
||||
|
||||
for (var groupEntry : byType.entrySet()) {
|
||||
var byProgress = groupEntry.getValue();
|
||||
for (var groupEntry : byType.entrySet()) {
|
||||
var byProgress = groupEntry.getValue();
|
||||
|
||||
GroupKey<?> shader = groupEntry.getKey();
|
||||
GroupKey<?> shader = groupEntry.getKey();
|
||||
|
||||
var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING, LightShaders.SMOOTH_WHEN_EMBEDDED);
|
||||
program.bind();
|
||||
var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING, LightShaders.SMOOTH_WHEN_EMBEDDED);
|
||||
program.bind();
|
||||
|
||||
for (var progressEntry : byProgress.int2ObjectEntrySet()) {
|
||||
Samplers.CRUMBLING.makeActive();
|
||||
TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey()));
|
||||
for (var progressEntry : byProgress.int2ObjectEntrySet()) {
|
||||
Samplers.CRUMBLING.makeActive();
|
||||
TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey()));
|
||||
|
||||
for (var instanceHandlePair : progressEntry.getValue()) {
|
||||
InstancedInstancer<?> instancer = instanceHandlePair.getFirst();
|
||||
var index = instanceHandlePair.getSecond().index;
|
||||
for (var instanceHandlePair : progressEntry.getValue()) {
|
||||
InstancedInstancer<?> instancer = instanceHandlePair.getFirst();
|
||||
var index = instanceHandlePair.getSecond().index;
|
||||
|
||||
program.setInt("_flw_baseInstance", index);
|
||||
program.setInt("_flw_baseInstance", index);
|
||||
|
||||
for (InstancedDraw draw : instancer.draws()) {
|
||||
CommonCrumbling.applyCrumblingProperties(crumblingMaterial, draw.material());
|
||||
uploadMaterialUniform(program, crumblingMaterial);
|
||||
for (InstancedDraw draw : instancer.draws()) {
|
||||
CommonCrumbling.applyCrumblingProperties(crumblingMaterial, draw.material());
|
||||
uploadMaterialUniform(program, crumblingMaterial);
|
||||
|
||||
MaterialRenderState.setup(crumblingMaterial);
|
||||
MaterialRenderState.setup(crumblingMaterial);
|
||||
|
||||
Samplers.INSTANCE_BUFFER.makeActive();
|
||||
Samplers.INSTANCE_BUFFER.makeActive();
|
||||
|
||||
draw.renderOne(instanceTexture);
|
||||
}
|
||||
draw.renderOne(instanceTexture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
}
|
||||
|
||||
MaterialRenderState.reset();
|
||||
TextureBinder.resetLightAndOverlay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerFallback() {
|
||||
InstancingPrograms.kill();
|
||||
Minecraft.getInstance().levelRenderer.allChanged();
|
||||
}
|
||||
|
||||
public static void uploadMaterialUniform(GlProgram program, Material material) {
|
||||
|
|
|
@ -11,7 +11,10 @@ import java.util.Map;
|
|||
|
||||
import org.jetbrains.annotations.VisibleForTesting;
|
||||
|
||||
import dev.engine_room.flywheel.backend.compile.FlwPrograms;
|
||||
import dev.engine_room.flywheel.lib.util.StringUtil;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
|
||||
/**
|
||||
|
@ -20,62 +23,96 @@ import net.minecraft.server.packs.resources.ResourceManager;
|
|||
public class ShaderSources {
|
||||
public static final String SHADER_DIR = "flywheel/";
|
||||
|
||||
private final ResourceManager manager;
|
||||
|
||||
@VisibleForTesting
|
||||
protected final Map<ResourceLocation, LoadResult> cache = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Tracks where we are in the mutual recursion to detect circular imports.
|
||||
*/
|
||||
private final Deque<ResourceLocation> findStack = new ArrayDeque<>();
|
||||
protected final Map<ResourceLocation, LoadResult> cache;
|
||||
|
||||
public ShaderSources(ResourceManager manager) {
|
||||
this.manager = manager;
|
||||
var sourceFinder = new SourceFinder(manager);
|
||||
|
||||
long loadStart = System.nanoTime();
|
||||
manager.listResources("flywheel", ShaderSources::isShader)
|
||||
.forEach(sourceFinder::rootLoad);
|
||||
|
||||
long loadEnd = System.nanoTime();
|
||||
|
||||
FlwPrograms.LOGGER.info("Loaded {} shader sources in {}", sourceFinder.results.size(), StringUtil.formatTime(loadEnd - loadStart));
|
||||
|
||||
this.cache = sourceFinder.results;
|
||||
}
|
||||
|
||||
public ShaderSources(ResourceManager manager, Map<ResourceLocation, LoadResult> preloadCache) {
|
||||
this.manager = manager;
|
||||
cache.putAll(preloadCache);
|
||||
private static ResourceLocation locationWithoutFlywheelPrefix(ResourceLocation loc) {
|
||||
return new ResourceLocation(loc.getNamespace(), loc.getPath()
|
||||
.substring(SHADER_DIR.length()));
|
||||
}
|
||||
|
||||
public LoadResult find(ResourceLocation location) {
|
||||
if (findStack.contains(location)) {
|
||||
// Make a copy of the find stack with the offending location added on top to show the full path.
|
||||
return cache.computeIfAbsent(location, loc -> new LoadResult.Failure(new LoadError.ResourceError(loc)));
|
||||
}
|
||||
|
||||
private static boolean isShader(ResourceLocation loc) {
|
||||
var path = loc.getPath();
|
||||
return path.endsWith(".glsl") || path.endsWith(".vert") || path.endsWith(".frag") || path.endsWith(".comp");
|
||||
}
|
||||
|
||||
private static class SourceFinder {
|
||||
private final Deque<ResourceLocation> findStack = new ArrayDeque<>();
|
||||
private final Map<ResourceLocation, LoadResult> results = new HashMap<>();
|
||||
private final ResourceManager manager;
|
||||
|
||||
public SourceFinder(ResourceManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
public void rootLoad(ResourceLocation loc, Resource resource) {
|
||||
var strippedLoc = locationWithoutFlywheelPrefix(loc);
|
||||
|
||||
if (results.containsKey(strippedLoc)) {
|
||||
// Some other source already #included this one.
|
||||
return;
|
||||
}
|
||||
|
||||
this.results.put(strippedLoc, readResource(strippedLoc, resource));
|
||||
}
|
||||
|
||||
public LoadResult recursiveLoad(ResourceLocation location) {
|
||||
if (findStack.contains(location)) {
|
||||
// Make a copy of the find stack with the offending location added on top to show the full path.
|
||||
findStack.addLast(location);
|
||||
var copy = List.copyOf(findStack);
|
||||
findStack.removeLast();
|
||||
return new LoadResult.Failure(new LoadError.CircularDependency(location, copy));
|
||||
}
|
||||
findStack.addLast(location);
|
||||
var copy = List.copyOf(findStack);
|
||||
|
||||
LoadResult out = _find(location);
|
||||
|
||||
findStack.removeLast();
|
||||
return new LoadResult.Failure(new LoadError.CircularDependency(location, copy));
|
||||
return out;
|
||||
}
|
||||
findStack.addLast(location);
|
||||
|
||||
LoadResult out = _find(location);
|
||||
|
||||
findStack.removeLast();
|
||||
return out;
|
||||
}
|
||||
|
||||
private LoadResult _find(ResourceLocation location) {
|
||||
// Can't use computeIfAbsent because mutual recursion causes ConcurrentModificationExceptions
|
||||
var out = cache.get(location);
|
||||
if (out == null) {
|
||||
out = load(location);
|
||||
cache.put(location, out);
|
||||
private LoadResult _find(ResourceLocation location) {
|
||||
// Can't use computeIfAbsent because mutual recursion causes ConcurrentModificationExceptions
|
||||
var out = results.get(location);
|
||||
if (out == null) {
|
||||
out = load(location);
|
||||
results.put(location, out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected LoadResult load(ResourceLocation loc) {
|
||||
return manager.getResource(loc.withPrefix(SHADER_DIR))
|
||||
.map(resource -> {
|
||||
try (InputStream stream = resource.open()) {
|
||||
String sourceString = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
return SourceFile.parse(this, loc, sourceString);
|
||||
} catch (IOException e) {
|
||||
return new LoadResult.Failure(new LoadError.IOError(loc, e));
|
||||
}
|
||||
})
|
||||
.orElseGet(() -> new LoadResult.Failure(new LoadError.ResourceError(loc)));
|
||||
private LoadResult load(ResourceLocation loc) {
|
||||
return manager.getResource(loc.withPrefix(SHADER_DIR))
|
||||
.map(resource -> readResource(loc, resource))
|
||||
.orElseGet(() -> new LoadResult.Failure(new LoadError.ResourceError(loc)));
|
||||
}
|
||||
|
||||
private LoadResult readResource(ResourceLocation loc, Resource resource) {
|
||||
try (InputStream stream = resource.open()) {
|
||||
String sourceString = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
return SourceFile.parse(this::recursiveLoad, loc, sourceString);
|
||||
} catch (IOException e) {
|
||||
return new LoadResult.Failure(new LoadError.IOError(loc, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.util.Collection;
|
|||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
@ -69,7 +70,7 @@ public class SourceFile implements SourceComponent {
|
|||
return new LoadResult.Success(new SourceFile(name, new SourceLines(name, ""), ImmutableMap.of(), ImmutableMap.of(), ImmutableList.of(), ImmutableMap.of(), ImmutableList.of(), ""));
|
||||
}
|
||||
|
||||
public static LoadResult parse(ShaderSources sourceFinder, ResourceLocation name, String stringSource) {
|
||||
public static LoadResult parse(Function<ResourceLocation, LoadResult> sourceFinder, ResourceLocation name, String stringSource) {
|
||||
var source = new SourceLines(name, stringSource);
|
||||
|
||||
var imports = Import.parseImports(source);
|
||||
|
@ -93,7 +94,7 @@ public class SourceFile implements SourceComponent {
|
|||
continue;
|
||||
}
|
||||
|
||||
var result = sourceFinder.find(location);
|
||||
var result = sourceFinder.apply(location);
|
||||
|
||||
if (result instanceof LoadResult.Success s) {
|
||||
included.add(s.unwrap());
|
||||
|
|
|
@ -1,36 +1,62 @@
|
|||
package dev.engine_room.flywheel.backend.glsl;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
|
||||
public class MockShaderSources extends ShaderSources {
|
||||
public class MockShaderSources {
|
||||
private final Map<ResourceLocation, String> sources = new HashMap<>();
|
||||
private final Map<ResourceLocation, LoadResult> cache = new HashMap<>();
|
||||
private final Deque<ResourceLocation> findStack = new ArrayDeque<>();
|
||||
|
||||
|
||||
public MockShaderSources() {
|
||||
super(ResourceManager.Empty.INSTANCE);
|
||||
|
||||
}
|
||||
|
||||
public void add(ResourceLocation loc, String source) {
|
||||
sources.put(loc, source);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LoadResult load(ResourceLocation loc) {
|
||||
public LoadResult find(ResourceLocation location) {
|
||||
if (findStack.contains(location)) {
|
||||
// Make a copy of the find stack with the offending location added on top to show the full path.
|
||||
findStack.addLast(location);
|
||||
var copy = List.copyOf(findStack);
|
||||
findStack.removeLast();
|
||||
return new LoadResult.Failure(new LoadError.CircularDependency(location, copy));
|
||||
}
|
||||
findStack.addLast(location);
|
||||
|
||||
LoadResult out = load(location);
|
||||
|
||||
findStack.removeLast();
|
||||
return out;
|
||||
}
|
||||
|
||||
private LoadResult load(ResourceLocation loc) {
|
||||
var out = cache.get(loc);
|
||||
if (out != null) {
|
||||
return out;
|
||||
}
|
||||
|
||||
var loadResult = _load(loc);
|
||||
|
||||
cache.put(loc, loadResult);
|
||||
|
||||
return loadResult;
|
||||
}
|
||||
|
||||
private LoadResult _load(ResourceLocation loc) {
|
||||
var maybeFound = sources.get(loc);
|
||||
if (maybeFound == null) {
|
||||
return new LoadResult.Failure(new LoadError.IOError(loc, new FileNotFoundException(loc.toString())));
|
||||
}
|
||||
return SourceFile.parse(this, loc, maybeFound);
|
||||
}
|
||||
|
||||
public LoadResult assertLoaded(ResourceLocation loc) {
|
||||
Assertions.assertTrue(cache.containsKey(loc), "Expected " + loc + " to be cached");
|
||||
return cache.get(loc);
|
||||
return SourceFile.parse(this::find, loc, maybeFound);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,6 @@ public class TestShaderSourceLoading extends TestBase {
|
|||
sources.add(FLW_B, "");
|
||||
|
||||
findAndAssertSuccess(sources, FLW_A);
|
||||
sources.assertLoaded(FLW_B);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -78,7 +77,6 @@ public class TestShaderSourceLoading extends TestBase {
|
|||
sources.add(FLW_B, "");
|
||||
|
||||
SourceFile a = findAndAssertSuccess(sources, FLW_A);
|
||||
sources.assertLoaded(FLW_B);
|
||||
|
||||
var includeB = assertSingletonList(a.imports);
|
||||
assertEquals(FLW_B.toString(), includeB.file()
|
||||
|
@ -99,7 +97,6 @@ public class TestShaderSourceLoading extends TestBase {
|
|||
sources.add(FLW_B, "");
|
||||
|
||||
SourceFile a = findAndAssertSuccess(sources, FLW_A);
|
||||
sources.assertLoaded(FLW_B);
|
||||
|
||||
assertEquals(2, a.imports.size());
|
||||
for (Import include : a.imports) {
|
||||
|
@ -112,7 +109,7 @@ public class TestShaderSourceLoading extends TestBase {
|
|||
|
||||
""", a.finalSource, "Both include statements should be elided.");
|
||||
|
||||
LoadResult bResult = sources.assertLoaded(FLW_B);
|
||||
LoadResult bResult = sources.find(FLW_B);
|
||||
SourceFile b = assertSuccessAndUnwrap(FLW_B, bResult);
|
||||
|
||||
assertEquals(ImmutableList.of(b), a.included);
|
||||
|
@ -148,7 +145,6 @@ public class TestShaderSourceLoading extends TestBase {
|
|||
""");
|
||||
|
||||
var aErr = findAndAssertError(LoadError.IncludeError.class, sources, FLW_A);
|
||||
sources.assertLoaded(FLW_B);
|
||||
|
||||
var recursiveInclude = assertSimpleNestedErrorsToDepth(LoadError.CircularDependency.class, aErr, 2);
|
||||
assertEquals(ImmutableList.of(FLW_A, FLW_B, FLW_A), recursiveInclude.stack());
|
||||
|
@ -168,8 +164,6 @@ public class TestShaderSourceLoading extends TestBase {
|
|||
""");
|
||||
|
||||
var aErr = findAndAssertError(LoadError.IncludeError.class, sources, FLW_A);
|
||||
sources.assertLoaded(FLW_B);
|
||||
sources.assertLoaded(FLW_C);
|
||||
|
||||
var recursiveInclude = assertSimpleNestedErrorsToDepth(LoadError.CircularDependency.class, aErr, 3);
|
||||
assertEquals(ImmutableList.of(FLW_A, FLW_B, FLW_C, FLW_A), recursiveInclude.stack());
|
||||
|
|
Loading…
Reference in a new issue