/*
* Copyright 2017 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.rendering.dag.nodes;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.config.Config;
import org.terasology.config.RenderingConfig;
import org.terasology.math.TeraMath;
import org.terasology.monitoring.PerformanceMonitor;
import org.terasology.registry.In;
import org.terasology.rendering.backdrop.BackdropProvider;
import org.terasology.rendering.dag.AbstractNode;
import org.terasology.rendering.nui.properties.Range;
import org.terasology.rendering.opengl.FBO;
import org.terasology.rendering.opengl.PBO;
import org.terasology.rendering.opengl.ScreenGrabber;
import org.terasology.rendering.opengl.fbms.ImmutableFBOs;
import java.nio.ByteBuffer;
/**
* An instance of this node takes advantage of a downsampled version of the scene,
* calculates its relative luminance (1) and updates the exposure parameter of the
* ScreenGrabber accordingly.
*
* Notice that while this node takes advantage of the content of an FBO, it
* doesn't actually render anything.
*
* (1) See https://en.wikipedia.org/wiki/Luma_(video)#Use_of_relative_luminance
*/
public class UpdateExposureNode extends AbstractNode {
private static final Logger logger = LoggerFactory.getLogger(UpdateExposureNode.class);
@Range(min = 0.0f, max = 10.0f)
private float hdrExposureDefault = 2.5f;
@Range(min = 0.0f, max = 10.0f)
private float hdrMaxExposure = 8.0f;
@Range(min = 0.0f, max = 10.0f)
private float hdrMaxExposureNight = 8.0f;
@Range(min = 0.0f, max = 10.0f)
private float hdrMinExposure = 1.0f;
@Range(min = 0.0f, max = 4.0f)
private float hdrTargetLuminance = 1.0f;
@Range(min = 0.0f, max = 0.5f)
private float hdrExposureAdjustmentSpeed = 0.05f;
@In
private ImmutableFBOs immutableFBOs;
@In
private Config config;
@In
private BackdropProvider backdropProvider;
@In
private ScreenGrabber screenGrabber;
private RenderingConfig renderingConfig;
private FBO downSampledScene;
private PBO writeOnlyPBO; // PBOs are 1x1 pixels buffers used to read GPU data back into the CPU.
// This data is then used in the context of eye adaptation.
/**
* Initializes an UpdateExposureNode instance.This method must be called once shortly after instantiation
* to fully initialize the node and make it ready for rendering.
*/
@Override
public void initialise() {
renderingConfig = config.getRendering();
downSampledScene = requiresFBO(DownSamplerForExposureNode.FBO_1X1_CONFIG, immutableFBOs);
writeOnlyPBO = new PBO(1, 1);
}
/**
* If Eye Adaptation is enabled, given the 1-pixel output of the downSamplerNode,
* calculates the relative luminance of the scene and updates the exposure accordingly.
*
* If Eye Adaptation is disabled, sets the exposure to default day/night values.
*/
// TODO: verify if this can be achieved entirely in the GPU, during tone mapping perhaps?
@Override
public void process() {
if (renderingConfig.isEyeAdaptation()) {
PerformanceMonitor.startActivity("rendering/updateExposure");
writeOnlyPBO.copyFromFBO(downSampledScene.fboId, 1, 1, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE);
ByteBuffer pixels = writeOnlyPBO.readBackPixels();
if (pixels.limit() < 3) {
logger.error("Failed to auto-update the exposure value.");
return;
}
float red = (pixels.get(2) & 0xFF) / 255.f;
float green = (pixels.get(1) & 0xFF) / 255.f;
float blue = (pixels.get(0) & 0xFF) / 255.f;
// See: https://en.wikipedia.org/wiki/Luma_(video)#Use_of_relative_luminance for the constants below.
float currentSceneLuminance = 0.2126f * red + 0.7152f * green + 0.0722f * blue;
float targetExposure = hdrMaxExposure;
if (currentSceneLuminance > 0) {
targetExposure = hdrTargetLuminance / currentSceneLuminance;
}
float maxExposure = hdrMaxExposure;
if (backdropProvider.getDaylight() == 0.0) {
maxExposure = hdrMaxExposureNight;
}
if (targetExposure > maxExposure) {
targetExposure = maxExposure;
} else if (targetExposure < hdrMinExposure) {
targetExposure = hdrMinExposure;
}
screenGrabber.setExposure(TeraMath.lerp(screenGrabber.getExposure(), targetExposure, hdrExposureAdjustmentSpeed));
PerformanceMonitor.endActivity();
} else {
if (backdropProvider.getDaylight() == 0.0) {
screenGrabber.setExposure(hdrMaxExposureNight);
} else {
screenGrabber.setExposure(hdrExposureDefault);
}
}
}
}