summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/basis_universal/image_compress_basisu.cpp89
-rw-r--r--modules/basis_universal/image_compress_basisu.h1
-rw-r--r--modules/betsy/SCsub1
-rw-r--r--modules/betsy/bc1.glsl483
-rw-r--r--modules/betsy/betsy_bc1.h1061
-rw-r--r--modules/betsy/image_compress_betsy.cpp440
-rw-r--r--modules/betsy/image_compress_betsy.h70
-rw-r--r--modules/betsy/register_types.cpp3
-rw-r--r--modules/csg/csg_shape.cpp7
-rw-r--r--modules/csg/editor/csg_gizmos.cpp4
-rw-r--r--modules/fbx/fbx_document.cpp8
-rw-r--r--modules/gdscript/.editorconfig5
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp1
-rw-r--r--modules/gdscript/gdscript.cpp7
-rw-r--r--modules/gdscript/gdscript.h2
-rw-r--r--modules/gdscript/gdscript_editor.cpp4
-rw-r--r--modules/gdscript/gdscript_tokenizer_buffer.cpp1
-rw-r--r--modules/gdscript/language_server/gdscript_language_server.cpp1
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp2
-rw-r--r--modules/gdscript/language_server/godot_lsp.h32
-rw-r--r--modules/gdscript/tests/scripts/.editorconfig12
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg9
-rw-r--r--modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd5
-rw-r--r--modules/gdscript/tests/scripts/parser/.editorconfig2
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.gd7
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/self_destruction.gd50
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/self_destruction.out5
-rw-r--r--modules/gltf/doc_classes/GLTFNode.xml7
-rw-r--r--modules/gltf/doc_classes/GLTFState.xml11
-rw-r--r--modules/gltf/gltf_document.cpp42
-rw-r--r--modules/gltf/gltf_document.h6
-rw-r--r--modules/gltf/gltf_state.cpp14
-rw-r--r--modules/gltf/gltf_state.h1
-rw-r--r--modules/gltf/structures/gltf_node.cpp5
-rw-r--r--modules/gltf/structures/gltf_node.h1
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp25
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.h2
-rw-r--r--modules/minimp3/audio_stream_mp3.cpp3
-rw-r--r--modules/mono/csharp_script.cpp5
-rw-r--r--modules/mono/csharp_script.h2
-rw-r--r--modules/mono/editor/bindings_generator.cpp6
-rw-r--r--modules/mono/editor/editor_internal_calls.cpp3
-rw-r--r--modules/mono/godotsharp_dirs.cpp10
-rw-r--r--modules/multiplayer/doc_classes/SceneMultiplayer.xml2
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp40
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.h6
-rw-r--r--modules/multiplayer/editor/multiplayer_editor_plugin.cpp2
-rw-r--r--modules/multiplayer/editor/replication_editor.cpp20
-rw-r--r--modules/multiplayer/editor/replication_editor.h1
-rw-r--r--modules/multiplayer/scene_rpc_interface.cpp14
-rw-r--r--modules/multiplayer/scene_rpc_interface.h2
-rw-r--r--modules/navigation/2d/godot_navigation_server_2d.cpp5
-rw-r--r--modules/navigation/2d/godot_navigation_server_2d.h1
-rw-r--r--modules/navigation/3d/godot_navigation_server_3d.cpp21
-rw-r--r--modules/navigation/3d/godot_navigation_server_3d.h3
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.cpp2
-rw-r--r--modules/navigation/nav_region.cpp17
-rw-r--r--modules/navigation/nav_region.h4
-rw-r--r--modules/openxr/doc_classes/OpenXRCompositionLayer.xml15
-rw-r--r--modules/openxr/editor/openxr_select_runtime.cpp1
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_extension.cpp257
-rw-r--r--modules/openxr/extensions/openxr_composition_layer_extension.h61
-rw-r--r--modules/openxr/openxr_api.cpp4
-rw-r--r--modules/openxr/scene/openxr_composition_layer.cpp179
-rw-r--r--modules/openxr/scene/openxr_composition_layer.h21
-rw-r--r--modules/openxr/scene/openxr_composition_layer_cylinder.cpp16
-rw-r--r--modules/openxr/scene/openxr_composition_layer_cylinder.h13
-rw-r--r--modules/openxr/scene/openxr_composition_layer_equirect.cpp17
-rw-r--r--modules/openxr/scene/openxr_composition_layer_equirect.h14
-rw-r--r--modules/openxr/scene/openxr_composition_layer_quad.cpp14
-rw-r--r--modules/openxr/scene/openxr_composition_layer_quad.h11
-rw-r--r--modules/vorbis/audio_stream_ogg_vorbis.cpp3
75 files changed, 2819 insertions, 421 deletions
diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp
index 8167fe8c73..ab20d00b5b 100644
--- a/modules/basis_universal/image_compress_basisu.cpp
+++ b/modules/basis_universal/image_compress_basisu.cpp
@@ -84,14 +84,12 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha
decompress_format = BASIS_DECOMPRESS_RGBA;
} break;
case Image::USED_CHANNELS_R: {
- decompress_format = BASIS_DECOMPRESS_RGB;
+ decompress_format = BASIS_DECOMPRESS_R;
} break;
case Image::USED_CHANNELS_RG: {
- // Currently RG textures are compressed as DXT5/ETC2_RGBA8 with a RA -> RG swizzle,
- // as BasisUniversal didn't use to support ETC2_RG11 transcoding.
params.m_force_alpha = true;
image->convert_rg_to_ra_rgba8();
- decompress_format = BASIS_DECOMPRESS_RG_AS_RA;
+ decompress_format = BASIS_DECOMPRESS_RG;
} break;
case Image::USED_CHANNELS_RGB: {
decompress_format = BASIS_DECOMPRESS_RGB;
@@ -219,15 +217,68 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
// Get supported compression formats.
bool bptc_supported = RS::get_singleton()->has_os_feature("bptc");
bool astc_supported = RS::get_singleton()->has_os_feature("astc");
+ bool rgtc_supported = RS::get_singleton()->has_os_feature("rgtc");
bool s3tc_supported = RS::get_singleton()->has_os_feature("s3tc");
bool etc2_supported = RS::get_singleton()->has_os_feature("etc2");
bool needs_ra_rg_swap = false;
+ bool needs_rg_trim = false;
+
+ BasisDecompressFormat decompress_format = (BasisDecompressFormat)(*(uint32_t *)(src_ptr));
- switch (*(uint32_t *)(src_ptr)) {
+ switch (decompress_format) {
+ case BASIS_DECOMPRESS_R: {
+ if (rgtc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC4_R;
+ image_format = Image::FORMAT_RGTC_R;
+ } else if (s3tc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC1;
+ image_format = Image::FORMAT_DXT1;
+ } else if (etc2_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_R11;
+ image_format = Image::FORMAT_ETC2_R11;
+ } else {
+ // No supported VRAM compression formats, decompress.
+ basisu_format = basist::transcoder_texture_format::cTFRGBA32;
+ image_format = Image::FORMAT_RGBA8;
+ needs_rg_trim = true;
+ }
+
+ } break;
case BASIS_DECOMPRESS_RG: {
- // RGTC transcoding is currently performed with RG_AS_RA, fail.
- ERR_FAIL_V(image);
+ if (rgtc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC5_RG;
+ image_format = Image::FORMAT_RGTC_RG;
+ } else if (s3tc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC3;
+ image_format = Image::FORMAT_DXT5_RA_AS_RG;
+ } else if (etc2_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFETC2_EAC_RG11;
+ image_format = Image::FORMAT_ETC2_RG11;
+ } else {
+ // No supported VRAM compression formats, decompress.
+ basisu_format = basist::transcoder_texture_format::cTFRGBA32;
+ image_format = Image::FORMAT_RGBA8;
+ needs_ra_rg_swap = true;
+ needs_rg_trim = true;
+ }
+
+ } break;
+ case BASIS_DECOMPRESS_RG_AS_RA: {
+ if (s3tc_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFBC3;
+ image_format = Image::FORMAT_DXT5_RA_AS_RG;
+ } else if (etc2_supported) {
+ basisu_format = basist::transcoder_texture_format::cTFETC2;
+ image_format = Image::FORMAT_ETC2_RA_AS_RG;
+ } else {
+ // No supported VRAM compression formats, decompress.
+ basisu_format = basist::transcoder_texture_format::cTFRGBA32;
+ image_format = Image::FORMAT_RGBA8;
+ needs_ra_rg_swap = true;
+ needs_rg_trim = true;
+ }
+
} break;
case BASIS_DECOMPRESS_RGB: {
if (bptc_supported) {
@@ -267,20 +318,7 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
basisu_format = basist::transcoder_texture_format::cTFRGBA32;
image_format = Image::FORMAT_RGBA8;
}
- } break;
- case BASIS_DECOMPRESS_RG_AS_RA: {
- if (s3tc_supported) {
- basisu_format = basist::transcoder_texture_format::cTFBC3;
- image_format = Image::FORMAT_DXT5_RA_AS_RG;
- } else if (etc2_supported) {
- basisu_format = basist::transcoder_texture_format::cTFETC2;
- image_format = Image::FORMAT_ETC2_RA_AS_RG;
- } else {
- // No supported VRAM compression formats, decompress.
- basisu_format = basist::transcoder_texture_format::cTFRGBA32;
- image_format = Image::FORMAT_RGBA8;
- needs_ra_rg_swap = true;
- }
+
} break;
}
@@ -324,6 +362,15 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) {
image->convert_ra_rgba8_to_rg();
}
+ if (needs_rg_trim) {
+ // Remove unnecessary color channels from uncompressed textures.
+ if (decompress_format == BASIS_DECOMPRESS_R) {
+ image->convert(Image::FORMAT_R8);
+ } else if (decompress_format == BASIS_DECOMPRESS_RG || decompress_format == BASIS_DECOMPRESS_RG_AS_RA) {
+ image->convert(Image::FORMAT_RG8);
+ }
+ }
+
return image;
}
diff --git a/modules/basis_universal/image_compress_basisu.h b/modules/basis_universal/image_compress_basisu.h
index ac5d62ae73..5e36d448f6 100644
--- a/modules/basis_universal/image_compress_basisu.h
+++ b/modules/basis_universal/image_compress_basisu.h
@@ -38,6 +38,7 @@ enum BasisDecompressFormat {
BASIS_DECOMPRESS_RGB,
BASIS_DECOMPRESS_RGBA,
BASIS_DECOMPRESS_RG_AS_RA,
+ BASIS_DECOMPRESS_R,
};
void basis_universal_init();
diff --git a/modules/betsy/SCsub b/modules/betsy/SCsub
index 9930e1f4cf..ed5dcbf58b 100644
--- a/modules/betsy/SCsub
+++ b/modules/betsy/SCsub
@@ -4,6 +4,7 @@ Import("env_modules")
env_betsy = env_modules.Clone()
env_betsy.GLSL_HEADER("bc6h.glsl")
+env_betsy.GLSL_HEADER("bc1.glsl")
env_betsy.Depends(Glob("*.glsl.gen.h"), ["#glsl_builders.py"])
# Thirdparty source files
diff --git a/modules/betsy/bc1.glsl b/modules/betsy/bc1.glsl
new file mode 100644
index 0000000000..f1b2c28254
--- /dev/null
+++ b/modules/betsy/bc1.glsl
@@ -0,0 +1,483 @@
+#[versions]
+
+standard = "";
+dithered = "#define BC1_DITHER";
+
+#[compute]
+#version 450
+
+#include "CrossPlatformSettings_piece_all.glsl"
+#include "UavCrossPlatform_piece_all.glsl"
+
+#define FLT_MAX 340282346638528859811704183484516925440.0f
+
+layout(binding = 0) uniform sampler2D srcTex;
+layout(binding = 1, rg32ui) uniform restrict writeonly uimage2D dstTexture;
+
+layout(std430, binding = 2) readonly restrict buffer globalBuffer {
+ float2 c_oMatch5[256];
+ float2 c_oMatch6[256];
+};
+
+layout(push_constant, std430) uniform Params {
+ uint p_numRefinements;
+ uint p_padding[3];
+}
+params;
+
+layout(local_size_x = 8, //
+ local_size_y = 8, //
+ local_size_z = 1) in;
+
+float3 rgb565to888(float rgb565) {
+ float3 retVal;
+ retVal.x = floor(rgb565 / 2048.0f);
+ retVal.y = floor(mod(rgb565, 2048.0f) / 32.0f);
+ retVal.z = floor(mod(rgb565, 32.0f));
+
+ // This is the correct 565 to 888 conversion:
+ // rgb = floor( rgb * ( 255.0f / float3( 31.0f, 63.0f, 31.0f ) ) + 0.5f )
+ //
+ // However stb_dxt follows a different one:
+ // rb = floor( rb * ( 256 / 32 + 8 / 32 ) );
+ // g = floor( g * ( 256 / 64 + 4 / 64 ) );
+ //
+ // I'm not sure exactly why but it's possible this is how the S3TC specifies it should be decoded
+ // It's quite possible this is the reason:
+ // http://www.ludicon.com/castano/blog/2009/03/gpu-dxt-decompression/
+ //
+ // Or maybe it's just because it's cheap to do with integer shifts.
+ // Anyway, we follow stb_dxt's conversion just in case
+ // (gives almost the same result, with 1 or -1 of difference for a very few values)
+ //
+ // Perhaps when we make 888 -> 565 -> 888 it doesn't matter
+ // because they end up mapping to the original number
+
+ return floor(retVal * float3(8.25f, 4.0625f, 8.25f));
+}
+
+float rgb888to565(float3 rgbValue) {
+ rgbValue.rb = floor(rgbValue.rb * 31.0f / 255.0f + 0.5f);
+ rgbValue.g = floor(rgbValue.g * 63.0f / 255.0f + 0.5f);
+
+ return rgbValue.r * 2048.0f + rgbValue.g * 32.0f + rgbValue.b;
+}
+
+// linear interpolation at 1/3 point between a and b, using desired rounding type
+float3 lerp13(float3 a, float3 b) {
+#ifdef STB_DXT_USE_ROUNDING_BIAS
+ // with rounding bias
+ return a + floor((b - a) * (1.0f / 3.0f) + 0.5f);
+#else
+ // without rounding bias
+ return floor((2.0f * a + b) / 3.0f);
+#endif
+}
+
+/// Unpacks a block of 4 colors from two 16-bit endpoints
+void EvalColors(out float3 colors[4], float c0, float c1) {
+ colors[0] = rgb565to888(c0);
+ colors[1] = rgb565to888(c1);
+ colors[2] = lerp13(colors[0], colors[1]);
+ colors[3] = lerp13(colors[1], colors[0]);
+}
+
+/** The color optimization function. (Clever code, part 1)
+@param outMinEndp16 [out]
+ Minimum endpoint, in RGB565
+@param outMaxEndp16 [out]
+ Maximum endpoint, in RGB565
+*/
+void OptimizeColorsBlock(const uint srcPixelsBlock[16], out float outMinEndp16, out float outMaxEndp16) {
+ // determine color distribution
+ float3 avgColor;
+ float3 minColor;
+ float3 maxColor;
+
+ avgColor = minColor = maxColor = unpackUnorm4x8(srcPixelsBlock[0]).xyz;
+ for (int i = 1; i < 16; ++i) {
+ const float3 currColorUnorm = unpackUnorm4x8(srcPixelsBlock[i]).xyz;
+ avgColor += currColorUnorm;
+ minColor = min(minColor, currColorUnorm);
+ maxColor = max(maxColor, currColorUnorm);
+ }
+
+ avgColor = round(avgColor * 255.0f / 16.0f);
+ maxColor *= 255.0f;
+ minColor *= 255.0f;
+
+ // determine covariance matrix
+ float cov[6];
+ for (int i = 0; i < 6; ++i)
+ cov[i] = 0;
+
+ for (int i = 0; i < 16; ++i) {
+ const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f;
+ float3 rgbDiff = currColor - avgColor;
+
+ cov[0] += rgbDiff.r * rgbDiff.r;
+ cov[1] += rgbDiff.r * rgbDiff.g;
+ cov[2] += rgbDiff.r * rgbDiff.b;
+ cov[3] += rgbDiff.g * rgbDiff.g;
+ cov[4] += rgbDiff.g * rgbDiff.b;
+ cov[5] += rgbDiff.b * rgbDiff.b;
+ }
+
+ // convert covariance matrix to float, find principal axis via power iter
+ for (int i = 0; i < 6; ++i)
+ cov[i] /= 255.0f;
+
+ float3 vF = maxColor - minColor;
+
+ const int nIterPower = 4;
+ for (int iter = 0; iter < nIterPower; ++iter) {
+ const float r = vF.r * cov[0] + vF.g * cov[1] + vF.b * cov[2];
+ const float g = vF.r * cov[1] + vF.g * cov[3] + vF.b * cov[4];
+ const float b = vF.r * cov[2] + vF.g * cov[4] + vF.b * cov[5];
+
+ vF.r = r;
+ vF.g = g;
+ vF.b = b;
+ }
+
+ float magn = max3(abs(vF.r), abs(vF.g), abs(vF.b));
+ float3 v;
+
+ if (magn < 4.0f) { // too small, default to luminance
+ v.r = 299.0f; // JPEG YCbCr luma coefs, scaled by 1000.
+ v.g = 587.0f;
+ v.b = 114.0f;
+ } else {
+ v = trunc(vF * (512.0f / magn));
+ }
+
+ // Pick colors at extreme points
+ float3 minEndpoint, maxEndpoint;
+ float minDot = FLT_MAX;
+ float maxDot = -FLT_MAX;
+ for (int i = 0; i < 16; ++i) {
+ const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f;
+ const float dotValue = dot(currColor, v);
+
+ if (dotValue < minDot) {
+ minDot = dotValue;
+ minEndpoint = currColor;
+ }
+
+ if (dotValue > maxDot) {
+ maxDot = dotValue;
+ maxEndpoint = currColor;
+ }
+ }
+
+ outMinEndp16 = rgb888to565(minEndpoint);
+ outMaxEndp16 = rgb888to565(maxEndpoint);
+}
+
+// The color matching function
+uint MatchColorsBlock(const uint srcPixelsBlock[16], float3 color[4]) {
+ uint mask = 0u;
+ float3 dir = color[0] - color[1];
+ float stops[4];
+
+ for (int i = 0; i < 4; ++i)
+ stops[i] = dot(color[i], dir);
+
+ // think of the colors as arranged on a line; project point onto that line, then choose
+ // next color out of available ones. we compute the crossover points for "best color in top
+ // half"/"best in bottom half" and then the same inside that subinterval.
+ //
+ // relying on this 1d approximation isn't always optimal in terms of euclidean distance,
+ // but it's very close and a lot faster.
+ // http://cbloomrants.blogspot.com/2008/12/12-08-08-dxtc-summary.html
+
+ float c0Point = trunc((stops[1] + stops[3]) * 0.5f);
+ float halfPoint = trunc((stops[3] + stops[2]) * 0.5f);
+ float c3Point = trunc((stops[2] + stops[0]) * 0.5f);
+
+#ifndef BC1_DITHER
+ // the version without dithering is straightforward
+ for (uint i = 16u; i-- > 0u;) {
+ const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f;
+
+ const float dotValue = dot(currColor, dir);
+ mask <<= 2u;
+
+ if (dotValue < halfPoint)
+ mask |= ((dotValue < c0Point) ? 1u : 3u);
+ else
+ mask |= ((dotValue < c3Point) ? 2u : 0u);
+ }
+#else
+ // with floyd-steinberg dithering
+ float4 ep1 = float4(0, 0, 0, 0);
+ float4 ep2 = float4(0, 0, 0, 0);
+
+ c0Point *= 16.0f;
+ halfPoint *= 16.0f;
+ c3Point *= 16.0f;
+
+ for (uint y = 0u; y < 4u; ++y) {
+ float ditherDot;
+ uint lmask, step;
+
+ float3 currColor;
+ float dotValue;
+
+ currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 0]).xyz * 255.0f;
+ dotValue = dot(currColor, dir);
+
+ ditherDot = (dotValue * 16.0f) + (3 * ep2[1] + 5 * ep2[0]);
+ if (ditherDot < halfPoint)
+ step = (ditherDot < c0Point) ? 1u : 3u;
+ else
+ step = (ditherDot < c3Point) ? 2u : 0u;
+ ep1[0] = dotValue - stops[step];
+ lmask = step;
+
+ currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 1]).xyz * 255.0f;
+ dotValue = dot(currColor, dir);
+
+ ditherDot = (dotValue * 16.0f) + (7 * ep1[0] + 3 * ep2[2] + 5 * ep2[1] + ep2[0]);
+ if (ditherDot < halfPoint)
+ step = (ditherDot < c0Point) ? 1u : 3u;
+ else
+ step = (ditherDot < c3Point) ? 2u : 0u;
+ ep1[1] = dotValue - stops[step];
+ lmask |= step << 2u;
+
+ currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 2]).xyz * 255.0f;
+ dotValue = dot(currColor, dir);
+
+ ditherDot = (dotValue * 16.0f) + (7 * ep1[1] + 3 * ep2[3] + 5 * ep2[2] + ep2[1]);
+ if (ditherDot < halfPoint)
+ step = (ditherDot < c0Point) ? 1u : 3u;
+ else
+ step = (ditherDot < c3Point) ? 2u : 0u;
+ ep1[2] = dotValue - stops[step];
+ lmask |= step << 4u;
+
+ currColor = unpackUnorm4x8(srcPixelsBlock[y * 4 + 2]).xyz * 255.0f;
+ dotValue = dot(currColor, dir);
+
+ ditherDot = (dotValue * 16.0f) + (7 * ep1[2] + 5 * ep2[3] + ep2[2]);
+ if (ditherDot < halfPoint)
+ step = (ditherDot < c0Point) ? 1u : 3u;
+ else
+ step = (ditherDot < c3Point) ? 2u : 0u;
+ ep1[3] = dotValue - stops[step];
+ lmask |= step << 6u;
+
+ mask |= lmask << (y * 8u);
+ {
+ float4 tmp = ep1;
+ ep1 = ep2;
+ ep2 = tmp;
+ } // swap
+ }
+#endif
+
+ return mask;
+}
+
+// The refinement function. (Clever code, part 2)
+// Tries to optimize colors to suit block contents better.
+// (By solving a least squares system via normal equations+Cramer's rule)
+bool RefineBlock(const uint srcPixelsBlock[16], uint mask, inout float inOutMinEndp16,
+ inout float inOutMaxEndp16) {
+ float newMin16, newMax16;
+ const float oldMin = inOutMinEndp16;
+ const float oldMax = inOutMaxEndp16;
+
+ if ((mask ^ (mask << 2u)) < 4u) // all pixels have the same index?
+ {
+ // yes, linear system would be singular; solve using optimal
+ // single-color match on average color
+ float3 rgbVal = float3(8.0f / 255.0f, 8.0f / 255.0f, 8.0f / 255.0f);
+ for (int i = 0; i < 16; ++i)
+ rgbVal += unpackUnorm4x8(srcPixelsBlock[i]).xyz;
+
+ rgbVal = floor(rgbVal * (255.0f / 16.0f));
+
+ newMax16 = c_oMatch5[uint(rgbVal.r)][0] * 2048.0f + //
+ c_oMatch6[uint(rgbVal.g)][0] * 32.0f + //
+ c_oMatch5[uint(rgbVal.b)][0];
+ newMin16 = c_oMatch5[uint(rgbVal.r)][1] * 2048.0f + //
+ c_oMatch6[uint(rgbVal.g)][1] * 32.0f + //
+ c_oMatch5[uint(rgbVal.b)][1];
+ } else {
+ const float w1Tab[4] = { 3, 0, 2, 1 };
+ const float prods[4] = { 589824.0f, 2304.0f, 262402.0f, 66562.0f };
+ // ^some magic to save a lot of multiplies in the accumulating loop...
+ // (precomputed products of weights for least squares system, accumulated inside one 32-bit
+ // register)
+
+ float akku = 0.0f;
+ uint cm = mask;
+ float3 at1 = float3(0, 0, 0);
+ float3 at2 = float3(0, 0, 0);
+ for (int i = 0; i < 16; ++i, cm >>= 2u) {
+ const float3 currColor = unpackUnorm4x8(srcPixelsBlock[i]).xyz * 255.0f;
+
+ const uint step = cm & 3u;
+ const float w1 = w1Tab[step];
+ akku += prods[step];
+ at1 += currColor * w1;
+ at2 += currColor;
+ }
+
+ at2 = 3.0f * at2 - at1;
+
+ // extract solutions and decide solvability
+ const float xx = floor(akku / 65535.0f);
+ const float yy = floor(mod(akku, 65535.0f) / 256.0f);
+ const float xy = mod(akku, 256.0f);
+
+ float2 f_rb_g;
+ f_rb_g.x = 3.0f * 31.0f / 255.0f / (xx * yy - xy * xy);
+ f_rb_g.y = f_rb_g.x * 63.0f / 31.0f;
+
+ // solve.
+ const float3 newMaxVal = clamp(floor((at1 * yy - at2 * xy) * f_rb_g.xyx + 0.5f),
+ float3(0.0f, 0.0f, 0.0f), float3(31, 63, 31));
+ newMax16 = newMaxVal.x * 2048.0f + newMaxVal.y * 32.0f + newMaxVal.z;
+
+ const float3 newMinVal = clamp(floor((at2 * xx - at1 * xy) * f_rb_g.xyx + 0.5f),
+ float3(0.0f, 0.0f, 0.0f), float3(31, 63, 31));
+ newMin16 = newMinVal.x * 2048.0f + newMinVal.y * 32.0f + newMinVal.z;
+ }
+
+ inOutMinEndp16 = newMin16;
+ inOutMaxEndp16 = newMax16;
+
+ return oldMin != newMin16 || oldMax != newMax16;
+}
+
+#ifdef BC1_DITHER
+/// Quantizes 'srcValue' which is originally in 888 (full range),
+/// converting it to 565 and then back to 888 (quantized)
+float3 quant(float3 srcValue) {
+ srcValue = clamp(srcValue, 0.0f, 255.0f);
+ // Convert 888 -> 565
+ srcValue = floor(srcValue * float3(31.0f / 255.0f, 63.0f / 255.0f, 31.0f / 255.0f) + 0.5f);
+ // Convert 565 -> 888 back
+ srcValue = floor(srcValue * float3(8.25f, 4.0625f, 8.25f));
+
+ return srcValue;
+}
+
+void DitherBlock(const uint srcPixBlck[16], out uint dthPixBlck[16]) {
+ float3 ep1[4] = { float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0) };
+ float3 ep2[4] = { float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0), float3(0, 0, 0) };
+
+ for (uint y = 0u; y < 16u; y += 4u) {
+ float3 srcPixel, dithPixel;
+
+ srcPixel = unpackUnorm4x8(srcPixBlck[y + 0u]).xyz * 255.0f;
+ dithPixel = quant(srcPixel + trunc((3 * ep2[1] + 5 * ep2[0]) * (1.0f / 16.0f)));
+ ep1[0] = srcPixel - dithPixel;
+ dthPixBlck[y + 0u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f));
+
+ srcPixel = unpackUnorm4x8(srcPixBlck[y + 1u]).xyz * 255.0f;
+ dithPixel = quant(
+ srcPixel + trunc((7 * ep1[0] + 3 * ep2[2] + 5 * ep2[1] + ep2[0]) * (1.0f / 16.0f)));
+ ep1[1] = srcPixel - dithPixel;
+ dthPixBlck[y + 1u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f));
+
+ srcPixel = unpackUnorm4x8(srcPixBlck[y + 2u]).xyz * 255.0f;
+ dithPixel = quant(
+ srcPixel + trunc((7 * ep1[1] + 3 * ep2[3] + 5 * ep2[2] + ep2[1]) * (1.0f / 16.0f)));
+ ep1[2] = srcPixel - dithPixel;
+ dthPixBlck[y + 2u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f));
+
+ srcPixel = unpackUnorm4x8(srcPixBlck[y + 3u]).xyz * 255.0f;
+ dithPixel = quant(srcPixel + trunc((7 * ep1[2] + 5 * ep2[3] + ep2[2]) * (1.0f / 16.0f)));
+ ep1[3] = srcPixel - dithPixel;
+ dthPixBlck[y + 3u] = packUnorm4x8(float4(dithPixel * (1.0f / 255.0f), 1.0f));
+
+ // swap( ep1, ep2 )
+ for (uint i = 0u; i < 4u; ++i) {
+ float3 tmp = ep1[i];
+ ep1[i] = ep2[i];
+ ep2[i] = tmp;
+ }
+ }
+}
+#endif
+
+void main() {
+ uint srcPixelsBlock[16];
+
+ bool bAllColorsEqual = true;
+
+ // Load the whole 4x4 block
+ const uint2 pixelsToLoadBase = gl_GlobalInvocationID.xy << 2u;
+ for (uint i = 0u; i < 16u; ++i) {
+ const uint2 pixelsToLoad = pixelsToLoadBase + uint2(i & 0x03u, i >> 2u);
+ const float3 srcPixels0 = OGRE_Load2D(srcTex, int2(pixelsToLoad), 0).xyz;
+ srcPixelsBlock[i] = packUnorm4x8(float4(srcPixels0, 1.0f));
+ bAllColorsEqual = bAllColorsEqual && srcPixelsBlock[0] == srcPixelsBlock[i];
+ }
+
+ float maxEndp16, minEndp16;
+ uint mask = 0u;
+
+ if (bAllColorsEqual) {
+ const uint3 rgbVal = uint3(unpackUnorm4x8(srcPixelsBlock[0]).xyz * 255.0f);
+ mask = 0xAAAAAAAAu;
+ maxEndp16 =
+ c_oMatch5[rgbVal.r][0] * 2048.0f + c_oMatch6[rgbVal.g][0] * 32.0f + c_oMatch5[rgbVal.b][0];
+ minEndp16 =
+ c_oMatch5[rgbVal.r][1] * 2048.0f + c_oMatch6[rgbVal.g][1] * 32.0f + c_oMatch5[rgbVal.b][1];
+ } else {
+#ifdef BC1_DITHER
+ uint ditherPixelsBlock[16];
+ // first step: compute dithered version for PCA if desired
+ DitherBlock(srcPixelsBlock, ditherPixelsBlock);
+#else
+#define ditherPixelsBlock srcPixelsBlock
+#endif
+
+ // second step: pca+map along principal axis
+ OptimizeColorsBlock(ditherPixelsBlock, minEndp16, maxEndp16);
+ if (minEndp16 != maxEndp16) {
+ float3 colors[4];
+ EvalColors(colors, maxEndp16, minEndp16); // Note min/max are inverted
+ mask = MatchColorsBlock(srcPixelsBlock, colors);
+ }
+
+ // third step: refine (multiple times if requested)
+ bool bStopRefinement = false;
+ for (uint i = 0u; i < params.p_numRefinements && !bStopRefinement; ++i) {
+ const uint lastMask = mask;
+
+ if (RefineBlock(ditherPixelsBlock, mask, minEndp16, maxEndp16)) {
+ if (minEndp16 != maxEndp16) {
+ float3 colors[4];
+ EvalColors(colors, maxEndp16, minEndp16); // Note min/max are inverted
+ mask = MatchColorsBlock(srcPixelsBlock, colors);
+ } else {
+ mask = 0u;
+ bStopRefinement = true;
+ }
+ }
+
+ bStopRefinement = mask == lastMask || bStopRefinement;
+ }
+ }
+
+ // write the color block
+ if (maxEndp16 < minEndp16) {
+ const float tmpValue = minEndp16;
+ minEndp16 = maxEndp16;
+ maxEndp16 = tmpValue;
+ mask ^= 0x55555555u;
+ }
+
+ uint2 outputBytes;
+ outputBytes.x = uint(maxEndp16) | (uint(minEndp16) << 16u);
+ outputBytes.y = mask;
+
+ uint2 dstUV = gl_GlobalInvocationID.xy;
+ imageStore(dstTexture, int2(dstUV), uint4(outputBytes.xy, 0u, 0u));
+}
diff --git a/modules/betsy/betsy_bc1.h b/modules/betsy/betsy_bc1.h
new file mode 100644
index 0000000000..2274ed0a81
--- /dev/null
+++ b/modules/betsy/betsy_bc1.h
@@ -0,0 +1,1061 @@
+/**************************************************************************/
+/* betsy_bc1.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef BETSY_BC1_H
+#define BETSY_BC1_H
+
+constexpr const float dxt1_encoding_table[1024] = {
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 0,
+ 1,
+ 1,
+ 0,
+ 1,
+ 0,
+ 1,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 2,
+ 0,
+ 2,
+ 0,
+ 0,
+ 4,
+ 2,
+ 1,
+ 2,
+ 1,
+ 2,
+ 1,
+ 3,
+ 0,
+ 3,
+ 0,
+ 3,
+ 0,
+ 3,
+ 1,
+ 1,
+ 5,
+ 3,
+ 2,
+ 3,
+ 2,
+ 4,
+ 0,
+ 4,
+ 0,
+ 4,
+ 1,
+ 4,
+ 1,
+ 4,
+ 2,
+ 4,
+ 2,
+ 4,
+ 2,
+ 3,
+ 5,
+ 5,
+ 1,
+ 5,
+ 1,
+ 5,
+ 2,
+ 4,
+ 4,
+ 5,
+ 3,
+ 5,
+ 3,
+ 5,
+ 3,
+ 6,
+ 2,
+ 6,
+ 2,
+ 6,
+ 2,
+ 6,
+ 3,
+ 5,
+ 5,
+ 6,
+ 4,
+ 6,
+ 4,
+ 4,
+ 8,
+ 7,
+ 3,
+ 7,
+ 3,
+ 7,
+ 3,
+ 7,
+ 4,
+ 7,
+ 4,
+ 7,
+ 4,
+ 7,
+ 5,
+ 5,
+ 9,
+ 7,
+ 6,
+ 7,
+ 6,
+ 8,
+ 4,
+ 8,
+ 4,
+ 8,
+ 5,
+ 8,
+ 5,
+ 8,
+ 6,
+ 8,
+ 6,
+ 8,
+ 6,
+ 7,
+ 9,
+ 9,
+ 5,
+ 9,
+ 5,
+ 9,
+ 6,
+ 8,
+ 8,
+ 9,
+ 7,
+ 9,
+ 7,
+ 9,
+ 7,
+ 10,
+ 6,
+ 10,
+ 6,
+ 10,
+ 6,
+ 10,
+ 7,
+ 9,
+ 9,
+ 10,
+ 8,
+ 10,
+ 8,
+ 8,
+ 12,
+ 11,
+ 7,
+ 11,
+ 7,
+ 11,
+ 7,
+ 11,
+ 8,
+ 11,
+ 8,
+ 11,
+ 8,
+ 11,
+ 9,
+ 9,
+ 13,
+ 11,
+ 10,
+ 11,
+ 10,
+ 12,
+ 8,
+ 12,
+ 8,
+ 12,
+ 9,
+ 12,
+ 9,
+ 12,
+ 10,
+ 12,
+ 10,
+ 12,
+ 10,
+ 11,
+ 13,
+ 13,
+ 9,
+ 13,
+ 9,
+ 13,
+ 10,
+ 12,
+ 12,
+ 13,
+ 11,
+ 13,
+ 11,
+ 13,
+ 11,
+ 14,
+ 10,
+ 14,
+ 10,
+ 14,
+ 10,
+ 14,
+ 11,
+ 13,
+ 13,
+ 14,
+ 12,
+ 14,
+ 12,
+ 12,
+ 16,
+ 15,
+ 11,
+ 15,
+ 11,
+ 15,
+ 11,
+ 15,
+ 12,
+ 15,
+ 12,
+ 15,
+ 12,
+ 15,
+ 13,
+ 13,
+ 17,
+ 15,
+ 14,
+ 15,
+ 14,
+ 16,
+ 12,
+ 16,
+ 12,
+ 16,
+ 13,
+ 16,
+ 13,
+ 16,
+ 14,
+ 16,
+ 14,
+ 16,
+ 14,
+ 15,
+ 17,
+ 17,
+ 13,
+ 17,
+ 13,
+ 17,
+ 14,
+ 16,
+ 16,
+ 17,
+ 15,
+ 17,
+ 15,
+ 17,
+ 15,
+ 18,
+ 14,
+ 18,
+ 14,
+ 18,
+ 14,
+ 18,
+ 15,
+ 17,
+ 17,
+ 18,
+ 16,
+ 18,
+ 16,
+ 16,
+ 20,
+ 19,
+ 15,
+ 19,
+ 15,
+ 19,
+ 15,
+ 19,
+ 16,
+ 19,
+ 16,
+ 19,
+ 16,
+ 19,
+ 17,
+ 17,
+ 21,
+ 19,
+ 18,
+ 19,
+ 18,
+ 20,
+ 16,
+ 20,
+ 16,
+ 20,
+ 17,
+ 20,
+ 17,
+ 20,
+ 18,
+ 20,
+ 18,
+ 20,
+ 18,
+ 19,
+ 21,
+ 21,
+ 17,
+ 21,
+ 17,
+ 21,
+ 18,
+ 20,
+ 20,
+ 21,
+ 19,
+ 21,
+ 19,
+ 21,
+ 19,
+ 22,
+ 18,
+ 22,
+ 18,
+ 22,
+ 18,
+ 22,
+ 19,
+ 21,
+ 21,
+ 22,
+ 20,
+ 22,
+ 20,
+ 20,
+ 24,
+ 23,
+ 19,
+ 23,
+ 19,
+ 23,
+ 19,
+ 23,
+ 20,
+ 23,
+ 20,
+ 23,
+ 20,
+ 23,
+ 21,
+ 21,
+ 25,
+ 23,
+ 22,
+ 23,
+ 22,
+ 24,
+ 20,
+ 24,
+ 20,
+ 24,
+ 21,
+ 24,
+ 21,
+ 24,
+ 22,
+ 24,
+ 22,
+ 24,
+ 22,
+ 23,
+ 25,
+ 25,
+ 21,
+ 25,
+ 21,
+ 25,
+ 22,
+ 24,
+ 24,
+ 25,
+ 23,
+ 25,
+ 23,
+ 25,
+ 23,
+ 26,
+ 22,
+ 26,
+ 22,
+ 26,
+ 22,
+ 26,
+ 23,
+ 25,
+ 25,
+ 26,
+ 24,
+ 26,
+ 24,
+ 24,
+ 28,
+ 27,
+ 23,
+ 27,
+ 23,
+ 27,
+ 23,
+ 27,
+ 24,
+ 27,
+ 24,
+ 27,
+ 24,
+ 27,
+ 25,
+ 25,
+ 29,
+ 27,
+ 26,
+ 27,
+ 26,
+ 28,
+ 24,
+ 28,
+ 24,
+ 28,
+ 25,
+ 28,
+ 25,
+ 28,
+ 26,
+ 28,
+ 26,
+ 28,
+ 26,
+ 27,
+ 29,
+ 29,
+ 25,
+ 29,
+ 25,
+ 29,
+ 26,
+ 28,
+ 28,
+ 29,
+ 27,
+ 29,
+ 27,
+ 29,
+ 27,
+ 30,
+ 26,
+ 30,
+ 26,
+ 30,
+ 26,
+ 30,
+ 27,
+ 29,
+ 29,
+ 30,
+ 28,
+ 30,
+ 28,
+ 30,
+ 28,
+ 31,
+ 27,
+ 31,
+ 27,
+ 31,
+ 27,
+ 31,
+ 28,
+ 31,
+ 28,
+ 31,
+ 28,
+ 31,
+ 29,
+ 31,
+ 29,
+ 31,
+ 30,
+ 31,
+ 30,
+ 31,
+ 30,
+ 31,
+ 31,
+ 31,
+ 31,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 1,
+ 0,
+ 1,
+ 1,
+ 2,
+ 0,
+ 2,
+ 1,
+ 3,
+ 0,
+ 3,
+ 0,
+ 3,
+ 1,
+ 4,
+ 0,
+ 4,
+ 0,
+ 4,
+ 1,
+ 5,
+ 0,
+ 5,
+ 1,
+ 6,
+ 0,
+ 6,
+ 0,
+ 6,
+ 1,
+ 7,
+ 0,
+ 7,
+ 0,
+ 7,
+ 1,
+ 8,
+ 0,
+ 8,
+ 1,
+ 8,
+ 1,
+ 8,
+ 2,
+ 9,
+ 1,
+ 9,
+ 2,
+ 9,
+ 2,
+ 9,
+ 3,
+ 10,
+ 2,
+ 10,
+ 3,
+ 10,
+ 3,
+ 10,
+ 4,
+ 11,
+ 3,
+ 11,
+ 4,
+ 11,
+ 4,
+ 11,
+ 5,
+ 12,
+ 4,
+ 12,
+ 5,
+ 12,
+ 5,
+ 12,
+ 6,
+ 13,
+ 5,
+ 13,
+ 6,
+ 8,
+ 16,
+ 13,
+ 7,
+ 14,
+ 6,
+ 14,
+ 7,
+ 9,
+ 17,
+ 14,
+ 8,
+ 15,
+ 7,
+ 15,
+ 8,
+ 11,
+ 16,
+ 15,
+ 9,
+ 15,
+ 10,
+ 16,
+ 8,
+ 16,
+ 9,
+ 16,
+ 10,
+ 15,
+ 13,
+ 17,
+ 9,
+ 17,
+ 10,
+ 17,
+ 11,
+ 15,
+ 16,
+ 18,
+ 10,
+ 18,
+ 11,
+ 18,
+ 12,
+ 16,
+ 16,
+ 19,
+ 11,
+ 19,
+ 12,
+ 19,
+ 13,
+ 17,
+ 17,
+ 20,
+ 12,
+ 20,
+ 13,
+ 20,
+ 14,
+ 19,
+ 16,
+ 21,
+ 13,
+ 21,
+ 14,
+ 21,
+ 15,
+ 20,
+ 17,
+ 22,
+ 14,
+ 22,
+ 15,
+ 25,
+ 10,
+ 22,
+ 16,
+ 23,
+ 15,
+ 23,
+ 16,
+ 26,
+ 11,
+ 23,
+ 17,
+ 24,
+ 16,
+ 24,
+ 17,
+ 27,
+ 12,
+ 24,
+ 18,
+ 25,
+ 17,
+ 25,
+ 18,
+ 28,
+ 13,
+ 25,
+ 19,
+ 26,
+ 18,
+ 26,
+ 19,
+ 29,
+ 14,
+ 26,
+ 20,
+ 27,
+ 19,
+ 27,
+ 20,
+ 30,
+ 15,
+ 27,
+ 21,
+ 28,
+ 20,
+ 28,
+ 21,
+ 28,
+ 21,
+ 28,
+ 22,
+ 29,
+ 21,
+ 29,
+ 22,
+ 24,
+ 32,
+ 29,
+ 23,
+ 30,
+ 22,
+ 30,
+ 23,
+ 25,
+ 33,
+ 30,
+ 24,
+ 31,
+ 23,
+ 31,
+ 24,
+ 27,
+ 32,
+ 31,
+ 25,
+ 31,
+ 26,
+ 32,
+ 24,
+ 32,
+ 25,
+ 32,
+ 26,
+ 31,
+ 29,
+ 33,
+ 25,
+ 33,
+ 26,
+ 33,
+ 27,
+ 31,
+ 32,
+ 34,
+ 26,
+ 34,
+ 27,
+ 34,
+ 28,
+ 32,
+ 32,
+ 35,
+ 27,
+ 35,
+ 28,
+ 35,
+ 29,
+ 33,
+ 33,
+ 36,
+ 28,
+ 36,
+ 29,
+ 36,
+ 30,
+ 35,
+ 32,
+ 37,
+ 29,
+ 37,
+ 30,
+ 37,
+ 31,
+ 36,
+ 33,
+ 38,
+ 30,
+ 38,
+ 31,
+ 41,
+ 26,
+ 38,
+ 32,
+ 39,
+ 31,
+ 39,
+ 32,
+ 42,
+ 27,
+ 39,
+ 33,
+ 40,
+ 32,
+ 40,
+ 33,
+ 43,
+ 28,
+ 40,
+ 34,
+ 41,
+ 33,
+ 41,
+ 34,
+ 44,
+ 29,
+ 41,
+ 35,
+ 42,
+ 34,
+ 42,
+ 35,
+ 45,
+ 30,
+ 42,
+ 36,
+ 43,
+ 35,
+ 43,
+ 36,
+ 46,
+ 31,
+ 43,
+ 37,
+ 44,
+ 36,
+ 44,
+ 37,
+ 44,
+ 37,
+ 44,
+ 38,
+ 45,
+ 37,
+ 45,
+ 38,
+ 40,
+ 48,
+ 45,
+ 39,
+ 46,
+ 38,
+ 46,
+ 39,
+ 41,
+ 49,
+ 46,
+ 40,
+ 47,
+ 39,
+ 47,
+ 40,
+ 43,
+ 48,
+ 47,
+ 41,
+ 47,
+ 42,
+ 48,
+ 40,
+ 48,
+ 41,
+ 48,
+ 42,
+ 47,
+ 45,
+ 49,
+ 41,
+ 49,
+ 42,
+ 49,
+ 43,
+ 47,
+ 48,
+ 50,
+ 42,
+ 50,
+ 43,
+ 50,
+ 44,
+ 48,
+ 48,
+ 51,
+ 43,
+ 51,
+ 44,
+ 51,
+ 45,
+ 49,
+ 49,
+ 52,
+ 44,
+ 52,
+ 45,
+ 52,
+ 46,
+ 51,
+ 48,
+ 53,
+ 45,
+ 53,
+ 46,
+ 53,
+ 47,
+ 52,
+ 49,
+ 54,
+ 46,
+ 54,
+ 47,
+ 57,
+ 42,
+ 54,
+ 48,
+ 55,
+ 47,
+ 55,
+ 48,
+ 58,
+ 43,
+ 55,
+ 49,
+ 56,
+ 48,
+ 56,
+ 49,
+ 59,
+ 44,
+ 56,
+ 50,
+ 57,
+ 49,
+ 57,
+ 50,
+ 60,
+ 45,
+ 57,
+ 51,
+ 58,
+ 50,
+ 58,
+ 51,
+ 61,
+ 46,
+ 58,
+ 52,
+ 59,
+ 51,
+ 59,
+ 52,
+ 62,
+ 47,
+ 59,
+ 53,
+ 60,
+ 52,
+ 60,
+ 53,
+ 60,
+ 53,
+ 60,
+ 54,
+ 61,
+ 53,
+ 61,
+ 54,
+ 61,
+ 54,
+ 61,
+ 55,
+ 62,
+ 54,
+ 62,
+ 55,
+ 62,
+ 55,
+ 62,
+ 56,
+ 63,
+ 55,
+ 63,
+ 56,
+ 63,
+ 56,
+ 63,
+ 57,
+ 63,
+ 58,
+ 63,
+ 59,
+ 63,
+ 59,
+ 63,
+ 60,
+ 63,
+ 61,
+ 63,
+ 62,
+ 63,
+ 62,
+ 63,
+ 63,
+};
+
+#endif // BETSY_BC1_H
diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp
index bc72203b2f..7b4d8b3dfb 100644
--- a/modules/betsy/image_compress_betsy.cpp
+++ b/modules/betsy/image_compress_betsy.cpp
@@ -30,39 +30,17 @@
#include "image_compress_betsy.h"
-#include "servers/rendering/rendering_device_binds.h"
-#include "servers/rendering/rendering_server_default.h"
+#include "core/config/project_settings.h"
-#if defined(VULKAN_ENABLED)
-#include "drivers/vulkan/rendering_context_driver_vulkan.h"
-#endif
-#if defined(METAL_ENABLED)
-#include "drivers/metal/rendering_context_driver_metal.h"
-#endif
+#include "betsy_bc1.h"
+#include "bc1.glsl.gen.h"
#include "bc6h.glsl.gen.h"
-struct BC6PushConstant {
- float sizeX;
- float sizeY;
- uint32_t padding[2];
-};
-
-static int get_next_multiple(int n, int m) {
- return n + (m - (n % m));
-}
-
-Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
- uint64_t start_time = OS::get_singleton()->get_ticks_msec();
-
- if (r_img->is_compressed()) {
- return ERR_INVALID_DATA;
- }
-
- ERR_FAIL_COND_V_MSG(r_img->get_format() < Image::FORMAT_RF || r_img->get_format() > Image::FORMAT_RGBE9995, ERR_INVALID_DATA, "Image is not an HDR image.");
-
- Error err = OK;
+static Mutex betsy_mutex;
+static BetsyCompressor *betsy = nullptr;
+void BetsyCompressor::_init() {
// Create local RD.
RenderingContextDriver *rcd = nullptr;
RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device();
@@ -81,7 +59,7 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
#endif
#endif
if (rcd != nullptr && rd != nullptr) {
- err = rcd->initialize();
+ Error err = rcd->initialize();
if (err == OK) {
err = rd->initialize(rcd);
}
@@ -95,58 +73,201 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
}
}
- ERR_FAIL_NULL_V_MSG(rd, err, "Unable to create a local RenderingDevice.");
+ ERR_FAIL_NULL_MSG(rd, "Unable to create a local RenderingDevice.");
+
+ compress_rd = rd;
+ compress_rcd = rcd;
+
+ // Create the sampler state.
+ RD::SamplerState src_sampler_state;
+ {
+ src_sampler_state.repeat_u = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;
+ src_sampler_state.repeat_v = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;
+ src_sampler_state.mag_filter = RD::SAMPLER_FILTER_NEAREST;
+ src_sampler_state.min_filter = RD::SAMPLER_FILTER_NEAREST;
+ src_sampler_state.mip_filter = RD::SAMPLER_FILTER_NEAREST;
+ }
+
+ src_sampler = compress_rd->sampler_create(src_sampler_state);
+}
+
+void BetsyCompressor::init() {
+ WorkerThreadPool::TaskID tid = WorkerThreadPool::get_singleton()->add_task(callable_mp(this, &BetsyCompressor::_thread_loop), true);
+ command_queue.set_pump_task_id(tid);
+ command_queue.push(this, &BetsyCompressor::_assign_mt_ids, tid);
+ command_queue.push_and_sync(this, &BetsyCompressor::_init);
+ DEV_ASSERT(task_id == tid);
+}
+
+void BetsyCompressor::_assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id) {
+ task_id = p_pump_task_id;
+}
+
+// Yield thread to WTP so other tasks can be done on it.
+// Automatically regains control as soon a task is pushed to the command queue.
+void BetsyCompressor::_thread_loop() {
+ while (!exit) {
+ WorkerThreadPool::get_singleton()->yield();
+ command_queue.flush_all();
+ }
+}
+
+void BetsyCompressor::_thread_exit() {
+ exit = true;
+
+ if (compress_rd != nullptr) {
+ if (dxt1_encoding_table_buffer.is_valid()) {
+ compress_rd->free(dxt1_encoding_table_buffer);
+ }
+
+ compress_rd->free(src_sampler);
+
+ // Clear the shader cache, pipelines will be unreferenced automatically.
+ for (KeyValue<String, BetsyShader> &E : cached_shaders) {
+ if (E.value.compiled.is_valid()) {
+ compress_rd->free(E.value.compiled);
+ }
+ }
+ cached_shaders.clear();
+ }
+}
+
+void BetsyCompressor::finish() {
+ command_queue.push(this, &BetsyCompressor::_thread_exit);
+ if (task_id != WorkerThreadPool::INVALID_TASK_ID) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(task_id);
+ task_id = WorkerThreadPool::INVALID_TASK_ID;
+ }
+
+ if (compress_rd != nullptr) {
+ // Free the RD (and RCD if necessary).
+ memdelete(compress_rd);
+ compress_rd = nullptr;
+ if (compress_rcd != nullptr) {
+ memdelete(compress_rcd);
+ compress_rcd = nullptr;
+ }
+ }
+}
+
+// Helper functions.
+
+static int get_next_multiple(int n, int m) {
+ return n + (m - (n % m));
+}
+
+static String get_shader_name(BetsyFormat p_format) {
+ switch (p_format) {
+ case BETSY_FORMAT_BC1:
+ case BETSY_FORMAT_BC1_DITHER:
+ return "BC1";
+
+ case BETSY_FORMAT_BC3:
+ return "BC3";
+
+ case BETSY_FORMAT_BC6_SIGNED:
+ case BETSY_FORMAT_BC6_UNSIGNED:
+ return "BC6";
+
+ default:
+ return "";
+ }
+}
+
+Error BetsyCompressor::_compress(BetsyFormat p_format, Image *r_img) {
+ uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+
+ if (r_img->is_compressed()) {
+ return ERR_INVALID_DATA;
+ }
- Ref<RDShaderFile> compute_shader;
- compute_shader.instantiate();
+ Error err = OK;
// Destination format.
Image::Format dest_format = Image::FORMAT_MAX;
+ RD::DataFormat dst_rd_format = RD::DATA_FORMAT_MAX;
String version = "";
switch (p_format) {
- case BETSY_FORMAT_BC6: {
- err = compute_shader->parse_versions_from_text(bc6h_shader_glsl);
-
- if (r_img->detect_signed(true)) {
- dest_format = Image::FORMAT_BPTC_RGBF;
- version = "signed";
- } else {
- dest_format = Image::FORMAT_BPTC_RGBFU;
- version = "unsigned";
- }
+ case BETSY_FORMAT_BC1:
+ version = "standard";
+ dst_rd_format = RD::DATA_FORMAT_R32G32_UINT;
+ dest_format = Image::FORMAT_DXT1;
+ break;
+
+ case BETSY_FORMAT_BC1_DITHER:
+ version = "dithered";
+ dst_rd_format = RD::DATA_FORMAT_R32G32_UINT;
+ dest_format = Image::FORMAT_DXT1;
+ break;
- } break;
+ case BETSY_FORMAT_BC6_SIGNED:
+ version = "signed";
+ dst_rd_format = RD::DATA_FORMAT_R32G32B32A32_UINT;
+ dest_format = Image::FORMAT_BPTC_RGBF;
+ break;
+
+ case BETSY_FORMAT_BC6_UNSIGNED:
+ version = "unsigned";
+ dst_rd_format = RD::DATA_FORMAT_R32G32B32A32_UINT;
+ dest_format = Image::FORMAT_BPTC_RGBFU;
+ break;
default:
err = ERR_INVALID_PARAMETER;
break;
}
- if (err != OK) {
- compute_shader->print_errors("Betsy compress shader");
- memdelete(rd);
- if (rcd != nullptr) {
- memdelete(rcd);
+ const String shader_name = get_shader_name(p_format) + "-" + version;
+ BetsyShader shader;
+
+ if (cached_shaders.has(shader_name)) {
+ shader = cached_shaders[shader_name];
+
+ } else {
+ Ref<RDShaderFile> source;
+ source.instantiate();
+
+ switch (p_format) {
+ case BETSY_FORMAT_BC1:
+ case BETSY_FORMAT_BC1_DITHER:
+ err = source->parse_versions_from_text(bc1_shader_glsl);
+ break;
+
+ case BETSY_FORMAT_BC6_UNSIGNED:
+ case BETSY_FORMAT_BC6_SIGNED:
+ err = source->parse_versions_from_text(bc6h_shader_glsl);
+ break;
+
+ default:
+ err = ERR_INVALID_PARAMETER;
+ break;
}
- return err;
- }
+ if (err != OK) {
+ source->print_errors("Betsy compress shader");
+ return err;
+ }
- // Compile the shader, return early if invalid.
- RID shader = rd->shader_create_from_spirv(compute_shader->get_spirv_stages(version));
+ // Compile the shader, return early if invalid.
+ shader.compiled = compress_rd->shader_create_from_spirv(source->get_spirv_stages(version));
+ if (shader.compiled.is_null()) {
+ return ERR_CANT_CREATE;
+ }
- if (shader.is_null()) {
- memdelete(rd);
- if (rcd != nullptr) {
- memdelete(rcd);
+ // Compile the pipeline, return early if invalid.
+ shader.pipeline = compress_rd->compute_pipeline_create(shader.compiled);
+ if (shader.pipeline.is_null()) {
+ return ERR_CANT_CREATE;
}
- return err;
+ cached_shaders[shader_name] = shader;
}
- RID pipeline = rd->compute_pipeline_create(shader);
+ if (shader.compiled.is_null() || shader.pipeline.is_null()) {
+ return ERR_INVALID_DATA;
+ }
// src_texture format information.
RD::TextureFormat src_texture_format;
@@ -159,6 +280,33 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
}
switch (r_img->get_format()) {
+ case Image::FORMAT_L8:
+ r_img->convert(Image::FORMAT_RGBA8);
+ src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+ break;
+
+ case Image::FORMAT_LA8:
+ r_img->convert(Image::FORMAT_RGBA8);
+ src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+ break;
+
+ case Image::FORMAT_R8:
+ src_texture_format.format = RD::DATA_FORMAT_R8_UNORM;
+ break;
+
+ case Image::FORMAT_RG8:
+ src_texture_format.format = RD::DATA_FORMAT_R8G8_UNORM;
+ break;
+
+ case Image::FORMAT_RGB8:
+ r_img->convert(Image::FORMAT_RGBA8);
+ src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+ break;
+
+ case Image::FORMAT_RGBA8:
+ src_texture_format.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+ break;
+
case Image::FORMAT_RH:
src_texture_format.format = RD::DATA_FORMAT_R16_SFLOAT;
break;
@@ -198,33 +346,23 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
break;
default: {
- rd->free(shader);
-
- memdelete(rd);
- if (rcd != nullptr) {
- memdelete(rcd);
- }
-
return err;
}
}
- // Create the sampler state.
- RD::SamplerState src_sampler_state;
- {
- src_sampler_state.repeat_u = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;
- src_sampler_state.repeat_v = RD::SAMPLER_REPEAT_MODE_CLAMP_TO_EDGE;
- src_sampler_state.mag_filter = RD::SAMPLER_FILTER_NEAREST;
- src_sampler_state.min_filter = RD::SAMPLER_FILTER_NEAREST;
- src_sampler_state.mip_filter = RD::SAMPLER_FILTER_NEAREST;
- }
-
- RID src_sampler = rd->sampler_create(src_sampler_state);
-
// For the destination format just copy the source format and change the usage bits.
RD::TextureFormat dst_texture_format = src_texture_format;
dst_texture_format.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT;
- dst_texture_format.format = RD::DATA_FORMAT_R32G32B32A32_UINT;
+ dst_texture_format.format = dst_rd_format;
+
+ // Encoding table setup.
+ if (dest_format == Image::FORMAT_DXT1 && dxt1_encoding_table_buffer.is_null()) {
+ Vector<uint8_t> data;
+ data.resize(1024 * 4);
+ memcpy(data.ptrw(), dxt1_encoding_table, 1024 * 4);
+
+ dxt1_encoding_table_buffer = compress_rd->storage_buffer_create(1024 * 4, data);
+ }
const int mip_count = r_img->get_mipmap_count() + 1;
@@ -256,8 +394,41 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
memcpy(src_image_ptr[0].ptrw(), r_img->ptr() + ofs, size);
// Create the textures on the GPU.
- RID src_texture = rd->texture_create(src_texture_format, RD::TextureView(), src_images);
- RID dst_texture = rd->texture_create(dst_texture_format, RD::TextureView());
+ RID src_texture = compress_rd->texture_create(src_texture_format, RD::TextureView(), src_images);
+ RID dst_texture = compress_rd->texture_create(dst_texture_format, RD::TextureView());
+
+ Vector<RD::Uniform> uniforms;
+ {
+ {
+ RD::Uniform u;
+ u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
+ u.binding = 0;
+ u.append_id(src_sampler);
+ u.append_id(src_texture);
+ uniforms.push_back(u);
+ }
+ {
+ RD::Uniform u;
+ u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
+ u.binding = 1;
+ u.append_id(dst_texture);
+ uniforms.push_back(u);
+ }
+
+ if (dest_format == Image::FORMAT_DXT1) {
+ RD::Uniform u;
+ u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER;
+ u.binding = 2;
+ u.append_id(dxt1_encoding_table_buffer);
+ uniforms.push_back(u);
+ }
+ }
+
+ RID uniform_set = compress_rd->uniform_set_create(uniforms, shader.compiled, 0);
+ RD::ComputeListID compute_list = compress_rd->compute_list_begin();
+
+ compress_rd->compute_list_bind_compute_pipeline(compute_list, shader.pipeline);
+ compress_rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
if (dest_format == Image::FORMAT_BPTC_RGBFU || dest_format == Image::FORMAT_BPTC_RGBF) {
BC6PushConstant push_constant;
@@ -266,47 +437,33 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
push_constant.padding[0] = 0;
push_constant.padding[1] = 0;
- Vector<RD::Uniform> uniforms;
- {
- {
- RD::Uniform u;
- u.uniform_type = RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE;
- u.binding = 0;
- u.append_id(src_sampler);
- u.append_id(src_texture);
- uniforms.push_back(u);
- }
- {
- RD::Uniform u;
- u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
- u.binding = 1;
- u.append_id(dst_texture);
- uniforms.push_back(u);
- }
- }
+ compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant));
- RID uniform_set = rd->uniform_set_create(uniforms, shader, 0);
- RD::ComputeListID compute_list = rd->compute_list_begin();
+ } else {
+ BC1PushConstant push_constant;
+ push_constant.num_refines = 2;
+ push_constant.padding[0] = 0;
+ push_constant.padding[1] = 0;
+ push_constant.padding[2] = 0;
- rd->compute_list_bind_compute_pipeline(compute_list, pipeline);
- rd->compute_list_bind_uniform_set(compute_list, uniform_set, 0);
- rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC6PushConstant));
- rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
- rd->compute_list_end();
+ compress_rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(BC1PushConstant));
}
- rd->submit();
- rd->sync();
+ compress_rd->compute_list_dispatch(compute_list, get_next_multiple(width, 32) / 32, get_next_multiple(height, 32) / 32, 1);
+ compress_rd->compute_list_end();
+
+ compress_rd->submit();
+ compress_rd->sync();
// Copy data from the GPU to the buffer.
- const Vector<uint8_t> texture_data = rd->texture_get_data(dst_texture, 0);
+ const Vector<uint8_t> texture_data = compress_rd->texture_get_data(dst_texture, 0);
int64_t dst_ofs = Image::get_image_mipmap_offset(r_img->get_width(), r_img->get_height(), dest_format, i);
memcpy(dst_data_ptr + dst_ofs, texture_data.ptr(), texture_data.size());
// Free the source and dest texture.
- rd->free(dst_texture);
- rd->free(src_texture);
+ compress_rd->free(dst_texture);
+ compress_rd->free(src_texture);
}
src_images.clear();
@@ -314,26 +471,67 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) {
// Set the compressed data to the image.
r_img->set_data(r_img->get_width(), r_img->get_height(), r_img->has_mipmaps(), dest_format, dst_data);
- // Free the shader (dependencies will be cleared automatically).
- rd->free(src_sampler);
- rd->free(shader);
-
- memdelete(rd);
- if (rcd != nullptr) {
- memdelete(rcd);
- }
-
print_verbose(vformat("Betsy: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time));
return OK;
}
+void ensure_betsy_exists() {
+ betsy_mutex.lock();
+ if (betsy == nullptr) {
+ betsy = memnew(BetsyCompressor);
+ betsy->init();
+ }
+ betsy_mutex.unlock();
+}
+
Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels) {
+ ensure_betsy_exists();
Image::Format format = r_img->get_format();
+ Error result = ERR_UNAVAILABLE;
if (format >= Image::FORMAT_RF && format <= Image::FORMAT_RGBE9995) {
- return _compress_betsy(BETSY_FORMAT_BC6, r_img);
+ if (r_img->detect_signed()) {
+ result = betsy->compress(BETSY_FORMAT_BC6_SIGNED, r_img);
+ } else {
+ result = betsy->compress(BETSY_FORMAT_BC6_UNSIGNED, r_img);
+ }
+ }
+
+ if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) {
+ free_device();
+ }
+
+ return result;
+}
+
+Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels) {
+ ensure_betsy_exists();
+ Error result = ERR_UNAVAILABLE;
+
+ switch (p_channels) {
+ case Image::USED_CHANNELS_RGB:
+ result = betsy->compress(BETSY_FORMAT_BC1_DITHER, r_img);
+ break;
+
+ case Image::USED_CHANNELS_L:
+ result = betsy->compress(BETSY_FORMAT_BC1, r_img);
+ break;
+
+ default:
+ break;
}
- return ERR_UNAVAILABLE;
+ if (!GLOBAL_GET("rendering/textures/vram_compression/cache_gpu_compressor")) {
+ free_device();
+ }
+
+ return result;
+}
+
+void free_device() {
+ if (betsy != nullptr) {
+ betsy->finish();
+ memdelete(betsy);
+ }
}
diff --git a/modules/betsy/image_compress_betsy.h b/modules/betsy/image_compress_betsy.h
index a64e586c76..70e4ae85ed 100644
--- a/modules/betsy/image_compress_betsy.h
+++ b/modules/betsy/image_compress_betsy.h
@@ -32,13 +32,79 @@
#define IMAGE_COMPRESS_BETSY_H
#include "core/io/image.h"
+#include "core/object/worker_thread_pool.h"
+#include "core/os/thread.h"
+#include "core/templates/command_queue_mt.h"
+
+#include "servers/rendering/rendering_device_binds.h"
+#include "servers/rendering/rendering_server_default.h"
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_context_driver_vulkan.h"
+#endif
+#if defined(METAL_ENABLED)
+#include "drivers/metal/rendering_context_driver_metal.h"
+#endif
enum BetsyFormat {
- BETSY_FORMAT_BC6,
+ BETSY_FORMAT_BC1,
+ BETSY_FORMAT_BC1_DITHER,
+ BETSY_FORMAT_BC3,
+ BETSY_FORMAT_BC6_SIGNED,
+ BETSY_FORMAT_BC6_UNSIGNED,
+};
+
+struct BC6PushConstant {
+ float sizeX;
+ float sizeY;
+ uint32_t padding[2];
};
-Error _compress_betsy(BetsyFormat p_format, Image *r_img);
+struct BC1PushConstant {
+ uint32_t num_refines;
+ uint32_t padding[3];
+};
+
+void free_device();
Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels);
+Error _betsy_compress_s3tc(Image *r_img, Image::UsedChannels p_channels);
+
+class BetsyCompressor : public Object {
+ mutable CommandQueueMT command_queue;
+ bool exit = false;
+ WorkerThreadPool::TaskID task_id = WorkerThreadPool::INVALID_TASK_ID;
+
+ struct BetsyShader {
+ RID compiled;
+ RID pipeline;
+ };
+
+ // Resources shared by all compression formats.
+ RenderingDevice *compress_rd = nullptr;
+ RenderingContextDriver *compress_rcd = nullptr;
+ HashMap<String, BetsyShader> cached_shaders;
+ RID src_sampler = RID();
+
+ // Format-specific resources.
+ RID dxt1_encoding_table_buffer = RID();
+
+ void _init();
+ void _assign_mt_ids(WorkerThreadPool::TaskID p_pump_task_id);
+ void _thread_loop();
+ void _thread_exit();
+
+ Error _compress(BetsyFormat p_format, Image *r_img);
+
+public:
+ void init();
+ void finish();
+
+ Error compress(BetsyFormat p_format, Image *r_img) {
+ Error err;
+ command_queue.push_and_ret(this, &BetsyCompressor::_compress, p_format, r_img, &err);
+ return err;
+ }
+};
#endif // IMAGE_COMPRESS_BETSY_H
diff --git a/modules/betsy/register_types.cpp b/modules/betsy/register_types.cpp
index 019099e67c..a3a3b5a99b 100644
--- a/modules/betsy/register_types.cpp
+++ b/modules/betsy/register_types.cpp
@@ -38,10 +38,13 @@ void initialize_betsy_module(ModuleInitializationLevel p_level) {
}
Image::_image_compress_bptc_rd_func = _betsy_compress_bptc;
+ Image::_image_compress_bc_rd_func = _betsy_compress_s3tc;
}
void uninitialize_betsy_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
+
+ free_device();
}
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 8777651545..8c81c0ce4e 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -513,7 +513,7 @@ Ref<ConcavePolygonShape3D> CSGShape3D::bake_collision_shape() {
}
bool CSGShape3D::_is_debug_collision_shape_visible() {
- return is_inside_tree() && (get_tree()->is_debugging_collisions_hint() || Engine::get_singleton()->is_editor_hint());
+ return !Engine::get_singleton()->is_editor_hint() && is_inside_tree() && get_tree()->is_debugging_collisions_hint();
}
void CSGShape3D::_update_debug_collision_shape() {
@@ -604,11 +604,6 @@ void CSGShape3D::_notification(int p_what) {
// Update this node's parent only if its own visibility has changed, not the visibility of parent nodes
parent_shape->_make_dirty();
}
- if (is_visible()) {
- _update_debug_collision_shape();
- } else {
- _clear_debug_collision_shape();
- }
last_visible = is_visible();
} break;
diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp
index 72676f4a40..95ffeed6c3 100644
--- a/modules/csg/editor/csg_gizmos.cpp
+++ b/modules/csg/editor/csg_gizmos.cpp
@@ -173,7 +173,7 @@ CSGShapeEditor::CSGShapeEditor() {
CSGShape3DGizmoPlugin::CSGShape3DGizmoPlugin() {
helper.instantiate();
- Color gizmo_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/csg", Color(0.0, 0.4, 1, 0.15));
+ Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/csg");
create_material("shape_union_material", gizmo_color);
create_material("shape_union_solid_material", gizmo_color);
gizmo_color.invert();
@@ -541,7 +541,7 @@ EditorPluginCSG::EditorPluginCSG() {
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
csg_shape_editor = memnew(CSGShapeEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(csg_shape_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(csg_shape_editor);
}
#endif // TOOLS_ENABLED
diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp
index 8d4d0234da..ce097092fb 100644
--- a/modules/fbx/fbx_document.cpp
+++ b/modules/fbx/fbx_document.cpp
@@ -288,14 +288,8 @@ String FBXDocument::_gen_unique_name(HashSet<String> &unique_names, const String
}
String FBXDocument::_sanitize_animation_name(const String &p_name) {
- // Animations disallow the normal node invalid characters as well as "," and "["
- // (See animation/animation_player.cpp::add_animation)
-
- // TODO: Consider adding invalid_characters or a validate_animation_name to animation_player to mirror Node.
String anim_name = p_name.validate_node_name();
- anim_name = anim_name.replace(",", "");
- anim_name = anim_name.replace("[", "");
- return anim_name;
+ return AnimationLibrary::validate_library_name(anim_name);
}
String FBXDocument::_gen_unique_animation_name(Ref<FBXState> p_state, const String &p_name) {
diff --git a/modules/gdscript/.editorconfig b/modules/gdscript/.editorconfig
index 640c205093..b380846f86 100644
--- a/modules/gdscript/.editorconfig
+++ b/modules/gdscript/.editorconfig
@@ -1,8 +1,3 @@
[*.gd]
-indent_style = tab
indent_size = 4
-insert_final_newline = true
trim_trailing_whitespace = true
-
-[*.out]
-insert_final_newline = true
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index eecce70202..d765cfa1ea 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -852,6 +852,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
comment_marker_colors[COMMENT_MARKER_NOTICE] = Color(0.24, 0.54, 0.09);
}
+ // TODO: Move to editor_settings.cpp
EDITOR_DEF("text_editor/theme/highlighting/gdscript/function_definition_color", function_definition_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/global_function_color", global_function_color);
EDITOR_DEF("text_editor/theme/highlighting/gdscript/node_path_color", node_path_color);
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 276a12f5de..7b9aa70686 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -880,6 +880,11 @@ Error GDScript::reload(bool p_keep_state) {
if (can_run && p_keep_state) {
_restore_old_static_data();
}
+
+ if (p_keep_state) {
+ // Update the properties in the inspector.
+ update_exports();
+ }
#endif
reloading = false;
@@ -906,7 +911,7 @@ void GDScript::get_members(HashSet<StringName> *p_members) {
}
}
-const Variant GDScript::get_rpc_config() const {
+Variant GDScript::get_rpc_config() const {
return rpc_config;
}
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 4d21651365..9bb39aac0f 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -334,7 +334,7 @@ public:
virtual void get_constants(HashMap<StringName, Variant> *p_constants) override;
virtual void get_members(HashSet<StringName> *p_members) override;
- virtual const Variant get_rpc_config() const override;
+ virtual Variant get_rpc_config() const override;
void unload_static() const;
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 524f528f76..cf1cd55355 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3486,10 +3486,10 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
opt = opt.substr(1);
}
- // The path needs quotes if at least one of its components (excluding `/` separations)
+ // The path needs quotes if at least one of its components (excluding `%` prefix and `/` separations)
// is not a valid identifier.
bool path_needs_quote = false;
- for (const String &part : opt.split("/")) {
+ for (const String &part : opt.trim_prefix("%").split("/")) {
if (!part.is_valid_ascii_identifier()) {
path_needs_quote = true;
break;
diff --git a/modules/gdscript/gdscript_tokenizer_buffer.cpp b/modules/gdscript/gdscript_tokenizer_buffer.cpp
index e53bc5bc41..2046480f0e 100644
--- a/modules/gdscript/gdscript_tokenizer_buffer.cpp
+++ b/modules/gdscript/gdscript_tokenizer_buffer.cpp
@@ -296,6 +296,7 @@ Vector<uint8_t> GDScriptTokenizerBuffer::parse_code_string(const String &p_code,
encode_uint32(identifier_map.size(), &contents.write[0]);
encode_uint32(constant_map.size(), &contents.write[4]);
encode_uint32(token_lines.size(), &contents.write[8]);
+ encode_uint32(0, &contents.write[12]); // Unused, kept for compatibility. Please remove at next `TOKENIZER_VERSION` increment.
encode_uint32(token_counter, &contents.write[16]);
int buf_pos = 20;
diff --git a/modules/gdscript/language_server/gdscript_language_server.cpp b/modules/gdscript/language_server/gdscript_language_server.cpp
index 3df26ea576..731988148d 100644
--- a/modules/gdscript/language_server/gdscript_language_server.cpp
+++ b/modules/gdscript/language_server/gdscript_language_server.cpp
@@ -39,6 +39,7 @@
int GDScriptLanguageServer::port_override = -1;
GDScriptLanguageServer::GDScriptLanguageServer() {
+ // TODO: Move to editor_settings.cpp
_EDITOR_DEF("network/language_server/remote_host", host);
_EDITOR_DEF("network/language_server/remote_port", port);
_EDITOR_DEF("network/language_server/enable_smart_resolve", true);
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index ad35513bc8..06e9775360 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -472,8 +472,6 @@ GDScriptTextDocument::GDScriptTextDocument() {
void GDScriptTextDocument::sync_script_content(const String &p_path, const String &p_content) {
String path = GDScriptLanguageProtocol::get_singleton()->get_workspace()->get_file_path(p_path);
GDScriptLanguageProtocol::get_singleton()->get_workspace()->parse_script(path, p_content);
-
- EditorFileSystem::get_singleton()->update_file(path);
}
void GDScriptTextDocument::show_native_symbol_in_editor(const String &p_symbol_id) {
diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h
index bdf339f5fe..6e19cd7a23 100644
--- a/modules/gdscript/language_server/godot_lsp.h
+++ b/modules/gdscript/language_server/godot_lsp.h
@@ -958,28 +958,30 @@ struct CompletionItem {
/**
* A string that should be used when comparing this item
- * with other items. When `falsy` the label is used.
+ * with other items. When omitted the label is used
+ * as the filter text for this item.
*/
String sortText;
/**
* A string that should be used when filtering a set of
- * completion items. When `falsy` the label is used.
+ * completion items. When omitted the label is used as the
+ * filter text for this item.
*/
String filterText;
/**
* A string that should be inserted into a document when selecting
- * this completion. When `falsy` the label is used.
+ * this completion. When omitted the label is used as the insert text
+ * for this item.
*
* The `insertText` is subject to interpretation by the client side.
* Some tools might not take the string literally. For example
- * VS Code when code complete is requested in this example `con<cursor position>`
- * and a completion item with an `insertText` of `console` is provided it
- * will only insert `sole`. Therefore it is recommended to use `textEdit` instead
- * since it avoids additional client side interpretation.
- *
- * @deprecated Use textEdit instead.
+ * VS Code when code complete is requested in this example
+ * `con<cursor position>` and a completion item with an `insertText` of
+ * `console` is provided it will only insert `sole`. Therefore it is
+ * recommended to use `textEdit` instead since it avoids additional client
+ * side interpretation.
*/
String insertText;
@@ -1034,14 +1036,20 @@ struct CompletionItem {
dict["label"] = label;
dict["kind"] = kind;
dict["data"] = data;
- dict["insertText"] = insertText;
+ if (!insertText.is_empty()) {
+ dict["insertText"] = insertText;
+ }
if (resolved) {
dict["detail"] = detail;
dict["documentation"] = documentation.to_json();
dict["deprecated"] = deprecated;
dict["preselect"] = preselect;
- dict["sortText"] = sortText;
- dict["filterText"] = filterText;
+ if (!sortText.is_empty()) {
+ dict["sortText"] = sortText;
+ }
+ if (!filterText.is_empty()) {
+ dict["filterText"] = filterText;
+ }
if (commitCharacters.size()) {
dict["commitCharacters"] = commitCharacters;
}
diff --git a/modules/gdscript/tests/scripts/.editorconfig b/modules/gdscript/tests/scripts/.editorconfig
new file mode 100644
index 0000000000..da1efefe3c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/.editorconfig
@@ -0,0 +1,12 @@
+# This file is required to workaround `.editorconfig` autogeneration (see #96845).
+
+# Some tests handle invalid syntax deliberately; exclude relevant attributes.
+
+[parser/features/mixed_indentation_on_blank_lines.gd]
+trim_trailing_whitespace = false
+
+[parser/warnings/empty_file_newline.notest.gd]
+insert_final_newline = false
+
+[parser/warnings/empty_file_newline_comment.notest.gd]
+insert_final_newline = false
diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg
new file mode 100644
index 0000000000..36c150f6e3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.cfg
@@ -0,0 +1,9 @@
+[input]
+scene="res://completion/get_node/get_node.tscn"
+[output]
+include=[
+ {"display": "%UniqueA"},
+]
+exclude=[
+ {"display": "\"%UniqueA\""},
+]
diff --git a/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd
new file mode 100644
index 0000000000..def050e938
--- /dev/null
+++ b/modules/gdscript/tests/scripts/completion/get_node/literal_scene/dollar_unique.gd
@@ -0,0 +1,5 @@
+extends Node
+
+func a():
+ $➡
+ pass
diff --git a/modules/gdscript/tests/scripts/parser/.editorconfig b/modules/gdscript/tests/scripts/parser/.editorconfig
deleted file mode 100644
index fa43b3ad78..0000000000
--- a/modules/gdscript/tests/scripts/parser/.editorconfig
+++ /dev/null
@@ -1,2 +0,0 @@
-[*.{gd,out}]
-trim_trailing_whitespace = false
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.gd
new file mode 100644
index 0000000000..2f0b3bd0eb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.gd
@@ -0,0 +1,7 @@
+func get_key() -> Variant:
+ return "key"
+
+func test():
+ var typed: Dictionary[int, int]
+ typed[get_key()] = 0
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.out
new file mode 100644
index 0000000000..5f6dd7f641
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_key.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_assign_differently_typed_key.gd
+>> 6
+>> Invalid assignment of property or key 'key' with value of type 'int' on a base object of type 'Dictionary[int, int]'.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.gd
new file mode 100644
index 0000000000..b171159aed
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.gd
@@ -0,0 +1,7 @@
+func get_value() -> Variant:
+ return "value"
+
+func test():
+ var typed: Dictionary[int, int]
+ typed[0] = get_value()
+ print("not ok")
diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.out
new file mode 100644
index 0000000000..f766d14261
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed_value.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/typed_dictionary_assign_differently_typed_value.gd
+>> 6
+>> Invalid assignment of property or key '0' with value of type 'String' on a base object of type 'Dictionary[int, int]'.
diff --git a/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd b/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd
new file mode 100644
index 0000000000..442335faeb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/self_destruction.gd
@@ -0,0 +1,50 @@
+# https://github.com/godotengine/godot/issues/75658
+
+class MyObj:
+ var callable: Callable
+
+ func run():
+ callable.call()
+
+ var prop:
+ set(value):
+ callable.call()
+ get:
+ callable.call()
+ return 0
+
+ func _on_some_signal():
+ callable.call()
+
+ func _init(p_callable: Callable):
+ self.callable = p_callable
+
+signal some_signal
+
+var obj: MyObj
+
+func test():
+ # Call.
+ obj = MyObj.new(nullify_obj)
+ obj.run()
+ print(obj)
+
+ # Get.
+ obj = MyObj.new(nullify_obj)
+ var _aux = obj.prop
+ print(obj)
+
+ # Set.
+ obj = MyObj.new(nullify_obj)
+ obj.prop = 1
+ print(obj)
+
+ # Signal handling.
+ obj = MyObj.new(nullify_obj)
+ @warning_ignore("return_value_discarded")
+ some_signal.connect(obj._on_some_signal)
+ some_signal.emit()
+ print(obj)
+
+func nullify_obj():
+ obj = null
diff --git a/modules/gdscript/tests/scripts/runtime/features/self_destruction.out b/modules/gdscript/tests/scripts/runtime/features/self_destruction.out
new file mode 100644
index 0000000000..ee4024a524
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/self_destruction.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+<null>
+<null>
+<null>
+<null>
diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml
index 2786c25e9a..a242a0d1d8 100644
--- a/modules/gltf/doc_classes/GLTFNode.xml
+++ b/modules/gltf/doc_classes/GLTFNode.xml
@@ -12,6 +12,13 @@
<link title="glTF scene and node spec">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md"</link>
</tutorials>
<methods>
+ <method name="append_child_index">
+ <return type="void" />
+ <param index="0" name="child_index" type="int" />
+ <description>
+ Appends the given child node index to the [member children] array.
+ </description>
+ </method>
<method name="get_additional_data">
<return type="Variant" />
<param index="0" name="extension_name" type="StringName" />
diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml
index c049acf557..de7ec2a4ca 100644
--- a/modules/gltf/doc_classes/GLTFState.xml
+++ b/modules/gltf/doc_classes/GLTFState.xml
@@ -28,6 +28,17 @@
Appends the given byte array data to the buffers and creates a [GLTFBufferView] for it. The index of the destination [GLTFBufferView] is returned. If [param deduplication] is true, the buffers will first be searched for duplicate data, otherwise new bytes will always be appended.
</description>
</method>
+ <method name="append_gltf_node">
+ <return type="int" />
+ <param index="0" name="gltf_node" type="GLTFNode" />
+ <param index="1" name="godot_scene_node" type="Node" />
+ <param index="2" name="parent_node_index" type="int" />
+ <description>
+ Append the given [GLTFNode] to the state, and return its new index. This can be used to export one Godot node as multiple glTF nodes, or inject new glTF nodes at import time. On import, this must be called before [method GLTFDocumentExtension._generate_scene_node] finishes for the parent node. On export, this must be called before [method GLTFDocumentExtension._export_node] runs for the parent node.
+ The [param godot_scene_node] parameter is the Godot scene node that corresponds to this glTF node. This is highly recommended to be set to a valid node, but may be null if there is no corresponding Godot scene node. One Godot scene node may be used for multiple glTF nodes, so if exporting multiple glTF nodes for one Godot scene node, use the same Godot scene node for each.
+ The [param parent_node_index] parameter is the index of the parent [GLTFNode] in the state. If [code]-1[/code], the node will be a root node, otherwise the new node will be added to the parent's list of children. The index will also be written to the [member GLTFNode.parent] property of the new node.
+ </description>
+ </method>
<method name="get_accessors">
<return type="GLTFAccessor[]" />
<description>
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 4653df7afe..56dae65831 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -414,7 +414,6 @@ static Vector<real_t> _xform_to_array(const Transform3D p_transform) {
Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) {
Array nodes;
- const int scene_node_count = p_state->scene_nodes.size();
for (int i = 0; i < p_state->nodes.size(); i++) {
Dictionary node;
Ref<GLTFNode> gltf_node = p_state->nodes[i];
@@ -465,7 +464,7 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) {
}
Node *scene_node = nullptr;
- if (i < scene_node_count) {
+ if (i < (int)p_state->scene_nodes.size()) {
scene_node = p_state->scene_nodes[i];
}
for (Ref<GLTFDocumentExtension> ext : document_extensions) {
@@ -491,14 +490,8 @@ String GLTFDocument::_gen_unique_name(Ref<GLTFState> p_state, const String &p_na
}
String GLTFDocument::_sanitize_animation_name(const String &p_name) {
- // Animations disallow the normal node invalid characters as well as "," and "["
- // (See animation/animation_player.cpp::add_animation)
-
- // TODO: Consider adding invalid_characters or a validate_animation_name to animation_player to mirror Node.
String anim_name = p_name.validate_node_name();
- anim_name = anim_name.replace(",", "");
- anim_name = anim_name.replace("[", "");
- return anim_name;
+ return AnimationLibrary::validate_library_name(anim_name);
}
String GLTFDocument::_gen_unique_animation_name(Ref<GLTFState> p_state, const String &p_name) {
@@ -5264,6 +5257,7 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
gltf_node.instantiate();
gltf_node->set_original_name(p_current->get_name());
gltf_node->set_name(_gen_unique_name(p_state, p_current->get_name()));
+ gltf_node->merge_meta_from(p_current);
if (cast_to<Node3D>(p_current)) {
Node3D *spatial = cast_to<Node3D>(p_current);
_convert_spatial(p_state, spatial, gltf_node);
@@ -5309,14 +5303,18 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current,
ERR_CONTINUE(ext.is_null());
ext->convert_scene_node(p_state, gltf_node, p_current);
}
- GLTFNodeIndex current_node_i = p_state->nodes.size();
- GLTFNodeIndex gltf_root = p_gltf_root;
- if (gltf_root == -1) {
- gltf_root = current_node_i;
- p_state->root_nodes.push_back(gltf_root);
+ GLTFNodeIndex current_node_i;
+ if (gltf_node->get_parent() == -1) {
+ current_node_i = p_state->append_gltf_node(gltf_node, p_current, p_gltf_parent);
+ } else if (gltf_node->get_parent() < -1) {
+ return;
+ } else {
+ current_node_i = p_state->nodes.size() - 1;
+ while (gltf_node != p_state->nodes[current_node_i]) {
+ current_node_i--;
+ }
}
- gltf_node->merge_meta_from(p_current);
- _create_gltf_node(p_state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node);
+ const GLTFNodeIndex gltf_root = (p_gltf_root == -1) ? current_node_i : p_gltf_root;
for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) {
_convert_scene_node(p_state, p_current->get_child(node_i), current_node_i, gltf_root);
}
@@ -5377,18 +5375,6 @@ void GLTFDocument::_convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeInd
}
#endif // MODULE_CSG_ENABLED
-void GLTFDocument::_create_gltf_node(Ref<GLTFState> p_state, Node *p_scene_parent, GLTFNodeIndex p_current_node_i,
- GLTFNodeIndex p_parent_node_index, GLTFNodeIndex p_root_gltf_node, Ref<GLTFNode> p_gltf_node) {
- p_state->scene_nodes.insert(p_current_node_i, p_scene_parent);
- p_state->nodes.push_back(p_gltf_node);
- ERR_FAIL_COND(p_current_node_i == p_parent_node_index);
- p_state->nodes.write[p_current_node_i]->parent = p_parent_node_index;
- if (p_parent_node_index == -1) {
- return;
- }
- p_state->nodes.write[p_parent_node_index]->children.push_back(p_current_node_i);
-}
-
void GLTFDocument::_convert_animation_player_to_gltf(AnimationPlayer *p_animation_player, Ref<GLTFState> p_state, GLTFNodeIndex p_gltf_current, GLTFNodeIndex p_gltf_root_index, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) {
ERR_FAIL_NULL(p_animation_player);
p_state->animation_players.push_back(p_animation_player);
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index b3e6dcf54a..d347d49102 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -342,12 +342,6 @@ public:
void _convert_csg_shape_to_gltf(CSGShape3D *p_current, GLTFNodeIndex p_gltf_parent, Ref<GLTFNode> p_gltf_node, Ref<GLTFState> p_state);
#endif // MODULE_CSG_ENABLED
- void _create_gltf_node(Ref<GLTFState> p_state,
- Node *p_scene_parent,
- GLTFNodeIndex p_current_node_i,
- GLTFNodeIndex p_parent_node_index,
- GLTFNodeIndex p_root_gltf_node,
- Ref<GLTFNode> p_gltf_node);
void _convert_animation_player_to_gltf(
AnimationPlayer *p_animation_player, Ref<GLTFState> p_state,
GLTFNodeIndex p_gltf_current,
diff --git a/modules/gltf/gltf_state.cpp b/modules/gltf/gltf_state.cpp
index 73a61ff77f..7763874d02 100644
--- a/modules/gltf/gltf_state.cpp
+++ b/modules/gltf/gltf_state.cpp
@@ -35,6 +35,7 @@
void GLTFState::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_used_extension", "extension_name", "required"), &GLTFState::add_used_extension);
ClassDB::bind_method(D_METHOD("append_data_to_buffers", "data", "deduplication"), &GLTFState::append_data_to_buffers);
+ ClassDB::bind_method(D_METHOD("append_gltf_node", "gltf_node", "godot_scene_node", "parent_node_index"), &GLTFState::append_gltf_node);
ClassDB::bind_method(D_METHOD("get_json"), &GLTFState::get_json);
ClassDB::bind_method(D_METHOD("set_json", "json"), &GLTFState::set_json);
@@ -441,3 +442,16 @@ GLTFBufferViewIndex GLTFState::append_data_to_buffers(const Vector<uint8_t> &p_d
buffer_views.push_back(buffer_view);
return new_index;
}
+
+GLTFNodeIndex GLTFState::append_gltf_node(Ref<GLTFNode> p_gltf_node, Node *p_godot_scene_node, GLTFNodeIndex p_parent_node_index) {
+ p_gltf_node->set_parent(p_parent_node_index);
+ const GLTFNodeIndex new_index = nodes.size();
+ nodes.append(p_gltf_node);
+ scene_nodes.insert(new_index, p_godot_scene_node);
+ if (p_parent_node_index == -1) {
+ root_nodes.append(new_index);
+ } else if (p_parent_node_index < new_index) {
+ nodes.write[p_parent_node_index]->append_child_index(new_index);
+ }
+ return new_index;
+}
diff --git a/modules/gltf/gltf_state.h b/modules/gltf/gltf_state.h
index 07efafe13b..7954049192 100644
--- a/modules/gltf/gltf_state.h
+++ b/modules/gltf/gltf_state.h
@@ -119,6 +119,7 @@ public:
void add_used_extension(const String &p_extension, bool p_required = false);
GLTFBufferViewIndex append_data_to_buffers(const Vector<uint8_t> &p_data, const bool p_deduplication);
+ GLTFNodeIndex append_gltf_node(Ref<GLTFNode> p_gltf_node, Node *p_godot_scene_node, GLTFNodeIndex p_parent_node_index);
enum GLTFHandleBinary {
HANDLE_BINARY_DISCARD_TEXTURES = 0,
diff --git a/modules/gltf/structures/gltf_node.cpp b/modules/gltf/structures/gltf_node.cpp
index 2934e4b5ee..ccee5e8ca4 100644
--- a/modules/gltf/structures/gltf_node.cpp
+++ b/modules/gltf/structures/gltf_node.cpp
@@ -55,6 +55,7 @@ void GLTFNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_scale", "scale"), &GLTFNode::set_scale);
ClassDB::bind_method(D_METHOD("get_children"), &GLTFNode::get_children);
ClassDB::bind_method(D_METHOD("set_children", "children"), &GLTFNode::set_children);
+ ClassDB::bind_method(D_METHOD("append_child_index", "child_index"), &GLTFNode::append_child_index);
ClassDB::bind_method(D_METHOD("get_light"), &GLTFNode::get_light);
ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light);
ClassDB::bind_method(D_METHOD("get_additional_data", "extension_name"), &GLTFNode::get_additional_data);
@@ -170,6 +171,10 @@ void GLTFNode::set_children(Vector<int> p_children) {
children = p_children;
}
+void GLTFNode::append_child_index(int p_child_index) {
+ children.append(p_child_index);
+}
+
GLTFLightIndex GLTFNode::get_light() {
return light;
}
diff --git a/modules/gltf/structures/gltf_node.h b/modules/gltf/structures/gltf_node.h
index 63399fb32b..f3f6bfa2f1 100644
--- a/modules/gltf/structures/gltf_node.h
+++ b/modules/gltf/structures/gltf_node.h
@@ -97,6 +97,7 @@ public:
Vector<int> get_children();
void set_children(Vector<int> p_children);
+ void append_child_index(int p_child_index);
GLTFLightIndex get_light();
void set_light(GLTFLightIndex p_light);
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index ea63e07104..4c11565c51 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -34,6 +34,7 @@
#include "core/input/input.h"
#include "core/os/keyboard.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -815,13 +816,14 @@ void GridMapEditor::_text_changed(const String &p_text) {
update_palette();
}
-void GridMapEditor::_sbox_input(const Ref<InputEvent> &p_ie) {
- const Ref<InputEventKey> k = p_ie;
-
- if (k.is_valid() && (k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::PAGEUP || k->get_keycode() == Key::PAGEDOWN)) {
- // Forward the key input to the ItemList so it can be scrolled
- mesh_library_palette->gui_input(k);
- search_box->accept_event();
+void GridMapEditor::_sbox_input(const Ref<InputEvent> &p_event) {
+ // Redirect navigational key events to the item list.
+ Ref<InputEventKey> key = p_event;
+ if (key.is_valid()) {
+ if (key->is_action("ui_up", true) || key->is_action("ui_down", true) || key->is_action("ui_page_up") || key->is_action("ui_page_down")) {
+ mesh_library_palette->gui_input(key);
+ search_box->accept_event();
+ }
}
}
@@ -958,7 +960,7 @@ void GridMapEditor::edit(GridMap *p_gridmap) {
_update_selection_transform();
_update_paste_indicator();
- spatial_editor = Object::cast_to<Node3DEditorPlugin>(EditorNode::get_singleton()->get_editor_plugin_screen());
+ spatial_editor = Object::cast_to<Node3DEditorPlugin>(EditorNode::get_singleton()->get_editor_main_screen()->get_selected_plugin());
if (!node) {
set_process(false);
@@ -1198,7 +1200,7 @@ GridMapEditor::GridMapEditor() {
ED_SHORTCUT("grid_map/clear_selection", TTR("Clear Selection"), Key::KEY_DELETE);
ED_SHORTCUT("grid_map/fill_selection", TTR("Fill Selection"), KeyModifierMask::CTRL + Key::F);
- int mw = EDITOR_DEF("editors/grid_map/palette_min_width", 230);
+ int mw = EDITOR_GET("editors/grid_map/palette_min_width");
Control *ec = memnew(Control);
ec->set_custom_minimum_size(Size2(mw, 0) * EDSCALE);
add_child(ec);
@@ -1308,8 +1310,6 @@ GridMapEditor::GridMapEditor() {
size_slider->connect(SceneStringName(value_changed), callable_mp(this, &GridMapEditor::_icon_size_changed));
add_child(size_slider);
- EDITOR_DEF("editors/grid_map/preview_size", 64);
-
mesh_library_palette = memnew(ItemList);
mesh_library_palette->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
add_child(mesh_library_palette);
@@ -1532,9 +1532,6 @@ void GridMapEditorPlugin::make_visible(bool p_visible) {
}
GridMapEditorPlugin::GridMapEditorPlugin() {
- EDITOR_DEF("editors/grid_map/editor_side", 1);
- EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/grid_map/editor_side", PROPERTY_HINT_ENUM, "Left,Right"));
-
grid_map_editor = memnew(GridMapEditor);
switch ((int)EDITOR_GET("editors/grid_map/editor_side")) {
case 0: { // Left.
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.h b/modules/gridmap/editor/grid_map_editor_plugin.h
index cfa0f0c35c..4294c93c93 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.h
+++ b/modules/gridmap/editor/grid_map_editor_plugin.h
@@ -199,7 +199,7 @@ class GridMapEditor : public VBoxContainer {
void _update_theme();
void _text_changed(const String &p_text);
- void _sbox_input(const Ref<InputEvent> &p_ie);
+ void _sbox_input(const Ref<InputEvent> &p_event);
void _mesh_library_palette_input(const Ref<InputEvent> &p_ie);
void _icon_size_changed(float p_value);
diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp
index 394213963a..e29fe9295a 100644
--- a/modules/minimp3/audio_stream_mp3.cpp
+++ b/modules/minimp3/audio_stream_mp3.cpp
@@ -159,6 +159,9 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackMP3::get_sample_playback() const {
void AudioStreamPlaybackMP3::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
sample_playback = p_playback;
+ if (sample_playback.is_valid()) {
+ sample_playback->stream_playback = Ref<AudioStreamPlayback>(this);
+ }
}
void AudioStreamPlaybackMP3::set_parameter(const StringName &p_name, const Variant &p_value) {
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 177859f270..f47e6d209a 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -1524,9 +1524,10 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const {
}
}
+ props.reverse();
for (PropertyInfo &prop : props) {
validate_property(prop);
- p_properties->push_back(prop);
+ p_properties->push_front(prop);
}
}
@@ -2716,7 +2717,7 @@ int CSharpScript::get_member_line(const StringName &p_member) const {
return -1;
}
-const Variant CSharpScript::get_rpc_config() const {
+Variant CSharpScript::get_rpc_config() const {
return rpc_config;
}
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index c48e1a95c9..ec7328be4a 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -284,7 +284,7 @@ public:
int get_member_line(const StringName &p_member) const override;
- const Variant get_rpc_config() const override;
+ Variant get_rpc_config() const override;
#ifdef TOOLS_ENABLED
bool is_placeholder_fallback_enabled() const override {
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 2ec073e4fa..3222c58c4e 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -3448,6 +3448,12 @@ StringName BindingsGenerator::_get_int_type_name_from_meta(GodotTypeInfo::Metada
case GodotTypeInfo::METADATA_INT_IS_UINT64:
return "ulong";
break;
+ case GodotTypeInfo::METADATA_INT_IS_CHAR16:
+ return "char";
+ break;
+ case GodotTypeInfo::METADATA_INT_IS_CHAR32:
+ // To prevent breaking compatibility, C# bindings need to keep using `long`.
+ return "long";
default:
// Assume INT64
return "long";
diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp
index 7322a47630..df240a5965 100644
--- a/modules/mono/editor/editor_internal_calls.cpp
+++ b/modules/mono/editor/editor_internal_calls.cpp
@@ -41,6 +41,7 @@
#include "core/os/os.h"
#include "core/version.h"
#include "editor/debugger/editor_debugger_node.h"
+#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
@@ -165,7 +166,7 @@ bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line,
}
void godot_icall_Internal_EditorNodeShowScriptScreen() {
- EditorNode::get_singleton()->editor_select(EditorNode::EDITOR_SCRIPT);
+ EditorNode::get_editor_main_screen()->select(EditorMainScreen::EDITOR_SCRIPT);
}
void godot_icall_Internal_EditorRunPlay() {
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 3935854a29..039263b405 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -192,8 +192,14 @@ private:
}
}
if (!has_data) {
- // 3. Extract the data to a temporary location to load from there.
- Ref<DirAccess> da = DirAccess::create_for_path(packed_path);
+ // 3. Extract the data to a temporary location to load from there, delete old data if it exists but is not up-to-date.
+ Ref<DirAccess> da;
+ if (DirAccess::exists(data_dir_root)) {
+ da = DirAccess::open(data_dir_root);
+ ERR_FAIL_COND(da.is_null());
+ ERR_FAIL_COND(da->erase_contents_recursive() != OK);
+ }
+ da = DirAccess::create_for_path(packed_path);
ERR_FAIL_COND(da.is_null());
ERR_FAIL_COND(da->copy_dir(packed_path, data_dir_root) != OK);
}
diff --git a/modules/multiplayer/doc_classes/SceneMultiplayer.xml b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
index 7abee8e2c8..42f32d4848 100644
--- a/modules/multiplayer/doc_classes/SceneMultiplayer.xml
+++ b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
@@ -68,7 +68,7 @@
The callback to execute when when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect.
</member>
<member name="auth_timeout" type="float" setter="set_auth_timeout" getter="get_auth_timeout" default="3.0">
- If set to a value greater than [code]0.0[/code], the maximum amount of time peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals.
+ If set to a value greater than [code]0.0[/code], the maximum duration in seconds peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals.
</member>
<member name="max_delta_packet_size" type="int" setter="set_max_delta_packet_size" getter="get_max_delta_packet_size" default="65535">
Maximum size of each delta packet. Higher values increase the chance of receiving full updates in a single frame, but also the chance of causing networking congestion (higher latency, disconnections). See [MultiplayerSynchronizer].
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
index 212fd1ef6b..3a51712c70 100644
--- a/modules/multiplayer/editor/editor_network_profiler.cpp
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -34,6 +34,7 @@
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
+#include "scene/gui/check_box.h"
void EditorNetworkProfiler::_bind_methods() {
ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable")));
@@ -170,15 +171,42 @@ void EditorNetworkProfiler::add_node_data(const NodeInfo &p_info) {
}
void EditorNetworkProfiler::_activate_pressed() {
+ _update_button_text();
+
if (activate->is_pressed()) {
refresh_timer->start();
+ } else {
+ refresh_timer->stop();
+ }
+
+ emit_signal(SNAME("enable_profiling"), activate->is_pressed());
+}
+
+void EditorNetworkProfiler::_update_button_text() {
+ if (activate->is_pressed()) {
activate->set_icon(theme_cache.stop_icon);
activate->set_text(TTR("Stop"));
} else {
- refresh_timer->stop();
activate->set_icon(theme_cache.play_icon);
activate->set_text(TTR("Start"));
}
+}
+
+void EditorNetworkProfiler::started() {
+ if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false)) {
+ set_profiling(true);
+ refresh_timer->start();
+ }
+}
+
+void EditorNetworkProfiler::stopped() {
+ set_profiling(false);
+ refresh_timer->stop();
+}
+
+void EditorNetworkProfiler::set_profiling(bool p_pressed) {
+ activate->set_pressed(p_pressed);
+ _update_button_text();
emit_signal(SNAME("enable_profiling"), activate->is_pressed());
}
@@ -192,6 +220,10 @@ void EditorNetworkProfiler::_clear_pressed() {
refresh_replication_data();
}
+void EditorNetworkProfiler::_autostart_toggled(bool p_toggled_on) {
+ EditorSettings::get_singleton()->set_project_metadata("debug_options", "autostart_network_profiler", p_toggled_on);
+}
+
void EditorNetworkProfiler::_replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button) {
if (!p_item) {
return;
@@ -268,6 +300,12 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
clear_button->connect(SceneStringName(pressed), callable_mp(this, &EditorNetworkProfiler::_clear_pressed));
hb->add_child(clear_button);
+ CheckBox *autostart_checkbox = memnew(CheckBox);
+ autostart_checkbox->set_text(TTR("Autostart"));
+ autostart_checkbox->set_pressed(EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false));
+ autostart_checkbox->connect(SceneStringName(toggled), callable_mp(this, &EditorNetworkProfiler::_autostart_toggled));
+ hb->add_child(autostart_checkbox);
+
hb->add_spacer();
Label *lb = memnew(Label);
diff --git a/modules/multiplayer/editor/editor_network_profiler.h b/modules/multiplayer/editor/editor_network_profiler.h
index b4f8ffa724..46931c9fc9 100644
--- a/modules/multiplayer/editor/editor_network_profiler.h
+++ b/modules/multiplayer/editor/editor_network_profiler.h
@@ -92,7 +92,9 @@ private:
void _activate_pressed();
void _clear_pressed();
+ void _autostart_toggled(bool p_toggled_on);
void _refresh();
+ void _update_button_text();
void _replication_button_clicked(TreeItem *p_item, int p_column, int p_idx, MouseButton p_button);
protected:
@@ -112,6 +114,10 @@ public:
void set_bandwidth(int p_incoming, int p_outgoing);
bool is_profiling();
+ void set_profiling(bool p_pressed);
+ void started();
+ void stopped();
+
EditorNetworkProfiler();
};
diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
index a496f5dfa2..817d503aec 100644
--- a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
+++ b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
@@ -106,6 +106,8 @@ void MultiplayerEditorDebugger::setup_session(int p_session_id) {
profiler->connect("enable_profiling", callable_mp(this, &MultiplayerEditorDebugger::_profiler_activate).bind(p_session_id));
profiler->connect("open_request", callable_mp(this, &MultiplayerEditorDebugger::_open_request));
profiler->set_name(TTR("Network Profiler"));
+ session->connect("started", callable_mp(profiler, &EditorNetworkProfiler::started));
+ session->connect("stopped", callable_mp(profiler, &EditorNetworkProfiler::stopped));
session->add_session_tab(profiler);
profilers[p_session_id] = profiler;
}
diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp
index 851ad85876..386feae4f9 100644
--- a/modules/multiplayer/editor/replication_editor.cpp
+++ b/modules/multiplayer/editor/replication_editor.cpp
@@ -93,24 +93,6 @@ void ReplicationEditor::_pick_node_select_recursive(TreeItem *p_item, const Stri
}
}
-void ReplicationEditor::_pick_node_filter_input(const Ref<InputEvent> &p_ie) {
- Ref<InputEventKey> k = p_ie;
-
- if (k.is_valid()) {
- switch (k->get_keycode()) {
- case Key::UP:
- case Key::DOWN:
- case Key::PAGEUP:
- case Key::PAGEDOWN: {
- pick_node->get_scene_tree()->get_scene_tree()->gui_input(k);
- pick_node->get_filter_line_edit()->accept_event();
- } break;
- default:
- break;
- }
- }
-}
-
void ReplicationEditor::_pick_node_selected(NodePath p_path) {
Node *root = current->get_node(current->get_root_path());
ERR_FAIL_NULL(root);
@@ -184,11 +166,9 @@ ReplicationEditor::ReplicationEditor() {
pick_node = memnew(SceneTreeDialog);
add_child(pick_node);
- pick_node->register_text_enter(pick_node->get_filter_line_edit());
pick_node->set_title(TTR("Pick a node to synchronize:"));
pick_node->connect("selected", callable_mp(this, &ReplicationEditor::_pick_node_selected));
pick_node->get_filter_line_edit()->connect(SceneStringName(text_changed), callable_mp(this, &ReplicationEditor::_pick_node_filter_text_changed));
- pick_node->get_filter_line_edit()->connect("gui_input", callable_mp(this, &ReplicationEditor::_pick_node_filter_input));
prop_selector = memnew(PropertySelector);
add_child(prop_selector);
diff --git a/modules/multiplayer/editor/replication_editor.h b/modules/multiplayer/editor/replication_editor.h
index 8f11774292..017fa73967 100644
--- a/modules/multiplayer/editor/replication_editor.h
+++ b/modules/multiplayer/editor/replication_editor.h
@@ -81,7 +81,6 @@ private:
void _pick_node_filter_text_changed(const String &p_newtext);
void _pick_node_select_recursive(TreeItem *p_item, const String &p_filter, Vector<Node *> &p_select_candidates);
- void _pick_node_filter_input(const Ref<InputEvent> &p_ie);
void _pick_node_selected(NodePath p_path);
void _pick_new_property();
diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp
index 592bb18a71..0938d7ef99 100644
--- a/modules/multiplayer/scene_rpc_interface.cpp
+++ b/modules/multiplayer/scene_rpc_interface.cpp
@@ -73,6 +73,16 @@ int get_packet_len(uint32_t p_node_target, int p_packet_len) {
}
}
+bool SceneRPCInterface::_sort_rpc_names(const Variant &p_l, const Variant &p_r) {
+ if (likely(p_l.is_string() && p_r.is_string())) {
+ return p_l.operator String() < p_r.operator String();
+ }
+ bool valid = false;
+ Variant res;
+ Variant::evaluate(Variant::OP_LESS, p_l, p_r, res, valid);
+ return valid ? res.operator bool() : false;
+}
+
void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_node, RPCConfigCache &r_cache) {
if (p_config.get_type() == Variant::NIL) {
return;
@@ -80,7 +90,7 @@ void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_no
ERR_FAIL_COND(p_config.get_type() != Variant::DICTIONARY);
const Dictionary config = p_config;
Array names = config.keys();
- names.sort(); // Ensure ID order
+ names.sort_custom(callable_mp_static(&SceneRPCInterface::_sort_rpc_names)); // Ensure ID order
for (int i = 0; i < names.size(); i++) {
ERR_CONTINUE(!names[i].is_string());
String name = names[i].operator String();
@@ -108,7 +118,7 @@ const SceneRPCInterface::RPCConfigCache &SceneRPCInterface::_get_node_config(con
return rpc_cache[oid];
}
RPCConfigCache cache;
- _parse_rpc_config(p_node->get_node_rpc_config(), true, cache);
+ _parse_rpc_config(p_node->get_rpc_config(), true, cache);
if (p_node->get_script_instance()) {
_parse_rpc_config(p_node->get_script_instance()->get_rpc_config(), false, cache);
}
diff --git a/modules/multiplayer/scene_rpc_interface.h b/modules/multiplayer/scene_rpc_interface.h
index 5c9b66d5f5..852cef7830 100644
--- a/modules/multiplayer/scene_rpc_interface.h
+++ b/modules/multiplayer/scene_rpc_interface.h
@@ -91,6 +91,8 @@ private:
#endif
protected:
+ static bool _sort_rpc_names(const Variant &p_l, const Variant &p_r);
+
void _process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int p_from, const uint8_t *p_packet, int p_packet_len, int p_offset);
void _send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const RPCConfig &p_config, const StringName &p_name, const Variant **p_arg, int p_argcount);
diff --git a/modules/navigation/2d/godot_navigation_server_2d.cpp b/modules/navigation/2d/godot_navigation_server_2d.cpp
index bf69adc14c..2af125d434 100644
--- a/modules/navigation/2d/godot_navigation_server_2d.cpp
+++ b/modules/navigation/2d/godot_navigation_server_2d.cpp
@@ -318,6 +318,11 @@ int FORWARD_1_C(region_get_connections_count, RID, p_region, rid_to_rid);
Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_start, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int);
Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_end, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int);
+Vector2 GodotNavigationServer2D::region_get_closest_point(RID p_region, const Vector2 &p_point) const {
+ Vector3 result = NavigationServer3D::get_singleton()->region_get_closest_point(p_region, v2_to_v3(p_point));
+ return v3_to_v2(result);
+}
+
Vector2 GodotNavigationServer2D::region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const {
Vector3 result = NavigationServer3D::get_singleton()->region_get_random_point(p_region, p_navigation_layers, p_uniformly);
return v3_to_v2(result);
diff --git a/modules/navigation/2d/godot_navigation_server_2d.h b/modules/navigation/2d/godot_navigation_server_2d.h
index ea77fa5e6e..1579ca2907 100644
--- a/modules/navigation/2d/godot_navigation_server_2d.h
+++ b/modules/navigation/2d/godot_navigation_server_2d.h
@@ -101,6 +101,7 @@ public:
virtual int region_get_connections_count(RID p_region) const override;
virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override;
virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override;
+ virtual Vector2 region_get_closest_point(RID p_region, const Vector2 &p_point) const override;
virtual Vector2 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override;
virtual RID link_create() override;
diff --git a/modules/navigation/3d/godot_navigation_server_3d.cpp b/modules/navigation/3d/godot_navigation_server_3d.cpp
index 11a5de608b..5dfc39f6f5 100644
--- a/modules/navigation/3d/godot_navigation_server_3d.cpp
+++ b/modules/navigation/3d/godot_navigation_server_3d.cpp
@@ -536,6 +536,27 @@ Vector3 GodotNavigationServer3D::region_get_connection_pathway_end(RID p_region,
return Vector3();
}
+Vector3 GodotNavigationServer3D::region_get_closest_point_to_segment(RID p_region, const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision) const {
+ const NavRegion *region = region_owner.get_or_null(p_region);
+ ERR_FAIL_NULL_V(region, Vector3());
+
+ return region->get_closest_point_to_segment(p_from, p_to, p_use_collision);
+}
+
+Vector3 GodotNavigationServer3D::region_get_closest_point(RID p_region, const Vector3 &p_point) const {
+ const NavRegion *region = region_owner.get_or_null(p_region);
+ ERR_FAIL_NULL_V(region, Vector3());
+
+ return region->get_closest_point_info(p_point).point;
+}
+
+Vector3 GodotNavigationServer3D::region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const {
+ const NavRegion *region = region_owner.get_or_null(p_region);
+ ERR_FAIL_NULL_V(region, Vector3());
+
+ return region->get_closest_point_info(p_point).normal;
+}
+
Vector3 GodotNavigationServer3D::region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const {
const NavRegion *region = region_owner.get_or_null(p_region);
ERR_FAIL_NULL_V(region, Vector3());
diff --git a/modules/navigation/3d/godot_navigation_server_3d.h b/modules/navigation/3d/godot_navigation_server_3d.h
index 12a1132f07..eae6ea2860 100644
--- a/modules/navigation/3d/godot_navigation_server_3d.h
+++ b/modules/navigation/3d/godot_navigation_server_3d.h
@@ -178,6 +178,9 @@ public:
virtual int region_get_connections_count(RID p_region) const override;
virtual Vector3 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override;
virtual Vector3 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override;
+ virtual Vector3 region_get_closest_point_to_segment(RID p_region, const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision = false) const override;
+ virtual Vector3 region_get_closest_point(RID p_region, const Vector3 &p_point) const override;
+ virtual Vector3 region_get_closest_point_normal(RID p_region, const Vector3 &p_point) const override;
virtual Vector3 region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const override;
virtual RID link_create() override;
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
index f37ed9b168..7f0cbc7b5e 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp
@@ -176,7 +176,7 @@ void NavigationMeshEditorPlugin::make_visible(bool p_visible) {
NavigationMeshEditorPlugin::NavigationMeshEditorPlugin() {
navigation_mesh_editor = memnew(NavigationMeshEditor);
- EditorNode::get_singleton()->get_main_screen_control()->add_child(navigation_mesh_editor);
+ EditorNode::get_singleton()->get_gui_base()->add_child(navigation_mesh_editor);
add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, navigation_mesh_editor->bake_hbox);
navigation_mesh_editor->hide();
navigation_mesh_editor->bake_hbox->hide();
diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp
index 7a44adecbc..2c91b80af2 100644
--- a/modules/navigation/nav_region.cpp
+++ b/modules/navigation/nav_region.cpp
@@ -105,7 +105,22 @@ void NavRegion::set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) {
polygons_dirty = true;
}
+Vector3 NavRegion::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision) const {
+ RWLockRead read_lock(region_rwlock);
+
+ return NavMeshQueries3D::polygons_get_closest_point_to_segment(
+ get_polygons(), p_from, p_to, p_use_collision);
+}
+
+gd::ClosestPointQueryResult NavRegion::get_closest_point_info(const Vector3 &p_point) const {
+ RWLockRead read_lock(region_rwlock);
+
+ return NavMeshQueries3D::polygons_get_closest_point_info(get_polygons(), p_point);
+}
+
Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const {
+ RWLockRead read_lock(region_rwlock);
+
if (!get_enabled()) {
return Vector3();
}
@@ -114,6 +129,8 @@ Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniform
}
bool NavRegion::sync() {
+ RWLockWrite write_lock(region_rwlock);
+
bool something_changed = polygons_dirty /* || something_dirty? */;
update_polygons();
diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h
index 662a32c47a..c015802b92 100644
--- a/modules/navigation/nav_region.h
+++ b/modules/navigation/nav_region.h
@@ -38,6 +38,8 @@
#include "scene/resources/navigation_mesh.h"
class NavRegion : public NavBase {
+ RWLock region_rwlock;
+
NavMap *map = nullptr;
Transform3D transform;
bool enabled = true;
@@ -88,6 +90,8 @@ public:
return polygons;
}
+ Vector3 get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, bool p_use_collision) const;
+ gd::ClosestPointQueryResult get_closest_point_info(const Vector3 &p_point) const;
Vector3 get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const;
real_t get_surface_area() const { return surface_area; };
diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
index 6f4ac00f3e..341b50065c 100644
--- a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
+++ b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml
@@ -10,6 +10,13 @@
<tutorials>
</tutorials>
<methods>
+ <method name="get_android_surface">
+ <return type="JavaObject" />
+ <description>
+ Returns a [JavaObject] representing an [code]android.view.Surface[/code] if [member use_android_surface] is enabled and OpenXR has created the surface. Otherwise, this will return [code]null[/code].
+ [b]Note:[/b] The surface can only be created during an active OpenXR session. So, if [member use_android_surface] is enabled outside of an OpenXR session, it won't be created until a new session fully starts.
+ </description>
+ </method>
<method name="intersects_ray" qualifiers="const">
<return type="Vector2" />
<param index="0" name="origin" type="Vector3" />
@@ -32,6 +39,9 @@
Enables the blending the layer using its alpha channel.
Can be combined with [member Viewport.transparent_bg] to give the layer a transparent background.
</member>
+ <member name="android_surface_size" type="Vector2i" setter="set_android_surface_size" getter="get_android_surface_size" default="Vector2i(1024, 1024)">
+ The size of the Android surface to create if [member use_android_surface] is enabled.
+ </member>
<member name="enable_hole_punch" type="bool" setter="set_enable_hole_punch" getter="get_enable_hole_punch" default="false">
Enables a technique called "hole punching", which allows putting the composition layer behind the main projection layer (i.e. setting [member sort_order] to a negative value) while "punching a hole" through everything rendered by Godot so that the layer is still visible.
This can be used to create the illusion that the composition layer exists in the same 3D space as everything rendered by Godot, allowing objects to appear to pass both behind or in front of the composition layer.
@@ -43,5 +53,10 @@
The sort order for this composition layer. Higher numbers will be shown in front of lower numbers.
[b]Note:[/b] This will have no effect if a fallback mesh is being used.
</member>
+ <member name="use_android_surface" type="bool" setter="set_use_android_surface" getter="get_use_android_surface" default="false">
+ If enabled, an Android surface will be created (with the dimensions from [member android_surface_size]) which will provide the 2D content for the composition layer, rather than using [member layer_viewport].
+ See [method get_android_surface] for information about how to get the surface so that your application can draw to it.
+ [b]Note:[/b] This will only work in Android builds.
+ </member>
</members>
</class>
diff --git a/modules/openxr/editor/openxr_select_runtime.cpp b/modules/openxr/editor/openxr_select_runtime.cpp
index 4d95b079e2..4a2a87cb88 100644
--- a/modules/openxr/editor/openxr_select_runtime.cpp
+++ b/modules/openxr/editor/openxr_select_runtime.cpp
@@ -119,6 +119,7 @@ OpenXRSelectRuntime::OpenXRSelectRuntime() {
default_runtimes["SteamVR"] = "~/.steam/steam/steamapps/common/SteamVR/steamxr_linux64.json";
#endif
+ // TODO: Move to editor_settings.cpp
EDITOR_DEF_RST("xr/openxr/runtime_paths", default_runtimes);
set_flat(true);
diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
index 8a448afc08..83e45ffe7f 100644
--- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp
+++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp
@@ -30,6 +30,12 @@
#include "openxr_composition_layer_extension.h"
+#ifdef ANDROID_ENABLED
+#include <openxr/openxr.h>
+#include <openxr/openxr_platform.h>
+#endif
+
+#include "platform/android/api/java_class_wrapper.h"
#include "servers/rendering/rendering_server_globals.h"
////////////////////////////////////////////////////////////////////////////
@@ -55,18 +61,37 @@ HashMap<String, bool *> OpenXRCompositionLayerExtension::get_requested_extension
request_extensions[XR_KHR_COMPOSITION_LAYER_CYLINDER_EXTENSION_NAME] = &cylinder_ext_available;
request_extensions[XR_KHR_COMPOSITION_LAYER_EQUIRECT2_EXTENSION_NAME] = &equirect_ext_available;
+#ifdef ANDROID_ENABLED
+ request_extensions[XR_KHR_ANDROID_SURFACE_SWAPCHAIN_EXTENSION_NAME] = &android_surface_ext_available;
+#endif
+
return request_extensions;
}
+void OpenXRCompositionLayerExtension::on_instance_created(const XrInstance p_instance) {
+#ifdef ANDROID_ENABLED
+ EXT_INIT_XR_FUNC(xrDestroySwapchain);
+ EXT_INIT_XR_FUNC(xrCreateSwapchainAndroidSurfaceKHR);
+#endif
+}
+
void OpenXRCompositionLayerExtension::on_session_created(const XrSession p_session) {
OpenXRAPI::get_singleton()->register_composition_layer_provider(this);
}
void OpenXRCompositionLayerExtension::on_session_destroyed() {
OpenXRAPI::get_singleton()->unregister_composition_layer_provider(this);
+
+#ifdef ANDROID_ENABLED
+ free_queued_android_surface_swapchains();
+#endif
}
void OpenXRCompositionLayerExtension::on_pre_render() {
+#ifdef ANDROID_ENABLED
+ free_queued_android_surface_swapchains();
+#endif
+
for (OpenXRViewportCompositionLayerProvider *composition_layer : composition_layers) {
composition_layer->on_pre_render();
}
@@ -113,6 +138,37 @@ bool OpenXRCompositionLayerExtension::is_available(XrStructureType p_which) {
}
}
+#ifdef ANDROID_ENABLED
+bool OpenXRCompositionLayerExtension::create_android_surface_swapchain(XrSwapchainCreateInfo *p_info, XrSwapchain *r_swapchain, jobject *r_surface) {
+ if (android_surface_ext_available) {
+ OpenXRAPI *openxr_api = OpenXRAPI::get_singleton();
+ ERR_FAIL_NULL_V(openxr_api, false);
+
+ // @todo We need a way to add to the next pointer chain.
+ XrResult result = xrCreateSwapchainAndroidSurfaceKHR(openxr_api->get_session(), p_info, r_swapchain, r_surface);
+ if (XR_FAILED(result)) {
+ print_line("OpenXR: Failed to create Android surface swapchain [", openxr_api->get_error_string(result), "]");
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void OpenXRCompositionLayerExtension::free_android_surface_swapchain(XrSwapchain p_swapchain) {
+ android_surface_swapchain_free_queue.push_back(p_swapchain);
+}
+
+void OpenXRCompositionLayerExtension::free_queued_android_surface_swapchains() {
+ for (XrSwapchain swapchain : android_surface_swapchain_free_queue) {
+ xrDestroySwapchain(swapchain);
+ }
+ android_surface_swapchain_free_queue.clear();
+}
+#endif
+
////////////////////////////////////////////////////////////////////////////
// OpenXRViewportCompositionLayerProvider
@@ -127,8 +183,12 @@ OpenXRViewportCompositionLayerProvider::~OpenXRViewportCompositionLayerProvider(
extension->on_viewport_composition_layer_destroyed(composition_layer);
}
- // This will reset the viewport and free the swapchain too.
- set_viewport(RID(), Size2i());
+ if (use_android_surface) {
+ free_swapchain();
+ } else {
+ // This will reset the viewport and free the swapchain too.
+ set_viewport(RID(), Size2i());
+ }
}
void OpenXRViewportCompositionLayerProvider::set_alpha_blend(bool p_alpha_blend) {
@@ -143,24 +203,92 @@ void OpenXRViewportCompositionLayerProvider::set_alpha_blend(bool p_alpha_blend)
}
void OpenXRViewportCompositionLayerProvider::set_viewport(RID p_viewport, Size2i p_size) {
+ ERR_FAIL_COND(use_android_surface);
+
RenderingServer *rs = RenderingServer::get_singleton();
ERR_FAIL_NULL(rs);
- if (viewport != p_viewport) {
- if (viewport.is_valid()) {
- RID rt = rs->viewport_get_render_target(viewport);
+ if (subviewport.viewport != p_viewport) {
+ if (subviewport.viewport.is_valid()) {
+ RID rt = rs->viewport_get_render_target(subviewport.viewport);
RSG::texture_storage->render_target_set_override(rt, RID(), RID(), RID());
}
- viewport = p_viewport;
+ subviewport.viewport = p_viewport;
- if (viewport.is_valid()) {
- viewport_size = p_size;
+ if (subviewport.viewport.is_valid()) {
+ subviewport.viewport_size = p_size;
} else {
free_swapchain();
- viewport_size = Size2i();
+ subviewport.viewport_size = Size2i();
+ }
+ }
+}
+
+void OpenXRViewportCompositionLayerProvider::set_use_android_surface(bool p_use_android_surface, Size2i p_size) {
+#ifdef ANDROID_ENABLED
+ if (p_use_android_surface == use_android_surface) {
+ return;
+ }
+
+ use_android_surface = p_use_android_surface;
+
+ if (use_android_surface) {
+ if (!composition_layer_extension->is_android_surface_swapchain_available()) {
+ ERR_PRINT_ONCE("OpenXR: Cannot use Android surface for composition layer because the extension isn't available");
}
+
+ if (subviewport.viewport.is_valid()) {
+ set_viewport(RID(), Size2i());
+ }
+
+ swapchain_size = p_size;
+ } else {
+ free_swapchain();
+ }
+#endif
+}
+
+#ifdef ANDROID_ENABLED
+void OpenXRViewportCompositionLayerProvider::create_android_surface() {
+ ERR_FAIL_COND(android_surface.swapchain != XR_NULL_HANDLE || android_surface.surface.is_valid());
+ ERR_FAIL_COND(!openxr_api || !openxr_api->is_running());
+
+ // The XR_FB_android_surface_swapchain_create extension mandates that format, sampleCount,
+ // faceCount, arraySize, and mipCount must be zero.
+ XrSwapchainCreateInfo info = {
+ XR_TYPE_SWAPCHAIN_CREATE_INFO, // type
+ nullptr, // next
+ 0, // createFlags
+ XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, // usageFlags
+ 0, // format
+ 0, // sampleCount
+ (uint32_t)swapchain_size.x, // width
+ (uint32_t)swapchain_size.y, // height
+ 0, // faceCount
+ 0, // arraySize
+ 0, // mipCount
+ };
+
+ jobject surface;
+ composition_layer_extension->create_android_surface_swapchain(&info, &android_surface.swapchain, &surface);
+
+ if (surface) {
+ android_surface.surface = Ref<JavaObject>(memnew(JavaObject(JavaClassWrapper::get_singleton()->wrap("android.view.Surface"), surface)));
+ }
+}
+#endif
+
+Ref<JavaObject> OpenXRViewportCompositionLayerProvider::get_android_surface() {
+#ifdef ANDROID_ENABLED
+ if (use_android_surface) {
+ if (android_surface.surface.is_null()) {
+ create_android_surface();
+ }
+ return android_surface.surface;
}
+#endif
+ return Ref<JavaObject>();
}
void OpenXRViewportCompositionLayerProvider::set_extension_property_values(const Dictionary &p_extension_property_values) {
@@ -169,16 +297,25 @@ void OpenXRViewportCompositionLayerProvider::set_extension_property_values(const
}
void OpenXRViewportCompositionLayerProvider::on_pre_render() {
+#ifdef ANDROID_ENABLED
+ if (use_android_surface) {
+ if (android_surface.surface.is_null()) {
+ create_android_surface();
+ }
+ return;
+ }
+#endif
+
RenderingServer *rs = RenderingServer::get_singleton();
ERR_FAIL_NULL(rs);
- if (viewport.is_valid() && openxr_api && openxr_api->is_running()) {
- RS::ViewportUpdateMode update_mode = rs->viewport_get_update_mode(viewport);
+ if (subviewport.viewport.is_valid() && openxr_api && openxr_api->is_running()) {
+ RS::ViewportUpdateMode update_mode = rs->viewport_get_update_mode(subviewport.viewport);
if (update_mode == RS::VIEWPORT_UPDATE_ONCE || update_mode == RS::VIEWPORT_UPDATE_ALWAYS) {
// Update our XR swapchain
if (update_and_acquire_swapchain(update_mode == RS::VIEWPORT_UPDATE_ONCE)) {
// Render to our XR swapchain image.
- RID rt = rs->viewport_get_render_target(viewport);
+ RID rt = rs->viewport_get_render_target(subviewport.viewport);
RSG::texture_storage->render_target_set_override(rt, get_current_swapchain_texture(), RID(), RID());
}
}
@@ -196,48 +333,36 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos
return nullptr;
}
- if (swapchain_info.get_swapchain() == XR_NULL_HANDLE) {
+ XrSwapchainSubImage subimage = {
+ 0, // swapchain
+ { { 0, 0 }, { 0, 0 } }, // imageRect
+ 0, // imageArrayIndex
+ };
+ update_swapchain_sub_image(subimage);
+
+ if (subimage.swapchain == XR_NULL_HANDLE) {
// Don't have a swapchain to display? Ignore our layer.
return nullptr;
}
- if (swapchain_info.is_image_acquired()) {
- swapchain_info.release();
- }
-
// Update the layer struct for the swapchain.
switch (composition_layer->type) {
case XR_TYPE_COMPOSITION_LAYER_QUAD: {
XrCompositionLayerQuad *quad_layer = (XrCompositionLayerQuad *)composition_layer;
quad_layer->space = openxr_api->get_play_space();
- quad_layer->subImage.swapchain = swapchain_info.get_swapchain();
- quad_layer->subImage.imageArrayIndex = 0;
- quad_layer->subImage.imageRect.offset.x = 0;
- quad_layer->subImage.imageRect.offset.y = 0;
- quad_layer->subImage.imageRect.extent.width = swapchain_size.width;
- quad_layer->subImage.imageRect.extent.height = swapchain_size.height;
+ quad_layer->subImage = subimage;
} break;
case XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR: {
XrCompositionLayerCylinderKHR *cylinder_layer = (XrCompositionLayerCylinderKHR *)composition_layer;
cylinder_layer->space = openxr_api->get_play_space();
- cylinder_layer->subImage.swapchain = swapchain_info.get_swapchain();
- cylinder_layer->subImage.imageArrayIndex = 0;
- cylinder_layer->subImage.imageRect.offset.x = 0;
- cylinder_layer->subImage.imageRect.offset.y = 0;
- cylinder_layer->subImage.imageRect.extent.width = swapchain_size.width;
- cylinder_layer->subImage.imageRect.extent.height = swapchain_size.height;
+ cylinder_layer->subImage = subimage;
} break;
case XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR: {
XrCompositionLayerEquirect2KHR *equirect_layer = (XrCompositionLayerEquirect2KHR *)composition_layer;
equirect_layer->space = openxr_api->get_play_space();
- equirect_layer->subImage.swapchain = swapchain_info.get_swapchain();
- equirect_layer->subImage.imageArrayIndex = 0;
- equirect_layer->subImage.imageRect.offset.x = 0;
- equirect_layer->subImage.imageRect.offset.y = 0;
- equirect_layer->subImage.imageRect.extent.width = swapchain_size.width;
- equirect_layer->subImage.imageRect.extent.height = swapchain_size.height;
+ equirect_layer->subImage = subimage;
} break;
default: {
@@ -261,27 +386,49 @@ XrCompositionLayerBaseHeader *OpenXRViewportCompositionLayerProvider::get_compos
return composition_layer;
}
+void OpenXRViewportCompositionLayerProvider::update_swapchain_sub_image(XrSwapchainSubImage &r_subimage) {
+#ifdef ANDROID_ENABLED
+ if (use_android_surface) {
+ r_subimage.swapchain = android_surface.swapchain;
+ } else
+#endif
+ {
+ XrSwapchain swapchain = subviewport.swapchain_info.get_swapchain();
+
+ if (swapchain && subviewport.swapchain_info.is_image_acquired()) {
+ subviewport.swapchain_info.release();
+ }
+
+ r_subimage.swapchain = swapchain;
+ }
+
+ r_subimage.imageRect.extent.width = swapchain_size.width;
+ r_subimage.imageRect.extent.height = swapchain_size.height;
+}
+
bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p_static_image) {
+ ERR_FAIL_COND_V(use_android_surface, false);
+
if (openxr_api == nullptr || composition_layer_extension == nullptr) {
// OpenXR not initialized or we're in the editor?
return false;
}
- if (!composition_layer_extension->is_available(composition_layer->type)) {
+ if (!composition_layer_extension->is_available(get_openxr_type())) {
// Selected type is not supported?
return false;
}
// See if our current swapchain is outdated.
- if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) {
+ if (subviewport.swapchain_info.get_swapchain() != XR_NULL_HANDLE) {
// If this swap chain, or the previous one, were static, then we can't reuse it.
- if (swapchain_size == viewport_size && !p_static_image && !static_image) {
+ if (swapchain_size == subviewport.viewport_size && !p_static_image && !subviewport.static_image) {
// We're all good! Just acquire it.
// We can ignore should_render here, return will be false.
bool should_render = true;
- return swapchain_info.acquire(should_render);
+ return subviewport.swapchain_info.acquire(should_render);
}
- swapchain_info.queue_free();
+ subviewport.swapchain_info.queue_free();
}
// Create our new swap chain
@@ -292,7 +439,7 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p
if (p_static_image) {
create_flags |= XR_SWAPCHAIN_CREATE_STATIC_IMAGE_BIT;
}
- if (!swapchain_info.create(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, viewport_size.width, viewport_size.height, sample_count, array_size)) {
+ if (!subviewport.swapchain_info.create(create_flags, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format, subviewport.viewport_size.width, subviewport.viewport_size.height, sample_count, array_size)) {
swapchain_size = Size2i();
return false;
}
@@ -300,26 +447,40 @@ bool OpenXRViewportCompositionLayerProvider::update_and_acquire_swapchain(bool p
// Acquire our image so we can start rendering into it,
// we can ignore should_render here, ret will be false.
bool should_render = true;
- bool ret = swapchain_info.acquire(should_render);
+ bool ret = subviewport.swapchain_info.acquire(should_render);
- swapchain_size = viewport_size;
- static_image = p_static_image;
+ swapchain_size = subviewport.viewport_size;
+ subviewport.static_image = p_static_image;
return ret;
}
void OpenXRViewportCompositionLayerProvider::free_swapchain() {
- if (swapchain_info.get_swapchain() != XR_NULL_HANDLE) {
- swapchain_info.queue_free();
+#ifdef ANDROID_ENABLED
+ if (use_android_surface) {
+ if (android_surface.swapchain != XR_NULL_HANDLE) {
+ composition_layer_extension->free_android_surface_swapchain(android_surface.swapchain);
+
+ android_surface.swapchain = XR_NULL_HANDLE;
+ android_surface.surface.unref();
+ }
+ } else
+#endif
+ {
+ if (subviewport.swapchain_info.get_swapchain() != XR_NULL_HANDLE) {
+ subviewport.swapchain_info.queue_free();
+ }
+ subviewport.static_image = false;
}
swapchain_size = Size2i();
- static_image = false;
}
RID OpenXRViewportCompositionLayerProvider::get_current_swapchain_texture() {
+ ERR_FAIL_COND_V(use_android_surface, RID());
+
if (openxr_api == nullptr) {
return RID();
}
- return swapchain_info.get_image();
+ return subviewport.swapchain_info.get_image();
}
diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.h b/modules/openxr/extensions/openxr_composition_layer_extension.h
index 34e330a60a..bce34f098c 100644
--- a/modules/openxr/extensions/openxr_composition_layer_extension.h
+++ b/modules/openxr/extensions/openxr_composition_layer_extension.h
@@ -36,6 +36,15 @@
#include "../openxr_api.h"
+#ifdef ANDROID_ENABLED
+#include <jni.h>
+
+// Copied here from openxr_platform.h, in order to avoid including that whole header,
+// which can cause compilation issues on some platforms.
+typedef XrResult(XRAPI_PTR *PFN_xrCreateSwapchainAndroidSurfaceKHR)(XrSession session, const XrSwapchainCreateInfo *info, XrSwapchain *swapchain, jobject *surface);
+#endif
+
+class JavaObject;
class OpenXRViewportCompositionLayerProvider;
// This extension provides access to composition layers for displaying 2D content through the XR compositor.
@@ -49,6 +58,7 @@ public:
virtual ~OpenXRCompositionLayerExtension() override;
virtual HashMap<String, bool *> get_requested_extensions() override;
+ virtual void on_instance_created(const XrInstance p_instance) override;
virtual void on_session_created(const XrSession p_session) override;
virtual void on_session_destroyed() override;
virtual void on_pre_render() override;
@@ -61,6 +71,12 @@ public:
void unregister_viewport_composition_layer_provider(OpenXRViewportCompositionLayerProvider *p_composition_layer);
bool is_available(XrStructureType p_which);
+ bool is_android_surface_swapchain_available() { return android_surface_ext_available; }
+
+#ifdef ANDROID_ENABLED
+ bool create_android_surface_swapchain(XrSwapchainCreateInfo *p_info, XrSwapchain *r_swapchain, jobject *r_surface);
+ void free_android_surface_swapchain(XrSwapchain p_swapchain);
+#endif
private:
static OpenXRCompositionLayerExtension *singleton;
@@ -69,6 +85,15 @@ private:
bool cylinder_ext_available = false;
bool equirect_ext_available = false;
+ bool android_surface_ext_available = false;
+
+#ifdef ANDROID_ENABLED
+ Vector<XrSwapchain> android_surface_swapchain_free_queue;
+ void free_queued_android_surface_swapchains();
+
+ EXT_PROTO_XRRESULT_FUNC1(xrDestroySwapchain, (XrSwapchain), swapchain)
+ EXT_PROTO_XRRESULT_FUNC4(xrCreateSwapchainAndroidSurfaceKHR, (XrSession), session, (const XrSwapchainCreateInfo *), info, (XrSwapchain *), swapchain, (jobject *), surface)
+#endif
};
class OpenXRViewportCompositionLayerProvider {
@@ -78,20 +103,37 @@ class OpenXRViewportCompositionLayerProvider {
Dictionary extension_property_values;
bool extension_property_values_changed = true;
- RID viewport;
- Size2i viewport_size;
-
- OpenXRAPI::OpenXRSwapChainInfo swapchain_info;
+ struct {
+ RID viewport;
+ Size2i viewport_size;
+ OpenXRAPI::OpenXRSwapChainInfo swapchain_info;
+ bool static_image = false;
+ } subviewport;
+
+#ifdef ANDROID_ENABLED
+ struct {
+ XrSwapchain swapchain = XR_NULL_HANDLE;
+ Ref<JavaObject> surface;
+ } android_surface;
+#endif
+
+ bool use_android_surface = false;
Size2i swapchain_size;
- bool static_image = false;
OpenXRAPI *openxr_api = nullptr;
OpenXRCompositionLayerExtension *composition_layer_extension = nullptr;
+ // Only for SubViewports.
bool update_and_acquire_swapchain(bool p_static_image);
- void free_swapchain();
RID get_current_swapchain_texture();
+ void update_swapchain_sub_image(XrSwapchainSubImage &r_swapchain_sub_image);
+ void free_swapchain();
+
+#ifdef ANDROID_ENABLED
+ void create_android_surface();
+#endif
+
public:
XrStructureType get_openxr_type() { return composition_layer->type; }
@@ -102,7 +144,12 @@ public:
bool get_alpha_blend() const { return alpha_blend; }
void set_viewport(RID p_viewport, Size2i p_size);
- RID get_viewport() const { return viewport; }
+ RID get_viewport() const { return subviewport.viewport; }
+
+ void set_use_android_surface(bool p_enable, Size2i p_size);
+ bool get_use_android_surface() const { return use_android_surface; }
+
+ Ref<JavaObject> get_android_surface();
void set_extension_property_values(const Dictionary &p_property_values);
diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp
index a19a75e722..73b6f6c1c9 100644
--- a/modules/openxr/openxr_api.cpp
+++ b/modules/openxr/openxr_api.cpp
@@ -273,7 +273,9 @@ Vector<OpenXRExtensionWrapper *> OpenXRAPI::registered_extension_wrappers;
bool OpenXRAPI::openxr_is_enabled(bool p_check_run_in_editor) {
if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) {
if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) {
- return GLOBAL_GET("xr/openxr/enabled.editor");
+ // For now, don't start OpenXR when the editor starts up. In the future, this may change
+ // if we want to integrate more XR features into the editor experience.
+ return false;
} else {
return GLOBAL_GET("xr/openxr/enabled");
}
diff --git a/modules/openxr/scene/openxr_composition_layer.cpp b/modules/openxr/scene/openxr_composition_layer.cpp
index f69a907be9..697369d516 100644
--- a/modules/openxr/scene/openxr_composition_layer.cpp
+++ b/modules/openxr/scene/openxr_composition_layer.cpp
@@ -38,6 +38,8 @@
#include "scene/3d/xr_nodes.h"
#include "scene/main/viewport.h"
+#include "platform/android/api/java_class_wrapper.h"
+
Vector<OpenXRCompositionLayer *> OpenXRCompositionLayer::composition_layer_nodes;
static const char *HOLE_PUNCH_SHADER_CODE =
@@ -47,7 +49,10 @@ static const char *HOLE_PUNCH_SHADER_CODE =
"\tALBEDO = vec3(0.0, 0.0, 0.0);\n"
"}\n";
-OpenXRCompositionLayer::OpenXRCompositionLayer() {
+OpenXRCompositionLayer::OpenXRCompositionLayer(XrCompositionLayerBaseHeader *p_composition_layer) {
+ composition_layer_base_header = p_composition_layer;
+ openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider(composition_layer_base_header));
+
openxr_api = OpenXRAPI::get_singleton();
composition_layer_extension = OpenXRCompositionLayerExtension::get_singleton();
@@ -85,6 +90,12 @@ void OpenXRCompositionLayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_layer_viewport", "viewport"), &OpenXRCompositionLayer::set_layer_viewport);
ClassDB::bind_method(D_METHOD("get_layer_viewport"), &OpenXRCompositionLayer::get_layer_viewport);
+ ClassDB::bind_method(D_METHOD("set_use_android_surface", "enable"), &OpenXRCompositionLayer::set_use_android_surface);
+ ClassDB::bind_method(D_METHOD("get_use_android_surface"), &OpenXRCompositionLayer::get_use_android_surface);
+
+ ClassDB::bind_method(D_METHOD("set_android_surface_size", "size"), &OpenXRCompositionLayer::set_android_surface_size);
+ ClassDB::bind_method(D_METHOD("get_android_surface_size"), &OpenXRCompositionLayer::get_android_surface_size);
+
ClassDB::bind_method(D_METHOD("set_enable_hole_punch", "enable"), &OpenXRCompositionLayer::set_enable_hole_punch);
ClassDB::bind_method(D_METHOD("get_enable_hole_punch"), &OpenXRCompositionLayer::get_enable_hole_punch);
@@ -94,11 +105,14 @@ void OpenXRCompositionLayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_alpha_blend", "enabled"), &OpenXRCompositionLayer::set_alpha_blend);
ClassDB::bind_method(D_METHOD("get_alpha_blend"), &OpenXRCompositionLayer::get_alpha_blend);
+ ClassDB::bind_method(D_METHOD("get_android_surface"), &OpenXRCompositionLayer::get_android_surface);
ClassDB::bind_method(D_METHOD("is_natively_supported"), &OpenXRCompositionLayer::is_natively_supported);
ClassDB::bind_method(D_METHOD("intersects_ray", "origin", "direction"), &OpenXRCompositionLayer::intersects_ray);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "layer_viewport", PROPERTY_HINT_NODE_TYPE, "SubViewport"), "set_layer_viewport", "get_layer_viewport");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_android_surface", PROPERTY_HINT_NONE, ""), "set_use_android_surface", "get_use_android_surface");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "android_surface_size", PROPERTY_HINT_NONE, ""), "set_android_surface_size", "get_android_surface_size");
ADD_PROPERTY(PropertyInfo(Variant::INT, "sort_order", PROPERTY_HINT_NONE, ""), "set_sort_order", "get_sort_order");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "alpha_blend", PROPERTY_HINT_NONE, ""), "set_alpha_blend", "get_alpha_blend");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enable_hole_punch", PROPERTY_HINT_NONE, ""), "set_enable_hole_punch", "get_enable_hole_punch");
@@ -108,7 +122,7 @@ bool OpenXRCompositionLayer::_should_use_fallback_node() {
if (Engine::get_singleton()->is_editor_hint()) {
return true;
} else if (openxr_session_running) {
- return enable_hole_punch || !is_natively_supported();
+ return enable_hole_punch || (!is_natively_supported() && !use_android_surface);
}
return false;
}
@@ -128,10 +142,36 @@ void OpenXRCompositionLayer::_remove_fallback_node() {
fallback = nullptr;
}
+void OpenXRCompositionLayer::_setup_composition_layer_provider() {
+ if (use_android_surface || layer_viewport) {
+ if (composition_layer_extension) {
+ composition_layer_extension->register_viewport_composition_layer_provider(openxr_layer_provider);
+ }
+
+ // NOTE: We don't setup/clear when using Android surfaces, so we don't destroy the surface unexpectedly.
+ if (layer_viewport) {
+ // Set our properties on the layer provider, which will create all the necessary resources (ex swap chains).
+ openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
+ }
+ }
+}
+
+void OpenXRCompositionLayer::_clear_composition_layer_provider() {
+ if (composition_layer_extension) {
+ composition_layer_extension->unregister_viewport_composition_layer_provider(openxr_layer_provider);
+ }
+
+ // NOTE: We don't setup/clear when using Android surfaces, so we don't destroy the surface unexpectedly.
+ if (!use_android_surface) {
+ // This will reset the viewport and free all the resources (ex swap chains) used by the layer.
+ openxr_layer_provider->set_viewport(RID(), Size2i());
+ }
+}
+
void OpenXRCompositionLayer::_on_openxr_session_begun() {
openxr_session_running = true;
- if (layer_viewport && is_natively_supported() && is_visible() && is_inside_tree()) {
- openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
+ if (is_natively_supported() && is_visible() && is_inside_tree()) {
+ _setup_composition_layer_provider();
}
if (!fallback && _should_use_fallback_node()) {
_create_fallback_node();
@@ -142,9 +182,8 @@ void OpenXRCompositionLayer::_on_openxr_session_stopping() {
openxr_session_running = false;
if (fallback && !_should_use_fallback_node()) {
_remove_fallback_node();
- } else {
- openxr_layer_provider->set_viewport(RID(), Size2i());
}
+ _clear_composition_layer_provider();
}
void OpenXRCompositionLayer::update_fallback_mesh() {
@@ -162,6 +201,7 @@ XrPosef OpenXRCompositionLayer::get_openxr_pose() {
}
bool OpenXRCompositionLayer::is_viewport_in_use(SubViewport *p_viewport) {
+ ERR_FAIL_NULL_V(p_viewport, false);
for (const OpenXRCompositionLayer *other_composition_layer : composition_layer_nodes) {
if (other_composition_layer != this && other_composition_layer->is_inside_tree() && other_composition_layer->get_layer_viewport() == p_viewport) {
return true;
@@ -178,6 +218,9 @@ void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) {
if (p_viewport != nullptr) {
ERR_FAIL_COND_EDMSG(is_viewport_in_use(p_viewport), RTR("Cannot use the same SubViewport with multiple OpenXR composition layers. Clear it from its current layer first."));
}
+ if (use_android_surface) {
+ ERR_FAIL_COND_MSG(p_viewport != nullptr, RTR("Cannot set SubViewport on an OpenXR composition layer when using an Android surface."));
+ }
layer_viewport = p_viewport;
@@ -200,6 +243,41 @@ void OpenXRCompositionLayer::set_layer_viewport(SubViewport *p_viewport) {
}
}
+void OpenXRCompositionLayer::set_use_android_surface(bool p_use_android_surface) {
+ if (use_android_surface == p_use_android_surface) {
+ return;
+ }
+
+ use_android_surface = p_use_android_surface;
+ if (use_android_surface) {
+ set_layer_viewport(nullptr);
+ openxr_layer_provider->set_use_android_surface(true, android_surface_size);
+ } else {
+ openxr_layer_provider->set_use_android_surface(false, Size2i());
+ }
+
+ notify_property_list_changed();
+}
+
+bool OpenXRCompositionLayer::get_use_android_surface() const {
+ return use_android_surface;
+}
+
+void OpenXRCompositionLayer::set_android_surface_size(Size2i p_size) {
+ if (android_surface_size == p_size) {
+ return;
+ }
+
+ android_surface_size = p_size;
+ if (use_android_surface) {
+ openxr_layer_provider->set_use_android_surface(true, android_surface_size);
+ }
+}
+
+Size2i OpenXRCompositionLayer::get_android_surface_size() const {
+ return android_surface_size;
+}
+
SubViewport *OpenXRCompositionLayer::get_layer_viewport() const {
return layer_viewport;
}
@@ -228,33 +306,23 @@ bool OpenXRCompositionLayer::get_enable_hole_punch() const {
}
void OpenXRCompositionLayer::set_sort_order(int p_order) {
- if (openxr_layer_provider) {
- openxr_layer_provider->set_sort_order(p_order);
- update_configuration_warnings();
- }
+ openxr_layer_provider->set_sort_order(p_order);
+ update_configuration_warnings();
}
int OpenXRCompositionLayer::get_sort_order() const {
- if (openxr_layer_provider) {
- return openxr_layer_provider->get_sort_order();
- }
- return 1;
+ return openxr_layer_provider->get_sort_order();
}
void OpenXRCompositionLayer::set_alpha_blend(bool p_alpha_blend) {
- if (openxr_layer_provider) {
- openxr_layer_provider->set_alpha_blend(p_alpha_blend);
- if (fallback) {
- _reset_fallback_material();
- }
+ openxr_layer_provider->set_alpha_blend(p_alpha_blend);
+ if (fallback) {
+ _reset_fallback_material();
}
}
bool OpenXRCompositionLayer::get_alpha_blend() const {
- if (openxr_layer_provider) {
- return openxr_layer_provider->get_alpha_blend();
- }
- return false;
+ return openxr_layer_provider->get_alpha_blend();
}
bool OpenXRCompositionLayer::is_natively_supported() const {
@@ -264,6 +332,10 @@ bool OpenXRCompositionLayer::is_natively_supported() const {
return false;
}
+Ref<JavaObject> OpenXRCompositionLayer::get_android_surface() {
+ return openxr_layer_provider->get_android_surface();
+}
+
Vector2 OpenXRCompositionLayer::intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const {
return Vector2(-1.0, -1.0);
}
@@ -301,10 +373,7 @@ void OpenXRCompositionLayer::_reset_fallback_material() {
Ref<ViewportTexture> texture = material->get_texture(StandardMaterial3D::TEXTURE_ALBEDO);
if (texture.is_null()) {
- texture.instantiate();
- // ViewportTexture can't be configured without a local scene, so use this hack to set it.
- HashMap<Ref<Resource>, Ref<Resource>> remap_cache;
- texture->configure_for_local_scene(this, remap_cache);
+ texture = layer_viewport->get_texture();
}
Node *loc_scene = texture->get_local_scene();
@@ -321,12 +390,10 @@ void OpenXRCompositionLayer::_notification(int p_what) {
case NOTIFICATION_POSTINITIALIZE: {
composition_layer_nodes.push_back(this);
- if (openxr_layer_provider) {
- for (OpenXRExtensionWrapper *extension : OpenXRAPI::get_registered_extension_wrappers()) {
- extension_property_values.merge(extension->get_viewport_composition_layer_extension_property_defaults());
- }
- openxr_layer_provider->set_extension_property_values(extension_property_values);
+ for (OpenXRExtensionWrapper *extension : OpenXRAPI::get_registered_extension_wrappers()) {
+ extension_property_values.merge(extension->get_viewport_composition_layer_extension_property_defaults());
}
+ openxr_layer_provider->set_extension_property_values(extension_property_values);
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (fallback) {
@@ -339,10 +406,10 @@ void OpenXRCompositionLayer::_notification(int p_what) {
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!fallback && openxr_session_running && is_inside_tree()) {
- if (layer_viewport && is_visible()) {
- openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
+ if (is_visible()) {
+ _setup_composition_layer_provider();
} else {
- openxr_layer_provider->set_viewport(RID(), Size2i());
+ _clear_composition_layer_provider();
}
}
update_configuration_warnings();
@@ -351,25 +418,15 @@ void OpenXRCompositionLayer::_notification(int p_what) {
update_configuration_warnings();
} break;
case NOTIFICATION_ENTER_TREE: {
- if (composition_layer_extension) {
- composition_layer_extension->register_viewport_composition_layer_provider(openxr_layer_provider);
- }
-
- if (is_viewport_in_use(layer_viewport)) {
- set_layer_viewport(nullptr);
- } else if (!fallback && layer_viewport && openxr_session_running && is_visible()) {
- openxr_layer_provider->set_viewport(layer_viewport->get_viewport_rid(), layer_viewport->get_size());
+ if (layer_viewport && is_viewport_in_use(layer_viewport)) {
+ _clear_composition_layer_provider();
+ } else if (openxr_session_running && is_visible()) {
+ _setup_composition_layer_provider();
}
} break;
case NOTIFICATION_EXIT_TREE: {
- if (composition_layer_extension) {
- composition_layer_extension->unregister_viewport_composition_layer_provider(openxr_layer_provider);
- }
-
- if (!fallback) {
- // This will clean up existing resources.
- openxr_layer_provider->set_viewport(RID(), Size2i());
- }
+ // This will clean up existing resources.
+ _clear_composition_layer_provider();
} break;
}
}
@@ -401,13 +458,27 @@ bool OpenXRCompositionLayer::_get(const StringName &p_property, Variant &r_value
bool OpenXRCompositionLayer::_set(const StringName &p_property, const Variant &p_value) {
extension_property_values[p_property] = p_value;
- if (openxr_layer_provider) {
- openxr_layer_provider->set_extension_property_values(extension_property_values);
- }
+ openxr_layer_provider->set_extension_property_values(extension_property_values);
return true;
}
+void OpenXRCompositionLayer::_validate_property(PropertyInfo &p_property) const {
+ if (p_property.name == "layer_viewport") {
+ if (use_android_surface) {
+ p_property.usage &= ~PROPERTY_USAGE_EDITOR;
+ } else {
+ p_property.usage |= PROPERTY_USAGE_EDITOR;
+ }
+ } else if (p_property.name == "android_surface_size") {
+ if (use_android_surface) {
+ p_property.usage |= PROPERTY_USAGE_EDITOR;
+ } else {
+ p_property.usage &= ~PROPERTY_USAGE_EDITOR;
+ }
+ }
+}
+
PackedStringArray OpenXRCompositionLayer::get_configuration_warnings() const {
PackedStringArray warnings = Node3D::get_configuration_warnings();
diff --git a/modules/openxr/scene/openxr_composition_layer.h b/modules/openxr/scene/openxr_composition_layer.h
index 55cae27d23..26b40236d2 100644
--- a/modules/openxr/scene/openxr_composition_layer.h
+++ b/modules/openxr/scene/openxr_composition_layer.h
@@ -35,6 +35,7 @@
#include "scene/3d/node_3d.h"
+class JavaObject;
class MeshInstance3D;
class Mesh;
class OpenXRAPI;
@@ -45,7 +46,12 @@ class SubViewport;
class OpenXRCompositionLayer : public Node3D {
GDCLASS(OpenXRCompositionLayer, Node3D);
+ XrCompositionLayerBaseHeader *composition_layer_base_header = nullptr;
+ OpenXRViewportCompositionLayerProvider *openxr_layer_provider = nullptr;
+
SubViewport *layer_viewport = nullptr;
+ bool use_android_surface = false;
+ Size2i android_surface_size = Size2i(1024, 1024);
bool enable_hole_punch = false;
MeshInstance3D *fallback = nullptr;
bool should_update_fallback_mesh = false;
@@ -58,10 +64,12 @@ class OpenXRCompositionLayer : public Node3D {
void _reset_fallback_material();
void _remove_fallback_node();
+ void _setup_composition_layer_provider();
+ void _clear_composition_layer_provider();
+
protected:
OpenXRAPI *openxr_api = nullptr;
OpenXRCompositionLayerExtension *composition_layer_extension = nullptr;
- OpenXRViewportCompositionLayerProvider *openxr_layer_provider = nullptr;
static void _bind_methods();
@@ -69,6 +77,7 @@ protected:
void _get_property_list(List<PropertyInfo> *p_property_list) const;
bool _get(const StringName &p_property, Variant &r_value) const;
bool _set(const StringName &p_property, const Variant &p_value);
+ void _validate_property(PropertyInfo &p_property) const;
virtual void _on_openxr_session_begun();
virtual void _on_openxr_session_stopping();
@@ -82,10 +91,18 @@ protected:
static Vector<OpenXRCompositionLayer *> composition_layer_nodes;
bool is_viewport_in_use(SubViewport *p_viewport);
+ OpenXRCompositionLayer(XrCompositionLayerBaseHeader *p_composition_layer);
+
public:
void set_layer_viewport(SubViewport *p_viewport);
SubViewport *get_layer_viewport() const;
+ void set_use_android_surface(bool p_use_android_surface);
+ bool get_use_android_surface() const;
+
+ void set_android_surface_size(Size2i p_size);
+ Size2i get_android_surface_size() const;
+
void set_enable_hole_punch(bool p_enable);
bool get_enable_hole_punch() const;
@@ -95,13 +112,13 @@ public:
void set_alpha_blend(bool p_alpha_blend);
bool get_alpha_blend() const;
+ Ref<JavaObject> get_android_surface();
bool is_natively_supported() const;
virtual PackedStringArray get_configuration_warnings() const override;
virtual Vector2 intersects_ray(const Vector3 &p_origin, const Vector3 &p_direction) const;
- OpenXRCompositionLayer();
~OpenXRCompositionLayer();
};
diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.cpp b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp
index 6c8d2fc885..727586467a 100644
--- a/modules/openxr/scene/openxr_composition_layer_cylinder.cpp
+++ b/modules/openxr/scene/openxr_composition_layer_cylinder.cpp
@@ -38,20 +38,8 @@
#include "scene/main/viewport.h"
#include "scene/resources/mesh.h"
-OpenXRCompositionLayerCylinder::OpenXRCompositionLayerCylinder() {
- composition_layer = {
- XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR, // type
- nullptr, // next
- 0, // layerFlags
- XR_NULL_HANDLE, // space
- XR_EYE_VISIBILITY_BOTH, // eyeVisibility
- {}, // subImage
- { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
- radius, // radius
- central_angle, // centralAngle
- aspect_ratio, // aspectRatio
- };
- openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer));
+OpenXRCompositionLayerCylinder::OpenXRCompositionLayerCylinder() :
+ OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) {
XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerCylinder::update_transform));
}
diff --git a/modules/openxr/scene/openxr_composition_layer_cylinder.h b/modules/openxr/scene/openxr_composition_layer_cylinder.h
index 9bd5a42d36..a701575972 100644
--- a/modules/openxr/scene/openxr_composition_layer_cylinder.h
+++ b/modules/openxr/scene/openxr_composition_layer_cylinder.h
@@ -38,7 +38,18 @@
class OpenXRCompositionLayerCylinder : public OpenXRCompositionLayer {
GDCLASS(OpenXRCompositionLayerCylinder, OpenXRCompositionLayer);
- XrCompositionLayerCylinderKHR composition_layer;
+ XrCompositionLayerCylinderKHR composition_layer = {
+ XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR, // type
+ nullptr, // next
+ 0, // layerFlags
+ XR_NULL_HANDLE, // space
+ XR_EYE_VISIBILITY_BOTH, // eyeVisibility
+ {}, // subImage
+ { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
+ 1.0, // radius
+ Math_PI / 2.0, // centralAngle
+ 1.0, // aspectRatio
+ };
float radius = 1.0;
float aspect_ratio = 1.0;
diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.cpp b/modules/openxr/scene/openxr_composition_layer_equirect.cpp
index b6f5d27ffe..2fce26c965 100644
--- a/modules/openxr/scene/openxr_composition_layer_equirect.cpp
+++ b/modules/openxr/scene/openxr_composition_layer_equirect.cpp
@@ -38,21 +38,8 @@
#include "scene/main/viewport.h"
#include "scene/resources/mesh.h"
-OpenXRCompositionLayerEquirect::OpenXRCompositionLayerEquirect() {
- composition_layer = {
- XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR, // type
- nullptr, // next
- 0, // layerFlags
- XR_NULL_HANDLE, // space
- XR_EYE_VISIBILITY_BOTH, // eyeVisibility
- {}, // subImage
- { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
- radius, // radius
- central_horizontal_angle, // centralHorizontalAngle
- upper_vertical_angle, // upperVerticalAngle
- -lower_vertical_angle, // lowerVerticalAngle
- };
- openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer));
+OpenXRCompositionLayerEquirect::OpenXRCompositionLayerEquirect() :
+ OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) {
XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerEquirect::update_transform));
}
diff --git a/modules/openxr/scene/openxr_composition_layer_equirect.h b/modules/openxr/scene/openxr_composition_layer_equirect.h
index af6cd92cbe..45a65fe5aa 100644
--- a/modules/openxr/scene/openxr_composition_layer_equirect.h
+++ b/modules/openxr/scene/openxr_composition_layer_equirect.h
@@ -38,7 +38,19 @@
class OpenXRCompositionLayerEquirect : public OpenXRCompositionLayer {
GDCLASS(OpenXRCompositionLayerEquirect, OpenXRCompositionLayer);
- XrCompositionLayerEquirect2KHR composition_layer;
+ XrCompositionLayerEquirect2KHR composition_layer = {
+ XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR, // type
+ nullptr, // next
+ 0, // layerFlags
+ XR_NULL_HANDLE, // space
+ XR_EYE_VISIBILITY_BOTH, // eyeVisibility
+ {}, // subImage
+ { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
+ 1.0, // radius
+ Math_PI / 2.0, // centralHorizontalAngle
+ Math_PI / 4.0, // upperVerticalAngle
+ -Math_PI / 4.0, // lowerVerticalAngle
+ };
float radius = 1.0;
float central_horizontal_angle = Math_PI / 2.0;
diff --git a/modules/openxr/scene/openxr_composition_layer_quad.cpp b/modules/openxr/scene/openxr_composition_layer_quad.cpp
index 21919496d6..4a00fd371e 100644
--- a/modules/openxr/scene/openxr_composition_layer_quad.cpp
+++ b/modules/openxr/scene/openxr_composition_layer_quad.cpp
@@ -38,18 +38,8 @@
#include "scene/main/viewport.h"
#include "scene/resources/3d/primitive_meshes.h"
-OpenXRCompositionLayerQuad::OpenXRCompositionLayerQuad() {
- composition_layer = {
- XR_TYPE_COMPOSITION_LAYER_QUAD, // type
- nullptr, // next
- 0, // layerFlags
- XR_NULL_HANDLE, // space
- XR_EYE_VISIBILITY_BOTH, // eyeVisibility
- {}, // subImage
- { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
- { (float)quad_size.x, (float)quad_size.y }, // size
- };
- openxr_layer_provider = memnew(OpenXRViewportCompositionLayerProvider((XrCompositionLayerBaseHeader *)&composition_layer));
+OpenXRCompositionLayerQuad::OpenXRCompositionLayerQuad() :
+ OpenXRCompositionLayer((XrCompositionLayerBaseHeader *)&composition_layer) {
XRServer::get_singleton()->connect("reference_frame_changed", callable_mp(this, &OpenXRCompositionLayerQuad::update_transform));
}
diff --git a/modules/openxr/scene/openxr_composition_layer_quad.h b/modules/openxr/scene/openxr_composition_layer_quad.h
index 0c3172dbb2..a4ccfc6d8e 100644
--- a/modules/openxr/scene/openxr_composition_layer_quad.h
+++ b/modules/openxr/scene/openxr_composition_layer_quad.h
@@ -38,7 +38,16 @@
class OpenXRCompositionLayerQuad : public OpenXRCompositionLayer {
GDCLASS(OpenXRCompositionLayerQuad, OpenXRCompositionLayer);
- XrCompositionLayerQuad composition_layer;
+ XrCompositionLayerQuad composition_layer = {
+ XR_TYPE_COMPOSITION_LAYER_QUAD, // type
+ nullptr, // next
+ 0, // layerFlags
+ XR_NULL_HANDLE, // space
+ XR_EYE_VISIBILITY_BOTH, // eyeVisibility
+ {}, // subImage
+ { { 0, 0, 0, 0 }, { 0, 0, 0 } }, // pose
+ { 1.0, 1.0 }, // size
+ };
Size2 quad_size = Size2(1.0, 1.0);
diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp
index ff032c88c6..c89534a60c 100644
--- a/modules/vorbis/audio_stream_ogg_vorbis.cpp
+++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp
@@ -390,6 +390,9 @@ Ref<AudioSamplePlayback> AudioStreamPlaybackOggVorbis::get_sample_playback() con
void AudioStreamPlaybackOggVorbis::set_sample_playback(const Ref<AudioSamplePlayback> &p_playback) {
sample_playback = p_playback;
+ if (sample_playback.is_valid()) {
+ sample_playback->stream_playback = Ref<AudioStreamPlayback>(this);
+ }
}
AudioStreamPlaybackOggVorbis::~AudioStreamPlaybackOggVorbis() {