Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revise bloom render ticket API #2243

Merged
merged 3 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions src/main/java/gregtech/client/particle/GTBloomParticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,14 @@

public abstract class GTBloomParticle extends GTParticle implements IBloomEffect {

private final BloomEffectUtil.BloomRenderTicket ticket;

public GTBloomParticle(double posX, double posY, double posZ) {
super(posX, posY, posZ);

this.ticket = BloomEffectUtil.registerBloomRender(getBloomRenderSetup(), getBloomType(), this);
BloomEffectUtil.registerBloomRender(getBloomRenderSetup(), getBloomType(), this, this);
}

@Nullable
protected abstract IRenderSetup getBloomRenderSetup();

@NotNull
protected abstract BloomType getBloomType();

@Override
protected void onExpired() {
this.ticket.invalidate();
}
}
185 changes: 120 additions & 65 deletions src/main/java/gregtech/client/utils/BloomEffectUtil.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
package gregtech.client.utils;

import gregtech.api.metatileentity.MetaTileEntity;
import gregtech.client.particle.GTParticle;
import gregtech.client.renderer.IRenderSetup;
import gregtech.client.shader.Shaders;
import gregtech.client.shader.postprocessing.BloomEffect;
import gregtech.client.shader.postprocessing.BloomType;
import gregtech.common.ConfigHolder;

import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.client.shader.Framebuffer;
import net.minecraft.entity.Entity;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.world.World;
import net.minecraftforge.common.util.EnumHelper;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.relauncher.Side;
Expand All @@ -24,7 +28,6 @@
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.CheckReturnValue;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -36,6 +39,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;

@SideOnly(Side.CLIENT)
public class BloomEffectUtil {
Expand All @@ -53,9 +57,6 @@ public class BloomEffectUtil {
private static BlockRenderLayer bloom;
private static Framebuffer bloomFBO;

@Nullable
private static World currentWorld = null;

/**
* @return {@link BlockRenderLayer} instance for the bloom render layer.
*/
Expand All @@ -79,8 +80,8 @@ public static BlockRenderLayer getRealBloomLayer() {
* layers can be changed depending on external factors, such as presence of Optifine. If the actual bloom layer is
* disabled, {@link BlockRenderLayer#CUTOUT} is returned instead.
*
* @return {@link BlockRenderLayer} instance for the bloom render layer, or {@link BlockRenderLayer#CUTOUT} if
* bloom layer is disabled
* @return {@link BlockRenderLayer} instance for the bloom render layer, or {@link BlockRenderLayer#CUTOUT} if bloom
* layer is disabled
* @see #getEffectiveBloomLayer(BlockRenderLayer)
*/
@NotNull
Expand Down Expand Up @@ -110,8 +111,8 @@ public static BlockRenderLayer getEffectiveBloomLayer(BlockRenderLayer fallback)
*
* @param isBloomActive Whether bloom layer should be active. If this value is {@code false}, {@code fallback} layer
* will be returned. Has no effect if Optifine is present.
* @return {@link BlockRenderLayer} instance for the bloom render layer, or {@link BlockRenderLayer#CUTOUT} if
* bloom layer is disabled
* @return {@link BlockRenderLayer} instance for the bloom render layer, or {@link BlockRenderLayer#CUTOUT} if bloom
* layer is disabled
* @see #getEffectiveBloomLayer(boolean, BlockRenderLayer)
*/
@NotNull
Expand Down Expand Up @@ -145,42 +146,107 @@ public static Framebuffer getBloomFBO() {

/**
* <p>
* Register a custom bloom render callback for subsequent world render. The render call persists until it is
* manually freed by calling {@link BloomRenderTicket#invalidate()}, or automatically freed on client world change.
* Register a custom bloom render callback for subsequent world render. The render call persists until the
* {@code metaTileEntity} is invalidated, or the ticket is manually freed by calling
* {@link BloomRenderTicket#invalidate()}.
* </p>
* <p>
* This method does nothing if Optifine is present.
* This method does not register bloom render ticket when Optifine is present, and {@code null} will be returned
* instead of a ticket instance.
* </p>
*
* @param setup Render setup, if exists
* @param bloomType Type of the bloom
* @param render Rendering callback
* @param metaTileEntity Meta tile entity instance
* @return Ticket for the registered bloom render callback
* @throws NullPointerException if {@code bloomType == null || render == null || metaTileEntity == null}
*/
@Nullable
public static BloomRenderTicket registerBloomRender(@Nullable IRenderSetup setup,
@NotNull BloomType bloomType,
@NotNull IBloomEffect render,
@NotNull MetaTileEntity metaTileEntity) {
Objects.requireNonNull(metaTileEntity, "metaTileEntity == null");
return registerBloomRender(setup, bloomType, render, t -> metaTileEntity.isValid());
}

/**
* <p>
* Register a custom bloom render callback for subsequent world render. The render call persists until the
* {@code particle} is invalidated, or the ticket is manually freed by calling
* {@link BloomRenderTicket#invalidate()}.
* </p>
* <p>
* This method does not register bloom render ticket when Optifine is present, and {@code null} will be returned
* instead of a ticket instance.
* </p>
*
* @param setup Render setup, if exists
* @param bloomType Type of the bloom
* @param render Rendering callback
* @param particle Particle instance
* @return Ticket for the registered bloom render callback
* @throws NullPointerException if {@code bloomType == null || render == null || metaTileEntity == null}
*/
@Nullable
public static BloomRenderTicket registerBloomRender(@Nullable IRenderSetup setup,
@NotNull BloomType bloomType,
@NotNull IBloomEffect render,
@NotNull GTParticle particle) {
Objects.requireNonNull(particle, "particle == null");
return registerBloomRender(setup, bloomType, render, t -> particle.isAlive());
}

/**
* <p>
* Register a custom bloom render callback for subsequent world render. The render call persists until it is
* manually freed by calling {@link BloomRenderTicket#invalidate()}, or invalidated by validity checker.
* </p>
* <p>
* This method does not register bloom render ticket when Optifine is present, and {@code null} will be returned
* instead of a ticket instance.
* </p>
*
* @param setup Render setup, if exists
* @param bloomType Type of the bloom
* @param render Rendering callback
* @param validityChecker Optional validity checker; returning {@code false} causes the ticket to be invalidated.
* Checked on both pre/post render each frame.
* @return Ticket for the registered bloom render callback
* @throws NullPointerException if {@code bloomType == null || render == null}
*/
@NotNull
@CheckReturnValue
@Nullable
public static BloomRenderTicket registerBloomRender(@Nullable IRenderSetup setup,
@NotNull BloomType bloomType,
@NotNull IBloomEffect render) {
BloomRenderTicket ticket = new BloomRenderTicket(setup, bloomType, render);
if (Shaders.isOptiFineShaderPackLoaded()) {
ticket.invalidate();
} else {
SCHEDULED_BLOOM_RENDERS.add(ticket);
}
@NotNull IBloomEffect render,
@Nullable Predicate<BloomRenderTicket> validityChecker) {
if (Shaders.isOptiFineShaderPackLoaded()) return null;
BloomRenderTicket ticket = new BloomRenderTicket(setup, bloomType, render, validityChecker);
SCHEDULED_BLOOM_RENDERS.add(ticket);
return ticket;
}

/**
* @deprecated use ticket-based bloom render hook
* {@link #registerBloomRender(IRenderSetup, BloomType, IBloomEffect)}
* @deprecated use ticket-based bloom render hooks
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.9")
public static void requestCustomBloom(IBloomRenderFast handler, Consumer<BufferBuilder> render) {
BloomType bloomType = BloomType.fromValue(handler.customBloomStyle());
registerBloomRender(handler, bloomType, (b, c) -> render.accept(b)).legacy = true;
var validityChecker = new Predicate<BloomRenderTicket>() {

boolean invalid;

@Override
public boolean test(BloomRenderTicket bloomRenderTicket) {
return !invalid;
}
};
registerBloomRender(handler, bloomType, (b, c) -> {
render.accept(b);
validityChecker.invalid = true;
}, validityChecker);
}

@SuppressWarnings({ "rawtypes", "unchecked" })
Expand Down Expand Up @@ -375,12 +441,22 @@ public static int renderBloomBlockLayer(RenderGlobal renderGlobal,
return result;
}

private static void preDraw() {
for (BloomRenderTicket ticket : SCHEDULED_BLOOM_RENDERS) {
if (!ticket.isValid()) continue;
BLOOM_RENDERS.computeIfAbsent(new BloomRenderKey(ticket.renderSetup, ticket.bloomType),
k -> new ArrayList<>()).add(ticket);
}
SCHEDULED_BLOOM_RENDERS.clear();
}

private static void draw(@NotNull BufferBuilder buffer, @NotNull EffectRenderContext context,
@NotNull List<BloomRenderTicket> tickets) {
boolean initialized = false;
@Nullable
IRenderSetup renderSetup = null;
for (BloomRenderTicket ticket : tickets) {
ticket.checkValidity();
if (!ticket.isValid() || !ticket.render.shouldRenderBloomEffect(context)) continue;
if (!initialized) {
initialized = true;
Expand All @@ -390,45 +466,21 @@ private static void draw(@NotNull BufferBuilder buffer, @NotNull EffectRenderCon
}
}
ticket.render.renderBloomEffect(buffer, context);
if (ticket.legacy) ticket.invalidate();
}
if (initialized && renderSetup != null) {
renderSetup.postDraw(buffer);
}
}

private static void preDraw() {
WorldClient world = Minecraft.getMinecraft().world;
if (currentWorld != world) {
for (List<BloomRenderTicket> list : BLOOM_RENDERS.values()) {
for (BloomRenderTicket ticket : list) {
ticket.invalidate();
}
}
BLOOM_RENDERS.clear();
if (currentWorld != null) {
for (BloomRenderTicket ticket : SCHEDULED_BLOOM_RENDERS) {
ticket.invalidate();
}
SCHEDULED_BLOOM_RENDERS.clear();
}
currentWorld = world;
}

for (BloomRenderTicket ticket : SCHEDULED_BLOOM_RENDERS) {
if (!ticket.isValid()) continue;
BLOOM_RENDERS.computeIfAbsent(new BloomRenderKey(ticket.renderSetup, ticket.bloomType),
k -> new ArrayList<>()).add(ticket);
}
SCHEDULED_BLOOM_RENDERS.clear();
}

private static void postDraw() {
for (var it = BLOOM_RENDERS.values().iterator(); it.hasNext();) {
List<BloomRenderTicket> list = it.next();

if (!list.isEmpty()) {
if (!list.removeIf(ticket -> !ticket.isValid()) || !list.isEmpty()) continue;
if (!list.removeIf(ticket -> {
ticket.checkValidity();
return !ticket.isValid();
}) || !list.isEmpty()) continue;
}

it.remove();
Expand All @@ -444,19 +496,17 @@ public static final class BloomRenderTicket {
private final IRenderSetup renderSetup;
private final BloomType bloomType;
private final IBloomEffect render;
@Nullable
private final Predicate<BloomRenderTicket> validityChecker;

private boolean valid = true;
/**
* Used to mark bloom tickets made by legacy API; this ticket will be automatically expired after 1 render call.
* Remove this method in 2.9 as well as the deprecated method.
*/
private boolean legacy;
private boolean invalidated;

BloomRenderTicket(@Nullable IRenderSetup renderSetup, @NotNull BloomType bloomType,
@NotNull IBloomEffect render) {
@NotNull IBloomEffect render, @Nullable Predicate<BloomRenderTicket> validityChecker) {
this.renderSetup = renderSetup;
this.bloomType = Objects.requireNonNull(bloomType, "bloomType == null");
this.render = Objects.requireNonNull(render, "render == null");
this.validityChecker = validityChecker;
}

@Nullable
Expand All @@ -470,17 +520,22 @@ public BloomType getBloomType() {
}

public boolean isValid() {
return this.valid;
return !this.invalidated;
}

public void invalidate() {
this.valid = false;
this.invalidated = true;
}

private void checkValidity() {
if (!this.invalidated && this.validityChecker != null && !this.validityChecker.test(this)) {
invalidate();
}
}
}

/**
* @deprecated use ticket-based bloom render hook
* {@link #registerBloomRender(IRenderSetup, BloomType, IBloomEffect)}
* @deprecated use ticket-based bloom render hooks
*/
@Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.9")
Expand Down
Loading