From 674327b78f9b02bb0b6f9571d2a748920df96849 Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Sun, 2 Aug 2020 07:49:03 +0100 Subject: [PATCH] GLES3 fix normal map flipping with nvidia workaround When not using TEXTURE_RECT path, flips have to sent via another method to the shader, to ensure that normal maps are correctly adjusted for direction. This PR adds an extra vertex attribute, LIGHT_ANGLE. For nvidia workarounds, where the shader still has access to the final transform and extra matrix, the LIGHT_ANGLE can be 0 (no adjustment), 180 degrees for a horizontal flip, and negative indicates a vertical flip. For batching path, the LIGHT_ANGLE can be used to directly specify the light angle for normal mapping, even when the final transform and extra matrix have been baked into vertex positions, so the same shader can be used for both. --- drivers/gles3/rasterizer_canvas_gles3.cpp | 93 ++++++++++++++++++++--- drivers/gles3/rasterizer_canvas_gles3.h | 9 ++- drivers/gles3/rasterizer_gles3.cpp | 36 +++++++++ drivers/gles3/rasterizer_gles3.h | 2 + drivers/gles3/shaders/canvas.glsl | 25 ++++++ 5 files changed, 153 insertions(+), 12 deletions(-) diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index f7254cede56..535f118b05b 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -170,6 +170,7 @@ void RasterizerCanvasGLES3::canvas_begin() { state.canvas_shader.set_conditional(CanvasShaderGLES3::SHADOW_FILTER_PCF13, false); state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_DISTANCE_FIELD, false); state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_NINEPATCH, false); + state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_LIGHT_ANGLE, false); state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_SKELETON, false); state.canvas_shader.set_custom_shader(0); @@ -191,6 +192,7 @@ void RasterizerCanvasGLES3::canvas_begin() { glBindVertexArray(data.canvas_quad_array); state.using_texture_rect = true; state.using_ninepatch = false; + state.using_light_angle = false; state.using_skeleton = false; } @@ -204,6 +206,7 @@ void RasterizerCanvasGLES3::canvas_end() { state.using_texture_rect = false; state.using_ninepatch = false; + state.using_light_angle = false; } RasterizerStorageGLES3::Texture *RasterizerCanvasGLES3::_bind_canvas_texture(const RID &p_texture, const RID &p_normal_map, bool p_force) { @@ -288,9 +291,9 @@ RasterizerStorageGLES3::Texture *RasterizerCanvasGLES3::_bind_canvas_texture(con return tex_return; } -void RasterizerCanvasGLES3::_set_texture_rect_mode(bool p_enable, bool p_ninepatch) { +void RasterizerCanvasGLES3::_set_texture_rect_mode(bool p_enable, bool p_ninepatch, bool p_light_angle) { - if (state.using_texture_rect == p_enable && state.using_ninepatch == p_ninepatch) + if (state.using_texture_rect == p_enable && state.using_ninepatch == p_ninepatch && state.using_light_angle == p_light_angle) return; if (p_enable) { @@ -304,6 +307,7 @@ void RasterizerCanvasGLES3::_set_texture_rect_mode(bool p_enable, bool p_ninepat state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_NINEPATCH, p_ninepatch && p_enable); state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_TEXTURE_RECT, p_enable); + state.canvas_shader.set_conditional(CanvasShaderGLES3::USE_LIGHT_ANGLE, p_light_angle); state.canvas_shader.bind(); state.canvas_shader.set_uniform(CanvasShaderGLES3::FINAL_MODULATE, state.canvas_item_modulate); state.canvas_shader.set_uniform(CanvasShaderGLES3::MODELVIEW_MATRIX, state.final_transform); @@ -319,6 +323,7 @@ void RasterizerCanvasGLES3::_set_texture_rect_mode(bool p_enable, bool p_ninepat } state.using_texture_rect = p_enable; state.using_ninepatch = p_ninepatch; + state.using_light_angle = p_light_angle; } void RasterizerCanvasGLES3::_draw_polygon(const int *p_indices, int p_index_count, int p_vertex_count, const Vector2 *p_vertices, const Vector2 *p_uvs, const Color *p_colors, bool p_singlecolor, const int *p_bones, const float *p_weights) { @@ -548,7 +553,7 @@ void RasterizerCanvasGLES3::_draw_generic_indices(GLuint p_primitive, const int glBindBuffer(GL_ARRAY_BUFFER, 0); } -void RasterizerCanvasGLES3::_draw_gui_primitive(int p_points, const Vector2 *p_vertices, const Color *p_colors, const Vector2 *p_uvs) { +void RasterizerCanvasGLES3::_draw_gui_primitive(int p_points, const Vector2 *p_vertices, const Color *p_colors, const Vector2 *p_uvs, const float *p_light_angles) { static const GLenum prim[5] = { GL_POINTS, GL_POINTS, GL_LINES, GL_TRIANGLES, GL_TRIANGLE_FAN }; @@ -557,6 +562,7 @@ void RasterizerCanvasGLES3::_draw_gui_primitive(int p_points, const Vector2 *p_v int version = 0; int color_ofs = 0; int uv_ofs = 0; + int light_angle_ofs = 0; int stride = 2; if (p_colors) { //color @@ -571,7 +577,13 @@ void RasterizerCanvasGLES3::_draw_gui_primitive(int p_points, const Vector2 *p_v stride += 2; } - float b[(2 + 2 + 4) * 4]; + if (p_light_angles) { //light_angles + version |= 4; + light_angle_ofs = stride; + stride += 1; + } + + float b[(2 + 2 + 4 + 1) * 4]; for (int i = 0; i < p_points; i++) { b[stride * i + 0] = p_vertices[i].x; @@ -596,6 +608,13 @@ void RasterizerCanvasGLES3::_draw_gui_primitive(int p_points, const Vector2 *p_v } } + if (p_light_angles) { + + for (int i = 0; i < p_points; i++) { + b[stride * i + light_angle_ofs] = p_light_angles[i]; + } + } + glBindBuffer(GL_ARRAY_BUFFER, data.polygon_buffer); #ifndef GLES_OVER_GL // Orphan the buffer to avoid CPU/GPU sync points caused by glBufferSubData @@ -623,10 +642,19 @@ static const GLenum gl_primitive[] = { void RasterizerCanvasGLES3::render_rect_nvidia_workaround(const Item::CommandRect *p_rect, const RasterizerStorageGLES3::Texture *p_texture) { - _set_texture_rect_mode(false); - if (p_texture) { + bool send_light_angles = false; + + // only need to use light angles when normal mapping + // otherwise we can use the default shader + if (state.current_normal != RID()) { + send_light_angles = true; + } + + // we don't want to use texture rect, and we want to send light angles if we are using normal mapping + _set_texture_rect_mode(false, false, send_light_angles); + bool untile = false; if (p_rect->flags & CANVAS_RECT_TILE && !(p_texture->flags & VS::TEXTURE_FLAG_REPEAT)) { @@ -663,6 +691,10 @@ void RasterizerCanvasGLES3::render_rect_nvidia_workaround(const Item::CommandRec src_rect.position + Vector2(0.0, src_rect.size.y), }; + // for encoding in light angle + bool flip_h = false; + bool flip_v = false; + if (p_rect->flags & CANVAS_RECT_TRANSPOSE) { SWAP(uvs[1], uvs[3]); } @@ -670,13 +702,42 @@ void RasterizerCanvasGLES3::render_rect_nvidia_workaround(const Item::CommandRec if (p_rect->flags & CANVAS_RECT_FLIP_H) { SWAP(uvs[0], uvs[1]); SWAP(uvs[2], uvs[3]); + flip_h = true; + flip_v = !flip_v; } if (p_rect->flags & CANVAS_RECT_FLIP_V) { SWAP(uvs[0], uvs[3]); SWAP(uvs[1], uvs[2]); + flip_v = !flip_v; } - _draw_gui_primitive(4, points, NULL, uvs); + if (send_light_angles) { + // for single rects, there is no need to fully utilize the light angle, + // we only need it to encode flips (horz and vert). But the shader can be reused with + // batching in which case the angle encodes the transform as well as + // the flips. + // Note transpose is NYI. I don't think it worked either with the non-nvidia method. + + // if horizontal flip, angle is 180 + float angle = 0.0f; + if (flip_h) + angle = Math_PI; + + // add 1 (to take care of zero floating point error with sign) + angle += 1.0f; + + // flip if necessary + if (flip_v) + angle *= -1.0f; + + // light angle must be sent for each vert, instead as a single uniform in the uniform draw method + // this has the benefit of enabling batching with light angles. + float light_angles[4] = { angle, angle, angle, angle }; + + _draw_gui_primitive(4, points, NULL, uvs, light_angles); + } else { + _draw_gui_primitive(4, points, NULL, uvs); + } if (untile) { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); @@ -684,6 +745,8 @@ void RasterizerCanvasGLES3::render_rect_nvidia_workaround(const Item::CommandRec } } else { + _set_texture_rect_mode(false); + state.canvas_shader.set_uniform(CanvasShaderGLES3::CLIP_RECT_UV, false); Vector2 points[4] = { @@ -2262,7 +2325,7 @@ void RasterizerCanvasGLES3::initialize() { uint32_t poly_size = GLOBAL_DEF_RST("rendering/limits/buffers/canvas_polygon_buffer_size_kb", 128); ProjectSettings::get_singleton()->set_custom_property_info("rendering/limits/buffers/canvas_polygon_buffer_size_kb", PropertyInfo(Variant::INT, "rendering/limits/buffers/canvas_polygon_buffer_size_kb", PROPERTY_HINT_RANGE, "0,256,1,or_greater")); poly_size *= 1024; //kb - poly_size = MAX(poly_size, (2 + 2 + 4) * 4 * sizeof(float)); + poly_size = MAX(poly_size, (2 + 2 + 4 + 1) * 4 * sizeof(float)); glGenBuffers(1, &data.polygon_buffer); glBindBuffer(GL_ARRAY_BUFFER, data.polygon_buffer); glBufferData(GL_ARRAY_BUFFER, poly_size, NULL, GL_DYNAMIC_DRAW); //allocate max size @@ -2270,13 +2333,14 @@ void RasterizerCanvasGLES3::initialize() { data.polygon_buffer_size = poly_size; //quad arrays - for (int i = 0; i < 4; i++) { + for (int i = 0; i < Data::NUM_QUAD_ARRAY_VARIATIONS; i++) { glGenVertexArrays(1, &data.polygon_buffer_quad_arrays[i]); glBindVertexArray(data.polygon_buffer_quad_arrays[i]); glBindBuffer(GL_ARRAY_BUFFER, data.polygon_buffer); int uv_ofs = 0; int color_ofs = 0; + int light_angle_ofs = 0; int stride = 2 * 4; if (i & 1) { //color @@ -2289,6 +2353,11 @@ void RasterizerCanvasGLES3::initialize() { stride += 2 * 4; } + if (i & 4) { //light_angle + light_angle_ofs = stride; + stride += 1 * 4; + } + glEnableVertexAttribArray(VS::ARRAY_VERTEX); glVertexAttribPointer(VS::ARRAY_VERTEX, 2, GL_FLOAT, GL_FALSE, stride, NULL); @@ -2302,6 +2371,12 @@ void RasterizerCanvasGLES3::initialize() { glVertexAttribPointer(VS::ARRAY_TEX_UV, 2, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(uv_ofs)); } + if (i & 4) { + // reusing tangent for light_angle + glEnableVertexAttribArray(VS::ARRAY_TANGENT); + glVertexAttribPointer(VS::ARRAY_TANGENT, 1, GL_FLOAT, GL_FALSE, stride, CAST_INT_TO_UCHAR_PTR(light_angle_ofs)); + } + glBindVertexArray(0); } diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h index f67d900da33..5179e48332f 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.h +++ b/drivers/gles3/rasterizer_canvas_gles3.h @@ -52,11 +52,13 @@ public: struct Data { + enum { NUM_QUAD_ARRAY_VARIATIONS = 8 }; + GLuint canvas_quad_vertices; GLuint canvas_quad_array; GLuint polygon_buffer; - GLuint polygon_buffer_quad_arrays[4]; + GLuint polygon_buffer_quad_arrays[NUM_QUAD_ARRAY_VARIATIONS]; GLuint polygon_buffer_pointer_array; GLuint polygon_index_buffer; @@ -78,6 +80,7 @@ public: bool using_texture_rect; bool using_ninepatch; + bool using_light_angle; RID current_tex; RID current_normal; @@ -127,10 +130,10 @@ public: virtual void canvas_begin(); virtual void canvas_end(); - _FORCE_INLINE_ void _set_texture_rect_mode(bool p_enable, bool p_ninepatch = false); + _FORCE_INLINE_ void _set_texture_rect_mode(bool p_enable, bool p_ninepatch = false, bool p_light_angle = false); _FORCE_INLINE_ RasterizerStorageGLES3::Texture *_bind_canvas_texture(const RID &p_texture, const RID &p_normal_map, bool p_force = false); - _FORCE_INLINE_ void _draw_gui_primitive(int p_points, const Vector2 *p_vertices, const Color *p_colors, const Vector2 *p_uvs); + _FORCE_INLINE_ void _draw_gui_primitive(int p_points, const Vector2 *p_vertices, const Color *p_colors, const Vector2 *p_uvs, const float *p_light_angles = nullptr); _FORCE_INLINE_ void _draw_polygon(const int *p_indices, int p_index_count, int p_vertex_count, const Vector2 *p_vertices, const Vector2 *p_uvs, const Color *p_colors, bool p_singlecolor, const int *p_bones, const float *p_weights); _FORCE_INLINE_ void _draw_generic(GLuint p_primitive, int p_vertex_count, const Vector2 *p_vertices, const Vector2 *p_uvs, const Color *p_colors, bool p_singlecolor); _FORCE_INLINE_ void _draw_generic_indices(GLuint p_primitive, const int *p_indices, int p_index_count, int p_vertex_count, const Vector2 *p_vertices, const Vector2 *p_uvs, const Color *p_colors, bool p_singlecolor); diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index a2f1b54e4d2..967e8a6645f 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -413,6 +413,42 @@ void RasterizerGLES3::register_config() { ProjectSettings::get_singleton()->set_custom_property_info("rendering/quality/filters/anisotropic_filter_level", PropertyInfo(Variant::INT, "rendering/quality/filters/anisotropic_filter_level", PROPERTY_HINT_RANGE, "1,16,1")); } +// returns NULL if no error, or an error string +const char *RasterizerGLES3::gl_check_for_error(bool p_print_error) { + GLenum err = glGetError(); + + const char *err_string = nullptr; + + switch (err) { + default: { + // not recognised + } break; + case GL_NO_ERROR: { + } break; + case GL_INVALID_ENUM: { + err_string = "GL_INVALID_ENUM"; + } break; + case GL_INVALID_VALUE: { + err_string = "GL_INVALID_VALUE"; + } break; + case GL_INVALID_OPERATION: { + err_string = "GL_INVALID_OPERATION"; + } break; + case GL_INVALID_FRAMEBUFFER_OPERATION: { + err_string = "GL_INVALID_FRAMEBUFFER_OPERATION"; + } break; + case GL_OUT_OF_MEMORY: { + err_string = "GL_OUT_OF_MEMORY"; + } break; + } + + if (p_print_error && err_string) { + print_line(err_string); + } + + return err_string; +} + RasterizerGLES3::RasterizerGLES3() { storage = memnew(RasterizerStorageGLES3); diff --git a/drivers/gles3/rasterizer_gles3.h b/drivers/gles3/rasterizer_gles3.h index eff733f8443..b15e5af2947 100644 --- a/drivers/gles3/rasterizer_gles3.h +++ b/drivers/gles3/rasterizer_gles3.h @@ -71,6 +71,8 @@ public: virtual bool is_low_end() const { return false; } + const char *gl_check_for_error(bool p_print_error = true); + RasterizerGLES3(); ~RasterizerGLES3(); }; diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl index e3a9e6d5d97..92f580a2b64 100644 --- a/drivers/gles3/shaders/canvas.glsl +++ b/drivers/gles3/shaders/canvas.glsl @@ -2,6 +2,11 @@ [vertex] layout(location = 0) in highp vec2 vertex; + +#ifdef USE_LIGHT_ANGLE +layout(location = 2) in highp float light_angle; +#endif + /* clang-format on */ layout(location = 3) in vec4 color_attrib; @@ -248,12 +253,32 @@ VERTEX_SHADER_CODE pos = outvec.xy; #endif +#ifdef USE_LIGHT_ANGLE + // we add a fixed offset because we are using the sign later, + // and don't want floating point error around 0.0 + float la = abs(light_angle) - 1.0; + + // vector light angle + vec4 vla; + vla.xy = vec2(cos(la), sin(la)); + vla.zw = vec2(-vla.y, vla.x); + vla.zw *= sign(light_angle); + + // apply the transform matrix. + // The rotate will be encoded in the transform matrix for single rects, + // and just the flips in the light angle. + // For batching we will encode the rotation and the flips + // in the light angle, and can use the same shader. + local_rot.xy = normalize((modelview_matrix * (extra_matrix_instance * vec4(vla.xy, 0.0, 0.0))).xy); + local_rot.zw = normalize((modelview_matrix * (extra_matrix_instance * vec4(vla.zw, 0.0, 0.0))).xy); +#else local_rot.xy = normalize((modelview_matrix * (extra_matrix_instance * vec4(1.0, 0.0, 0.0, 0.0))).xy); local_rot.zw = normalize((modelview_matrix * (extra_matrix_instance * vec4(0.0, 1.0, 0.0, 0.0))).xy); #ifdef USE_TEXTURE_RECT local_rot.xy *= sign(src_rect.z); local_rot.zw *= sign(src_rect.w); #endif +#endif // not using light angle #endif }