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 967149ae557..cc9f0b884c6 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -1485,7 +1485,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) { @@ -1600,10 +1600,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) {