Skip to content
This repository has been archived by the owner on Sep 3, 2022. It is now read-only.

Commit

Permalink
Added Rasterizer API for display color management.
Browse files Browse the repository at this point in the history
Use VisualServer::set_screen_lut to set a 3D texture for color management.

Its X, Y, Z axes correspond to R, G, B channels.

LUT transformation happens in Rasterizer, ensuring that all content blitted
to screen are processed exactly once.

This implements part of godotengine#26826.
Add color management module for ICC support.

This module adds Color Management to engine settings. It depends on LittleCMS
for ICC profile handling, and LUT support in Rasterizer for rendering.

This completes godotengine#26826.
  • Loading branch information
fire committed Oct 5, 2020
1 parent 2882284 commit d93e704
Show file tree
Hide file tree
Showing 59 changed files with 39,614 additions and 23 deletions.
2 changes: 2 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ opts.Add(BoolVariable("builtin_xatlas", "Use the built-in xatlas library", True)
opts.Add(BoolVariable("builtin_zlib", "Use the built-in zlib library", True))
opts.Add(BoolVariable("builtin_zstd", "Use the built-in Zstd library", True))

opts.Add(BoolVariable("builtin_lcms", "Use the built-in lcms library", True))

# Compilation environment setup
opts.Add("CXX", "C++ compiler")
opts.Add("CC", "C compiler")
Expand Down
13 changes: 13 additions & 0 deletions doc/classes/VisualServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3435,6 +3435,19 @@
Sets a boot image. The color defines the background color. If [code]scale[/code] is [code]true[/code], the image will be scaled to fit the screen size. If [code]use_filter[/code] is [code]true[/code], the image will be scaled with linear interpolation. If [code]use_filter[/code] is [code]false[/code], the image will be scaled with nearest-neighbor interpolation.
</description>
</method>
<method name="set_screen_lut">
<return type="void">
</return>
<argument index="0" name="lut" type="Image">
</argument>
<argument index="1" name="h_slices" type="int">
</argument>
<argument index="2" name="v_slices" type="int">
</argument>
<description>
Sets a 3D output LUT. It will be used to transform all screen output. If lut is [code]null[/code] the current LUT will be removed.
</description>
</method>
<method name="set_debug_generate_wireframes">
<return type="void">
</return>
Expand Down
1 change: 1 addition & 0 deletions drivers/dummy/rasterizer_dummy.h
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,7 @@ class RasterizerDummy : public Rasterizer {

void set_boot_image(const Ref<Image> &p_image, const Color &p_color, bool p_scale, bool p_use_filter = true) {}
void set_shader_time_scale(float p_scale) {}
void set_screen_lut(const Ref<Image> &p_lut, int p_h_slices, int p_v_slices) {}

void initialize() {}
void begin_frame(double frame_step) {}
Expand Down
78 changes: 61 additions & 17 deletions drivers/gles2/rasterizer_gles2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ void RasterizerGLES2::initialize() {
storage->initialize();
canvas->initialize();
scene->initialize();
state.lut_shader.init();
}

void RasterizerGLES2::begin_frame(double frame_step) {
Expand Down Expand Up @@ -400,34 +401,73 @@ void RasterizerGLES2::set_shader_time_scale(float p_scale) {
time_scale = p_scale;
}

void RasterizerGLES2::set_screen_lut(const Ref<Image> &p_lut, int p_h_slices, int p_v_slices) {

if (state.screen_lut.is_valid()) {
storage->free(state.screen_lut);
state.screen_lut = RID();
}

if (p_lut.is_null() || p_lut->empty())
return;

RID texture = storage->texture_create();
storage->texture_allocate(texture, p_lut->get_width(), p_lut->get_height(), 0, p_lut->get_format(), VS::TEXTURE_TYPE_2D, VS::TEXTURE_FLAG_FILTER);
storage->texture_set_data(texture, p_lut);

state.screen_lut = texture;
state.lut_texel_count = Vector2(p_lut->get_width(), p_lut->get_height());
state.lut_chunk_count = Vector2(p_h_slices, p_v_slices);
}

void RasterizerGLES2::blit_render_target_to_screen(RID p_render_target, const Rect2 &p_screen_rect, int p_screen) {

ERR_FAIL_COND(storage->frame.current_rt);

RasterizerStorageGLES2::RenderTarget *rt = storage->render_target_owner.getornull(p_render_target);
ERR_FAIL_COND(!rt);

canvas->_set_texture_rect_mode(true);
if (!state.screen_lut.is_valid()) {
// draw normally
canvas->_set_texture_rect_mode(true);

canvas->state.canvas_shader.set_custom_shader(0);
canvas->state.canvas_shader.bind();
canvas->state.canvas_shader.set_custom_shader(0);
canvas->state.canvas_shader.bind();

canvas->canvas_begin();
glDisable(GL_BLEND);
glBindFramebuffer(GL_FRAMEBUFFER, RasterizerStorageGLES2::system_fbo);
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 1);
if (rt->external.fbo != 0) {
glBindTexture(GL_TEXTURE_2D, rt->external.color);
canvas->canvas_begin();
glDisable(GL_BLEND);
glBindFramebuffer(GL_FRAMEBUFFER, RasterizerStorageGLES2::system_fbo);
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 1);
if (rt->external.fbo != 0) {
glBindTexture(GL_TEXTURE_2D, rt->external.color);
} else {
glBindTexture(GL_TEXTURE_2D, rt->color);
}
canvas->draw_generic_textured_rect(p_screen_rect, Rect2(0, 0, 1, -1));
glBindTexture(GL_TEXTURE_2D, 0);
canvas->canvas_end();
} else {
glBindTexture(GL_TEXTURE_2D, rt->color);
// draw with lut
Size2 win_size = OS::get_singleton()->get_window_size();
glDisable(GL_BLEND);
glBindFramebuffer(GL_FRAMEBUFFER, RasterizerStorageGLES2::system_fbo);
glActiveTexture(GL_TEXTURE0);
if (rt->external.fbo != 0) {
glBindTexture(GL_TEXTURE_2D, rt->external.color);
} else {
glBindTexture(GL_TEXTURE_2D, rt->color);
}
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, storage->texture_owner.get(state.screen_lut)->tex_id);
state.lut_shader.bind();
state.lut_shader.set_uniform(LutTransformShaderGLES2::SCREEN_RECT, Color(p_screen_rect.position.x / win_size.width, 1.0 - (p_screen_rect.position.y + p_screen_rect.size.height) / win_size.height, p_screen_rect.size.width / win_size.width, p_screen_rect.size.height / win_size.height));
state.lut_shader.set_uniform(LutTransformShaderGLES2::TEXEL_COUNT, state.lut_texel_count);
state.lut_shader.set_uniform(LutTransformShaderGLES2::CHUNK_COUNT, state.lut_chunk_count);
storage->bind_quad_array();
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}

// TODO normals

canvas->draw_generic_textured_rect(p_screen_rect, Rect2(0, 0, 1, -1));

glBindTexture(GL_TEXTURE_2D, 0);
canvas->canvas_end();
}

void RasterizerGLES2::output_lens_distorted_to_screen(RID p_render_target, const Rect2 &p_screen_rect, float p_k1, float p_k2, const Vector2 &p_eye_center, float p_oversample) {
Expand Down Expand Up @@ -469,6 +509,10 @@ void RasterizerGLES2::end_frame(bool p_swap_buffers) {
}

void RasterizerGLES2::finalize() {

if (state.screen_lut.is_valid()) {
storage->free(state.screen_lut);
}
}

Rasterizer *RasterizerGLES2::_create_current() {
Expand Down
10 changes: 10 additions & 0 deletions drivers/gles2/rasterizer_gles2.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
#include "rasterizer_storage_gles2.h"
#include "servers/visual/rasterizer.h"

#include "shaders/lut_transform.glsl.gen.h"

class RasterizerGLES2 : public Rasterizer {

static Rasterizer *_create_current();
Expand All @@ -47,13 +49,21 @@ class RasterizerGLES2 : public Rasterizer {
double time_total;
float time_scale;

struct State {
RID screen_lut;
Vector2 lut_texel_count;
Vector2 lut_chunk_count;
LutTransformShaderGLES2 lut_shader;
} state;

public:
virtual RasterizerStorage *get_storage();
virtual RasterizerCanvas *get_canvas();
virtual RasterizerScene *get_scene();

virtual void set_boot_image(const Ref<Image> &p_image, const Color &p_color, bool p_scale, bool p_use_filter = true);
virtual void set_shader_time_scale(float p_scale);
virtual void set_screen_lut(const Ref<Image> &p_lut, int p_h_slices, int p_v_slices);

virtual void initialize();
virtual void begin_frame(double frame_step);
Expand Down
1 change: 1 addition & 0 deletions drivers/gles2/shaders/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ if "GLES2_GLSL" in env["BUILDERS"]:
env.GLES2_GLSL("tonemap.glsl")
# env.GLES2_GLSL('particles.glsl');
env.GLES2_GLSL("lens_distorted.glsl")
env.GLES2_GLSL("lut_transform.glsl")
48 changes: 48 additions & 0 deletions drivers/gles2/shaders/lut_transform.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* clang-format off */
[vertex]

attribute highp vec4 vertex_attrib; // attrib:0
/* clang-format on */

attribute vec2 uv_in; // attrib:4

uniform highp vec4 screen_rect;

varying vec2 uv_interp;

void main() {
uv_interp = uv_in;
gl_Position = vec4(screen_rect.xy + vertex_attrib.xy * screen_rect.zw, vertex_attrib.zw);
}

/* clang-format off */
[fragment]

uniform vec2 texel_count;
uniform vec2 chunk_count;

uniform sampler2D source; //texunit:0
uniform sampler2D lut; //texunit:1
/* clang-format on */

varying vec2 uv_interp;

vec2 flatten_3d(vec2 chunk_size, vec2 texel_size, vec2 xy, float z_scaled) {
float row = floor(z_scaled / chunk_count.x);
float col = mod(z_scaled, chunk_count.x);
return chunk_size * vec2(col, row) + (chunk_size - texel_size) * xy + texel_size * 0.5;
}

void main() {
vec4 color = texture2D(source, uv_interp);
vec3 clamped_rgb = clamp(color.rgb, 0.0, 1.0);

vec2 texel_size = 1.0 / texel_count;
vec2 chunk_size = 1.0 / chunk_count;

float z_scaled = clamped_rgb.b * (chunk_count.x * chunk_count.y - 1.0);
vec4 low = texture2D(lut, flatten_3d(chunk_size, texel_size, clamped_rgb.rg, floor(z_scaled)));
vec4 high = texture2D(lut, flatten_3d(chunk_size, texel_size, clamped_rgb.rg, ceil(z_scaled)));

gl_FragColor = vec4(mix(low.rgb, high.rgb, mod(z_scaled, 1.0)), color.a);
}
82 changes: 76 additions & 6 deletions drivers/gles3/rasterizer_gles3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ void RasterizerGLES3::initialize() {
storage->initialize();
canvas->initialize();
scene->initialize();
state.lut_shader.init();
}

void RasterizerGLES3::begin_frame(double frame_step) {
Expand Down Expand Up @@ -336,22 +337,88 @@ void RasterizerGLES3::set_shader_time_scale(float p_scale) {
time_scale = p_scale;
}

void RasterizerGLES3::set_screen_lut(const Ref<Image> &p_lut, int p_h_slices, int p_v_slices) {

if (state.screen_lut.is_valid()) {
storage->free(state.screen_lut);
state.screen_lut = RID();
}

if (p_lut.is_null() || p_lut->empty())
return;

int slice_w = p_lut->get_width() / p_h_slices;
int slice_h = p_lut->get_height() / p_v_slices;

RID texture = storage->texture_create();
storage->texture_allocate(texture, slice_w, slice_h, p_h_slices * p_v_slices, p_lut->get_format(), VS::TEXTURE_TYPE_3D, VS::TEXTURE_FLAG_FILTER);

for (int i = 0; i < p_v_slices; i++) {
for (int j = 0; j < p_h_slices; j++) {
int x = slice_w * j;
int y = slice_h * i;
storage->texture_set_data(texture, p_lut->get_rect(Rect2(x, y, slice_w, slice_h)), i * p_h_slices + j);
}
}

state.screen_lut = texture;
state.lut_texel_count = Vector3(slice_w, slice_h, p_h_slices * p_v_slices);
}

void RasterizerGLES3::blit_render_target_to_screen(RID p_render_target, const Rect2 &p_screen_rect, int p_screen) {

ERR_FAIL_COND(storage->frame.current_rt);

RasterizerStorageGLES3::RenderTarget *rt = storage->render_target_owner.getornull(p_render_target);
ERR_FAIL_COND(!rt);

#if 1
Size2 win_size = OS::get_singleton()->get_window_size();
if (rt->external.fbo != 0) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->external.fbo);

if (!state.screen_lut.is_valid()) {
// no lut, just blit buffer
if (rt->external.fbo != 0) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->external.fbo);
} else {
glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->fbo);
}
glReadBuffer(GL_COLOR_ATTACHMENT0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, RasterizerStorageGLES3::system_fbo);
glBlitFramebuffer(0, 0, rt->width, rt->height, p_screen_rect.position.x, win_size.height - p_screen_rect.position.y - p_screen_rect.size.height, p_screen_rect.position.x + p_screen_rect.size.width, win_size.height - p_screen_rect.position.y, GL_COLOR_BUFFER_BIT, GL_NEAREST);
} else {
glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->fbo);
// draw with lut
glDisable(GL_BLEND);
if (rt->external.fbo != 0) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->external.fbo);
} else {
glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->fbo);
}
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, RasterizerStorageGLES3::system_fbo);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, rt->color);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_3D, storage->texture_owner.get(state.screen_lut)->tex_id);
state.lut_shader.bind();
state.lut_shader.set_uniform(LutTransformShaderGLES3::SCREEN_RECT, Color(p_screen_rect.position.x / win_size.width, 1.0 - (p_screen_rect.position.y + p_screen_rect.size.height) / win_size.height, p_screen_rect.size.width / win_size.width, p_screen_rect.size.height / win_size.height));
state.lut_shader.set_uniform(LutTransformShaderGLES3::TEXEL_COUNT, state.lut_texel_count);
glBindVertexArray(storage->resources.quadie_array);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
glReadBuffer(GL_COLOR_ATTACHMENT0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, RasterizerStorageGLES3::system_fbo);
glBlitFramebuffer(0, 0, rt->width, rt->height, p_screen_rect.position.x, win_size.height - p_screen_rect.position.y - p_screen_rect.size.height, p_screen_rect.position.x + p_screen_rect.size.width, win_size.height - p_screen_rect.position.y, GL_COLOR_BUFFER_BIT, GL_NEAREST);
#else
canvas->canvas_begin();
glDisable(GL_BLEND);
glBindFramebuffer(GL_FRAMEBUFFER, RasterizerStorageGLES3::system_fbo);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, rt->color);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, storage->resources.normal_tex);
canvas->draw_generic_textured_rect(p_screen_rect, Rect2(0, 0, 1, -1));
glBindTexture(GL_TEXTURE_2D, 0);
canvas->canvas_end();
#endif
}

void RasterizerGLES3::output_lens_distorted_to_screen(RID p_render_target, const Rect2 &p_screen_rect, float p_k1, float p_k2, const Vector2 &p_eye_center, float p_oversample) {
Expand Down Expand Up @@ -393,6 +460,9 @@ void RasterizerGLES3::end_frame(bool p_swap_buffers) {
}

void RasterizerGLES3::finalize() {
if (state.screen_lut.is_valid()) {
storage->free(state.screen_lut);
}

storage->finalize();
canvas->finalize();
Expand Down
9 changes: 9 additions & 0 deletions drivers/gles3/rasterizer_gles3.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
#include "rasterizer_storage_gles3.h"
#include "servers/visual/rasterizer.h"

#include "shaders/lut_transform.glsl.gen.h"

class RasterizerGLES3 : public Rasterizer {

static Rasterizer *_create_current();
Expand All @@ -47,13 +49,20 @@ class RasterizerGLES3 : public Rasterizer {
double time_total;
float time_scale;

struct State {
RID screen_lut;
Vector3 lut_texel_count;
LutTransformShaderGLES3 lut_shader;
} state;

public:
virtual RasterizerStorage *get_storage();
virtual RasterizerCanvas *get_canvas();
virtual RasterizerScene *get_scene();

virtual void set_boot_image(const Ref<Image> &p_image, const Color &p_color, bool p_scale, bool p_use_filter = true);
virtual void set_shader_time_scale(float p_scale);
virtual void set_screen_lut(const Ref<Image> &p_lut, int p_h_slices, int p_v_slices);

virtual void initialize();
virtual void begin_frame(double frame_step);
Expand Down
1 change: 1 addition & 0 deletions drivers/gles3/shaders/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ if "GLES3_GLSL" in env["BUILDERS"]:
env.GLES3_GLSL("tonemap.glsl")
env.GLES3_GLSL("particles.glsl")
env.GLES3_GLSL("lens_distorted.glsl")
env.GLES3_GLSL("lut_transform.glsl")
Loading

0 comments on commit d93e704

Please sign in to comment.