Fix #147618: PyGPU incorrect colors when drawing images

644fb2b679 fixed a long standing issue
that offscreen example showed the wrong colors. However the fix assumes
that input texture color space is always sRGB.

This adds a shader variation that draws textures that are stored in scene referred
linear color space (like all of our Image data-block).

Co-authored-by: Clément Foucault <foucault.clem@gmail.com>
Pull Request: https://projects.blender.org/blender/blender/pulls/147788
This commit is contained in:
Jeroen Bakker
2025-10-16 19:12:16 +02:00
committed by Clément Foucault
parent ce88d773db
commit e2dc63c5de
15 changed files with 178 additions and 34 deletions

View File

@@ -12,7 +12,7 @@ IMAGE_NAME = "Untitled"
image = bpy.data.images[IMAGE_NAME] image = bpy.data.images[IMAGE_NAME]
texture = gpu.texture.from_image(image) texture = gpu.texture.from_image(image)
shader = gpu.shader.from_builtin('IMAGE') shader = gpu.shader.from_builtin('IMAGE_SCENE_LINEAR_TO_REC709_SRGB')
batch = batch_for_shader( batch = batch_for_shader(
shader, 'TRI_FAN', shader, 'TRI_FAN',
{ {
@@ -45,7 +45,7 @@ IMAGE_NAME = "Untitled"
image = bpy.data.images[IMAGE_NAME] image = bpy.data.images[IMAGE_NAME]
texture = gpu.texture.from_image(image) texture = gpu.texture.from_image(image)
shader = gpu.shader.from_builtin('IMAGE') shader = gpu.shader.from_builtin('IMAGE_SCENE_LINEAR_TO_REC709_SRGB')
batch = batch_for_shader( batch = batch_for_shader(
shader, 'TRIS', shader, 'TRIS',
{ {

View File

@@ -58,7 +58,7 @@ def draw_circle_2d(position, color, radius, *, segments=None):
batch.draw(shader) batch.draw(shader)
def draw_texture_2d(texture, position, width, height): def draw_texture_2d(texture, position, width, height, is_scene_linear_with_rec709_srgb_target=False):
""" """
Draw a 2d texture. Draw a 2d texture.
@@ -71,6 +71,13 @@ def draw_texture_2d(texture, position, width, height):
:type width: float :type width: float
:arg height: Height of the image when drawn. :arg height: Height of the image when drawn.
:type height: float :type height: float
:arg is_scene_linear_with_rec709_srgb_target:
True if the `texture` is stored in scene linear color space and
the destination framebuffer uses the Rec.709 sRGB color space
(which is true when drawing textures acquired from :class:`bpy.types.Image` inside a
'PRE_VIEW', 'POST_VIEW' or 'POST_PIXEL' draw handler).
Otherwise the color space is assumed to match the one of the framebuffer. (default=False)
:type is_scene_linear_with_rec709_srgb_target: bool
""" """
import gpu import gpu
from . batch import batch_for_shader from . batch import batch_for_shader
@@ -78,7 +85,8 @@ def draw_texture_2d(texture, position, width, height):
coords = ((0, 0), (1, 0), (1, 1), (0, 1)) coords = ((0, 0), (1, 0), (1, 1), (0, 1))
indices = ((0, 1, 2), (2, 3, 0)) indices = ((0, 1, 2), (2, 3, 0))
shader = gpu.shader.from_builtin('IMAGE') shader = gpu.shader.from_builtin(
'IMAGE_SCENE_LINEAR_TO_REC709_SRGB' if is_scene_linear_with_rec709_srgb_target else 'IMAGE')
batch = batch_for_shader( batch = batch_for_shader(
shader, 'TRIS', shader, 'TRIS',
{"pos": coords, "texCoord": coords}, {"pos": coords, "texCoord": coords},
@@ -89,7 +97,6 @@ def draw_texture_2d(texture, position, width, height):
gpu.matrix.translate(position) gpu.matrix.translate(position)
gpu.matrix.scale((width, height)) gpu.matrix.scale((width, height))
shader = gpu.shader.from_builtin('IMAGE')
shader.uniform_sampler("image", texture) shader.uniform_sampler("image", texture)
batch.draw(shader) batch.draw(shader)

View File

@@ -376,8 +376,9 @@ enum GPUUniformBuiltin {
GPU_UNIFORM_RESOURCE_CHUNK, /* int resourceChunk */ GPU_UNIFORM_RESOURCE_CHUNK, /* int resourceChunk */
GPU_UNIFORM_RESOURCE_ID, /* int resourceId */ GPU_UNIFORM_RESOURCE_ID, /* int resourceId */
GPU_UNIFORM_SRGB_TRANSFORM, /* bool srgbTarget */ GPU_UNIFORM_SRGB_TRANSFORM, /* bool srgbTarget */
GPU_UNIFORM_SCENE_LINEAR_XFORM, /* float3x3 gpu_scene_linear_to_xyz */
}; };
#define GPU_NUM_UNIFORMS (GPU_UNIFORM_SRGB_TRANSFORM + 1) #define GPU_NUM_UNIFORMS (GPU_UNIFORM_SCENE_LINEAR_XFORM + 1)
/** /**
* TODO: To be moved as private API. Not really used outside of gpu_matrix.cc and doesn't really * TODO: To be moved as private API. Not really used outside of gpu_matrix.cc and doesn't really

View File

@@ -136,7 +136,9 @@ enum GPUBuiltinShader {
GPU_SHADER_3D_POINT_UNIFORM_COLOR, GPU_SHADER_3D_POINT_UNIFORM_COLOR,
/** /**
* Draw a texture in 3D. Take a 3D position and a 2D texture coordinate for each vertex. * Draw a sRGB color space texture in 3D.
* Texture color space is assumed to match the framebuffer.
* Take a 3D position and a 2D texture coordinate for each vertex.
* *
* \param image: uniform sampler2D * \param image: uniform sampler2D
* \param texCoord: in vec2 * \param texCoord: in vec2
@@ -144,6 +146,17 @@ enum GPUBuiltinShader {
*/ */
GPU_SHADER_3D_IMAGE, GPU_SHADER_3D_IMAGE,
/** /**
* Draw a scene linear color space texture in 3D.
* Texture value is transformed to the Rec.709 sRGB color space.
* Take a 3D position and a 2D texture coordinate for each vertex.
*
* \param image: uniform sampler2D
* \param texCoord: in vec2
* \param pos: in vec3
*/
GPU_SHADER_3D_IMAGE_SCENE_LINEAR_TO_REC709_SRGB,
/**
* Draw a sRGB color space (with Rec.709 primaries) texture in 3D.
* Take a 3D position and color for each vertex with linear interpolation in window space. * Take a 3D position and color for each vertex with linear interpolation in window space.
* *
* \param color: uniform vec4 * \param color: uniform vec4
@@ -152,8 +165,19 @@ enum GPUBuiltinShader {
* \param pos: in vec3 * \param pos: in vec3
*/ */
GPU_SHADER_3D_IMAGE_COLOR, GPU_SHADER_3D_IMAGE_COLOR,
/**
* Draw a scene linear color space texture in 3D.
* Texture value is transformed to the Rec.709 sRGB color space.
* Take a 3D position and color for each vertex with linear interpolation in window space.
*
* \param color: uniform vec4
* \param image: uniform sampler2D
* \param texCoord: in vec2
* \param pos: in vec3
*/
GPU_SHADER_3D_IMAGE_COLOR_SCENE_LINEAR_TO_REC709_SRGB,
}; };
#define GPU_SHADER_BUILTIN_LEN (GPU_SHADER_3D_IMAGE_COLOR + 1) #define GPU_SHADER_BUILTIN_LEN (GPU_SHADER_3D_IMAGE_COLOR_SCENE_LINEAR_TO_REC709_SRGB + 1)
/** Support multiple configurations. */ /** Support multiple configurations. */
enum GPUShaderConfig { enum GPUShaderConfig {

View File

@@ -6,9 +6,10 @@
* \ingroup gpu * \ingroup gpu
*/ */
#include "BLI_colorspace.hh"
#include "BLI_math_matrix.h" #include "BLI_math_matrix.h"
#include "BLI_math_matrix_types.hh"
#include "BLI_string.h" #include "BLI_string.h"
#include "BLI_time.h"
#include "GPU_capabilities.hh" #include "GPU_capabilities.hh"
#include "GPU_debug.hh" #include "GPU_debug.hh"
@@ -265,6 +266,9 @@ void GPU_shader_bind(blender::gpu::Shader *gpu_shader,
shader->bind(constants_state); shader->bind(constants_state);
GPU_matrix_bind(gpu_shader); GPU_matrix_bind(gpu_shader);
Shader::set_srgb_uniform(ctx, gpu_shader); Shader::set_srgb_uniform(ctx, gpu_shader);
/* Blender working color space do not change during the drawing of the frame.
* So we can just set the uniform once. */
Shader::set_scene_linear_to_xyz_uniform(gpu_shader);
} }
else { else {
if (constants_state) { if (constants_state) {
@@ -634,6 +638,15 @@ void Shader::set_srgb_uniform(Context *ctx, blender::gpu::Shader *shader)
ctx->shader_builtin_srgb_is_dirty = false; ctx->shader_builtin_srgb_is_dirty = false;
} }
void Shader::set_scene_linear_to_xyz_uniform(blender::gpu::Shader *shader)
{
int32_t loc = GPU_shader_get_builtin_uniform(shader, GPU_UNIFORM_SCENE_LINEAR_XFORM);
if (loc != -1) {
GPU_shader_uniform_float_ex(
shader, loc, 9, 1, blender::colorspace::scene_linear_to_rec709.ptr()[0]);
}
}
void Shader::set_framebuffer_srgb_target(int use_srgb_to_linear) void Shader::set_framebuffer_srgb_target(int use_srgb_to_linear)
{ {
Context *ctx = Context::get(); Context *ctx = Context::get();

View File

@@ -35,8 +35,12 @@ static const char *builtin_shader_create_info_name(GPUBuiltinShader shader)
return "gpu_shader_simple_lighting"; return "gpu_shader_simple_lighting";
case GPU_SHADER_3D_IMAGE: case GPU_SHADER_3D_IMAGE:
return "gpu_shader_3D_image"; return "gpu_shader_3D_image";
case GPU_SHADER_3D_IMAGE_SCENE_LINEAR_TO_REC709_SRGB:
return "gpu_shader_3D_image_scene_linear";
case GPU_SHADER_3D_IMAGE_COLOR: case GPU_SHADER_3D_IMAGE_COLOR:
return "gpu_shader_3D_image_color"; return "gpu_shader_3D_image_color";
case GPU_SHADER_3D_IMAGE_COLOR_SCENE_LINEAR_TO_REC709_SRGB:
return "gpu_shader_3D_image_color_scene_linear";
case GPU_SHADER_2D_CHECKER: case GPU_SHADER_2D_CHECKER:
return "gpu_shader_2D_checker"; return "gpu_shader_2D_checker";
case GPU_SHADER_2D_DIAG_STRIPES: case GPU_SHADER_2D_DIAG_STRIPES:

View File

@@ -220,6 +220,8 @@ inline const char *ShaderInterface::builtin_uniform_name(GPUUniformBuiltin u)
return "drw_ResourceID"; return "drw_ResourceID";
case GPU_UNIFORM_SRGB_TRANSFORM: case GPU_UNIFORM_SRGB_TRANSFORM:
return "srgbTarget"; return "srgbTarget";
case GPU_UNIFORM_SCENE_LINEAR_XFORM:
return "gpu_scene_linear_to_rec709";
default: default:
return nullptr; return nullptr;

View File

@@ -115,6 +115,7 @@ class Shader {
return parent_shader_; return parent_shader_;
} }
static void set_scene_linear_to_xyz_uniform(gpu::Shader *shader);
static void set_srgb_uniform(Context *ctx, gpu::Shader *shader); static void set_srgb_uniform(Context *ctx, gpu::Shader *shader);
static void set_framebuffer_srgb_target(int use_srgb_to_linear); static void set_framebuffer_srgb_target(int use_srgb_to_linear);

View File

@@ -2,6 +2,10 @@
* *
* SPDX-License-Identifier: GPL-2.0-or-later */ * SPDX-License-Identifier: GPL-2.0-or-later */
/**
*/
#pragma once #pragma once
#include "infos/gpu_srgb_to_framebuffer_space_infos.hh" #include "infos/gpu_srgb_to_framebuffer_space_infos.hh"
@@ -11,20 +15,66 @@ SHADER_LIBRARY_CREATE_INFO(gpu_srgb_to_framebuffer_space)
/* Undefine the macro that avoids compilation errors. */ /* Undefine the macro that avoids compilation errors. */
#undef blender_srgb_to_framebuffer_space #undef blender_srgb_to_framebuffer_space
/* Raw python shaders don't have create infos and thus don't generate the needed `srgbTarget` /**
* uniform automatically. For API compatibility, we sill define this loose uniform, but it will * Input is Rec.709 sRGB.
* not be parsed by the Metal or Vulkan backend. */ * Output is Rec.709 linear if hardware will add a Linear to sRGB comversion, noop otherwise.
#ifdef GPU_RAW_PYTHON_SHADER * NOTE: Old naming convention, but avoids breaking compatibility for python shaders.
uniform bool srgbTarget = false; *
#endif * As per GPU API design, all framebuffers with SRGBA_8_8_8_8 attachments will always enable SRGB
* rendering. In this mode the shader output is expected to be in a linear color space. This allows
float4 blender_srgb_to_framebuffer_space(float4 in_color) * to do the blending stage with linear values (more correct) and then store the result in 8bpc
* keeping accurate colors.
*
* To ensure consistent result (blending excluded) between a framebuffer using SRGBA_8_8_8_8 and
* one using RGBA_8_8_8_8, we need to do the sRGB > linear conversion to counteract the hardware
* encoding during framebuffer output.
*
* For reference: https://wikis.khronos.org/opengl/framebuffer#Colorspace
*/
float4 blender_srgb_to_framebuffer_space(float4 srgb_color)
{ {
if (srgbTarget) { /**
float3 c = max(in_color.rgb, float3(0.0f)); * IMPORTANT: srgbTarget denote that the output is expected to be in __linear__ space.
* https://wikis.khronos.org/opengl/framebuffer#Colorspace
*/
if (!srgbTarget) {
/* Input should already be in sRGB. */
return srgb_color;
}
/* Note that this is simply counteracting the hardware Linear > sRGB conversion. */
float3 c = max(srgb_color.rgb, float3(0.0f));
float3 c1 = c * (1.0f / 12.92f); float3 c1 = c * (1.0f / 12.92f);
float3 c2 = pow((c + 0.055f) * (1.0f / 1.055f), float3(2.4f)); float3 c2 = pow((c + 0.055f) * (1.0f / 1.055f), float3(2.4f));
in_color.rgb = mix(c1, c2, step(float3(0.04045f), c)); float4 linear_color;
linear_color.rgb = mix(c1, c2, step(float3(0.04045f), c));
linear_color.a = srgb_color.a;
return linear_color;
} }
return in_color;
/**
* Input is Rec.709 sRGB.
* Output is Rec.709 linear if hardware will add a Linear to sRGB comversion, noop otherwise.
*/
float4 blender_rec709_srgb_to_output_space(float4 srgb_color)
{
return blender_srgb_to_framebuffer_space(srgb_color);
}
/* Input is Blender Scene Linear. Output is Rec.709 sRGB. */
float4 blender_scene_linear_to_rec709_srgb(float3x3 scene_linear_to_rec709,
float4 scene_linear_color)
{
float3 rec709_linear = scene_linear_to_rec709 * scene_linear_color.rgb;
/* TODO(fclem): For wide gamut (extended sRGB), we need to encode negative values in a certain
* way here. */
/* Linear to sRGB transform. */
float3 c = max(rec709_linear, float3(0.0f));
float3 c1 = c * 12.92f;
float3 c2 = 1.055f * pow(c, float3(1.0f / 2.4f)) - 0.055f;
float4 srgb_color;
srgb_color.rgb = mix(c1, c2, step(float3(0.0031308f), c));
srgb_color.a = scene_linear_color.a;
return srgb_color;
} }

View File

@@ -2,14 +2,17 @@
* *
* SPDX-License-Identifier: GPL-2.0-or-later */ * SPDX-License-Identifier: GPL-2.0-or-later */
#include "infos/gpu_shader_2D_image_rect_color_infos.hh" #include "infos/gpu_shader_3D_image_infos.hh"
FRAGMENT_SHADER_CREATE_INFO(gpu_shader_2D_image_rect_color) FRAGMENT_SHADER_CREATE_INFO(gpu_shader_3D_image_color_scene_linear)
#include "gpu_shader_colorspace_lib.glsl" #include "gpu_shader_colorspace_lib.glsl"
void main() void main()
{ {
fragColor = texture(image, texCoord_interp) * color; fragColor = texture(image, texCoord_interp) * color;
fragColor = blender_srgb_to_framebuffer_space(fragColor); #ifdef BLENDER_SCENE_LINEAR_TO_REC709
fragColor = blender_scene_linear_to_rec709_srgb(gpu_scene_linear_to_rec709, fragColor);
#endif
fragColor = blender_rec709_srgb_to_output_space(fragColor);
} }

View File

@@ -2,14 +2,17 @@
* *
* SPDX-License-Identifier: GPL-2.0-or-later */ * SPDX-License-Identifier: GPL-2.0-or-later */
#include "infos/gpu_shader_2D_image_infos.hh" #include "infos/gpu_shader_3D_image_infos.hh"
FRAGMENT_SHADER_CREATE_INFO(gpu_shader_2D_image_common) FRAGMENT_SHADER_CREATE_INFO(gpu_shader_3D_image)
#include "gpu_shader_colorspace_lib.glsl" #include "gpu_shader_colorspace_lib.glsl"
void main() void main()
{ {
fragColor = texture(image, texCoord_interp); fragColor = texture(image, texCoord_interp);
fragColor = blender_srgb_to_framebuffer_space(fragColor); #ifdef BLENDER_SCENE_LINEAR_TO_REC709
fragColor = blender_scene_linear_to_rec709_srgb(gpu_scene_linear_to_rec709, fragColor);
#endif
fragColor = blender_rec709_srgb_to_output_space(fragColor);
} }

View File

@@ -28,6 +28,7 @@ PUSH_CONSTANT(float4, rect_geom)
SAMPLER(0, sampler2D, image) SAMPLER(0, sampler2D, image)
VERTEX_SOURCE("gpu_shader_2D_image_rect_vert.glsl") VERTEX_SOURCE("gpu_shader_2D_image_rect_vert.glsl")
FRAGMENT_SOURCE("gpu_shader_image_color_frag.glsl") FRAGMENT_SOURCE("gpu_shader_image_color_frag.glsl")
COMPILATION_CONSTANT(bool, is_scene_linear_image, false)
ADDITIONAL_INFO(gpu_srgb_to_framebuffer_space) ADDITIONAL_INFO(gpu_srgb_to_framebuffer_space)
DO_STATIC_COMPILATION() DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END() GPU_SHADER_CREATE_END()

View File

@@ -31,12 +31,31 @@ GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(gpu_shader_3D_image) GPU_SHADER_CREATE_INFO(gpu_shader_3D_image)
ADDITIONAL_INFO(gpu_shader_3D_image_common) ADDITIONAL_INFO(gpu_shader_3D_image_common)
COMPILATION_CONSTANT(bool, is_scene_linear_image, false)
FRAGMENT_SOURCE("gpu_shader_image_frag.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(gpu_shader_3D_image_scene_linear)
ADDITIONAL_INFO(gpu_shader_3D_image_common)
ADDITIONAL_INFO(gpu_scene_linear_to_rec709_space)
COMPILATION_CONSTANT(bool, is_scene_linear_image, true)
FRAGMENT_SOURCE("gpu_shader_image_frag.glsl") FRAGMENT_SOURCE("gpu_shader_image_frag.glsl")
DO_STATIC_COMPILATION() DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END() GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(gpu_shader_3D_image_color) GPU_SHADER_CREATE_INFO(gpu_shader_3D_image_color)
ADDITIONAL_INFO(gpu_shader_3D_image_common) ADDITIONAL_INFO(gpu_shader_3D_image_common)
COMPILATION_CONSTANT(bool, is_scene_linear_image, false)
PUSH_CONSTANT(float4, color)
FRAGMENT_SOURCE("gpu_shader_image_color_frag.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(gpu_shader_3D_image_color_scene_linear)
ADDITIONAL_INFO(gpu_shader_3D_image_common)
ADDITIONAL_INFO(gpu_scene_linear_to_rec709_space)
COMPILATION_CONSTANT(bool, is_scene_linear_image, true)
PUSH_CONSTANT(float4, color) PUSH_CONSTANT(float4, color)
FRAGMENT_SOURCE("gpu_shader_image_color_frag.glsl") FRAGMENT_SOURCE("gpu_shader_image_color_frag.glsl")
DO_STATIC_COMPILATION() DO_STATIC_COMPILATION()

View File

@@ -19,3 +19,8 @@ GPU_SHADER_CREATE_INFO(gpu_srgb_to_framebuffer_space)
PUSH_CONSTANT(bool, srgbTarget) PUSH_CONSTANT(bool, srgbTarget)
DEFINE("blender_srgb_to_framebuffer_space(a) a") DEFINE("blender_srgb_to_framebuffer_space(a) a")
GPU_SHADER_CREATE_END() GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(gpu_scene_linear_to_rec709_space)
PUSH_CONSTANT(float3x3, gpu_scene_linear_to_rec709)
DEFINE("BLENDER_SCENE_LINEAR_TO_REC709")
GPU_SHADER_CREATE_END()

View File

@@ -42,9 +42,17 @@
"``IMAGE``\n" \ "``IMAGE``\n" \
" :Attributes: vec3 pos, vec2 texCoord\n" \ " :Attributes: vec3 pos, vec2 texCoord\n" \
" :Uniforms: sampler2D image\n" \ " :Uniforms: sampler2D image\n" \
"``IMAGE_SCENE_LINEAR_TO_REC709_SRGB``\n" \
" :Attributes: vec3 pos, vec2 texCoord\n" \
" :Uniforms: sampler2D image\n" \
" :Note: Expect texture to be in scene linear color space\n" \
"``IMAGE_COLOR``\n" \ "``IMAGE_COLOR``\n" \
" :Attributes: vec3 pos, vec2 texCoord\n" \ " :Attributes: vec3 pos, vec2 texCoord\n" \
" :Uniforms: sampler2D image, vec4 color\n" \ " :Uniforms: sampler2D image, vec4 color\n" \
"``IMAGE_COLOR_SCENE_LINEAR_TO_REC709_SRGB``\n" \
" :Attributes: vec3 pos, vec2 texCoord\n" \
" :Uniforms: sampler2D image, vec4 color\n" \
" :Note: Expect texture to be in scene linear color space\n" \
"``SMOOTH_COLOR``\n" \ "``SMOOTH_COLOR``\n" \
" :Attributes: vec3 pos, vec4 color\n" \ " :Attributes: vec3 pos, vec4 color\n" \
" :Uniforms: none\n" \ " :Uniforms: none\n" \
@@ -70,7 +78,10 @@
static const PyC_StringEnumItems pygpu_shader_builtin_items[] = { static const PyC_StringEnumItems pygpu_shader_builtin_items[] = {
{GPU_SHADER_3D_FLAT_COLOR, "FLAT_COLOR"}, {GPU_SHADER_3D_FLAT_COLOR, "FLAT_COLOR"},
{GPU_SHADER_3D_IMAGE, "IMAGE"}, {GPU_SHADER_3D_IMAGE, "IMAGE"},
{GPU_SHADER_3D_IMAGE_SCENE_LINEAR_TO_REC709_SRGB, "IMAGE_SCENE_LINEAR_TO_REC709_SRGB"},
{GPU_SHADER_3D_IMAGE_COLOR, "IMAGE_COLOR"}, {GPU_SHADER_3D_IMAGE_COLOR, "IMAGE_COLOR"},
{GPU_SHADER_3D_IMAGE_COLOR_SCENE_LINEAR_TO_REC709_SRGB,
"IMAGE_COLOR_SCENE_LINEAR_TO_REC709_SRGB"},
{GPU_SHADER_3D_SMOOTH_COLOR, "SMOOTH_COLOR"}, {GPU_SHADER_3D_SMOOTH_COLOR, "SMOOTH_COLOR"},
{GPU_SHADER_3D_UNIFORM_COLOR, "UNIFORM_COLOR"}, {GPU_SHADER_3D_UNIFORM_COLOR, "UNIFORM_COLOR"},
{GPU_SHADER_3D_POLYLINE_FLAT_COLOR, "POLYLINE_FLAT_COLOR"}, {GPU_SHADER_3D_POLYLINE_FLAT_COLOR, "POLYLINE_FLAT_COLOR"},