diff options
35 files changed, 469 insertions, 199 deletions
diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml index 745926d4a8..f597cb7efc 100644 --- a/doc/classes/RenderingDevice.xml +++ b/doc/classes/RenderingDevice.xml @@ -113,7 +113,7 @@ </method> <method name="compute_pipeline_is_valid"> <return type="bool" /> - <param index="0" name="compute_pieline" type="RID" /> + <param index="0" name="compute_pipeline" type="RID" /> <description> </description> </method> diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index 4f38785369..4118274732 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -1784,6 +1784,9 @@ <constant name="GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL" value="2048" enum="GraphemeFlag" is_bitfield="true"> It is safe to insert a U+0640 before this grapheme for elongation. </constant> + <constant name="GRAPHEME_IS_EMBEDDED_OBJECT" value="4096" enum="GraphemeFlag" is_bitfield="true"> + Grapheme is an object replacement character for the embedded object. + </constant> <constant name="HINTING_NONE" value="0" enum="Hinting"> Disables font hinting (smoother but less crisp). </constant> diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index 522685bf87..19f2cfd47d 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -1310,6 +1310,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) { uint32_t instance_color_offset = 0; bool instance_uses_color = false; bool instance_uses_custom_data = false; + bool use_instancing = false; if (state.canvas_instance_batches[p_index].command_type == Item::Command::TYPE_MESH) { const Item::CommandMesh *m = static_cast<const Item::CommandMesh *>(state.canvas_instance_batches[p_index].command); @@ -1336,6 +1337,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) { instance_color_offset = mesh_storage->multimesh_get_color_offset(multimesh); instance_uses_color = mesh_storage->multimesh_uses_colors(multimesh); instance_uses_custom_data = mesh_storage->multimesh_uses_custom_data(multimesh); + use_instancing = true; } else if (state.canvas_instance_batches[p_index].command_type == Item::Command::TYPE_PARTICLES) { const Item::CommandParticles *pt = static_cast<const Item::CommandParticles *>(state.canvas_instance_batches[p_index].command); @@ -1362,6 +1364,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) { instance_color_offset = 8; // 8 bytes for instance transform. instance_uses_color = true; instance_uses_custom_data = true; + use_instancing = true; } ERR_FAIL_COND(mesh.is_null()); @@ -1397,7 +1400,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) { use_index_buffer = true; } - if (instance_count > 1) { + if (use_instancing) { if (instance_buffer == 0) { break; } @@ -1426,7 +1429,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) { } glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - if (instance_count > 1) { + if (use_instancing) { glDisableVertexAttribArray(5); glDisableVertexAttribArray(6); glDisableVertexAttribArray(7); diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index b3d6b01c6c..3d8f7924a7 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1948,7 +1948,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glDepthFunc(GL_LEQUAL); glDepthMask(GL_TRUE); scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED; - scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_OPAQUE; + scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_ALWAYS; if (!fb_cleared) { glClearDepth(1.0f); @@ -1976,19 +1976,17 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ _render_list_template<PASS_MODE_COLOR>(&render_list_params, &render_data, 0, render_list[RENDER_LIST_OPAQUE].elements.size()); + glDepthMask(GL_FALSE); + scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_DISABLED; + if (draw_sky) { RENDER_TIMESTAMP("Render Sky"); - if (scene_state.current_depth_test != GLES3::SceneShaderData::DEPTH_TEST_ENABLED) { - glEnable(GL_DEPTH_TEST); - scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED; - } + glEnable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); glDisable(GL_BLEND); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); scene_state.current_depth_test = GLES3::SceneShaderData::DEPTH_TEST_ENABLED; - scene_state.current_depth_draw = GLES3::SceneShaderData::DEPTH_DRAW_DISABLED; scene_state.cull_mode = GLES3::SceneShaderData::CULL_BACK; _draw_sky(render_data.environment, render_data.cam_projection, render_data.cam_transform, sky_energy_multiplier, p_camera_data->view_count > 1, flip_y); diff --git a/drivers/gles3/shaders/particles.glsl b/drivers/gles3/shaders/particles.glsl index f8741a22ab..40881a1808 100644 --- a/drivers/gles3/shaders/particles.glsl +++ b/drivers/gles3/shaders/particles.glsl @@ -300,28 +300,23 @@ void main() { vec3 rel_vec = xform[3].xyz - attractors[i].transform[3].xyz; vec3 local_pos = rel_vec * mat3(attractors[i].transform); - switch (attractors[i].type) { - case ATTRACTOR_TYPE_SPHERE: { - dir = safe_normalize(rel_vec); - float d = length(local_pos) / attractors[i].extents.x; - if (d > 1.0) { - continue; - } - amount = max(0.0, 1.0 - d); - } break; - case ATTRACTOR_TYPE_BOX: { - dir = safe_normalize(rel_vec); - - vec3 abs_pos = abs(local_pos / attractors[i].extents.xyz); - float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z)); - if (d > 1.0) { - continue; - } - amount = max(0.0, 1.0 - d); - - } break; - case ATTRACTOR_TYPE_VECTOR_FIELD: { - } break; + if (attractors[i].type == ATTRACTOR_TYPE_SPHERE) { + dir = safe_normalize(rel_vec); + float d = length(local_pos) / attractors[i].extents.x; + if (d > 1.0) { + continue; + } + amount = max(0.0, 1.0 - d); + } else if (attractors[i].type == ATTRACTOR_TYPE_BOX) { + dir = safe_normalize(rel_vec); + + vec3 abs_pos = abs(local_pos / attractors[i].extents.xyz); + float d = max(abs_pos.x, max(abs_pos.y, abs_pos.z)); + if (d > 1.0) { + continue; + } + amount = max(0.0, 1.0 - d); + } else if (attractors[i].type == ATTRACTOR_TYPE_VECTOR_FIELD) { } amount = pow(amount, attractors[i].attenuation); dir = safe_normalize(mix(dir, attractors[i].transform[2].xyz, attractors[i].directionality)); @@ -383,80 +378,72 @@ void main() { vec3 rel_vec = xform[3].xyz - colliders[i].transform[3].xyz; vec3 local_pos = rel_vec * mat3(colliders[i].transform); - switch (colliders[i].type) { - case COLLIDER_TYPE_SPHERE: { - float d = length(rel_vec) - (particle_size + colliders[i].extents.x); + if (colliders[i].type == COLLIDER_TYPE_SPHERE) { + float d = length(rel_vec) - (particle_size + colliders[i].extents.x); - if (d < 0.0) { - col = true; - depth = -d; - normal = normalize(rel_vec); - } + if (d < 0.0) { + col = true; + depth = -d; + normal = normalize(rel_vec); + } + } else if (colliders[i].type == COLLIDER_TYPE_BOX) { + vec3 abs_pos = abs(local_pos); + vec3 sgn_pos = sign(local_pos); - } break; - case COLLIDER_TYPE_BOX: { - vec3 abs_pos = abs(local_pos); - vec3 sgn_pos = sign(local_pos); - - if (any(greaterThan(abs_pos, colliders[i].extents.xyz))) { - //point outside box - - vec3 closest = min(abs_pos, colliders[i].extents.xyz); - vec3 rel = abs_pos - closest; - depth = length(rel) - particle_size; - if (depth < 0.0) { - col = true; - normal = mat3(colliders[i].transform) * (normalize(rel) * sgn_pos); - depth = -depth; - } - } else { - //point inside box - vec3 axis_len = colliders[i].extents.xyz - abs_pos; - // there has to be a faster way to do this? - if (all(lessThan(axis_len.xx, axis_len.yz))) { - normal = vec3(1, 0, 0); - } else if (all(lessThan(axis_len.yy, axis_len.xz))) { - normal = vec3(0, 1, 0); - } else { - normal = vec3(0, 0, 1); - } + if (any(greaterThan(abs_pos, colliders[i].extents.xyz))) { + //point outside box + vec3 closest = min(abs_pos, colliders[i].extents.xyz); + vec3 rel = abs_pos - closest; + depth = length(rel) - particle_size; + if (depth < 0.0) { col = true; - depth = dot(normal * axis_len, vec3(1)) + particle_size; - normal = mat3(colliders[i].transform) * (normal * sgn_pos); + normal = mat3(colliders[i].transform) * (normalize(rel) * sgn_pos); + depth = -depth; } - - } break; - case COLLIDER_TYPE_SDF: { - } break; - case COLLIDER_TYPE_HEIGHT_FIELD: { - vec3 local_pos_bottom = local_pos; - local_pos_bottom.y -= particle_size; - - if (any(greaterThan(abs(local_pos_bottom), colliders[i].extents.xyz))) { - continue; + } else { + //point inside box + vec3 axis_len = colliders[i].extents.xyz - abs_pos; + // there has to be a faster way to do this? + if (all(lessThan(axis_len.xx, axis_len.yz))) { + normal = vec3(1, 0, 0); + } else if (all(lessThan(axis_len.yy, axis_len.xz))) { + normal = vec3(0, 1, 0); + } else { + normal = vec3(0, 0, 1); } - const float DELTA = 1.0 / 8192.0; - vec3 uvw_pos = vec3(local_pos_bottom / colliders[i].extents.xyz) * 0.5 + 0.5; + col = true; + depth = dot(normal * axis_len, vec3(1)) + particle_size; + normal = mat3(colliders[i].transform) * (normal * sgn_pos); + } + } else if (colliders[i].type == COLLIDER_TYPE_SDF) { + } else if (colliders[i].type == COLLIDER_TYPE_HEIGHT_FIELD) { + vec3 local_pos_bottom = local_pos; + local_pos_bottom.y -= particle_size; - float y = 1.0 - texture(height_field_texture, uvw_pos.xz).r; + if (any(greaterThan(abs(local_pos_bottom), colliders[i].extents.xyz))) { + continue; + } + const float DELTA = 1.0 / 8192.0; - if (y > uvw_pos.y) { - //inside heightfield + vec3 uvw_pos = vec3(local_pos_bottom / colliders[i].extents.xyz) * 0.5 + 0.5; - vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz; - vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz; - vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * colliders[i].extents.xyz; + float y = 1.0 - texture(height_field_texture, uvw_pos.xz).r; - normal = normalize(cross(pos1 - pos2, pos1 - pos3)); - float local_y = (vec3(local_pos / colliders[i].extents.xyz) * 0.5 + 0.5).y; + if (y > uvw_pos.y) { + //inside heightfield - col = true; - depth = dot(normal, pos1) - dot(normal, local_pos_bottom); - } + vec3 pos1 = (vec3(uvw_pos.x, y, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz; + vec3 pos2 = (vec3(uvw_pos.x + DELTA, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(DELTA, 0)).r, uvw_pos.z) * 2.0 - 1.0) * colliders[i].extents.xyz; + vec3 pos3 = (vec3(uvw_pos.x, 1.0 - texture(height_field_texture, uvw_pos.xz + vec2(0, DELTA)).r, uvw_pos.z + DELTA) * 2.0 - 1.0) * colliders[i].extents.xyz; - } break; + normal = normalize(cross(pos1 - pos2, pos1 - pos3)); + float local_y = (vec3(local_pos / colliders[i].extents.xyz) * 0.5 + 0.5).y; + + col = true; + depth = dot(normal, pos1) - dot(normal, local_pos_bottom); + } } if (col) { diff --git a/drivers/gles3/storage/particles_storage.cpp b/drivers/gles3/storage/particles_storage.cpp index 2b47271408..4b64e2aec1 100644 --- a/drivers/gles3/storage/particles_storage.cpp +++ b/drivers/gles3/storage/particles_storage.cpp @@ -919,7 +919,7 @@ void ParticlesStorage::_particles_update_instance_buffer(Particles *particles, c glBeginTransformFeedback(GL_POINTS); if (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME) { - uint32_t lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1); + uint32_t lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount; uint32_t stride = particles->process_buffer_stride_cache; glBindBuffer(GL_ARRAY_BUFFER, particles->back_process_buffer); @@ -1135,14 +1135,13 @@ void ParticlesStorage::_particles_reverse_lifetime_sort(Particles *particles) { glGetBufferSubData(GL_ARRAY_BUFFER, 0, buffer_size, particle_array); #endif - uint32_t lifetime_split = MIN(particles->amount * particles->sort_buffer_phase, particles->amount - 1); - + uint32_t lifetime_split = (MIN(int(particles->amount * particles->sort_buffer_phase), particles->amount - 1) + 1) % particles->amount; for (uint32_t i = 0; i < lifetime_split / 2; i++) { - SWAP(particle_array[i], particle_array[lifetime_split - i]); + SWAP(particle_array[i], particle_array[lifetime_split - i - 1]); } for (uint32_t i = 0; i < (particles->amount - lifetime_split) / 2; i++) { - SWAP(particle_array[lifetime_split + i + 1], particle_array[particles->amount - 1 - i]); + SWAP(particle_array[lifetime_split + i], particle_array[particles->amount - 1 - i]); } #ifndef __EMSCRIPTEN__ diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 8d199ad989..1113a10e38 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -276,6 +276,11 @@ void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) { return; } + if (unlikely(log->is_updating())) { + // The new message arrived during log RTL text processing/redraw (invalid BiDi control characters / font error), ignore it to avoid RTL data corruption. + return; + } + // Only add the message to the log if it passes the filters. bool filter_active = type_filter_map[p_message.type]->is_active(); String search_text = search_box->get_text(); diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index f7c789a453..be66fbafac 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -38,34 +38,48 @@ #include "editor/gui/editor_spin_slider.h" #include "editor/inspector_dock.h" #include "scene/gui/button.h" +#include "scene/resources/packed_scene.h" bool EditorPropertyArrayObject::_set(const StringName &p_name, const Variant &p_value) { String name = p_name; - if (name.begins_with("indices")) { - int index = name.get_slicec('/', 1).to_int(); - array.set(index, p_value); - return true; + if (!name.begins_with("indices") && !name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) { + return false; } - return false; + int index; + if (name.begins_with("metadata/")) { + index = name.get_slice("/", 2).to_int(); + } else { + index = name.get_slice("/", 1).to_int(); + } + + array.set(index, p_value); + return true; } bool EditorPropertyArrayObject::_get(const StringName &p_name, Variant &r_ret) const { String name = p_name; - if (name.begins_with("indices")) { - int index = name.get_slicec('/', 1).to_int(); - bool valid; - r_ret = array.get(index, &valid); - if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) { - r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id(); - } + if (!name.begins_with("indices") && !name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) { + return false; + } - return valid; + int index; + if (name.begins_with("metadata/")) { + index = name.get_slice("/", 2).to_int(); + } else { + index = name.get_slice("/", 1).to_int(); } - return false; + bool valid; + r_ret = array.get(index, &valid); + + if (r_ret.get_type() == Variant::OBJECT && Object::cast_to<EncodedObjectAsID>(r_ret)) { + r_ret = Object::cast_to<EncodedObjectAsID>(r_ret)->get_object_id(); + } + + return valid; } void EditorPropertyArrayObject::set_array(const Variant &p_array) { @@ -178,15 +192,22 @@ void EditorPropertyArray::initialize_array(Variant &p_array) { } void EditorPropertyArray::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { - if (p_property.begins_with("indices")) { - int index = p_property.get_slice("/", 1).to_int(); - - Variant array = object->get_array().duplicate(); - array.set(index, p_value); + if (!p_property.begins_with("indices") && !p_property.begins_with(PackedScene::META_POINTER_PROPERTY_BASE + "indices")) { + return; + } - object->set_array(array); - emit_changed(get_edited_property(), array, "", true); + int index; + if (p_property.begins_with("metadata/")) { + index = p_property.get_slice("/", 2).to_int(); + } else { + index = p_property.get_slice("/", 1).to_int(); } + + Array array; + array.assign(object->get_array().duplicate()); + array.set(index, p_value); + object->set_array(array); + emit_changed(get_edited_property(), array, "", true); } void EditorPropertyArray::_change_type(Object *p_button, int p_index) { @@ -690,11 +711,6 @@ EditorPropertyArray::EditorPropertyArray() { add_child(edit); add_focusable(edit); - container = nullptr; - property_vbox = nullptr; - size_slider = nullptr; - button_add_item = nullptr; - paginator = nullptr; change_type = memnew(PopupMenu); add_child(change_type); change_type->connect("id_pressed", callable_mp(this, &EditorPropertyArray::_change_type_menu)); diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 43c7f49395..dfc820050f 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -4138,6 +4138,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start new_sd->direction = sd->direction; new_sd->custom_punct = sd->custom_punct; new_sd->para_direction = sd->para_direction; + new_sd->base_para_direction = sd->base_para_direction; for (int i = 0; i < TextServer::SPACING_MAX; i++) { new_sd->extra_spacing[i] = sd->extra_spacing[i]; } @@ -4188,9 +4189,33 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S if (U_SUCCESS(err)) { ubidi_setLine(p_sd->bidi_iter[ov], start, end, bidi_iter, &err); if (U_FAILURE(err)) { - ubidi_close(bidi_iter); - bidi_iter = nullptr; - ERR_PRINT(vformat("BiDi reordering for the line failed: %s", u_errorName(err))); + // Line BiDi failed (string contains incompatible control characters), try full paragraph BiDi instead. + err = U_ZERO_ERROR; + const UChar *data = p_sd->utf16.get_data(); + switch (static_cast<TextServer::Direction>(p_sd->bidi_override[ov].z)) { + case DIRECTION_LTR: { + ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_LTR, nullptr, &err); + } break; + case DIRECTION_RTL: { + ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err); + } break; + case DIRECTION_INHERITED: { + ubidi_setPara(bidi_iter, data + start, end - start, p_sd->base_para_direction, nullptr, &err); + } break; + case DIRECTION_AUTO: { + UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start); + if (direction != UBIDI_NEUTRAL) { + ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err); + } else { + ubidi_setPara(bidi_iter, data + start, end - start, p_sd->base_para_direction, nullptr, &err); + } + } break; + } + if (U_FAILURE(err)) { + ubidi_close(bidi_iter); + bidi_iter = nullptr; + ERR_PRINT(vformat("BiDi reordering for the line failed: %s", u_errorName(err))); + } } } else { bidi_iter = nullptr; @@ -5619,25 +5644,25 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { sd->script_iter = memnew(ScriptIterator(sd->text, 0, sd->text.length())); } - int base_para_direction = UBIDI_DEFAULT_LTR; + sd->base_para_direction = UBIDI_DEFAULT_LTR; switch (sd->direction) { case DIRECTION_LTR: { sd->para_direction = DIRECTION_LTR; - base_para_direction = UBIDI_LTR; + sd->base_para_direction = UBIDI_LTR; } break; case DIRECTION_RTL: { sd->para_direction = DIRECTION_RTL; - base_para_direction = UBIDI_RTL; + sd->base_para_direction = UBIDI_RTL; } break; case DIRECTION_INHERITED: case DIRECTION_AUTO: { UBiDiDirection direction = ubidi_getBaseDirection(data, sd->utf16.length()); if (direction != UBIDI_NEUTRAL) { sd->para_direction = (direction == UBIDI_RTL) ? DIRECTION_RTL : DIRECTION_LTR; - base_para_direction = direction; + sd->base_para_direction = direction; } else { sd->para_direction = DIRECTION_LTR; - base_para_direction = UBIDI_DEFAULT_LTR; + sd->base_para_direction = UBIDI_DEFAULT_LTR; } } break; } @@ -5666,14 +5691,14 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { ubidi_setPara(bidi_iter, data + start, end - start, UBIDI_RTL, nullptr, &err); } break; case DIRECTION_INHERITED: { - ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err); + ubidi_setPara(bidi_iter, data + start, end - start, sd->base_para_direction, nullptr, &err); } break; case DIRECTION_AUTO: { UBiDiDirection direction = ubidi_getBaseDirection(data + start, end - start); if (direction != UBIDI_NEUTRAL) { ubidi_setPara(bidi_iter, data + start, end - start, direction, nullptr, &err); } else { - ubidi_setPara(bidi_iter, data + start, end - start, base_para_direction, nullptr, &err); + ubidi_setPara(bidi_iter, data + start, end - start, sd->base_para_direction, nullptr, &err); } } break; } @@ -5760,7 +5785,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { gl.start = span.start; gl.end = span.end; gl.count = 1; - gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL; + gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT; if (sd->orientation == ORIENTATION_HORIZONTAL) { gl.advance = sd->objects[span.embedded_key].rect.size.x; } else { diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index af3a0c7876..4b8c3f7cd3 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -476,6 +476,7 @@ class TextServerAdvanced : public TextServerExtension { /* Shaped data */ TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction. + int base_para_direction = UBIDI_DEFAULT_LTR; bool valid = false; // String is shaped. bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted). bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string. diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 9095be46af..8b210bdc38 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -3670,7 +3670,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { gl.end = span.end; gl.count = 1; gl.index = 0; - gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_VIRTUAL; + gl.flags = GRAPHEME_IS_VALID | GRAPHEME_IS_EMBEDDED_OBJECT; if (sd->orientation == ORIENTATION_HORIZONTAL) { gl.advance = sd->objects[span.embedded_key].rect.size.x; } else { diff --git a/modules/webxr/doc_classes/WebXRInterface.xml b/modules/webxr/doc_classes/WebXRInterface.xml index 758c37e2ad..ed162b1da2 100644 --- a/modules/webxr/doc_classes/WebXRInterface.xml +++ b/modules/webxr/doc_classes/WebXRInterface.xml @@ -93,6 +93,18 @@ <link title="How to make a VR game for WebXR with Godot 4">https://www.snopekgames.com/tutorial/2023/how-make-vr-game-webxr-godot-4</link> </tutorials> <methods> + <method name="get_available_display_refresh_rates" qualifiers="const"> + <return type="Array" /> + <description> + Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the web browser and after the interface has been initialized. + </description> + </method> + <method name="get_display_refresh_rate" qualifiers="const"> + <return type="float" /> + <description> + Returns the display refresh rate for the current HMD. Not supported on all HMDs and browsers. It may not report an accurate value until after using [method set_display_refresh_rate]. + </description> + </method> <method name="get_input_source_target_ray_mode" qualifiers="const"> <return type="int" enum="WebXRInterface.TargetRayMode" /> <param index="0" name="input_source_id" type="int" /> @@ -132,6 +144,13 @@ This method returns nothing, instead it emits the [signal session_supported] signal with the result. </description> </method> + <method name="set_display_refresh_rate"> + <return type="void" /> + <param index="0" name="refresh_rate" type="float" /> + <description> + Sets the display refresh rate for the current HMD. Not supported on all HMDs and browsers. It won't take effect right away until after [signal display_refresh_rate_changed] is emitted. + </description> + </method> </methods> <members> <member name="optional_features" type="String" setter="set_optional_features" getter="get_optional_features"> @@ -167,6 +186,11 @@ </member> </members> <signals> + <signal name="display_refresh_rate_changed"> + <description> + Emitted after the display's refresh rate has changed. + </description> + </signal> <signal name="reference_space_reset"> <description> Emitted to indicate that the reference space has been reset or reconfigured. diff --git a/modules/webxr/godot_webxr.h b/modules/webxr/godot_webxr.h index c636be1576..554ca0398b 100644 --- a/modules/webxr/godot_webxr.h +++ b/modules/webxr/godot_webxr.h @@ -90,6 +90,10 @@ extern bool godot_webxr_update_input_source( extern char *godot_webxr_get_visibility_state(); extern int godot_webxr_get_bounds_geometry(float **r_points); +extern float godot_webxr_get_frame_rate(); +extern void godot_webxr_update_target_frame_rate(float p_frame_rate); +extern int godot_webxr_get_supported_frame_rates(float **r_frame_rates); + #ifdef __cplusplus } #endif diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js index 5c01d88a30..fe47de02b0 100644 --- a/modules/webxr/native/library_godot_webxr.js +++ b/modules/webxr/native/library_godot_webxr.js @@ -42,6 +42,7 @@ const GodotWebXR = { view_count: 1, input_sources: new Array(16), touches: new Array(5), + onsimpleevent: null, // Monkey-patch the requestAnimationFrame() used by Emscripten for the main // loop, so that we can swap it out for XRSession.requestAnimationFrame() @@ -283,6 +284,9 @@ const GodotWebXR = { GodotRuntime.free(c_str); }); + // Store onsimpleevent so we can use it later. + GodotWebXR.onsimpleevent = onsimpleevent; + const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef const gl = GL.getContext(gl_context_handle).GLctx; GodotWebXR.gl = gl; @@ -368,6 +372,7 @@ const GodotWebXR = { GodotWebXR.view_count = 1; GodotWebXR.input_sources = new Array(16); GodotWebXR.touches = new Array(5); + GodotWebXR.onsimpleevent = null; // Disable the monkey-patched window.requestAnimationFrame() and // pause/restart the main loop to activate it on all platforms. @@ -594,6 +599,51 @@ const GodotWebXR = { return point_count; }, + + godot_webxr_get_frame_rate__proxy: 'sync', + godot_webxr_get_frame_rate__sig: 'i', + godot_webxr_get_frame_rate: function () { + if (!GodotWebXR.session || GodotWebXR.session.frameRate === undefined) { + return 0; + } + return GodotWebXR.session.frameRate; + }, + + godot_webxr_update_target_frame_rate__proxy: 'sync', + godot_webxr_update_target_frame_rate__sig: 'vi', + godot_webxr_update_target_frame_rate: function (p_frame_rate) { + if (!GodotWebXR.session || GodotWebXR.session.updateTargetFrameRate === undefined) { + return; + } + + GodotWebXR.session.updateTargetFrameRate(p_frame_rate).then(() => { + const c_str = GodotRuntime.allocString('display_refresh_rate_changed'); + GodotWebXR.onsimpleevent(c_str); + GodotRuntime.free(c_str); + }); + }, + + godot_webxr_get_supported_frame_rates__proxy: 'sync', + godot_webxr_get_supported_frame_rates__sig: 'ii', + godot_webxr_get_supported_frame_rates: function (r_frame_rates) { + if (!GodotWebXR.session || GodotWebXR.session.supportedFrameRates === undefined) { + return 0; + } + + const frame_rate_count = GodotWebXR.session.supportedFrameRates.length; + if (frame_rate_count === 0) { + return 0; + } + + const buf = GodotRuntime.malloc(frame_rate_count * 4); + for (let i = 0; i < frame_rate_count; i++) { + GodotRuntime.setHeapValue(buf + (i * 4), GodotWebXR.session.supportedFrameRates[i], 'float'); + } + GodotRuntime.setHeapValue(r_frame_rates, buf, 'i32'); + + return frame_rate_count; + }, + }; autoAddDeps(GodotWebXR, '$GodotWebXR'); diff --git a/modules/webxr/native/webxr.externs.js b/modules/webxr/native/webxr.externs.js index 4b88820b19..7f7c297acc 100644 --- a/modules/webxr/native/webxr.externs.js +++ b/modules/webxr/native/webxr.externs.js @@ -68,6 +68,16 @@ XRSession.prototype.inputSources; XRSession.prototype.visibilityState; /** + * @type {?number} + */ +XRSession.prototype.frameRate; + +/** + * @type {?Float32Array} + */ +XRSession.prototype.supportedFrameRates; + +/** * @type {?function (Event)} */ XRSession.prototype.onend; @@ -142,6 +152,12 @@ XRSession.prototype.end = function () {}; XRSession.prototype.requestReferenceSpace = function (referenceSpaceType) {}; /** + * @param {number} rate + * @return {Promise<undefined>} + */ +XRSession.prototype.updateTargetFrameRate = function (rate) {}; + +/** * @typedef {function(number, XRFrame): undefined} */ var XRFrameRequestCallback; diff --git a/modules/webxr/webxr_interface.cpp b/modules/webxr/webxr_interface.cpp index d7f4247768..5e30d0c996 100644 --- a/modules/webxr/webxr_interface.cpp +++ b/modules/webxr/webxr_interface.cpp @@ -46,6 +46,9 @@ void WebXRInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_input_source_tracker", "input_source_id"), &WebXRInterface::get_input_source_tracker); ClassDB::bind_method(D_METHOD("get_input_source_target_ray_mode", "input_source_id"), &WebXRInterface::get_input_source_target_ray_mode); ClassDB::bind_method(D_METHOD("get_visibility_state"), &WebXRInterface::get_visibility_state); + ClassDB::bind_method(D_METHOD("get_display_refresh_rate"), &WebXRInterface::get_display_refresh_rate); + ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &WebXRInterface::set_display_refresh_rate); + ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &WebXRInterface::get_available_display_refresh_rates); ADD_PROPERTY(PropertyInfo(Variant::STRING, "session_mode", PROPERTY_HINT_NONE), "set_session_mode", "get_session_mode"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "required_features", PROPERTY_HINT_NONE), "set_required_features", "get_required_features"); @@ -68,6 +71,7 @@ void WebXRInterface::_bind_methods() { ADD_SIGNAL(MethodInfo("visibility_state_changed")); ADD_SIGNAL(MethodInfo("reference_space_reset")); + ADD_SIGNAL(MethodInfo("display_refresh_rate_changed")); BIND_ENUM_CONSTANT(TARGET_RAY_MODE_UNKNOWN); BIND_ENUM_CONSTANT(TARGET_RAY_MODE_GAZE); diff --git a/modules/webxr/webxr_interface.h b/modules/webxr/webxr_interface.h index f18b4b40e6..abaa8c01f8 100644 --- a/modules/webxr/webxr_interface.h +++ b/modules/webxr/webxr_interface.h @@ -66,6 +66,9 @@ public: virtual Ref<XRPositionalTracker> get_input_source_tracker(int p_input_source_id) const = 0; virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const = 0; virtual String get_visibility_state() const = 0; + virtual float get_display_refresh_rate() const = 0; + virtual void set_display_refresh_rate(float p_refresh_rate) = 0; + virtual Array get_available_display_refresh_rates() const = 0; }; VARIANT_ENUM_CAST(WebXRInterface::TargetRayMode); diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index d3710bd0df..e1df2ea94e 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -202,6 +202,30 @@ PackedVector3Array WebXRInterfaceJS::get_play_area() const { return ret; } +float WebXRInterfaceJS::get_display_refresh_rate() const { + return godot_webxr_get_frame_rate(); +} + +void WebXRInterfaceJS::set_display_refresh_rate(float p_refresh_rate) { + godot_webxr_update_target_frame_rate(p_refresh_rate); +} + +Array WebXRInterfaceJS::get_available_display_refresh_rates() const { + Array ret; + + float *rates; + int rate_count = godot_webxr_get_supported_frame_rates(&rates); + if (rate_count > 0) { + ret.resize(rate_count); + for (int i = 0; i < rate_count; i++) { + ret[i] = rates[i]; + } + free(rates); + } + + return ret; +} + StringName WebXRInterfaceJS::get_name() const { return "WebXR"; }; diff --git a/modules/webxr/webxr_interface_js.h b/modules/webxr/webxr_interface_js.h index 20516e89e2..d85eb8cad7 100644 --- a/modules/webxr/webxr_interface_js.h +++ b/modules/webxr/webxr_interface_js.h @@ -102,6 +102,10 @@ public: virtual String get_visibility_state() const override; virtual PackedVector3Array get_play_area() const override; + virtual float get_display_refresh_rate() const override; + virtual void set_display_refresh_rate(float p_refresh_rate) override; + virtual Array get_available_display_refresh_rates() const override; + virtual StringName get_name() const override; virtual uint32_t get_capabilities() const override; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index dcd99f4374..d699236053 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -952,7 +952,7 @@ void LineEdit::_notification(int p_what) { if (ceil(ofs.x) >= x_ofs && (ofs.x + glyphs[i].advance) <= ofs_max) { if (glyphs[i].font_rid != RID()) { TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, selected ? font_selected_color : font_color); - } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { TS->draw_hex_code_box(ci, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, selected ? font_selected_color : font_color); } } diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index bd081074ec..97a1483204 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -74,16 +74,26 @@ void Range::Shared::emit_changed(const char *p_what) { } } +void Range::Shared::redraw_owners() { + for (Range *E : owners) { + Range *r = E; + if (!r->is_inside_tree()) { + continue; + } + r->queue_redraw(); + } +} + void Range::set_value(double p_val) { double prev_val = shared->val; - set_value_no_signal(p_val); + _set_value_no_signal(p_val); if (shared->val != prev_val) { shared->emit_value_changed(); } } -void Range::set_value_no_signal(double p_val) { +void Range::_set_value_no_signal(double p_val) { if (shared->step > 0) { p_val = Math::round((p_val - shared->min) / shared->step) * shared->step + shared->min; } @@ -107,6 +117,15 @@ void Range::set_value_no_signal(double p_val) { shared->val = p_val; } +void Range::set_value_no_signal(double p_val) { + double prev_val = shared->val; + _set_value_no_signal(p_val); + + if (shared->val != prev_val) { + shared->redraw_owners(); + } +} + void Range::set_min(double p_min) { if (shared->min == p_min) { return; diff --git a/scene/gui/range.h b/scene/gui/range.h index bf71fcc1c9..9b4f0707e6 100644 --- a/scene/gui/range.h +++ b/scene/gui/range.h @@ -48,6 +48,7 @@ class Range : public Control { HashSet<Range *> owners; void emit_value_changed(); void emit_changed(const char *p_what = ""); + void redraw_owners(); }; Shared *shared = nullptr; @@ -59,6 +60,7 @@ class Range : public Control { void _value_changed_notify(); void _changed_notify(const char *p_what = ""); + void _set_value_no_signal(double p_val); protected: virtual void _value_changed(double p_value); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 1ae24b6d70..43c68a8eca 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -1323,7 +1323,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (!skip) { if (frid != RID()) { TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); - } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + } else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); } } @@ -2705,6 +2705,10 @@ bool RichTextLabel::is_ready() const { return (main->first_invalid_line.load() == (int)main->lines.size() && main->first_resized_line.load() == (int)main->lines.size() && main->first_invalid_font_line.load() == (int)main->lines.size()); } +bool RichTextLabel::is_updating() const { + return updating.load() || validating.load(); +} + void RichTextLabel::set_threaded(bool p_threaded) { if (threaded != p_threaded) { _stop_thread(); @@ -2729,6 +2733,7 @@ bool RichTextLabel::_validate_line_caches() { if (updating.load()) { return false; } + validating.store(true); if (main->first_invalid_line.load() == (int)main->lines.size()) { MutexLock data_lock(data_mutex); Rect2 text_rect = _get_text_rect(); @@ -2747,6 +2752,7 @@ bool RichTextLabel::_validate_line_caches() { if (main->first_resized_line.load() == (int)main->lines.size()) { vscroll->set_value(old_scroll); + validating.store(false); return true; } @@ -2798,8 +2804,10 @@ bool RichTextLabel::_validate_line_caches() { if (fit_content) { update_minimum_size(); } + validating.store(false); return true; } + validating.store(false); stop_thread.store(false); if (threaded) { updating.store(true); @@ -2809,7 +2817,9 @@ bool RichTextLabel::_validate_line_caches() { loading_started = OS::get_singleton()->get_ticks_msec(); return false; } else { + updating.store(true); _process_line_caches(); + updating.store(false); queue_redraw(); return true; } @@ -5895,6 +5905,7 @@ RichTextLabel::RichTextLabel(const String &p_text) { set_text(p_text); updating.store(false); + validating.store(false); stop_thread.store(false); set_clip_contents(true); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index fef34f7260..567528652c 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -377,6 +377,7 @@ private: bool threaded = false; std::atomic<bool> stop_thread; std::atomic<bool> updating; + std::atomic<bool> validating; std::atomic<double> loaded; uint64_t loading_started = 0; @@ -678,6 +679,7 @@ public: void deselect(); bool is_ready() const; + bool is_updating() const; void set_threaded(bool p_threaded); bool is_threaded() const; diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index f99b2edd54..e4c7be33f0 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -39,7 +39,7 @@ Size2 SpinBox::get_minimum_size() const { return ms; } -void SpinBox::_value_changed(double p_value) { +void SpinBox::_update_text() { String value = String::num(get_value(), Math::range_step_decimals(get_step())); if (is_localizing_numeral_system()) { value = TS->format_number(value); @@ -55,7 +55,6 @@ void SpinBox::_value_changed(double p_value) { } line_edit->set_text(value); - Range::_value_changed(p_value); } void SpinBox::_text_submitted(const String &p_string) { @@ -73,7 +72,7 @@ void SpinBox::_text_submitted(const String &p_string) { if (value.get_type() != Variant::NIL) { set_value(value); } - _value_changed(0); + _update_text(); } void SpinBox::_text_changed(const String &p_string) { @@ -192,7 +191,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) { void SpinBox::_line_edit_focus_enter() { int col = line_edit->get_caret_column(); - _value_changed(0); // Update the LineEdit's text. + _update_text(); line_edit->set_caret_column(col); // LineEdit text might change and it clears any selection. Have to re-select here. @@ -202,6 +201,10 @@ void SpinBox::_line_edit_focus_enter() { } void SpinBox::_line_edit_focus_exit() { + // Discontinue because the focus_exit was caused by left-clicking the arrows. + if (get_viewport()->gui_get_focus_owner() == get_line_edit()) { + return; + } // Discontinue because the focus_exit was caused by right-click context menu. if (line_edit->is_menu_visible()) { return; @@ -228,6 +231,7 @@ void SpinBox::_update_theme_item_cache() { void SpinBox::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { + _update_text(); _adjust_width_for_icon(theme_cache.updown_icon); RID ci = get_canvas_item(); @@ -242,7 +246,7 @@ void SpinBox::_notification(int p_what) { case NOTIFICATION_ENTER_TREE: { _adjust_width_for_icon(theme_cache.updown_icon); - _value_changed(0); + _update_text(); } break; case NOTIFICATION_EXIT_TREE: { @@ -250,7 +254,6 @@ void SpinBox::_notification(int p_what) { } break; case NOTIFICATION_TRANSLATION_CHANGED: { - _value_changed(0); queue_redraw(); } break; @@ -279,7 +282,7 @@ void SpinBox::set_suffix(const String &p_suffix) { } suffix = p_suffix; - _value_changed(0); + _update_text(); } String SpinBox::get_suffix() const { @@ -292,7 +295,7 @@ void SpinBox::set_prefix(const String &p_prefix) { } prefix = p_prefix; - _value_changed(0); + _update_text(); } String SpinBox::get_prefix() const { diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 0e9d424f68..29b278c50e 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -46,8 +46,8 @@ class SpinBox : public Range { void _range_click_timeout(); void _release_mouse(); + void _update_text(); void _text_submitted(const String &p_string); - virtual void _value_changed(double p_value) override; void _text_changed(const String &p_string); String prefix; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 0839e4066d..db674c50fb 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1280,7 +1280,7 @@ void TextEdit::_notification(int p_what) { if (glyphs[j].font_rid != RID()) { TS->font_draw_glyph(glyphs[j].font_rid, ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color); had_glyphs_drawn = true; - } else if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + } else if (((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[j].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) { TS->draw_hex_code_box(ci, glyphs[j].font_size, Vector2(char_margin + char_ofs + ofs_x + glyphs[j].x_off, ofs_y + glyphs[j].y_off), glyphs[j].index, gl_color); had_glyphs_drawn = true; } diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index e71c236dc7..e22890562f 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -4594,8 +4594,8 @@ int Tree::get_item_offset(TreeItem *p_item) const { return ofs; } - ofs += compute_item_height(it); - if (it != root || !hide_root) { + if ((it != root || !hide_root) && it->is_visible()) { + ofs += compute_item_height(it); ofs += theme_cache.v_separation; } diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 1e9038139e..a1e7558653 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -44,7 +44,9 @@ #include "scene/property_utils.h" #define PACKED_SCENE_VERSION 3 -#define META_POINTER_PROPERTY_BASE "metadata/_editor_prop_ptr_" + +const String PackedScene::META_POINTER_PROPERTY_BASE = "metadata/_editor_prop_ptr_"; + bool SceneState::can_instantiate() const { return nodes.size() > 0; } @@ -239,15 +241,38 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { if (nprops[j].name & FLAG_PATH_PROPERTY_IS_NODE) { uint32_t name_idx = nprops[j].name & (FLAG_PATH_PROPERTY_IS_NODE - 1); ERR_FAIL_UNSIGNED_INDEX_V(name_idx, (uint32_t)sname_count, nullptr); - if (Engine::get_singleton()->is_editor_hint()) { - // If editor, just set the metadata and be it - node->set(META_POINTER_PROPERTY_BASE + String(snames[name_idx]), props[nprops[j].value]); + + const StringName &prop_name = snames[name_idx]; + const Variant &prop_variant = props[nprops[j].value]; + + if (prop_variant.get_type() == Variant::ARRAY) { + if (Engine::get_singleton()->is_editor_hint()) { + // If editor, simply set the original array of NodePaths. + node->set(prop_name, prop_variant); + continue; + } + + const Array &array = prop_variant; + for (int k = 0; k < array.size(); k++) { + DeferredNodePathProperties dnp; + dnp.path = array[k]; + dnp.base = node; + // Use special property name to signify an array. This is only used in deferred_node_paths. + dnp.property = String(prop_name) + "/indices/" + itos(k); + deferred_node_paths.push_back(dnp); + } + } else { + if (Engine::get_singleton()->is_editor_hint()) { + // If editor, just set the metadata and be it. + node->set(PackedScene::META_POINTER_PROPERTY_BASE + String(prop_name), prop_variant); + continue; + } // Do an actual deferred sed of the property path. DeferredNodePathProperties dnp; - dnp.path = props[nprops[j].value]; + dnp.path = prop_variant; dnp.base = node; - dnp.property = snames[name_idx]; + dnp.property = prop_name; deferred_node_paths.push_back(dnp); } continue; @@ -415,8 +440,26 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } for (const DeferredNodePathProperties &dnp : deferred_node_paths) { + // Replace properties stored as NodePaths with actual Nodes. Node *other = dnp.base->get_node_or_null(dnp.path); - dnp.base->set(dnp.property, other); + + const String string_property = dnp.property; + if (string_property.contains("/indices/")) { + // For properties with "/indices/", the replacement takes place inside an Array. + const String base_property = string_property.get_slice("/", 0); + const int index = string_property.get_slice("/", 2).to_int(); + + Array array = dnp.base->get(base_property); + if (array.size() >= index) { + array.push_back(other); + } else { + array.set(index, other); + } + + dnp.base->set(base_property, array); + } else { + dnp.base->set(dnp.property, other); + } } for (KeyValue<Ref<Resource>, Ref<Resource>> &E : resources_local_to_scene) { @@ -584,7 +627,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has if (E.name == META_PROPERTY_MISSING_RESOURCES) { continue; // Ignore this property when packing. } - if (E.name.begins_with(META_POINTER_PROPERTY_BASE)) { + if (E.name.begins_with(PackedScene::META_POINTER_PROPERTY_BASE)) { continue; // do not save. } @@ -600,7 +643,7 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has bool use_deferred_node_path_bit = false; if (E.type == Variant::OBJECT && E.hint == PROPERTY_HINT_NODE_TYPE) { - value = p_node->get(META_POINTER_PROPERTY_BASE + E.name); + value = p_node->get(PackedScene::META_POINTER_PROPERTY_BASE + E.name); if (value.get_type() != Variant::NODE_PATH) { continue; //was never set, ignore. } @@ -611,6 +654,20 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has if (ures.is_null()) { value = missing_resource_properties[E.name]; } + } else if (E.type == Variant::ARRAY && E.hint == PROPERTY_HINT_TYPE_STRING) { + int hint_subtype_separator = E.hint_string.find(":"); + if (hint_subtype_separator >= 0) { + String subtype_string = E.hint_string.substr(0, hint_subtype_separator); + int slash_pos = subtype_string.find("/"); + PropertyHint subtype_hint = PropertyHint::PROPERTY_HINT_NONE; + if (slash_pos >= 0) { + subtype_hint = PropertyHint(subtype_string.get_slice("/", 1).to_int()); + subtype_string = subtype_string.substr(0, slash_pos); + } + Variant::Type subtype = Variant::Type(subtype_string.to_int()); + + use_deferred_node_path_bit = subtype == Variant::OBJECT && subtype_hint == PROPERTY_HINT_NODE_TYPE; + } } if (!pinned_props.has(name)) { @@ -1736,7 +1793,7 @@ void SceneState::add_editable_instance(const NodePath &p_path) { } String SceneState::get_meta_pointer_property(const String &p_property) { - return META_POINTER_PROPERTY_BASE + p_property; + return PackedScene::META_POINTER_PROPERTY_BASE + p_property; } Vector<String> SceneState::_get_node_groups(int p_idx) const { diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index 5c53ffdb45..1967deab36 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -221,6 +221,8 @@ protected: virtual void reset_state() override; public: + static const String META_POINTER_PROPERTY_BASE; + enum GenEditState { GEN_EDIT_STATE_DISABLED, GEN_EDIT_STATE_INSTANCE, diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp index d8b78de526..b36e027f07 100644 --- a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp @@ -1202,7 +1202,7 @@ void ParticlesStorage::particles_set_view_axis(RID p_particles, const Vector3 &p } copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME); - copy_push_constant.lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1); + copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount; copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME; copy_push_constant.frame_remainder = particles->interpolate ? particles->frame_remainder : 0.0; @@ -1520,7 +1520,7 @@ void ParticlesStorage::update_particles() { } copy_push_constant.order_by_lifetime = (particles->draw_order == RS::PARTICLES_DRAW_ORDER_LIFETIME || particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME); - copy_push_constant.lifetime_split = MIN(particles->amount * particles->phase, particles->amount - 1); + copy_push_constant.lifetime_split = (MIN(int(particles->amount * particles->phase), particles->amount - 1) + 1) % particles->amount; copy_push_constant.lifetime_reverse = particles->draw_order == RS::PARTICLES_DRAW_ORDER_REVERSE_LIFETIME; RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(); diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp index 286d1b683f..744fd051f5 100644 --- a/servers/rendering/rendering_device.cpp +++ b/servers/rendering/rendering_device.cpp @@ -752,7 +752,7 @@ void RenderingDevice::_bind_methods() { ClassDB::bind_method(D_METHOD("render_pipeline_is_valid", "render_pipeline"), &RenderingDevice::render_pipeline_is_valid); ClassDB::bind_method(D_METHOD("compute_pipeline_create", "shader", "specialization_constants"), &RenderingDevice::_compute_pipeline_create, DEFVAL(TypedArray<RDPipelineSpecializationConstant>())); - ClassDB::bind_method(D_METHOD("compute_pipeline_is_valid", "compute_pieline"), &RenderingDevice::compute_pipeline_is_valid); + ClassDB::bind_method(D_METHOD("compute_pipeline_is_valid", "compute_pipeline"), &RenderingDevice::compute_pipeline_is_valid); ClassDB::bind_method(D_METHOD("screen_get_width", "screen"), &RenderingDevice::screen_get_width, DEFVAL(DisplayServer::MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("screen_get_height", "screen"), &RenderingDevice::screen_get_height, DEFVAL(DisplayServer::MAIN_WINDOW_ID)); diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index 084fb64a53..12e99ba606 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -10529,36 +10529,42 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_ r_options->push_back(option); } - } else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT)) && !completion_base_array) { + } else if ((int(completion_base) > int(TYPE_MAT4) && int(completion_base) < int(TYPE_STRUCT))) { Vector<String> options; - if (current_uniform_filter == FILTER_DEFAULT) { - options.push_back("filter_linear"); - options.push_back("filter_linear_mipmap"); - options.push_back("filter_linear_mipmap_anisotropic"); - options.push_back("filter_nearest"); - options.push_back("filter_nearest_mipmap"); - options.push_back("filter_nearest_mipmap_anisotropic"); - } - if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) { - options.push_back("hint_anisotropy"); - options.push_back("hint_default_black"); - options.push_back("hint_default_white"); - options.push_back("hint_default_transparent"); - options.push_back("hint_normal"); - options.push_back("hint_roughness_a"); - options.push_back("hint_roughness_b"); - options.push_back("hint_roughness_g"); - options.push_back("hint_roughness_gray"); - options.push_back("hint_roughness_normal"); - options.push_back("hint_roughness_r"); - options.push_back("hint_screen_texture"); - options.push_back("hint_normal_roughness_texture"); - options.push_back("hint_depth_texture"); - options.push_back("source_color"); - } - if (current_uniform_repeat == REPEAT_DEFAULT) { - options.push_back("repeat_enable"); - options.push_back("repeat_disable"); + if (completion_base_array) { + if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) { + options.push_back("source_color"); + } + } else { + if (current_uniform_filter == FILTER_DEFAULT) { + options.push_back("filter_linear"); + options.push_back("filter_linear_mipmap"); + options.push_back("filter_linear_mipmap_anisotropic"); + options.push_back("filter_nearest"); + options.push_back("filter_nearest_mipmap"); + options.push_back("filter_nearest_mipmap_anisotropic"); + } + if (current_uniform_hint == ShaderNode::Uniform::HINT_NONE) { + options.push_back("hint_anisotropy"); + options.push_back("hint_default_black"); + options.push_back("hint_default_white"); + options.push_back("hint_default_transparent"); + options.push_back("hint_normal"); + options.push_back("hint_roughness_a"); + options.push_back("hint_roughness_b"); + options.push_back("hint_roughness_g"); + options.push_back("hint_roughness_gray"); + options.push_back("hint_roughness_normal"); + options.push_back("hint_roughness_r"); + options.push_back("hint_screen_texture"); + options.push_back("hint_normal_roughness_texture"); + options.push_back("hint_depth_texture"); + options.push_back("source_color"); + } + if (current_uniform_repeat == REPEAT_DEFAULT) { + options.push_back("repeat_enable"); + options.push_back("repeat_disable"); + } } for (int i = 0; i < options.size(); i++) { diff --git a/servers/text_server.cpp b/servers/text_server.cpp index 88f1e76811..36c3137472 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -547,6 +547,7 @@ void TextServer::_bind_methods() { BIND_BITFIELD_FLAG(GRAPHEME_IS_UNDERSCORE); BIND_BITFIELD_FLAG(GRAPHEME_IS_CONNECTED); BIND_BITFIELD_FLAG(GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL); + BIND_BITFIELD_FLAG(GRAPHEME_IS_EMBEDDED_OBJECT); /* Hinting */ BIND_ENUM_CONSTANT(HINTING_NONE); @@ -1449,7 +1450,7 @@ void TextServer::shaped_text_draw(const RID &p_shaped, const RID &p_canvas, cons if (glyphs[i].font_rid != RID()) { font_draw_glyph(glyphs[i].font_rid, p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color); - } else if (hex_codes && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL)) { + } else if (hex_codes && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & GRAPHEME_IS_EMBEDDED_OBJECT) != GRAPHEME_IS_EMBEDDED_OBJECT)) { TextServer::draw_hex_code_box(p_canvas, glyphs[i].font_size, ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, p_color); } if (orientation == ORIENTATION_HORIZONTAL) { diff --git a/servers/text_server.h b/servers/text_server.h index e3c668bd5c..af6efb8c7d 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -125,18 +125,19 @@ public: }; enum GraphemeFlag { - GRAPHEME_IS_VALID = 1 << 0, // Glyph is valid. - GRAPHEME_IS_RTL = 1 << 1, // Glyph is right-to-left. - GRAPHEME_IS_VIRTUAL = 1 << 2, // Glyph is not part of source string (added by fit_to_width function, do not affect caret movement). + GRAPHEME_IS_VALID = 1 << 0, // Grapheme is valid. + GRAPHEME_IS_RTL = 1 << 1, // Grapheme is right-to-left. + GRAPHEME_IS_VIRTUAL = 1 << 2, // Grapheme is not part of source string (added by fit_to_width function, do not affect caret movement). GRAPHEME_IS_SPACE = 1 << 3, // Is whitespace (for justification and word breaks). GRAPHEME_IS_BREAK_HARD = 1 << 4, // Is line break (mandatory break, e.g. "\n"). GRAPHEME_IS_BREAK_SOFT = 1 << 5, // Is line break (optional break, e.g. space). GRAPHEME_IS_TAB = 1 << 6, // Is tab or vertical tab. - GRAPHEME_IS_ELONGATION = 1 << 7, // Elongation (e.g. kashida), glyph can be duplicated or truncated to fit line to width. + GRAPHEME_IS_ELONGATION = 1 << 7, // Elongation (e.g. kashida), grapheme can be duplicated or truncated to fit line to width. GRAPHEME_IS_PUNCTUATION = 1 << 8, // Punctuation, except underscore (can be used as word break, but not line break or justifiction). GRAPHEME_IS_UNDERSCORE = 1 << 9, // Underscore (can be used as word break). GRAPHEME_IS_CONNECTED = 1 << 10, // Connected to previous grapheme. GRAPHEME_IS_SAFE_TO_INSERT_TATWEEL = 1 << 11, // It is safe to insert a U+0640 before this grapheme for elongation. + GRAPHEME_IS_EMBEDDED_OBJECT = 1 << 12, // Grapheme is an object replacement character for the embedded object. }; enum Hinting { |