GLES2 Batching - Prevent baking colors with COLOR writes

Writing to COLOR in a custom shader can result in incorrect results if colors are baked (vertex color and modulate). This PR prevents baking with COLOR output, except under the special circumstances that final modulate is (1, 1, 1, 1), in which case the result will be correct. This should still allow color baking in many scenarios with custom shaders.
This commit is contained in:
Author Lawnjelly 2020-05-11 21:19:34 +02:00 committed by lawnjelly
parent 44c1c5840c
commit 94ed206bfc
4 changed files with 42 additions and 27 deletions

View File

@ -1908,6 +1908,7 @@ void RasterizerCanvasGLES2::join_sorted_items() {
_render_item_state.joined_item->num_item_refs = 1;
_render_item_state.joined_item->bounding_rect = ci->global_rect_cache;
_render_item_state.joined_item->z_index = z;
_render_item_state.joined_item->flags = bdata.joined_item_batch_flags;
// add the reference
BItemRef *r = bdata.item_refs.request_with_grow();
@ -2184,7 +2185,8 @@ bool RasterizerCanvasGLES2::try_join_item(Item *p_ci, RenderItemState &r_ris, bo
}
// if there are any state changes we change join to false
// we also set r_batch_break to true if we don't want this item joined
// we also set r_batch_break to true if we don't want this item joined to the next
// (e.g. an item that must not be joined at all)
r_batch_break = false;
bool join = true;
@ -2271,17 +2273,6 @@ bool RasterizerCanvasGLES2::try_join_item(Item *p_ci, RenderItemState &r_ris, bo
bool unshaded = r_ris.shader_cache && (r_ris.shader_cache->canvas_item.light_mode == RasterizerStorageGLES2::Shader::CanvasItem::LIGHT_MODE_UNSHADED || (blend_mode != RasterizerStorageGLES2::Shader::CanvasItem::BLEND_MODE_MIX && blend_mode != RasterizerStorageGLES2::Shader::CanvasItem::BLEND_MODE_PMALPHA));
bool reclip = false;
// does the shader contain BUILTINs which should break the batching?
if (r_ris.shader_cache && !unshaded) {
const unsigned int test_flags = RasterizerStorageGLES2::Shader::CanvasItem::PREVENT_COLOR_BAKING | RasterizerStorageGLES2::Shader::CanvasItem::PREVENT_VERTEX_BAKING;
if (r_ris.shader_cache->canvas_item.batch_flags & test_flags) {
// we will do this same test on the shader during the rendering pass in order to set a bool not to bake vertex colors
// instead of saving this info as it is cheap to calculate
join = false;
r_batch_break = true;
}
}
// we are precalculating the final_modulate ahead of time because we need this for baking of final modulate into vertex colors
// (only in software transform mode)
// This maybe inefficient storing it...
@ -2292,6 +2283,33 @@ bool RasterizerCanvasGLES2::try_join_item(Item *p_ci, RenderItemState &r_ris, bo
r_ris.last_blend_mode = blend_mode;
}
// does the shader contain BUILTINs which should break the batching?
bdata.joined_item_batch_flags = 0;
if (r_ris.shader_cache && !unshaded) {
unsigned int and_flags = r_ris.shader_cache->canvas_item.batch_flags & (RasterizerStorageGLES2::Shader::CanvasItem::PREVENT_COLOR_BAKING | RasterizerStorageGLES2::Shader::CanvasItem::PREVENT_VERTEX_BAKING);
if (and_flags) {
bool break_batching = true;
if (and_flags == RasterizerStorageGLES2::Shader::CanvasItem::PREVENT_COLOR_BAKING) {
// in some circumstances, if the modulate is identity, we still allow baking because reading modulate / color
// will still be okay to do in the shader with no ill effects
if (r_ris.final_modulate == Color(1, 1, 1, 1)) {
break_batching = false;
}
}
if (break_batching) {
join = false;
r_batch_break = true;
// save the flags so that they don't need to be recalculated in the 2nd pass
bdata.joined_item_batch_flags |= r_ris.shader_cache->canvas_item.batch_flags;
}
}
}
if ((blend_mode == RasterizerStorageGLES2::Shader::CanvasItem::BLEND_MODE_MIX || blend_mode == RasterizerStorageGLES2::Shader::CanvasItem::BLEND_MODE_PMALPHA) && r_ris.item_group_light && !unshaded) {
// we cannot join lit items easily.
@ -3004,12 +3022,6 @@ void RasterizerCanvasGLES2::render_joined_item(const BItemJoined &p_bij, RenderI
bool unshaded = r_ris.shader_cache && (r_ris.shader_cache->canvas_item.light_mode == RasterizerStorageGLES2::Shader::CanvasItem::LIGHT_MODE_UNSHADED || (blend_mode != RasterizerStorageGLES2::Shader::CanvasItem::BLEND_MODE_MIX && blend_mode != RasterizerStorageGLES2::Shader::CanvasItem::BLEND_MODE_PMALPHA));
bool reclip = false;
// does the shader contain BUILTINs which break the batching and should prevent color baking or vertex baking?
bdata.joined_item_batch_flags = 0;
if (r_ris.shader_cache && !unshaded) {
bdata.joined_item_batch_flags = r_ris.shader_cache->canvas_item.batch_flags;
}
if (r_ris.last_blend_mode != blend_mode) {
switch (blend_mode) {

View File

@ -135,7 +135,10 @@ class RasterizerCanvasGLES2 : public RasterizerCanvasBaseGLES2 {
// note the z_index may only be correct for the first of the joined item references
// this has implications for light culling with z ranged lights.
int z_index;
int16_t z_index;
// these are defined in RasterizerStorageGLES2::Shader::CanvasItem::BatchFlags
uint16_t flags;
// we are always splitting items with lots of commands,
// and items with unhandled primitives (default)
@ -205,7 +208,7 @@ class RasterizerCanvasGLES2 : public RasterizerCanvasBaseGLES2 {
// if the shader is reading VERTEX, we prevent baking vertex positions with extra matrices etc
// to prevent the read position being incorrect.
// These flags are defined in RasterizerStorageGLES2::Shader::CanvasItem::BatchFlags
unsigned int joined_item_batch_flags;
uint32_t joined_item_batch_flags;
// measured in pixels, recalculated each frame
float scissor_threshold_area;

View File

@ -1425,7 +1425,7 @@ void RasterizerStorageGLES2::_update_shader(Shader *p_shader) const {
p_shader->canvas_item.uses_screen_uv = false;
p_shader->canvas_item.uses_time = false;
p_shader->canvas_item.uses_modulate = false;
p_shader->canvas_item.reads_color = false;
p_shader->canvas_item.uses_color = false;
p_shader->canvas_item.reads_vertex = false;
p_shader->canvas_item.batch_flags = 0;
@ -1443,8 +1443,8 @@ void RasterizerStorageGLES2::_update_shader(Shader *p_shader) const {
shaders.actions_canvas.usage_flag_pointers["SCREEN_TEXTURE"] = &p_shader->canvas_item.uses_screen_texture;
shaders.actions_canvas.usage_flag_pointers["TIME"] = &p_shader->canvas_item.uses_time;
shaders.actions_canvas.usage_flag_pointers["MODULATE"] = &p_shader->canvas_item.uses_modulate;
shaders.actions_canvas.usage_flag_pointers["COLOR"] = &p_shader->canvas_item.uses_color;
shaders.actions_canvas.read_flag_pointers["COLOR"] = &p_shader->canvas_item.reads_color;
shaders.actions_canvas.read_flag_pointers["VERTEX"] = &p_shader->canvas_item.reads_vertex;
actions = &shaders.actions_canvas;
@ -1535,7 +1535,7 @@ void RasterizerStorageGLES2::_update_shader(Shader *p_shader) const {
// some logic for batching
if (p_shader->mode == VS::SHADER_CANVAS_ITEM) {
if (p_shader->canvas_item.uses_modulate | p_shader->canvas_item.reads_color) {
if (p_shader->canvas_item.uses_modulate | p_shader->canvas_item.uses_color) {
p_shader->canvas_item.batch_flags |= Shader::CanvasItem::PREVENT_COLOR_BAKING;
}
if (p_shader->canvas_item.reads_vertex) {

View File

@ -447,20 +447,20 @@ public:
int light_mode;
// these flags are specifically for batching
// some of the logic is thus in rasterizer_storage.cpp
// we could alternatively set bitflags for each 'uses' and test on the fly
enum BatchFlags {
PREVENT_COLOR_BAKING = 1 << 0,
PREVENT_VERTEX_BAKING = 1 << 1,
};
// these flags are specifically for batching
// some of the logic is thus in rasterizer_storage.cpp
// we could alternatively set bitflags for each 'uses' and test on the fly
unsigned int batch_flags;
bool uses_screen_texture;
bool uses_screen_uv;
bool uses_time;
bool uses_modulate;
bool reads_color;
bool uses_color;
bool reads_vertex;
} canvas_item;