diff --git a/fabric/src/main/resources/canvas.accesswidener b/fabric/src/main/resources/canvas.accesswidener index 57c80d821..8fa06cffe 100644 --- a/fabric/src/main/resources/canvas.accesswidener +++ b/fabric/src/main/resources/canvas.accesswidener @@ -154,3 +154,5 @@ accessible field net/minecraft/client/renderer/entity/EntityRenderDispatcher SHA accessible field net/minecraft/client/renderer/ShaderInstance name Ljava/lang/String; extendable method com/mojang/blaze3d/pipeline/MainTarget createFrameBuffer (II)V + +accessible class net/minecraft/client/resources/model/ModelManager$ReloadState diff --git a/fabric/src/main/resources/mixins.canvas.client.json b/fabric/src/main/resources/mixins.canvas.client.json index 313c9038a..91b88b78f 100644 --- a/fabric/src/main/resources/mixins.canvas.client.json +++ b/fabric/src/main/resources/mixins.canvas.client.json @@ -33,6 +33,7 @@ "MixinLightTexture", "MixinMinecraft", "MixinModelBlockRenderer", + "MixinModelManager", "MixinNativeImage", "MixinParticle", "MixinParticleEngine", @@ -43,6 +44,7 @@ "MixinSimpleTexture", "MixinSpriteContents", "MixinSpriteInterpolation", + "MixinStitchResult", "MixinTextureAtlas", "MixinTextureSheetParticle", "MixinTropicalFishModelA", diff --git a/src/main/java/grondag/canvas/mixin/MixinModelManager.java b/src/main/java/grondag/canvas/mixin/MixinModelManager.java new file mode 100644 index 000000000..abcbda7aa --- /dev/null +++ b/src/main/java/grondag/canvas/mixin/MixinModelManager.java @@ -0,0 +1,60 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.mixin; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.model.ModelManager; +import net.minecraft.server.packs.resources.PreparableReloadListener; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.profiling.ProfilerFiller; + +import grondag.canvas.texture.pbr.PbrLoader; + +@Mixin(ModelManager.class) +public abstract class MixinModelManager { + @Shadow public abstract CompletableFuture reload(PreparableReloadListener.PreparationBarrier preparationBarrier, ResourceManager resourceManager, ProfilerFiller profilerFiller, ProfilerFiller profilerFiller2, Executor executor, Executor executor2); + + private static ResourceManager canvas_resourceManager; + + @Inject(method = "reload", at = @At("HEAD")) + private void onReload(PreparableReloadListener.PreparationBarrier preparationBarrier, ResourceManager resourceManager, ProfilerFiller profilerFiller, ProfilerFiller profilerFiller2, Executor executor, Executor executor2, CallbackInfoReturnable> cir) { + canvas_resourceManager = resourceManager; + } + @Inject(method = "apply", at = @At("HEAD")) + private void onApply(ModelManager.ReloadState reloadState, ProfilerFiller profilerFiller, CallbackInfo ci) { + if (canvas_resourceManager == null) { + PbrLoader.errorOrdering(); + } else { + PbrLoader.reload(canvas_resourceManager, reloadState.atlasPreparations().values()); + canvas_resourceManager = null; + } + } +} diff --git a/src/main/java/grondag/canvas/mixin/MixinStitchResult.java b/src/main/java/grondag/canvas/mixin/MixinStitchResult.java new file mode 100644 index 000000000..558c86cff --- /dev/null +++ b/src/main/java/grondag/canvas/mixin/MixinStitchResult.java @@ -0,0 +1,47 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.mixin; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.client.renderer.texture.SpriteLoader; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.resources.model.AtlasSet; + +import grondag.canvas.mixinterface.StitchResultExt; + +@Mixin(AtlasSet.StitchResult.class) +public class MixinStitchResult implements StitchResultExt { + @Final @Shadow private TextureAtlas atlas; + @Final @Shadow private SpriteLoader.Preparations preparations; + + @Override + public TextureAtlas canvas_atlas() { + return atlas; + } + + @Override + public SpriteLoader.Preparations canvas_preparations() { + return preparations; + } +} diff --git a/src/main/java/grondag/canvas/mixinterface/StitchResultExt.java b/src/main/java/grondag/canvas/mixinterface/StitchResultExt.java new file mode 100644 index 000000000..f1d5791ea --- /dev/null +++ b/src/main/java/grondag/canvas/mixinterface/StitchResultExt.java @@ -0,0 +1,29 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.mixinterface; + +import net.minecraft.client.renderer.texture.SpriteLoader; +import net.minecraft.client.renderer.texture.TextureAtlas; + +public interface StitchResultExt { + TextureAtlas canvas_atlas(); + SpriteLoader.Preparations canvas_preparations(); +} diff --git a/src/main/java/grondag/canvas/texture/pbr/ColorEncode.java b/src/main/java/grondag/canvas/texture/pbr/ColorEncode.java new file mode 100644 index 000000000..855ddc4b3 --- /dev/null +++ b/src/main/java/grondag/canvas/texture/pbr/ColorEncode.java @@ -0,0 +1,49 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.texture.pbr; + +// TODO: this class has to already exist somewhere.. +public class ColorEncode { + static int encode(int r, int g, int b, int a) { + return ((r & 0xFF) << 0) | ((g & 0xFF) << 8) | ((b & 0xFF) << 16) | ((a & 0xFF) << 24); + } + + static int r(int color) { + return (color >> 0) & 0xFF; + } + + static int g(int color) { + return (color >> 8) & 0xFF; + } + + static int b(int color) { + return (color >> 16) & 0xFF; + } + + static int a(int color) { + return (color >> 24) & 0xFF; + } + + static final int RED_BYTE_OFFSET = 0; + static final int GREEN_BYTE_OFFSET = 1; + static final int BLUE_BYTE_OFFSET = 2; + static final int ALPHA_BYTE_OFFSET = 3; +} diff --git a/src/main/java/grondag/canvas/texture/pbr/InputTextureManager.java b/src/main/java/grondag/canvas/texture/pbr/InputTextureManager.java new file mode 100644 index 000000000..872666d2f --- /dev/null +++ b/src/main/java/grondag/canvas/texture/pbr/InputTextureManager.java @@ -0,0 +1,158 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.texture.pbr; + +import java.io.InputStream; +import java.nio.ByteBuffer; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; + +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.platform.TextureUtil; + +import net.minecraft.client.renderer.texture.atlas.SpriteSource; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; + +import grondag.canvas.CanvasMod; +import grondag.canvas.render.CanvasTextureState; +import grondag.canvas.varia.GFX; + +public class InputTextureManager { + final Object2ObjectOpenHashMap textures = new Object2ObjectOpenHashMap<>(); + private final InputTexture EMPTY_INPUT = new InputTexture(); + + void addInputResource(ResourceManager resourceManager, ResourceLocation location) { + if (!textures.containsKey(location)) { + final var resource = resourceManager.getResource(location); + InputTexture added = EMPTY_INPUT; + + if (resource.isPresent()) { + try (InputStream inputStream = resource.get().open()) { + added = new ImageInput(location, NativeImage.read(inputStream), true); + } catch (Throwable e) { + CanvasMod.LOG.error("Unable to load PBR texture " + location + " due to exception:\n" + e); + } + } + + textures.put(location, added); + } + } + + void addInputImage(ResourceLocation id, NativeImage image) { + var location = SpriteSource.TEXTURE_ID_CONVERTER.idToFile(id); + + if (!textures.containsKey(location)) { + textures.put(location, new ImageInput(location, image, false)); + } + } + + InputTexture getInput(ResourceLocation location) { + return textures.get(location); + } + + InputTexture getSpriteDefault(ResourceLocation id) { + var location = SpriteSource.TEXTURE_ID_CONVERTER.idToFile(id); + + return textures.get(location); + } + + void uploadInputs() { + GFX.pixelStore(GFX.GL_UNPACK_SKIP_PIXELS, 0); + GFX.pixelStore(GFX.GL_UNPACK_SKIP_ROWS, 0); + GFX.pixelStore(GFX.GL_UNPACK_ROW_LENGTH, 0); + GFX.pixelStore(GFX.GL_UNPACK_ALIGNMENT, 1); + + for (var i : textures.values()) { + i.upload(); + } + } + + void clear() { + for (var entry : textures.values()) { + entry.close(); + } + + textures.clear(); + } + + static class InputTexture { + boolean present() { + return false; + } + + int getTexId() { + return -1; + } + + protected void upload() { + } + + protected void close() { + } + } + + static class ImageInput extends InputTexture { + final ResourceLocation location; + final NativeImage image; + final boolean autoClose; + private int texId = -1; + + public ImageInput(ResourceLocation location, NativeImage image, boolean autoClose) { + this.location = location; + this.image = image; + this.autoClose = autoClose; + } + + @Override + public boolean present() { + return texId != -1; + } + + @Override + public int getTexId() { + return texId; + } + + @Override + protected void upload() { + if (texId == -1) { + texId = TextureUtil.generateTextureId(); + } + + CanvasTextureState.bindTexture(texId); + GFX.texImage2D(GFX.GL_TEXTURE_2D, 0, GFX.GL_RGBA8, image.getWidth(), image.getHeight(), 0, image.format().glFormat(), GFX.GL_UNSIGNED_BYTE, (ByteBuffer) null); + image.upload(0, 0, 0, autoClose); + } + + @Override + protected void close() { + if (texId != -1) { + TextureUtil.releaseTextureId(texId); + texId = -1; + } + + if (autoClose) { + image.close(); + } + } + } +} diff --git a/src/main/java/grondag/canvas/texture/pbr/PbrLoader.java b/src/main/java/grondag/canvas/texture/pbr/PbrLoader.java new file mode 100644 index 000000000..990f08a6b --- /dev/null +++ b/src/main/java/grondag/canvas/texture/pbr/PbrLoader.java @@ -0,0 +1,88 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.texture.pbr; + +import java.util.ArrayList; +import java.util.Collection; + +import net.minecraft.client.resources.model.AtlasSet; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; + +import grondag.canvas.CanvasMod; +import grondag.canvas.mixinterface.SpriteContentsExt; +import grondag.canvas.mixinterface.StitchResultExt; +import grondag.canvas.varia.GFX; + +public class PbrLoader { + static final InputTextureManager inputTextureManager = new InputTextureManager(); + + public static void errorOrdering() { + CanvasMod.LOG.error("Can't load PBR atlases due to load ordering error!"); + } + + public static void reload(ResourceManager manager, Collection atlasPreparations) { + var atlases = new ArrayList(); + var debugProcess = new PbrProcess(new ResourceLocation("frex:shaders/func/normal_from_luminance.frag")); + + for (var rawEntry : atlasPreparations) { + final var entry = (StitchResultExt) rawEntry; + final var atlas = entry.canvas_atlas(); + final var preps = entry.canvas_preparations(); + + PbrMapAtlas pbrAtlas = new PbrMapAtlas(atlas.location(), preps.width(), preps.height(), preps.mipLevel()); + atlases.add(pbrAtlas); + + for (var spriteEntry : preps.regions().entrySet()) { + var sprite = spriteEntry.getValue(); + var contents = spriteEntry.getValue().contents(); + var contentsExt = (SpriteContentsExt) contents; + inputTextureManager.addInputImage(spriteEntry.getKey(), contentsExt.canvas_images()[0]); + + var pbrSprite = new PbrMapSprite(atlas.location(), spriteEntry.getKey(), sprite.getX(), sprite.getY(), contents.width(), contents.height()); + pbrAtlas.sprites.add(pbrSprite); + + // debug + pbrSprite.withProcess(PbrMapSpriteLayer.NORMAL, debugProcess, new InputTextureManager.InputTexture[]{inputTextureManager.getSpriteDefault(spriteEntry.getKey())}); + + // CanvasMod.LOG.info(atlas.location() + ", " + spriteEntry.getKey() + ", " + spriteEntry.getValue().contentsExt().name()); + } + } + + // we are in Render thread the whole time + inputTextureManager.uploadInputs(); + + GFX.depthMask(false); + GFX.disableBlend(); + GFX.disableCull(); + GFX.disableDepthTest(); + + var context = new PbrProcess.DrawContext(); + context.drawBuffer.bind(); + + for (var atlas : atlases) { + atlas.processAndUpload(); + } + + inputTextureManager.clear(); + context.close(); + } +} diff --git a/src/main/java/grondag/canvas/texture/pbr/PbrMapAtlas.java b/src/main/java/grondag/canvas/texture/pbr/PbrMapAtlas.java new file mode 100644 index 000000000..9678a45cb --- /dev/null +++ b/src/main/java/grondag/canvas/texture/pbr/PbrMapAtlas.java @@ -0,0 +1,204 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.texture.pbr; + +import java.io.File; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Locale; + +import org.lwjgl.system.MemoryUtil; + +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.platform.TextureUtil; +import com.mojang.blaze3d.systems.RenderSystem; + +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceLocation; + +import grondag.canvas.CanvasMod; +import grondag.canvas.config.Configurator; +import grondag.canvas.render.CanvasTextureState; +import grondag.canvas.varia.GFX; + +public class PbrMapAtlas { + + static final int GL_TARGET = GFX.GL_TEXTURE_2D_ARRAY; + static final int GL_INTERNAL_FORMAT = GFX.GL_RGBA8; + static final int GL_FORMAT = GFX.GL_RGBA; + static final int GL_TYPE = GFX.GL_UNSIGNED_BYTE; + + final ResourceLocation location; + final ArrayList sprites = new ArrayList<>(); + final int width; + final int height; + final int lod; + int texId; + + public PbrMapAtlas(ResourceLocation atlasLocation, int width, int height, int lod) { + this.location = atlasLocation; + this.width = width; + this.height = height; + this.lod = lod; + } + + void processAndUpload() { + for (var sprite : sprites) { + sprite.process(); + } + + GFX.pixelStore(GFX.GL_PACK_SKIP_PIXELS, 0); + GFX.pixelStore(GFX.GL_PACK_SKIP_ROWS, 0); + GFX.pixelStore(GFX.GL_PACK_ROW_LENGTH, 0); + + for (var sprite : sprites) { + sprite.download(); + } + + texId = TextureUtil.generateTextureId(); + CanvasTextureState.bindTexture(GL_TARGET, texId); + GFX.objectLabel(GFX.GL_TEXTURE, texId, "IMG " + labelAtlas()); + + GFX.texParameter(GL_TARGET, GFX.GL_TEXTURE_MIN_FILTER, GFX.GL_NEAREST); + GFX.texParameter(GL_TARGET, GFX.GL_TEXTURE_MAG_FILTER, GFX.GL_NEAREST); + GFX.texParameter(GL_TARGET, GFX.GL_TEXTURE_WRAP_S, GFX.GL_CLAMP_TO_EDGE); + GFX.texParameter(GL_TARGET, GFX.GL_TEXTURE_WRAP_T, GFX.GL_CLAMP_TO_EDGE); + + // if (lod > 0) { + // GFX.texParameter(GL_TARGET, GFX.GL_TEXTURE_MIN_LOD, 0); + // GFX.texParameter(GL_TARGET, GFX.GL_TEXTURE_MAX_LOD, lod); + // GFX.texParameter(GL_TARGET, GFX.GL_TEXTURE_MAX_LEVEL, lod); + // GFX.texParameter(GL_TARGET, GFX.GL_TEXTURE_LOD_BIAS, 0.0F); + // } + + GFX.pixelStore(GFX.GL_UNPACK_SKIP_PIXELS, 0); + GFX.pixelStore(GFX.GL_UNPACK_SKIP_ROWS, 0); + GFX.pixelStore(GFX.GL_UNPACK_ROW_LENGTH, 0); + GFX.pixelStore(GFX.GL_UNPACK_ALIGNMENT, 4); + + final int layerCount = PbrMapAtlasLayer.values().length; + + // prepare. necessary? + GFX.texImage3D(GL_TARGET, 0, GL_INTERNAL_FORMAT, width, height, layerCount, 0, GL_FORMAT, GL_TYPE, null); + + // TODO: lod, CPU side or GPU side? + final int w = width; + final int h = height; + + for (var atlasLayer : PbrMapAtlasLayer.values()) { + ByteBuffer pixels = MemoryUtil.memAlloc(width * height * 4 * layerCount); + + for (var sprite : sprites) { + for (int y = 0; y < sprite.height; y++) { + pixels.position(sprite.x * 4 + ((sprite.y + y) * width * 4)); + + for (int x = 0; x < sprite.width; x++) { + // pixels.putInt(ColorEncode.encode(sprite.r(PbrMapSpriteLayer.NORMAL, x, y), sprite.g(PbrMapSpriteLayer.NORMAL, x, y), sprite.b(PbrMapSpriteLayer.NORMAL, x, y), 255)); + pixels.putInt(ColorEncode.encode(atlasLayer.swizzler.r(sprite, x, y), atlasLayer.swizzler.g(sprite, x, y), atlasLayer.swizzler.b(sprite, x, y), atlasLayer.swizzler.a(sprite, x, y))); + // pixels.putInt(ColorEncode.encode(atlasLayer.swizzler.r(sprite, x, y), atlasLayer.swizzler.g(sprite, x, y), atlasLayer.swizzler.b(sprite, x, y), 255)); + } + } + } + + pixels.position(0); + GFX.glTexSubImage3D(PbrMapAtlas.GL_TARGET, 0, 0, 0, atlasLayer.layer, w, h, 1, PbrMapAtlas.GL_FORMAT, PbrMapAtlas.GL_TYPE, pixels); + + MemoryUtil.memFree(pixels); + } + + for (var sprite : sprites) { + sprite.close(); + } + + // debug output + if (Configurator.debugSpriteAtlas) { + debugOutput(); + } + } + + public void debugOutput() { + RenderSystem.assertOnRenderThreadOrInit(); + + final int layerCount = PbrMapAtlasLayer.values().length; + final ByteBuffer debugPixels = MemoryUtil.memAlloc(width * height * 4 * layerCount); + CanvasTextureState.bindTexture(GL_TARGET, texId); + GFX.pixelStore(GFX.GL_PACK_SKIP_PIXELS, 0); + GFX.pixelStore(GFX.GL_PACK_SKIP_ROWS, 0); + GFX.pixelStore(GFX.GL_PACK_ROW_LENGTH, 0); + GFX.pixelStore(GFX.GL_PACK_ALIGNMENT, 4); + GFX.glGetTexImage(GL_TARGET, 0, GL_FORMAT, GL_TYPE, debugPixels); + + Util.ioPool().execute(() -> { + NativeImage[] images = new NativeImage[layerCount]; + + try { + int i = 0; + + for (var layer : PbrMapAtlasLayer.values()) { + var location = label(layer); + final Path path = Minecraft.getInstance().gameDirectory.toPath().normalize().resolve("atlas_debug"); + final File atlasDir = path.toFile(); + + if (!atlasDir.exists()) { + atlasDir.mkdir(); + CanvasMod.LOG.info("Created atlas debug output folder" + atlasDir.toString()); + } + + final File file = new File(atlasDir.getAbsolutePath() + File.separator + location.replaceAll("[_/:]", "_")); + images[i] = new NativeImage(width, height, false); + + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int pixel = debugPixels.getInt((i * width * height * 4) + x * 4 + width * y * 4); + // we have faith in NativeImage byte ordering as the benchmark + images[i].setPixelRGBA(x, y, pixel); + } + } + + images[i].writeToFile(file); + + i++; + } + + } catch (final Exception e) { + CanvasMod.LOG.warn("Couldn't save atlas image", e); + } finally { + for (int i = 0; i < layerCount; i++) { + if (images[i] != null) { + images[i].close(); + } + } + + MemoryUtil.memFree(debugPixels); + } + }); + } + + String label(PbrMapAtlasLayer layer) { + return location.toString().replace(".png", "") + "_" + layer.name().toLowerCase(Locale.ROOT) + ".png"; + } + + String labelAtlas() { + return location.toString().replace(".png", "") + "_pbr.png"; + } +} diff --git a/src/main/java/grondag/canvas/texture/pbr/PbrMapAtlasLayer.java b/src/main/java/grondag/canvas/texture/pbr/PbrMapAtlasLayer.java new file mode 100644 index 000000000..2d54d9bb6 --- /dev/null +++ b/src/main/java/grondag/canvas/texture/pbr/PbrMapAtlasLayer.java @@ -0,0 +1,91 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.texture.pbr; + +import static grondag.canvas.texture.pbr.PbrMapSpriteLayer.AO; +import static grondag.canvas.texture.pbr.PbrMapSpriteLayer.EMISSIVE; +import static grondag.canvas.texture.pbr.PbrMapSpriteLayer.HEIGHT; +import static grondag.canvas.texture.pbr.PbrMapSpriteLayer.NORMAL; +import static grondag.canvas.texture.pbr.PbrMapSpriteLayer.REFLECTANCE; +import static grondag.canvas.texture.pbr.PbrMapSpriteLayer.ROUGHNESS; + +public enum PbrMapAtlasLayer { + NORMAL_HEIGHT(0, new Swizzler() { + @Override + public int r(PbrMapSpriteLayer.LayeredImage image, int x, int y) { + return image.r(NORMAL, x, y); + } + + @Override + public int g(PbrMapSpriteLayer.LayeredImage image, int x, int y) { + return image.g(NORMAL, x, y); + } + + @Override + public int b(PbrMapSpriteLayer.LayeredImage image, int x, int y) { + return image.b(NORMAL, x, y); + } + + @Override + public int a(PbrMapSpriteLayer.LayeredImage image, int x, int y) { + return image.r(HEIGHT, x, y); + } + }), + REFLECTANCE_ROUGHNESS_EMISSIVE_AO(1, new Swizzler() { + @Override + public int r(PbrMapSpriteLayer.LayeredImage image, int x, int y) { + return image.r(REFLECTANCE, x, y); + } + + @Override + public int g(PbrMapSpriteLayer.LayeredImage image, int x, int y) { + return image.r(ROUGHNESS, x, y); + } + + @Override + public int b(PbrMapSpriteLayer.LayeredImage image, int x, int y) { + return image.r(EMISSIVE, x, y); + } + + @Override + public int a(PbrMapSpriteLayer.LayeredImage image, int x, int y) { + return image.r(AO, x, y); + } + }); + + public final int layer; + public final Swizzler swizzler; + + PbrMapAtlasLayer(int layer, Swizzler swizzler) { + this.layer = layer; + this.swizzler = swizzler; + } + + public interface Swizzler { + int r(PbrMapSpriteLayer.LayeredImage image, int x, int y); + + int g(PbrMapSpriteLayer.LayeredImage image, int x, int y); + + int b(PbrMapSpriteLayer.LayeredImage image, int x, int y); + + int a(PbrMapSpriteLayer.LayeredImage image, int x, int y); + } +} diff --git a/src/main/java/grondag/canvas/texture/pbr/PbrMapSprite.java b/src/main/java/grondag/canvas/texture/pbr/PbrMapSprite.java new file mode 100644 index 000000000..3841445b5 --- /dev/null +++ b/src/main/java/grondag/canvas/texture/pbr/PbrMapSprite.java @@ -0,0 +1,227 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.texture.pbr; + +import java.nio.ByteBuffer; +import java.util.Map; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import org.lwjgl.system.MemoryUtil; + +import com.mojang.blaze3d.platform.TextureUtil; + +import net.minecraft.resources.ResourceLocation; + +import grondag.canvas.CanvasMod; +import grondag.canvas.render.CanvasTextureState; +import grondag.canvas.varia.GFX; + +public class PbrMapSprite implements PbrMapSpriteLayer.LayeredImage { + private final ResourceLocation atlasLocation; + private final ResourceLocation location; + final int x; + final int y; + final int width; + final int height; + private final Map processMap = new Object2ObjectOpenHashMap<>(); + private final Map pixelMap = new Object2ObjectOpenHashMap<>(); + private boolean processed = false; + private boolean downloaded = false; + private boolean closed = false; + + public PbrMapSprite(ResourceLocation atlasLocation, ResourceLocation location, int x, int y, int width, int height) { + this.atlasLocation = atlasLocation; + this.location = location; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + void withProcess(PbrMapSpriteLayer layer, PbrProcess process, InputTextureManager.InputTexture[] inputTexture) { + if (processMap.containsKey(layer)) { + CanvasMod.LOG.warn("Not applying duplicate PBR processor for sprite " + location + " on layer " + layer.name()); + } else { + int pbrOutput = TextureUtil.generateTextureId(); + CanvasTextureState.bindTexture(pbrOutput); + + GFX.texParameter(GFX.GL_TEXTURE_2D, GFX.GL_TEXTURE_MIN_FILTER, GFX.GL_NEAREST); + GFX.texParameter(GFX.GL_TEXTURE_2D, GFX.GL_TEXTURE_MAG_FILTER, GFX.GL_NEAREST); + GFX.texParameter(GFX.GL_TEXTURE_2D, GFX.GL_TEXTURE_WRAP_S, GFX.GL_CLAMP_TO_EDGE); + GFX.texParameter(GFX.GL_TEXTURE_2D, GFX.GL_TEXTURE_WRAP_T, GFX.GL_CLAMP_TO_EDGE); + + // if (lod > 0) { + // GFX.texParameter(GFX.GL_TEXTURE_2D, GFX.GL_TEXTURE_MIN_LOD, 0); + // GFX.texParameter(GFX.GL_TEXTURE_2D, GFX.GL_TEXTURE_MAX_LOD, lod); + // GFX.texParameter(GFX.GL_TEXTURE_2D, GFX.GL_TEXTURE_MAX_LEVEL, lod); + // GFX.texParameter(GFX.GL_TEXTURE_2D, GFX.GL_TEXTURE_LOD_BIAS, 0.0F); + // } + + GFX.texImage2D(GFX.GL_TEXTURE_2D, 0, layer.glInternalFormat, width, height, 0, layer.glFormat, layer.glPixelDataType, (ByteBuffer) null); + + processMap.put(layer, new ProcessInfo(process, inputTexture, pbrOutput)); + } + } + + void process() { + if (processed) { + throw new IllegalStateException("Processing PBR map twice for " + location); + } + + for (var process : processMap.values()) { + process.processStatus = process.process.process(location.toString(), width, height, process.inputTexture, process.outputTexId); + } + + processed = true; + } + + void download() { + if (downloaded) { + throw new IllegalStateException("Downloading PBR map result twice for " + location); + } + + for (var layer : PbrMapSpriteLayer.values()) { + if (processMap.containsKey(layer) && processMap.get(layer).processStatus) { + final ByteBuffer pixelBuffer = MemoryUtil.memAlloc(width * height * layer.bytes); + + GFX.pixelStore(GFX.GL_PACK_ALIGNMENT, layer.alignment); + + CanvasTextureState.bindTexture(processMap.get(layer).outputTexId); + GFX.glGetTexImage(GFX.GL_TEXTURE_2D, 0, layer.glFormat, layer.glPixelDataType, pixelBuffer); + + pixelMap.put(layer, pixelBuffer); + } else { + // TODO: this is DEBUG, should just spit out default instead of this + // var input = PbrLoader.inputTextureManager.getSpriteDefault(location); + // + // if (input instanceof InputTextureManager.ImageInput imageInput) { + // final ByteBuffer pixelBuffer = MemoryUtil.memAlloc(width * height * 4); + // final var nativeImage = imageInput.image; + // + // for (int row = 0; row < height; row++) { + // for (int i = 0; i < width; i++) { + // // all of this is unnecessarily complex for the sake of consistency + // for (int byteOrder = 0; byteOrder < layer.bytes; byteOrder++) { + // byte toPut = switch (byteOrder) { + // case (ColorEncode.RED_BYTE_OFFSET) -> nativeImage.format().hasRed() ? nativeImage.getRedOrLuminance(i, row) : 0; + // case (ColorEncode.GREEN_BYTE_OFFSET) -> nativeImage.format().hasGreen() ? nativeImage.getGreenOrLuminance(i, row) : 0; + // case (ColorEncode.BLUE_BYTE_OFFSET) -> nativeImage.format().hasBlue() ? nativeImage.getBlueOrLuminance(i, row) : 0; + // case (ColorEncode.ALPHA_BYTE_OFFSET) -> nativeImage.format().hasAlpha() ? nativeImage.getLuminanceOrAlpha(i, row) : 0xF; + // default -> 0; + // }; + // + // pixelBuffer.put(toPut); + // } + // } + // } + // + // pixelMap.put(layer, pixelBuffer); + // } + } + } + + downloaded = true; + } + + void close() { + if (closed) { + throw new IllegalStateException("Closing PBR sprite twice for " + location); + } + + for (var pixel : pixelMap.values()) { + MemoryUtil.memFree(pixel); + } + + for (var process : processMap.values()) { + TextureUtil.releaseTextureId(process.outputTexId); + } + + processMap.clear(); + pixelMap.clear(); + + closed = true; + } + + @Override + public int r(PbrMapSpriteLayer layer, int x, int y) { + if (closed) { + throw new IllegalStateException("Accessed closed PBR sprite " + location); + } + + if (pixelMap.containsKey(layer)) { + return pixelMap.get(layer).get((x + y * width) * layer.bytes + Math.min(layer.bytes, ColorEncode.RED_BYTE_OFFSET)); + } else { + return ColorEncode.r(layer.defaultValue); + } + } + + @Override + public int g(PbrMapSpriteLayer layer, int x, int y) { + if (closed) { + throw new IllegalStateException("Accessed closed PBR sprite " + location); + } + + if (pixelMap.containsKey(layer)) { + return pixelMap.get(layer).get((x + y * width) * layer.bytes + Math.min(layer.bytes, ColorEncode.GREEN_BYTE_OFFSET)); + } else { + return ColorEncode.g(layer.defaultValue); + } + } + + @Override + public int b(PbrMapSpriteLayer layer, int x, int y) { + if (closed) { + throw new IllegalStateException("Accessed closed PBR sprite " + location); + } + + if (pixelMap.containsKey(layer)) { + return pixelMap.get(layer).get((x + y * width) * layer.bytes + Math.min(layer.bytes, ColorEncode.BLUE_BYTE_OFFSET)); + } else { + return ColorEncode.b(layer.defaultValue); + } + } + + @Override + public int a(PbrMapSpriteLayer layer, int x, int y) { + if (closed) { + throw new IllegalStateException("Accessed closed PBR sprite " + location); + } + + if (pixelMap.containsKey(layer)) { + return pixelMap.get(layer).get((x + y * width) * layer.bytes + Math.min(layer.bytes, ColorEncode.ALPHA_BYTE_OFFSET)); + } else { + return ColorEncode.a(layer.defaultValue); + } + } + + static final class ProcessInfo { + private final PbrProcess process; + private final InputTextureManager.InputTexture[] inputTexture; + private final int outputTexId; + private boolean processStatus = false; + + ProcessInfo(PbrProcess process, InputTextureManager.InputTexture[] inputTexture, int outputTexId) { + this.process = process; + this.inputTexture = inputTexture; + this.outputTexId = outputTexId; + } + } +} diff --git a/src/main/java/grondag/canvas/texture/pbr/PbrMapSpriteLayer.java b/src/main/java/grondag/canvas/texture/pbr/PbrMapSpriteLayer.java new file mode 100644 index 000000000..79b9ff612 --- /dev/null +++ b/src/main/java/grondag/canvas/texture/pbr/PbrMapSpriteLayer.java @@ -0,0 +1,68 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.texture.pbr; + +import java.nio.ByteBuffer; + +import grondag.canvas.varia.GFX; + +public enum PbrMapSpriteLayer { + NORMAL(ColorEncode.encode(128, 128, 255, 0), 1, 3, GFX.GL_RGB8, GFX.GL_RGB, GFX.GL_UNSIGNED_BYTE), + HEIGHT(ColorEncode.encode(128, 0, 0, 0), 1, 1, GFX.GL_R8, GFX.GL_RED, GFX.GL_UNSIGNED_BYTE), + REFLECTANCE(ColorEncode.encode(10, 0, 0, 0), 1, 1, GFX.GL_R8, GFX.GL_RED, GFX.GL_UNSIGNED_BYTE), + ROUGHNESS(ColorEncode.encode(255, 0, 0, 0), 1, 1, GFX.GL_R8, GFX.GL_RED, GFX.GL_UNSIGNED_BYTE), + EMISSIVE(ColorEncode.encode(0, 0, 0, 0), 1, 1, GFX.GL_R8, GFX.GL_RED, GFX.GL_UNSIGNED_BYTE), + AO(ColorEncode.encode(255, 0, 0, 0), 1, 1, GFX.GL_R8, GFX.GL_RED, GFX.GL_UNSIGNED_BYTE); + + public final int defaultValue; + public final int alignment; + public final int bytes; + public final int glInternalFormat; + public final int glFormat; + public final int glPixelDataType; + + PbrMapSpriteLayer(int defaultValue, int alignment, int bytes, int glInternalFormat, int glFormat, int glPixelDataType) { + this.defaultValue = defaultValue; + this.alignment = alignment; + this.bytes = bytes; + this.glInternalFormat = glInternalFormat; + this.glFormat = glFormat; + this.glPixelDataType = glPixelDataType; + } + + public void drawDefault(ByteBuffer buffer) { + buffer.position(0); + + while (buffer.position() < buffer.limit() - 4) { + buffer.putInt(defaultValue); + } + } + + public interface LayeredImage { + int r(PbrMapSpriteLayer layer, int x, int y); + + int g(PbrMapSpriteLayer layer, int x, int y); + + int b(PbrMapSpriteLayer layer, int x, int y); + + int a(PbrMapSpriteLayer layer, int x, int y); + } +} diff --git a/src/main/java/grondag/canvas/texture/pbr/PbrProcess.java b/src/main/java/grondag/canvas/texture/pbr/PbrProcess.java new file mode 100644 index 000000000..369e35153 --- /dev/null +++ b/src/main/java/grondag/canvas/texture/pbr/PbrProcess.java @@ -0,0 +1,151 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.texture.pbr; + +import org.joml.Matrix4f; + +import net.minecraft.resources.ResourceLocation; + +import grondag.canvas.CanvasMod; +import grondag.canvas.buffer.format.CanvasVertexFormats; +import grondag.canvas.buffer.input.DrawableVertexCollector; +import grondag.canvas.buffer.input.SimpleVertexCollector; +import grondag.canvas.buffer.render.StaticDrawBuffer; +import grondag.canvas.buffer.render.TransferBuffer; +import grondag.canvas.buffer.render.TransferBuffers; +import grondag.canvas.material.state.RenderState; +import grondag.canvas.pipeline.GlSymbolLookup; +import grondag.canvas.render.CanvasTextureState; +import grondag.canvas.shader.ProcessProgram; +import grondag.canvas.varia.GFX; + +public class PbrProcess { + private static final String[] SAMPLERS; + private static final int INPUT_COUNT = 8; + private static int prevWidth = -1; + private static int prevHeight = -1; + private static int lastProgram = 0; + + static { + SAMPLERS = new String[INPUT_COUNT]; + + for (int i = 0; i < INPUT_COUNT; i++) { + SAMPLERS[i] = "frx_source" + i; + } + } + + private final ProcessProgram program; + + public PbrProcess(ResourceLocation fragSource) { + this.program = new ProcessProgram(fragSource.toString(), new ResourceLocation("canvas:shaders/internal/pbr.vert"), fragSource, SAMPLERS); + } + + public PbrProcess() { + this.program = new ProcessProgram("pbr_process", new ResourceLocation("canvas:shaders/internal/pbr.vert"), new ResourceLocation("frex:shaders/func/color.frag"), SAMPLERS); + } + + boolean process(String debugLabel, int width, int height, InputTextureManager.InputTexture[] inputTexture, int outputTextureId) { + if (inputTexture.length > INPUT_COUNT - 1) { + throw new IllegalStateException("PBR process is made to accept more than " + INPUT_COUNT + " image sources"); + } + + GFX.glFramebufferTexture2D(GFX.GL_FRAMEBUFFER, GFX.GL_COLOR_ATTACHMENT0, GFX.GL_TEXTURE_2D, outputTextureId, 0); + final int check = GFX.checkFramebufferStatus(GFX.GL_FRAMEBUFFER); + + if (check != GFX.GL_FRAMEBUFFER_COMPLETE) { + CanvasMod.LOG.warn("Failed PBR processing: Framebuffer " + debugLabel + " has invalid status " + check + " " + GlSymbolLookup.reverseLookup(check)); + return false; + } + + final boolean resized = width != prevWidth || height != prevHeight; + + prevWidth = width; + prevHeight = height; + + if (resized) { + GFX.viewport(0, 0, width, height); + } + + for (int i = 0; i < inputTexture.length; ++i) { + CanvasTextureState.ensureTextureOfTextureUnit(GFX.GL_TEXTURE0 + i, GFX.GL_TEXTURE_2D, inputTexture[i].getTexId()); + } + + program.activate(); + program.lod(0).layer(0).size(width, height); + + final boolean programChanged = lastProgram != program.programId(); + + lastProgram = program.programId(); + + if (resized || programChanged) { + program.projection(new Matrix4f().setOrtho(0, width, height, 0, 1000.0F, 3000.0F)); + } + + GFX.drawArrays(GFX.GL_TRIANGLES, 0, 6); + + return true; + } + + void close() { + program.unload(); + } + + static class DrawContext { + final StaticDrawBuffer drawBuffer; + final int fboGlId; + + { + final DrawableVertexCollector collector = new SimpleVertexCollector(RenderState.missing(), new int[64]); + final int[] v = collector.target(); + addVertex(0f, 0f, 0.2f, 0f, 1f, v, 0); + addVertex(1f, 0f, 0.2f, 1f, 1f, v, 5); + addVertex(1f, 1f, 0.2f, 1f, 0f, v, 10); + addVertex(1f, 1f, 0.2f, 1f, 0f, v, 15); + addVertex(0f, 1f, 0.2f, 0f, 0f, v, 20); + addVertex(0f, 0f, 0.2f, 0f, 1f, v, 25); + collector.commit(30); + + final TransferBuffer transfer = TransferBuffers.claim(collector.byteSize()); + collector.toBuffer(transfer, 0); + drawBuffer = new StaticDrawBuffer(CanvasVertexFormats.PROCESS_VERTEX_UV, transfer); + drawBuffer.upload(); + collector.clear(); + + fboGlId = GFX.genFramebuffer(); + GFX.bindFramebuffer(GFX.GL_FRAMEBUFFER, fboGlId); + GFX.objectLabel(GFX.GL_FRAMEBUFFER, fboGlId, "FBO pbr_context"); + GFX.glDrawBuffers(new int[]{GFX.GL_COLOR_ATTACHMENT0}); + } + + void close() { + drawBuffer.release(); + GFX.deleteFramebuffer(fboGlId); + } + + private static void addVertex(float x, float y, float z, float u, float v, int[] target, int index) { + target[index] = Float.floatToRawIntBits(x); + target[++index] = Float.floatToRawIntBits(y); + target[++index] = Float.floatToRawIntBits(z); + target[++index] = Float.floatToRawIntBits(u); + target[++index] = Float.floatToRawIntBits(v); + } + } +} diff --git a/src/main/resources/assets/canvas/shaders/internal/pbr.vert b/src/main/resources/assets/canvas/shaders/internal/pbr.vert new file mode 100644 index 000000000..2eb5e3dc9 --- /dev/null +++ b/src/main/resources/assets/canvas/shaders/internal/pbr.vert @@ -0,0 +1,19 @@ +#include frex:shaders/api/header.glsl + +/****************************************************** + canvas:shaders/pipeline/internal/pbr.vert +******************************************************/ + +uniform mat4 frxu_frameProjectionMatrix; +uniform ivec2 frxu_size; + +in vec3 in_vertex; +in vec2 in_uv; + +out vec2 frx_texcoord; + +void main() { + vec4 outPos = frxu_frameProjectionMatrix * vec4(in_vertex.xy * frxu_size, 0.0, 1.0); + gl_Position = vec4(outPos.xy, 0.2, 1.0); + frx_texcoord = in_uv; +} diff --git a/src/main/resources/assets/canvas/shaders/pipeline/post/copy.frag b/src/main/resources/assets/canvas/shaders/pipeline/post/copy.frag index efe607be3..cbfa1fa85 100644 --- a/src/main/resources/assets/canvas/shaders/pipeline/post/copy.frag +++ b/src/main/resources/assets/canvas/shaders/pipeline/post/copy.frag @@ -2,7 +2,7 @@ #include canvas:shaders/pipeline/pipeline.glsl /****************************************************** - canvas:shaders/pipeline/post/copy.frag + canvas:shaders/pipeline/post/color.frag ******************************************************/ uniform sampler2D _cvu_input; diff --git a/src/main/resources/assets/frex/shaders/func/alpha.frag b/src/main/resources/assets/frex/shaders/func/alpha.frag new file mode 100644 index 000000000..ba6a83bb8 --- /dev/null +++ b/src/main/resources/assets/frex/shaders/func/alpha.frag @@ -0,0 +1,15 @@ +#include frex:shaders/api/header.glsl + +/****************************************************** + frex:shaders/func/alpha.frag +******************************************************/ + +uniform sampler2D frxs_source0; + +in vec2 frx_texcoord; + +out vec4 outColor; + +void main() { + outColor = vec4(texture(frxs_source0, frx_texcoord).a); +} diff --git a/src/main/resources/assets/frex/shaders/func/blue.frag b/src/main/resources/assets/frex/shaders/func/blue.frag new file mode 100644 index 000000000..8e31d3866 --- /dev/null +++ b/src/main/resources/assets/frex/shaders/func/blue.frag @@ -0,0 +1,15 @@ +#include frex:shaders/api/header.glsl + +/****************************************************** + frex:shaders/func/blue.frag +******************************************************/ + +uniform sampler2D frxs_source0; + +in vec2 frx_texcoord; + +out vec4 outColor; + +void main() { + outColor = vec4(texture(frxs_source0, frx_texcoord).b); +} diff --git a/src/main/resources/assets/frex/shaders/func/color.frag b/src/main/resources/assets/frex/shaders/func/color.frag new file mode 100644 index 000000000..4b5702e37 --- /dev/null +++ b/src/main/resources/assets/frex/shaders/func/color.frag @@ -0,0 +1,15 @@ +#include frex:shaders/api/header.glsl + +/****************************************************** + frex:shaders/func/color.frag +******************************************************/ + +uniform sampler2D frxs_source0; + +in vec2 frx_texcoord; + +out vec4 outColor; + +void main() { + outColor = texture(frxs_source0, frx_texcoord); +} diff --git a/src/main/resources/assets/frex/shaders/func/green.frag b/src/main/resources/assets/frex/shaders/func/green.frag new file mode 100644 index 000000000..7a1b96371 --- /dev/null +++ b/src/main/resources/assets/frex/shaders/func/green.frag @@ -0,0 +1,15 @@ +#include frex:shaders/api/header.glsl + +/****************************************************** + frex:shaders/func/green.frag +******************************************************/ + +uniform sampler2D frxs_source0; + +in vec2 frx_texcoord; + +out vec4 outColor; + +void main() { + outColor = vec4(texture(frxs_source0, frx_texcoord).g); +} diff --git a/src/main/resources/assets/frex/shaders/func/luminance.frag b/src/main/resources/assets/frex/shaders/func/luminance.frag new file mode 100644 index 000000000..1313b27b4 --- /dev/null +++ b/src/main/resources/assets/frex/shaders/func/luminance.frag @@ -0,0 +1,17 @@ +#include frex:shaders/api/header.glsl +#include frex:shaders/lib/math.glsl + +/****************************************************** + frex:shaders/func/luminance.frag +******************************************************/ + +uniform sampler2D frxs_source0; + +in vec2 frx_texcoord; + +out vec4 outColor; + +void main() { + float luminance = frx_luminance(texture(frxs_source0, frx_texcoord).rgb); + outColor = vec4(luminance); +} diff --git a/src/main/resources/assets/frex/shaders/func/normal_from_luminance.frag b/src/main/resources/assets/frex/shaders/func/normal_from_luminance.frag new file mode 100644 index 000000000..6b97bf019 --- /dev/null +++ b/src/main/resources/assets/frex/shaders/func/normal_from_luminance.frag @@ -0,0 +1,18 @@ +#include frex:shaders/api/header.glsl +#include frex:shaders/lib/math.glsl + +/****************************************************** + frex:shaders/func/normal_from_luminance.frag +******************************************************/ + +uniform sampler2D frxs_source0; +uniform ivec2 frxu_size; + +in vec2 frx_texcoord; + +out vec4 outColor; + +void main() { + float luminance = frx_luminance(texture(frxs_source0, frx_texcoord).rgb); + outColor = vec4(normalize(vec3(dFdx(luminance), dFdy(luminance), luminance)) * 0.5 + 0.5, 1.0); +} diff --git a/src/main/resources/assets/frex/shaders/func/one.frag b/src/main/resources/assets/frex/shaders/func/one.frag new file mode 100644 index 000000000..11b71b02b --- /dev/null +++ b/src/main/resources/assets/frex/shaders/func/one.frag @@ -0,0 +1,11 @@ +#include frex:shaders/api/header.glsl + +/****************************************************** + frex:shaders/func/one.frag +******************************************************/ + +out vec4 outColor; + +void main() { + outColor = vec4(1.0); +} diff --git a/src/main/resources/assets/frex/shaders/func/red.frag b/src/main/resources/assets/frex/shaders/func/red.frag new file mode 100644 index 000000000..26b1e9285 --- /dev/null +++ b/src/main/resources/assets/frex/shaders/func/red.frag @@ -0,0 +1,15 @@ +#include frex:shaders/api/header.glsl + +/****************************************************** + frex:shaders/func/red.frag +******************************************************/ + +uniform sampler2D frxs_source0; + +in vec2 frx_texcoord; + +out vec4 outColor; + +void main() { + outColor = vec4(texture(frxs_source0, frx_texcoord).r); +} diff --git a/src/main/resources/assets/frex/shaders/func/zero.frag b/src/main/resources/assets/frex/shaders/func/zero.frag new file mode 100644 index 000000000..1f6d27d28 --- /dev/null +++ b/src/main/resources/assets/frex/shaders/func/zero.frag @@ -0,0 +1,11 @@ +#include frex:shaders/api/header.glsl + +/****************************************************** + frex:shaders/func/zero.frag +******************************************************/ + +out vec4 outColor; + +void main() { + outColor = vec4(0.0); +} diff --git a/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/post/copy.frag b/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/post/copy.frag index e1c47e073..b089aab2a 100644 --- a/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/post/copy.frag +++ b/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/post/copy.frag @@ -2,7 +2,7 @@ #include abstract:shaders/pipeline/pipeline.glsl /****************************************************** - abstract:shaders/pipeline/post/copy.frag + abstract:shaders/pipeline/post/color.frag ******************************************************/ uniform sampler2D _cvu_input;