From f33e22001f184f17522bcb3342dba9c639049396 Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Mon, 3 May 2021 12:18:56 +0100 Subject: [PATCH] Fix 2d software skinning relative transforms All my earlier test cases for software skinning had the polys parent transform to be identity. This works fine until you had cases where the user had moved the transform of the parent nodes of skinned polys. This PR fixes this situation by taking into account the final (concatenated) transform of the polys RELATIVE to the skeleton base transform. It does this by applying the inverse skeleton base transform to the poly final transform. --- drivers/gles2/rasterizer_canvas_gles2.cpp | 6 ++-- drivers/gles3/rasterizer_canvas_gles3.cpp | 6 ++-- .../gles_common/rasterizer_canvas_batcher.h | 33 +++++++++++++++---- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/drivers/gles2/rasterizer_canvas_gles2.cpp b/drivers/gles2/rasterizer_canvas_gles2.cpp index eefdde51487..c7573d561e8 100644 --- a/drivers/gles2/rasterizer_canvas_gles2.cpp +++ b/drivers/gles2/rasterizer_canvas_gles2.cpp @@ -2194,7 +2194,7 @@ void RasterizerCanvasGLES2::render_joined_item(const BItemJoined &p_bij, RenderI _set_uniforms(); if (unshaded || (state.uniforms.final_modulate.a > 0.001 && (!r_ris.shader_cache || r_ris.shader_cache->canvas_item.light_mode != RasterizerStorageGLES2::Shader::CanvasItem::LIGHT_MODE_LIGHT_ONLY) && !ci->light_masked)) - render_joined_item_commands(p_bij, NULL, reclip, material_ptr, false); + render_joined_item_commands(p_bij, NULL, reclip, material_ptr, false, r_ris); r_ris.rebind_shader = true; // hacked in for now. @@ -2288,10 +2288,10 @@ void RasterizerCanvasGLES2::render_joined_item(const BItemJoined &p_bij, RenderI // this can greatly reduce fill rate .. // at the cost of glScissor commands, so is optional if (!bdata.settings_scissor_lights || r_ris.current_clip) { - render_joined_item_commands(p_bij, NULL, reclip, material_ptr, true); + render_joined_item_commands(p_bij, NULL, reclip, material_ptr, true, r_ris); } else { bool scissor = _light_scissor_begin(p_bij.bounding_rect, light->xform_cache, light->rect_cache); - render_joined_item_commands(p_bij, NULL, reclip, material_ptr, true); + render_joined_item_commands(p_bij, NULL, reclip, material_ptr, true, r_ris); if (scissor) { glDisable(GL_SCISSOR_TEST); } diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index 2778cf14ffc..3eb86e59887 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -1487,7 +1487,7 @@ void RasterizerCanvasGLES3::render_joined_item(const BItemJoined &p_bij, RenderI } if (unshaded || (state.canvas_item_modulate.a > 0.001 && (!r_ris.shader_cache || r_ris.shader_cache->canvas_item.light_mode != RasterizerStorageGLES3::Shader::CanvasItem::LIGHT_MODE_LIGHT_ONLY) && !p_ci->light_masked)) { RasterizerStorageGLES3::Material *material_ptr = nullptr; - render_joined_item_commands(p_bij, NULL, reclip, material_ptr, false); + render_joined_item_commands(p_bij, NULL, reclip, material_ptr, false, r_ris); } if ((blend_mode == RasterizerStorageGLES3::Shader::CanvasItem::BLEND_MODE_MIX || blend_mode == RasterizerStorageGLES3::Shader::CanvasItem::BLEND_MODE_PMALPHA) && r_ris.item_group_light && !unshaded) { @@ -1602,10 +1602,10 @@ void RasterizerCanvasGLES3::render_joined_item(const BItemJoined &p_bij, RenderI // this can greatly reduce fill rate .. // at the cost of glScissor commands, so is optional if (!bdata.settings_scissor_lights || r_ris.current_clip) { - render_joined_item_commands(p_bij, NULL, reclip, nullptr, true); + render_joined_item_commands(p_bij, NULL, reclip, nullptr, true, r_ris); } else { bool scissor = _light_scissor_begin(p_bij.bounding_rect, light->xform_cache, light->rect_cache); - render_joined_item_commands(p_bij, NULL, reclip, nullptr, true); + render_joined_item_commands(p_bij, NULL, reclip, nullptr, true, r_ris); if (scissor) { glDisable(GL_SCISSOR_TEST); } diff --git a/drivers/gles_common/rasterizer_canvas_batcher.h b/drivers/gles_common/rasterizer_canvas_batcher.h index f1e96f74a67..b4c68c24359 100644 --- a/drivers/gles_common/rasterizer_canvas_batcher.h +++ b/drivers/gles_common/rasterizer_canvas_batcher.h @@ -504,6 +504,7 @@ public: bool extra_matrix_sent; // whether sent on this item (in which case sofware transform can't be used untl end of item) int transform_extra_command_number_p1; // plus one to allow fast checking against zero Transform2D transform_combined; // final * extra + Transform2D skeleton_base_inverse_xform; // used in software skinning }; // used during try_join @@ -587,7 +588,7 @@ protected: bool _detect_item_batch_break(RenderItemState &r_ris, RasterizerCanvas::Item *p_ci, bool &r_batch_break); // drives the loop filling batches and flushing - void render_joined_item_commands(const BItemJoined &p_bij, RasterizerCanvas::Item *p_current_clip, bool &r_reclip, typename T_STORAGE::Material *p_material, bool p_lit); + void render_joined_item_commands(const BItemJoined &p_bij, RasterizerCanvas::Item *p_current_clip, bool &r_reclip, typename T_STORAGE::Material *p_material, bool p_lit, const RenderItemState &p_ris); private: // flush once full or end of joined item @@ -1824,9 +1825,12 @@ PREAMBLE(bool)::_software_skin_poly(RasterizerCanvas::Item::CommandPolygon *p_po Vector2 *pTemps = (Vector2 *)alloca(num_verts * sizeof(Vector2)); memset((void *)pTemps, 0, num_verts * sizeof(Vector2)); - // these are used in the shader but don't appear to be needed for software transform - // const Transform2D &skel_trans = get_this()->state.skeleton_transform; - // const Transform2D &skel_trans_inv = get_this()->state.skeleton_transform_inverse; + // only the inverse appears to be needed + const Transform2D &skel_trans_inv = p_fill_state.skeleton_base_inverse_xform; + // we can't get this from the state, because more than one skeleton item may have been joined together.. + // we need to handle the base skeleton on a per item basis as the joined item is rendered. + // const Transform2D &skel_trans = get_this()->state.skeleton_transform; + // const Transform2D &skel_trans_inv = get_this()->state.skeleton_transform_inverse; // get the bone transforms. // this is not ideal because we don't know in advance which bones are needed @@ -1838,7 +1842,10 @@ PREAMBLE(bool)::_software_skin_poly(RasterizerCanvas::Item::CommandPolygon *p_po if (num_verts && (p_poly->bones.size() == num_verts * 4) && (p_poly->weights.size() == p_poly->bones.size())) { - const Transform2D &item_transform = p_item->xform; + // instead of using the p_item->xform we use the final transform, + // because we want the poly transform RELATIVE to the base skeleton. + Transform2D item_transform = skel_trans_inv * p_item->final_transform; + Transform2D item_transform_inv = item_transform.affine_inverse(); for (int n = 0; n < num_verts; n++) { @@ -2534,7 +2541,7 @@ PREAMBLE(void)::flush_render_batches(RasterizerCanvas::Item *p_first_item, Raste #endif } -PREAMBLE(void)::render_joined_item_commands(const BItemJoined &p_bij, RasterizerCanvas::Item *p_current_clip, bool &r_reclip, typename T_STORAGE::Material *p_material, bool p_lit) { +PREAMBLE(void)::render_joined_item_commands(const BItemJoined &p_bij, RasterizerCanvas::Item *p_current_clip, bool &r_reclip, typename T_STORAGE::Material *p_material, bool p_lit, const RenderItemState &p_ris) { RasterizerCanvas::Item *item = 0; RasterizerCanvas::Item *first_item = bdata.item_refs[p_bij.first_item_ref].item; @@ -2581,6 +2588,20 @@ PREAMBLE(void)::render_joined_item_commands(const BItemJoined &p_bij, Rasterizer // prefill_joined_item() fill_state.transform_combined = item->final_transform; + // calculate skeleton base inverse transform if required for software skinning + // put in the fill state as this is readily accessible from the software skinner + if (item->skeleton.is_valid() && bdata.settings_use_software_skinning && get_storage()->skeleton_owner.owns(item->skeleton)) { + typename T_STORAGE::Skeleton *skeleton = nullptr; + skeleton = get_storage()->skeleton_owner.get(item->skeleton); + + if (skeleton->use_2d) { + // with software skinning we still need to know the skeleton inverse transform, the other two aren't needed + // but are left in for simplicity here + Transform2D skeleton_transform = p_ris.item_group_base_transform * skeleton->base_transform_2d; + fill_state.skeleton_base_inverse_xform = skeleton_transform.affine_inverse(); + } + } + // decide the initial transform mode, and make a backup // in orig_transform_mode in case we need to switch back if (fill_state.use_software_transform) {