Files
Angelica/src/main/java/net/coderbot/iris/rendertarget/RenderTargets.java
T

343 lines
12 KiB
Java

package net.coderbot.iris.rendertarget;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import lombok.Getter;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gl.framebuffer.GlFramebuffer;
import net.coderbot.iris.gl.texture.DepthBufferFormat;
import net.coderbot.iris.gl.texture.DepthCopyStrategy;
import net.coderbot.iris.shaderpack.PackDirectives;
import net.coderbot.iris.shaderpack.PackRenderTargetDirectives;
import org.joml.Vector2i;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class RenderTargets {
private final RenderTarget[] targets;
private int currentDepthTexture;
private DepthBufferFormat currentDepthFormat;
@Getter
private final DepthTexture noTranslucents;
private final DepthTexture noHand;
private final GlFramebuffer depthSourceFb;
private final GlFramebuffer noTranslucentsDestFb;
private final GlFramebuffer noHandDestFb;
private DepthCopyStrategy copyStrategy;
private final List<GlFramebuffer> ownedFramebuffers;
private int cachedWidth;
private int cachedHeight;
@Getter
private boolean fullClearRequired;
private boolean translucentDepthDirty;
private boolean handDepthDirty;
private int cachedDepthBufferVersion;
public RenderTargets(int width, int height, int depthTexture, int depthBufferVersion, DepthBufferFormat depthFormat, Map<Integer, PackRenderTargetDirectives.RenderTargetSettings> renderTargets, PackDirectives packDirectives) {
targets = new RenderTarget[renderTargets.size()];
renderTargets.forEach((index, settings) -> {
// TODO: Handle mipmapping?
Vector2i dimensions = packDirectives.getTextureScaleOverride(index, width, height);
// Apply format fallback for opengl versions with limited color-renderable format support (e.g., macOS GL 2.1)
var requestedFormat = settings.getInternalFormat();
var actualFormat = requestedFormat.getColorRenderableFallback();
if (actualFormat != requestedFormat) {
Iris.logger.info("Render target {} using fallback format {} (requested {})", index, actualFormat, requestedFormat);
}
targets[index] = RenderTarget.builder().setDimensions(dimensions.x, dimensions.y)
.setInternalFormat(actualFormat)
.setPixelFormat(actualFormat.getPixelFormat()).build();
});
this.currentDepthTexture = depthTexture;
this.currentDepthFormat = depthFormat;
this.copyStrategy = DepthCopyStrategy.fastest(currentDepthFormat.isCombinedStencil());
this.cachedWidth = width;
this.cachedHeight = height;
this.cachedDepthBufferVersion = depthBufferVersion;
this.ownedFramebuffers = new ArrayList<>();
// NB: Make sure all buffers are cleared so that they don't contain undefined
// data. Otherwise very weird things can happen.
fullClearRequired = true;
this.depthSourceFb = createFramebufferWritingToMain(new int[] {0});
this.noTranslucents = new DepthTexture(width, height, currentDepthFormat);
this.noHand = new DepthTexture(width, height, currentDepthFormat);
this.noTranslucentsDestFb = createFramebufferWritingToMain(new int[] {0});
this.noTranslucentsDestFb.addDepthAttachment(this.noTranslucents.getTextureId());
this.noHandDestFb = createFramebufferWritingToMain(new int[] {0});
this.noHandDestFb.addDepthAttachment(this.noHand.getTextureId());
this.translucentDepthDirty = true;
this.handDepthDirty = true;
}
public void destroy() {
for (GlFramebuffer owned : ownedFramebuffers) {
owned.destroy();
}
for (RenderTarget target : targets) {
target.destroy();
}
noTranslucents.destroy();
noHand.destroy();
}
public int getRenderTargetCount() {
return targets.length;
}
public RenderTarget get(int index) {
return targets[index];
}
public int getDepthTexture() {
return currentDepthTexture;
}
public DepthTexture getDepthTextureNoTranslucents() {
return noTranslucents;
}
public DepthTexture getDepthTextureNoHand() {
return noHand;
}
public boolean resizeIfNeeded(int newDepthBufferVersion, int newDepthTextureId, int newWidth, int newHeight, DepthBufferFormat newDepthFormat, PackDirectives packDirectives) {
boolean recreateDepth = false;
if (cachedDepthBufferVersion != newDepthBufferVersion) {
recreateDepth = true;
currentDepthTexture = newDepthTextureId;
cachedDepthBufferVersion = newDepthBufferVersion;
}
boolean sizeChanged = newWidth != cachedWidth || newHeight != cachedHeight;
boolean depthFormatChanged = newDepthFormat != currentDepthFormat;
if (depthFormatChanged) {
currentDepthFormat = newDepthFormat;
// Might need a new copy strategy
copyStrategy = DepthCopyStrategy.fastest(currentDepthFormat.isCombinedStencil());
}
if (recreateDepth) {
// Re-attach the depth textures with the new depth texture ID, since Minecraft re-creates
// the depth texture when resizing its render targets.
//
// I'm not sure if our framebuffers holding on to the old depth texture between frames
// could be a concern, in the case of resizing and similar. I think it should work
// based on what I've seen of the spec, though - it seems like deleting a texture
// automatically detaches it from its framebuffers.
for (GlFramebuffer framebuffer : ownedFramebuffers) {
if (framebuffer == noHandDestFb || framebuffer == noTranslucentsDestFb) {
// NB: Do not change the depth attachment of these framebuffers
// as it is intentionally different
continue;
}
if (framebuffer.hasDepthAttachment()) {
framebuffer.addDepthAttachment(newDepthTextureId);
}
}
}
if (depthFormatChanged || sizeChanged) {
// Reallocate depth buffers
noTranslucents.resize(newWidth, newHeight, newDepthFormat);
noHand.resize(newWidth, newHeight, newDepthFormat);
this.translucentDepthDirty = true;
this.handDepthDirty = true;
}
if (sizeChanged) {
cachedWidth = newWidth;
cachedHeight = newHeight;
for (int i = 0; i < targets.length; i++) {
targets[i].resize(packDirectives.getTextureScaleOverride(i, newWidth, newHeight));
}
fullClearRequired = true;
}
return sizeChanged;
}
public void copyPreTranslucentDepth() {
if (translucentDepthDirty) {
translucentDepthDirty = false;
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, noTranslucents.getTextureId());
depthSourceFb.bindAsReadBuffer();
RenderSystem.copyTexImage2D(GL11.GL_TEXTURE_2D, 0, currentDepthFormat.getGlInternalFormat(), 0, 0, cachedWidth, cachedHeight, 0);
} else {
copyStrategy.copy(depthSourceFb, getDepthTexture(), noTranslucentsDestFb, noTranslucents.getTextureId(), getCurrentWidth(), getCurrentHeight());
}
}
public void copyPreHandDepth() {
if (handDepthDirty) {
handDepthDirty = false;
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, noHand.getTextureId());
depthSourceFb.bindAsReadBuffer();
RenderSystem.copyTexImage2D(GL11.GL_TEXTURE_2D, 0, currentDepthFormat.getGlInternalFormat(), 0, 0, cachedWidth, cachedHeight, 0);
} else {
copyStrategy.copy(depthSourceFb, getDepthTexture(), noHandDestFb, noHand.getTextureId(), getCurrentWidth(), getCurrentHeight());
}
}
public void onFullClear() {
fullClearRequired = false;
}
public GlFramebuffer createFramebufferWritingToMain(int[] drawBuffers) {
return createFullFramebuffer(false, drawBuffers);
}
public GlFramebuffer createFramebufferWritingToAlt(int[] drawBuffers) {
return createFullFramebuffer(true, drawBuffers);
}
public GlFramebuffer createClearFramebuffer(boolean alt, int[] clearBuffers) {
ImmutableSet<Integer> stageWritesToMain = ImmutableSet.of();
if (!alt) {
stageWritesToMain = invert(ImmutableSet.of(), clearBuffers);
}
return createColorFramebuffer(stageWritesToMain, clearBuffers);
}
private ImmutableSet<Integer> invert(ImmutableSet<Integer> base, int[] relevant) {
ImmutableSet.Builder<Integer> inverted = ImmutableSet.builder();
for (int i : relevant) {
if (!base.contains(i)) {
inverted.add(i);
}
}
return inverted.build();
}
private GlFramebuffer createEmptyFramebuffer() {
GlFramebuffer framebuffer = new GlFramebuffer();
ownedFramebuffers.add(framebuffer);
framebuffer.addDepthAttachment(currentDepthTexture);
// NB: Before OpenGL 3.0, all framebuffers are required to have a color attachment no matter what.
framebuffer.addColorAttachment(0, get(0).getMainTexture());
framebuffer.noDrawBuffers();
return framebuffer;
}
public GlFramebuffer createDHFramebuffer(ImmutableSet<Integer> stageWritesToAlt, int[] drawBuffers) {
if (drawBuffers.length == 0) {
return createEmptyFramebuffer();
}
ImmutableSet<Integer> stageWritesToMain = invert(stageWritesToAlt, drawBuffers);
return createColorFramebuffer(stageWritesToMain, drawBuffers);
}
public GlFramebuffer createGbufferFramebuffer(ImmutableSet<Integer> stageWritesToAlt, int[] drawBuffers) {
if (drawBuffers.length == 0) {
return createEmptyFramebuffer();
}
ImmutableSet<Integer> stageWritesToMain = invert(stageWritesToAlt, drawBuffers);
GlFramebuffer framebuffer = createColorFramebuffer(stageWritesToMain, drawBuffers);
framebuffer.addDepthAttachment(currentDepthTexture);
return framebuffer;
}
private GlFramebuffer createFullFramebuffer(boolean clearsAlt, int[] drawBuffers) {
if (drawBuffers.length == 0) {
return createEmptyFramebuffer();
}
ImmutableSet<Integer> stageWritesToMain = ImmutableSet.of();
if (!clearsAlt) {
stageWritesToMain = invert(ImmutableSet.of(), drawBuffers);
}
return createColorFramebufferWithDepth(stageWritesToMain, drawBuffers);
}
public GlFramebuffer createColorFramebufferWithDepth(ImmutableSet<Integer> stageWritesToMain, int[] drawBuffers) {
final GlFramebuffer framebuffer = createColorFramebuffer(stageWritesToMain, drawBuffers);
framebuffer.addDepthAttachment(currentDepthTexture);
return framebuffer;
}
public GlFramebuffer createColorFramebuffer(ImmutableSet<Integer> stageWritesToMain, int[] drawBuffers) {
if (drawBuffers.length == 0) {
throw new IllegalArgumentException("Framebuffer must have at least one color buffer");
}
final GlFramebuffer framebuffer = new GlFramebuffer();
ownedFramebuffers.add(framebuffer);
final int[] actualDrawBuffers = new int[drawBuffers.length];
for (int i = 0; i < drawBuffers.length; i++) {
actualDrawBuffers[i] = i;
if (drawBuffers[i] >= getRenderTargetCount()) {
// TODO: This causes resource leaks, also we should really verify this in the shaderpack parser...
throw new IllegalStateException("Render target with index " + drawBuffers[i] + " is not supported, only "
+ getRenderTargetCount() + " render targets are supported.");
}
final RenderTarget target = this.get(drawBuffers[i]);
final int textureId = stageWritesToMain.contains(drawBuffers[i]) ? target.getMainTexture() : target.getAltTexture();
framebuffer.addColorAttachment(i, textureId);
}
framebuffer.drawBuffers(actualDrawBuffers);
framebuffer.readBuffer(0);
if (!framebuffer.isComplete()) {
throw new IllegalStateException("Unexpected error while creating framebuffer");
}
return framebuffer;
}
public void destroyFramebuffer(GlFramebuffer framebuffer) {
framebuffer.destroy();
ownedFramebuffers.remove(framebuffer);
}
public int getCurrentWidth() {
return cachedWidth;
}
public int getCurrentHeight() {
return cachedHeight;
}
}