diff options
Diffstat (limited to 'modules')
100 files changed, 3236 insertions, 575 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/bmp/image_loader_bmp.cpp b/modules/bmp/image_loader_bmp.cpp index 72b540496d..1804d73a69 100644 --- a/modules/bmp/image_loader_bmp.cpp +++ b/modules/bmp/image_loader_bmp.cpp @@ -59,30 +59,6 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, size_t height = (size_t)p_header.bmp_info_header.bmp_height; size_t bits_per_pixel = (size_t)p_header.bmp_info_header.bmp_bit_count; - // Check whether we can load it - - if (bits_per_pixel == 1) { - // Requires bit unpacking... - ERR_FAIL_COND_V_MSG(width % 8 != 0, ERR_UNAVAILABLE, - vformat("1-bpp BMP images must have a width that is a multiple of 8, but the imported BMP is %d pixels wide.", int(width))); - ERR_FAIL_COND_V_MSG(height % 8 != 0, ERR_UNAVAILABLE, - vformat("1-bpp BMP images must have a height that is a multiple of 8, but the imported BMP is %d pixels tall.", int(height))); - - } else if (bits_per_pixel == 2) { - // Requires bit unpacking... - ERR_FAIL_COND_V_MSG(width % 4 != 0, ERR_UNAVAILABLE, - vformat("2-bpp BMP images must have a width that is a multiple of 4, but the imported BMP is %d pixels wide.", int(width))); - ERR_FAIL_COND_V_MSG(height % 4 != 0, ERR_UNAVAILABLE, - vformat("2-bpp BMP images must have a height that is a multiple of 4, but the imported BMP is %d pixels tall.", int(height))); - - } else if (bits_per_pixel == 4) { - // Requires bit unpacking... - ERR_FAIL_COND_V_MSG(width % 2 != 0, ERR_UNAVAILABLE, - vformat("4-bpp BMP images must have a width that is a multiple of 2, but the imported BMP is %d pixels wide.", int(width))); - ERR_FAIL_COND_V_MSG(height % 2 != 0, ERR_UNAVAILABLE, - vformat("4-bpp BMP images must have a height that is a multiple of 2, but the imported BMP is %d pixels tall.", int(height))); - } - // Image data (might be indexed) Vector<uint8_t> data; int data_len = 0; @@ -98,55 +74,32 @@ Error ImageLoaderBMP::convert_to_image(Ref<Image> p_image, uint8_t *data_w = data.ptrw(); uint8_t *write_buffer = data_w; - const uint32_t width_bytes = width * bits_per_pixel / 8; - const uint32_t line_width = (width_bytes + 3) & ~3; + const uint32_t width_bytes = (width * bits_per_pixel + 7) / 8; + const uint32_t line_width = (width_bytes + 3) & ~3; // Padded to 4 bytes. - // The actual data traversal is determined by - // the data width in case of 8/4/2/1 bit images - const uint32_t w = bits_per_pixel >= 16 ? width : width_bytes; const uint8_t *line = p_buffer + (line_width * (height - 1)); const uint8_t *end_buffer = p_buffer + p_header.bmp_file_header.bmp_file_size - p_header.bmp_file_header.bmp_file_offset; + ERR_FAIL_COND_V(line + line_width > end_buffer, ERR_FILE_CORRUPT); for (uint64_t i = 0; i < height; i++) { const uint8_t *line_ptr = line; - for (unsigned int j = 0; j < w; j++) { - ERR_FAIL_COND_V(line_ptr >= end_buffer, ERR_FILE_CORRUPT); + for (unsigned int j = 0; j < width; j++) { switch (bits_per_pixel) { case 1: { - uint8_t color_index = *line_ptr; + write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 8))) & 0x01; - write_buffer[index + 0] = (color_index >> 7) & 1; - write_buffer[index + 1] = (color_index >> 6) & 1; - write_buffer[index + 2] = (color_index >> 5) & 1; - write_buffer[index + 3] = (color_index >> 4) & 1; - write_buffer[index + 4] = (color_index >> 3) & 1; - write_buffer[index + 5] = (color_index >> 2) & 1; - write_buffer[index + 6] = (color_index >> 1) & 1; - write_buffer[index + 7] = (color_index >> 0) & 1; - - index += 8; - line_ptr += 1; + index++; } break; case 2: { - uint8_t color_index = *line_ptr; + write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 4))) & 0x03; - write_buffer[index + 0] = (color_index >> 6) & 3; - write_buffer[index + 1] = (color_index >> 4) & 3; - write_buffer[index + 2] = (color_index >> 2) & 3; - write_buffer[index + 3] = color_index & 3; - - index += 4; - line_ptr += 1; + index++; } break; case 4: { - uint8_t color_index = *line_ptr; - - write_buffer[index + 0] = (color_index >> 4) & 0x0f; - write_buffer[index + 1] = color_index & 0x0f; + write_buffer[index] = (line[(j * bits_per_pixel) / 8] >> (8 - bits_per_pixel * (1 + j % 2))) & 0x0f; - index += 2; - line_ptr += 1; + index++; } break; case 8: { uint8_t color_index = *line_ptr; 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_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 2de5811bca..d6fd5d043b 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -97,25 +97,25 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V } if (captures_amount > 0) { - Vector<const Variant *> args; - args.resize(p_argcount + captures_amount); + const int total_argcount = p_argcount + captures_amount; + const Variant **args = (const Variant **)alloca(sizeof(Variant *) * total_argcount); for (int i = 0; i < captures_amount; i++) { - args.write[i] = &captures[i]; + args[i] = &captures[i]; if (captures[i].get_type() == Variant::OBJECT) { bool was_freed = false; captures[i].get_validated_object_with_check(was_freed); if (was_freed) { ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i)); static Variant nil; - args.write[i] = &nil; + args[i] = &nil; } } } for (int i = 0; i < p_argcount; i++) { - args.write[i + captures_amount] = p_arguments[i]; + args[i + captures_amount] = p_arguments[i]; } - r_return_value = function->call(nullptr, args.ptrw(), args.size(), r_call_error); + r_return_value = function->call(nullptr, args, total_argcount, r_call_error); switch (r_call_error.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: r_call_error.argument -= captures_amount; @@ -229,25 +229,25 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun } if (captures_amount > 0) { - Vector<const Variant *> args; - args.resize(p_argcount + captures_amount); + const int total_argcount = p_argcount + captures_amount; + const Variant **args = (const Variant **)alloca(sizeof(Variant *) * total_argcount); for (int i = 0; i < captures_amount; i++) { - args.write[i] = &captures[i]; + args[i] = &captures[i]; if (captures[i].get_type() == Variant::OBJECT) { bool was_freed = false; captures[i].get_validated_object_with_check(was_freed); if (was_freed) { ERR_PRINT(vformat(R"(Lambda capture at index %d was freed. Passed "null" instead.)", i)); static Variant nil; - args.write[i] = &nil; + args[i] = &nil; } } } for (int i = 0; i < p_argcount; i++) { - args.write[i + captures_amount] = p_arguments[i]; + args[i + captures_amount] = p_arguments[i]; } - r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args.ptrw(), args.size(), r_call_error); + r_return_value = function->call(static_cast<GDScriptInstance *>(object->get_script_instance()), args, total_argcount, r_call_error); switch (r_call_error.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: r_call_error.argument -= captures_amount; 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_extend_parser.h b/modules/gdscript/language_server/gdscript_extend_parser.h index a808f19e5b..239f7d9f43 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.h +++ b/modules/gdscript/language_server/gdscript_extend_parser.h @@ -37,10 +37,10 @@ #include "core/variant/variant.h" #ifndef LINE_NUMBER_TO_INDEX -#define LINE_NUMBER_TO_INDEX(p_line) ((p_line)-1) +#define LINE_NUMBER_TO_INDEX(p_line) ((p_line) - 1) #endif #ifndef COLUMN_NUMBER_TO_INDEX -#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column)-1) +#define COLUMN_NUMBER_TO_INDEX(p_column) ((p_column) - 1) #endif #ifndef SYMBOL_SEPERATOR 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 fa5f279db9..06e9775360 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -229,19 +229,6 @@ Array GDScriptTextDocument::completion(const Dictionary &p_params) { arr[i] = item.to_json(); i++; } - } else if (GDScriptLanguageProtocol::get_singleton()->is_smart_resolve_enabled()) { - arr = native_member_completions.duplicate(); - - for (KeyValue<String, ExtendGDScriptParser *> &E : GDScriptLanguageProtocol::get_singleton()->get_workspace()->scripts) { - ExtendGDScriptParser *scr = E.value; - const Array &items = scr->get_member_completions(); - - const int start_size = arr.size(); - arr.resize(start_size + items.size()); - for (int i = start_size; i < arr.size(); i++) { - arr[i] = items[i - start_size]; - } - } } return arr; } @@ -485,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/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml index 5c548c472f..b33e296e1c 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -18,7 +18,7 @@ <param index="1" name="gltf_node" type="GLTFNode" /> <param index="2" name="scene_node" type="Node" /> <description> - Part of the export process. This method is run after [method _export_preflight] and before [method _export_preserialize]. + Part of the export process. This method is run after [method _export_preflight] and before [method _export_post_convert]. Runs when converting the data from a Godot scene node. This method can be used to process the Godot scene node data into a format that can be used by [method _export_node]. </description> </method> @@ -41,6 +41,15 @@ This method can be used to modify the final JSON of the generated glTF file. </description> </method> + <method name="_export_post_convert" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="state" type="GLTFState" /> + <param index="1" name="root" type="Node" /> + <description> + Part of the export process. This method is run after [method _convert_scene_node] and before [method _export_preserialize]. + This method can be used to modify the converted node data structures before serialization with any additional data from the scene tree. + </description> + </method> <method name="_export_preflight" qualifiers="virtual"> <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> @@ -54,7 +63,7 @@ <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <description> - Part of the export process. This method is run after [method _convert_scene_node] and before [method _get_saveable_image_formats]. + Part of the export process. This method is run after [method _export_post_convert] and before [method _get_saveable_image_formats]. This method can be used to alter the state before performing serialization. It runs every time when generating a buffer with [method GLTFDocument.generate_buffer] or writing to the file system with [method GLTFDocument.write_to_filesystem]. </description> </method> @@ -64,7 +73,7 @@ <param index="1" name="gltf_node" type="GLTFNode" /> <param index="2" name="scene_parent" type="Node" /> <description> - Part of the import process. This method is run after [method _import_post_parse] and before [method _import_node]. + Part of the import process. This method is run after [method _import_pre_generate] and before [method _import_node]. Runs when generating a Godot scene node from a GLTFNode. The returned node will be added to the scene tree. Multiple nodes can be generated in this step if they are added as a child of the returned node. [b]Note:[/b] The [param scene_parent] parameter may be null if this is the single root node. </description> @@ -113,8 +122,16 @@ <return type="int" enum="Error" /> <param index="0" name="state" type="GLTFState" /> <description> - Part of the import process. This method is run after [method _parse_node_extensions] and before [method _generate_scene_node]. - This method can be used to modify any of the data imported so far after parsing, before generating the nodes and then running the final per-node import step. + Part of the import process. This method is run after [method _parse_node_extensions] and before [method _import_pre_generate]. + This method can be used to modify any of the data imported so far after parsing each node, but before generating the scene or any of its nodes. + </description> + </method> + <method name="_import_pre_generate" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="state" type="GLTFState" /> + <description> + Part of the import process. This method is run after [method _import_post_parse] and before [method _generate_scene_node]. + This method can be used to modify or read from any of the processed data structures, before generating the nodes and then running the final per-node import step. </description> </method> <method name="_import_preflight" qualifiers="virtual"> 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/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp index c6540ebb22..6e611762b6 100644 --- a/modules/gltf/extensions/gltf_document_extension.cpp +++ b/modules/gltf/extensions/gltf_document_extension.cpp @@ -38,13 +38,15 @@ void GLTFDocumentExtension::_bind_methods() { GDVIRTUAL_BIND(_parse_image_data, "state", "image_data", "mime_type", "ret_image"); GDVIRTUAL_BIND(_get_image_file_extension); GDVIRTUAL_BIND(_parse_texture_json, "state", "texture_json", "ret_gltf_texture"); - GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent"); GDVIRTUAL_BIND(_import_post_parse, "state"); + GDVIRTUAL_BIND(_import_pre_generate, "state"); + GDVIRTUAL_BIND(_generate_scene_node, "state", "gltf_node", "scene_parent"); GDVIRTUAL_BIND(_import_node, "state", "gltf_node", "json", "node"); GDVIRTUAL_BIND(_import_post, "state", "root"); // Export process. GDVIRTUAL_BIND(_export_preflight, "state", "root"); GDVIRTUAL_BIND(_convert_scene_node, "state", "gltf_node", "scene_node"); + GDVIRTUAL_BIND(_export_post_convert, "state", "root"); GDVIRTUAL_BIND(_export_preserialize, "state"); GDVIRTUAL_BIND(_get_saveable_image_formats); GDVIRTUAL_BIND(_serialize_image_to_bytes, "state", "image", "image_dict", "image_format", "lossy_quality"); @@ -98,6 +100,20 @@ Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Di return err; } +Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) { + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + Error err = OK; + GDVIRTUAL_CALL(_import_post_parse, p_state, err); + return err; +} + +Error GLTFDocumentExtension::import_pre_generate(Ref<GLTFState> p_state) { + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + Error err = OK; + GDVIRTUAL_CALL(_import_pre_generate, p_state, err); + return err; +} + Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { ERR_FAIL_COND_V(p_state.is_null(), nullptr); ERR_FAIL_COND_V(p_gltf_node.is_null(), nullptr); @@ -106,13 +122,6 @@ Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<G return ret_node; } -Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) { - ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); - Error err = OK; - GDVIRTUAL_CALL(_import_post_parse, p_state, err); - return err; -} - Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER); @@ -145,6 +154,14 @@ void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFN GDVIRTUAL_CALL(_convert_scene_node, p_state, p_gltf_node, p_scene_node); } +Error GLTFDocumentExtension::export_post_convert(Ref<GLTFState> p_state, Node *p_root) { + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); + Error err = OK; + GDVIRTUAL_CALL(_export_post_convert, p_state, p_root, err); + return err; +} + Error GLTFDocumentExtension::export_preserialize(Ref<GLTFState> p_state) { ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; diff --git a/modules/gltf/extensions/gltf_document_extension.h b/modules/gltf/extensions/gltf_document_extension.h index 761dff725c..b70710e015 100644 --- a/modules/gltf/extensions/gltf_document_extension.h +++ b/modules/gltf/extensions/gltf_document_extension.h @@ -50,12 +50,14 @@ public: virtual String get_image_file_extension(); virtual Error parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture); virtual Error import_post_parse(Ref<GLTFState> p_state); + virtual Error import_pre_generate(Ref<GLTFState> p_state); virtual Node3D *generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent); virtual Error import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_json, Node *p_node); virtual Error import_post(Ref<GLTFState> p_state, Node *p_node); // Export process. virtual Error export_preflight(Ref<GLTFState> p_state, Node *p_root); virtual void convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node); + virtual Error export_post_convert(Ref<GLTFState> p_state, Node *p_root); virtual Error export_preserialize(Ref<GLTFState> p_state); virtual Vector<String> get_saveable_image_formats(); virtual PackedByteArray serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality); @@ -71,13 +73,15 @@ public: GDVIRTUAL4R(Error, _parse_image_data, Ref<GLTFState>, PackedByteArray, String, Ref<Image>); GDVIRTUAL0R(String, _get_image_file_extension); GDVIRTUAL3R(Error, _parse_texture_json, Ref<GLTFState>, Dictionary, Ref<GLTFTexture>); - GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); GDVIRTUAL1R(Error, _import_post_parse, Ref<GLTFState>); + GDVIRTUAL1R(Error, _import_pre_generate, Ref<GLTFState>); + GDVIRTUAL3R(Node3D *, _generate_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); GDVIRTUAL4R(Error, _import_node, Ref<GLTFState>, Ref<GLTFNode>, Dictionary, Node *); GDVIRTUAL2R(Error, _import_post, Ref<GLTFState>, Node *); // Export process. GDVIRTUAL2R(Error, _export_preflight, Ref<GLTFState>, Node *); GDVIRTUAL3(_convert_scene_node, Ref<GLTFState>, Ref<GLTFNode>, Node *); + GDVIRTUAL2R(Error, _export_post_convert, Ref<GLTFState>, Node *); GDVIRTUAL1R(Error, _export_preserialize, Ref<GLTFState>); GDVIRTUAL0R(Vector<String>, _get_saveable_image_formats); GDVIRTUAL5R(PackedByteArray, _serialize_image_to_bytes, Ref<GLTFState>, Ref<Image>, Dictionary, String, float); diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 4653df7afe..992075e980 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); @@ -5548,6 +5534,10 @@ void GLTFDocument::_convert_skeleton_to_gltf(Skeleton3D *p_skeleton3d, Ref<GLTFS joint_node->set_name(_gen_unique_name(p_state, skeleton->get_bone_name(bone_i))); joint_node->transform = skeleton->get_bone_pose(bone_i); joint_node->joint = true; + + if (p_skeleton3d->has_bone_meta(bone_i, "extras")) { + joint_node->set_meta("extras", p_skeleton3d->get_bone_meta(bone_i, "extras")); + } GLTFNodeIndex current_node_i = p_state->nodes.size(); p_state->scene_nodes.insert(current_node_i, skeleton); p_state->nodes.push_back(joint_node); @@ -7215,6 +7205,12 @@ Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) { ERR_FAIL_COND_V_MSG(err != OK, nullptr, "glTF: Failed to create skeletons."); err = _create_skins(p_state); ERR_FAIL_COND_V_MSG(err != OK, nullptr, "glTF: Failed to create skins."); + // Run pre-generate for each extension, in case an extension needs to do something before generating the scene. + for (Ref<GLTFDocumentExtension> ext : document_extensions) { + ERR_CONTINUE(ext.is_null()); + err = ext->import_pre_generate(p_state); + ERR_CONTINUE(err != OK); + } // Generate the node tree. Node *single_root; if (p_state->extensions_used.has("GODOT_single_root")) { @@ -7449,6 +7445,12 @@ Error GLTFDocument::append_from_scene(Node *p_node, Ref<GLTFState> p_state, uint state->extensions_used.append("GODOT_single_root"); } _convert_scene_node(state, p_node, -1, -1); + // Run post-convert for each extension, in case an extension needs to do something after converting the scene. + for (Ref<GLTFDocumentExtension> ext : document_extensions) { + ERR_CONTINUE(ext.is_null()); + Error err = ext->export_post_convert(p_state, p_node); + ERR_CONTINUE(err != OK); + } return OK; } 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/skin_tool.cpp b/modules/gltf/skin_tool.cpp index a344334d93..1522c0e324 100644 --- a/modules/gltf/skin_tool.cpp +++ b/modules/gltf/skin_tool.cpp @@ -602,6 +602,11 @@ Error SkinTool::_create_skeletons( skeleton->set_bone_pose_rotation(bone_index, node->transform.basis.get_rotation_quaternion()); skeleton->set_bone_pose_scale(bone_index, node->transform.basis.get_scale()); + // Store bone-level GLTF extras in skeleton per bone meta. + if (node->has_meta("extras")) { + skeleton->set_bone_meta(bone_index, "extras", node->get_meta("extras")); + } + if (node->parent >= 0 && nodes[node->parent]->skeleton == skel_i) { const int bone_parent = skeleton->find_bone(nodes[node->parent]->get_name()); ERR_FAIL_COND_V(bone_parent < 0, FAILED); 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/gltf/tests/test_gltf_extras.h b/modules/gltf/tests/test_gltf_extras.h index 96aadf3023..37c8f6925c 100644 --- a/modules/gltf/tests/test_gltf_extras.h +++ b/modules/gltf/tests/test_gltf_extras.h @@ -41,6 +41,7 @@ #include "modules/gltf/gltf_document.h" #include "modules/gltf/gltf_state.h" #include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/skeleton_3d.h" #include "scene/main/window.h" #include "scene/resources/3d/primitive_meshes.h" #include "scene/resources/material.h" @@ -158,6 +159,62 @@ TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import" memdelete(original); memdelete(loaded); } + +TEST_CASE("[SceneTree][Node] GLTF test skeleton and bone export and import") { + // Setup scene. + Skeleton3D *skeleton = memnew(Skeleton3D); + skeleton->set_name("skeleton"); + Dictionary skeleton_extras; + skeleton_extras["node_type"] = "skeleton"; + skeleton->set_meta("extras", skeleton_extras); + + skeleton->add_bone("parent"); + skeleton->set_bone_rest(0, Transform3D()); + Dictionary parent_bone_extras; + parent_bone_extras["bone"] = "i_am_parent_bone"; + skeleton->set_bone_meta(0, "extras", parent_bone_extras); + + skeleton->add_bone("child"); + skeleton->set_bone_rest(1, Transform3D()); + skeleton->set_bone_parent(1, 0); + Dictionary child_bone_extras; + child_bone_extras["bone"] = "i_am_child_bone"; + skeleton->set_bone_meta(1, "extras", child_bone_extras); + + // We have to have a mesh to link with skeleton or it will not get imported. + Ref<PlaneMesh> meshdata = memnew(PlaneMesh); + meshdata->set_name("planemesh"); + + MeshInstance3D *mesh = memnew(MeshInstance3D); + mesh->set_mesh(meshdata); + mesh->set_name("mesh_instance_3d"); + + Node3D *scene = memnew(Node3D); + SceneTree::get_singleton()->get_root()->add_child(scene); + scene->add_child(skeleton); + scene->add_child(mesh); + scene->set_name("node3d"); + + // Now that both skeleton and mesh are part of scene, link them. + mesh->set_skeleton_path(mesh->get_path_to(skeleton)); + + // Convert to GLFT and back. + String tempfile = OS::get_singleton()->get_cache_path().path_join("gltf_bone_extras"); + Node *loaded = _gltf_export_then_import(scene, tempfile); + + // Compare the results. + CHECK(loaded->get_name() == "node3d"); + Skeleton3D *result = Object::cast_to<Skeleton3D>(loaded->find_child("Skeleton3D", false, true)); + CHECK(result->get_bone_name(0) == "parent"); + CHECK(Dictionary(result->get_bone_meta(0, "extras"))["bone"] == "i_am_parent_bone"); + CHECK(result->get_bone_name(1) == "child"); + CHECK(Dictionary(result->get_bone_meta(1, "extras"))["bone"] == "i_am_child_bone"); + + memdelete(skeleton); + memdelete(mesh); + memdelete(scene); + memdelete(loaded); +} } // namespace TestGltfExtras #endif // TOOLS_ENABLED 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..4963cfdf1a 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) { @@ -218,10 +221,9 @@ void AudioStreamMP3::clear_data() { void AudioStreamMP3::set_data(const Vector<uint8_t> &p_data) { int src_data_len = p_data.size(); - const uint8_t *src_datar = p_data.ptr(); mp3dec_ex_t *mp3d = memnew(mp3dec_ex_t); - int err = mp3dec_ex_open_buf(mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE); + int err = mp3dec_ex_open_buf(mp3d, p_data.ptr(), src_data_len, MP3D_SEEK_TO_SAMPLE); if (err || mp3d->info.hz == 0) { memdelete(mp3d); ERR_FAIL_MSG("Failed to decode mp3 file. Make sure it is a valid mp3 audio file."); @@ -234,10 +236,7 @@ void AudioStreamMP3::set_data(const Vector<uint8_t> &p_data) { mp3dec_ex_close(mp3d); memdelete(mp3d); - clear_data(); - - data.resize(src_data_len); - memcpy(data.ptrw(), src_datar, src_data_len); + data = p_data; data_len = src_data_len; } diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h index 39d389b8cd..bb4c7f524c 100644 --- a/modules/minimp3/audio_stream_mp3.h +++ b/modules/minimp3/audio_stream_mp3.h @@ -96,7 +96,7 @@ class AudioStreamMP3 : public AudioStream { friend class AudioStreamPlaybackMP3; - PackedByteArray data; + LocalVector<uint8_t> data; uint32_t data_len = 0; float sample_rate = 1.0; diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 177859f270..3d12994469 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1497,11 +1497,23 @@ bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { List<PropertyInfo> props; - script->get_script_property_list(&props); + ERR_FAIL_COND(!script.is_valid()); +#ifdef TOOLS_ENABLED + for (const PropertyInfo &prop : script->exported_members_cache) { + props.push_back(prop); + } +#else + for (const KeyValue<StringName, PropertyInfo> &E : script->member_info) { + props.push_front(E.value); + } +#endif - // Call _get_property_list + for (PropertyInfo &prop : props) { + validate_property(prop); + p_properties->push_back(prop); + } - ERR_FAIL_COND(!script.is_valid()); + // Call _get_property_list StringName method = SNAME("_get_property_list"); @@ -1524,9 +1536,25 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { } } - for (PropertyInfo &prop : props) { - validate_property(prop); - p_properties->push_back(prop); + CSharpScript *top = script.ptr()->base_script.ptr(); + while (top != nullptr) { + props.clear(); +#ifdef TOOLS_ENABLED + for (const PropertyInfo &prop : top->exported_members_cache) { + props.push_back(prop); + } +#else + for (const KeyValue<StringName, PropertyInfo> &E : top->member_info) { + props.push_front(E.value); + } +#endif + + for (PropertyInfo &prop : props) { + validate_property(prop); + p_properties->push_back(prop); + } + + top = top->base_script.ptr(); } } @@ -2716,7 +2744,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/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index ee624a443d..c5f2dfee4b 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -30,6 +30,7 @@ <None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk"> <Link>Sdk\SdkPackageVersions.props</Link> </None> + <None Include="Sdk\Android.props" Pack="true" PackagePath="Sdk" /> <None Include="Sdk\iOSNativeAOT.props" Pack="true" PackagePath="Sdk" /> <None Include="Sdk\iOSNativeAOT.targets" Pack="true" PackagePath="Sdk" /> </ItemGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Android.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Android.props new file mode 100644 index 0000000000..3926a4b22a --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Android.props @@ -0,0 +1,5 @@ +<Project> + <PropertyGroup> + <UseMonoRuntime Condition=" '$(UseMonoRuntime)' == '' and '$(PublishAot)' != 'true' ">true</UseMonoRuntime> + </PropertyGroup> +</Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index c4034f1f9f..d10f9ae0ab 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -112,5 +112,6 @@ <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants> </PropertyGroup> + <Import Project="$(MSBuildThisFileDirectory)\Android.props" Condition=" '$(GodotTargetPlatform)' == 'android' " /> <Import Project="$(MSBuildThisFileDirectory)\iOSNativeAOT.props" Condition=" '$(GodotTargetPlatform)' == 'ios' " /> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj index f23f2b9a8c..208e6d8f41 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj @@ -5,7 +5,6 @@ <TargetFramework>net6.0-windows</TargetFramework> <LangVersion>10</LangVersion> <Nullable>enable</Nullable> - <RuntimeIdentifier>win-x86</RuntimeIdentifier> <SelfContained>False</SelfContained> <RollForward>LatestMajor</RollForward> </PropertyGroup> diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index a5f24fb67b..6fd84d3834 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -245,7 +245,6 @@ namespace GodotTools.Export { publishOutputDir = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, "godot-publish-dotnet", $"{buildConfig}-{runtimeIdentifier}"); - } outputPaths.Add(publishOutputDir); @@ -322,6 +321,30 @@ namespace GodotTools.Export { if (embedBuildResults) { + if (platform == OS.Platforms.Android) + { + if (IsSharedObject(Path.GetFileName(path))) + { + AddSharedObject(path, tags: new string[] { arch }, + Path.Join(projectDataDirName, + Path.GetRelativePath(publishOutputDir, + Path.GetDirectoryName(path)!))); + + return; + } + + static bool IsSharedObject(string fileName) + { + if (fileName.EndsWith(".so") || fileName.EndsWith(".a") + || fileName.EndsWith(".jar") || fileName.EndsWith(".dex")) + { + return true; + } + + return false; + } + } + string filePath = SanitizeSlashes(Path.GetRelativePath(publishOutputDir, path)); byte[] fileData = File.ReadAllBytes(path); string hash = Convert.ToBase64String(SHA512.HashData(fileData)); diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index d3899c809a..e84b4e92c7 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -259,11 +259,12 @@ namespace GodotTools var args = new List<string> { + Path.Combine(GodotSharpDirs.DataEditorToolsDir, "GodotTools.OpenVisualStudio.dll"), GodotSharpDirs.ProjectSlnPath, line >= 0 ? $"{scriptPath};{line + 1};{col + 1}" : scriptPath }; - string command = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "GodotTools.OpenVisualStudio.exe"); + string command = DotNetFinder.FindDotNetExe() ?? "dotnet"; try { diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 2ec073e4fa..d0adf39fb2 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -1452,7 +1452,7 @@ Error BindingsGenerator::_populate_method_icalls_table(const TypeInterface &p_it } const TypeInterface *return_type = _get_type_or_null(imethod.return_type); - ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found + ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + imethod.return_type.cname + "' was not found."); String im_unique_sig = get_ret_unique_sig(return_type) + ",CallMethodBind"; @@ -1463,7 +1463,7 @@ Error BindingsGenerator::_populate_method_icalls_table(const TypeInterface &p_it // Get arguments information for (const ArgumentInterface &iarg : imethod.arguments) { const TypeInterface *arg_type = _get_type_or_null(iarg.type); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found."); im_unique_sig += ","; im_unique_sig += get_arg_unique_sig(*arg_type); @@ -2313,7 +2313,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str const ArgumentInterface &iarg = *itr; const TypeInterface *arg_type = _get_type_or_null(iarg.type); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found."); if (i != 0) { output << ", "; @@ -2333,7 +2333,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str if (imethod.return_type.cname != name_cache.type_void) { const TypeInterface *return_type = _get_type_or_null(imethod.return_type); - ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found + ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + imethod.return_type.cname + "' was not found."); output << INDENT3 "ret = " << sformat(return_type->cs_managed_to_variant, "callRet", return_type->cs_type, return_type->name) @@ -2552,7 +2552,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte const TypeReference &proptype_name = getter ? getter->return_type : setter->arguments.back()->get().type; const TypeInterface *prop_itype = _get_type_or_singleton_or_null(proptype_name); - ERR_FAIL_NULL_V(prop_itype, ERR_BUG); // Property type not found + ERR_FAIL_NULL_V_MSG(prop_itype, ERR_BUG, "Property type '" + proptype_name.cname + "' was not found."); ERR_FAIL_COND_V_MSG(prop_itype->is_singleton, ERR_BUG, "Property type is a singleton: '" + p_itype.name + "." + String(p_iprop.cname) + "'."); @@ -2651,7 +2651,7 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) { const TypeInterface *return_type = _get_type_or_singleton_or_null(p_imethod.return_type); - ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found + ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_imethod.return_type.cname + "' was not found."); ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG, "Method return type is a singleton: '" + p_itype.name + "." + p_imethod.name + "'."); @@ -2690,7 +2690,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf const ArgumentInterface &first = p_imethod.arguments.front()->get(); for (const ArgumentInterface &iarg : p_imethod.arguments) { const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found."); ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, "Argument type is a singleton: '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'."); @@ -2944,7 +2944,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf const ArgumentInterface &first = p_isignal.arguments.front()->get(); for (const ArgumentInterface &iarg : p_isignal.arguments) { const TypeInterface *arg_type = _get_type_or_singleton_or_null(iarg.type); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found."); ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, "Argument type is a singleton: '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "'."); @@ -3013,7 +3013,7 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf int idx = 0; for (const ArgumentInterface &iarg : p_isignal.arguments) { const TypeInterface *arg_type = _get_type_or_null(iarg.type); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + iarg.type.cname + "' was not found."); if (idx != 0) { p_output << ", "; @@ -3113,7 +3113,7 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, bool ret_void = p_icall.return_type.cname == name_cache.type_void; const TypeInterface *return_type = _get_type_or_null(p_icall.return_type); - ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found + ERR_FAIL_NULL_V_MSG(return_type, ERR_BUG, "Return type '" + p_icall.return_type.cname + "' was not found."); StringBuilder c_func_sig; StringBuilder c_in_statements; @@ -3129,7 +3129,7 @@ Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, int i = 0; for (const TypeReference &arg_type_ref : p_icall.argument_types) { const TypeInterface *arg_type = _get_type_or_null(arg_type_ref); - ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Return type not found + ERR_FAIL_NULL_V_MSG(arg_type, ERR_BUG, "Argument type '" + arg_type_ref.cname + "' was not found."); String c_param_name = "arg" + itos(i + 1); @@ -3389,7 +3389,7 @@ const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface String params = "<"; for (const TypeReference ¶m_type : p_generic_type_parameters) { const TypeInterface *param_itype = _get_type_or_singleton_or_null(param_type); - ERR_FAIL_NULL_V(param_itype, ""); // Parameter type not found + ERR_FAIL_NULL_V_MSG(param_itype, "", "Parameter type '" + param_type.cname + "' was not found."); ERR_FAIL_COND_V_MSG(param_itype->is_singleton, "", "Generic type parameter is a singleton: '" + param_itype->name + "'."); @@ -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/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index 0f534d477f..0dc143edea 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -468,8 +468,8 @@ namespace Godot { for (int j = 0; j < 3; j++) { - real_t e = transform.Basis[i][j] * min[j]; - real_t f = transform.Basis[i][j] * max[j]; + real_t e = transform.Basis[j][i] * min[j]; + real_t f = transform.Basis[j][i] * max[j]; if (e < f) { tmin[i] += e; 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/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 48caae8523..6abf0193cf 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -61,6 +61,14 @@ hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config = hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr; hostfxr_close_fn hostfxr_close = nullptr; +#ifndef TOOLS_ENABLED +typedef int(CORECLR_DELEGATE_CALLTYPE *coreclr_create_delegate_fn)(void *hostHandle, unsigned int domainId, const char *entryPointAssemblyName, const char *entryPointTypeName, const char *entryPointMethodName, void **delegate); +typedef int(CORECLR_DELEGATE_CALLTYPE *coreclr_initialize_fn)(const char *exePath, const char *appDomainFriendlyName, int propertyCount, const char **propertyKeys, const char **propertyValues, void **hostHandle, unsigned int *domainId); + +coreclr_create_delegate_fn coreclr_create_delegate = nullptr; +coreclr_initialize_fn coreclr_initialize = nullptr; +#endif + #ifdef _WIN32 static_assert(sizeof(char_t) == sizeof(char16_t)); using HostFxrCharString = Char16String; @@ -142,6 +150,56 @@ String find_hostfxr() { #endif } +#ifndef TOOLS_ENABLED +String find_monosgen() { +#if defined(ANDROID_ENABLED) + // Android includes all native libraries in the libs directory of the APK + // so we assume it exists and use only the name to dlopen it. + return "libmonosgen-2.0.so"; +#else +#if defined(WINDOWS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("monosgen-2.0.dll"); +#elif defined(MACOS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("libmonosgen-2.0.dylib"); +#elif defined(UNIX_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("libmonosgen-2.0.so"); +#else +#error "Platform not supported (yet?)" +#endif + + if (FileAccess::exists(probe_path)) { + return probe_path; + } + + return String(); +#endif +} + +String find_coreclr() { +#if defined(WINDOWS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("coreclr.dll"); +#elif defined(MACOS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("libcoreclr.dylib"); +#elif defined(UNIX_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .path_join("libcoreclr.so"); +#else +#error "Platform not supported (yet?)" +#endif + + if (FileAccess::exists(probe_path)) { + return probe_path; + } + + return String(); +} +#endif + bool load_hostfxr(void *&r_hostfxr_dll_handle) { String hostfxr_path = find_hostfxr(); @@ -182,6 +240,47 @@ bool load_hostfxr(void *&r_hostfxr_dll_handle) { hostfxr_close); } +#ifndef TOOLS_ENABLED +bool load_coreclr(void *&r_coreclr_dll_handle) { + String coreclr_path = find_coreclr(); + + bool is_monovm = false; + if (coreclr_path.is_empty()) { + // Fallback to MonoVM (should have the same API as CoreCLR). + coreclr_path = find_monosgen(); + is_monovm = true; + } + + if (coreclr_path.is_empty()) { + return false; + } + + const String coreclr_name = is_monovm ? "monosgen" : "coreclr"; + print_verbose("Found " + coreclr_name + ": " + coreclr_path); + + Error err = OS::get_singleton()->open_dynamic_library(coreclr_path, r_coreclr_dll_handle); + + if (err != OK) { + return false; + } + + void *lib = r_coreclr_dll_handle; + + void *symbol = nullptr; + + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "coreclr_initialize", symbol); + ERR_FAIL_COND_V(err != OK, false); + coreclr_initialize = (coreclr_initialize_fn)symbol; + + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "coreclr_create_delegate", symbol); + ERR_FAIL_COND_V(err != OK, false); + coreclr_create_delegate = (coreclr_create_delegate_fn)symbol; + + return (coreclr_initialize && + coreclr_create_delegate); +} +#endif + #ifdef TOOLS_ENABLED load_assembly_and_get_function_pointer_fn initialize_hostfxr_for_config(const char_t *p_config_path) { hostfxr_handle cxt = nullptr; @@ -339,6 +438,56 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) } #endif +#ifndef TOOLS_ENABLED +String make_tpa_list() { + String tpa_list; + +#if defined(WINDOWS_ENABLED) + String separator = ";"; +#else + String separator = ":"; +#endif + + String assemblies_dir = GodotSharpDirs::get_api_assemblies_dir(); + PackedStringArray files = DirAccess::get_files_at(assemblies_dir); + for (const String &file : files) { + tpa_list += assemblies_dir.path_join(file); + tpa_list += separator; + } + + return tpa_list; +} + +godot_plugins_initialize_fn initialize_coreclr_and_godot_plugins(bool &r_runtime_initialized) { + godot_plugins_initialize_fn godot_plugins_initialize = nullptr; + + String assembly_name = path::get_csharp_project_name(); + + String tpa_list = make_tpa_list(); + const char *prop_keys[] = { "TRUSTED_PLATFORM_ASSEMBLIES" }; + const char *prop_values[] = { tpa_list.utf8().get_data() }; + int nprops = sizeof(prop_keys) / sizeof(prop_keys[0]); + + void *coreclr_handle = nullptr; + unsigned int domain_id = 0; + int rc = coreclr_initialize(nullptr, nullptr, nprops, (const char **)&prop_keys, (const char **)&prop_values, &coreclr_handle, &domain_id); + ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to initialize CoreCLR."); + + r_runtime_initialized = true; + + print_verbose(".NET: CoreCLR initialized"); + + coreclr_create_delegate(coreclr_handle, domain_id, + assembly_name.utf8().get_data(), + "GodotPlugins.Game.Main", + "InitializeFromGameProject", + (void **)&godot_plugins_initialize); + ERR_FAIL_NULL_V_MSG(godot_plugins_initialize, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer"); + + return godot_plugins_initialize; +} +#endif + } // namespace bool GDMono::should_initialize() { @@ -382,14 +531,21 @@ void GDMono::initialize() { } #endif - if (!load_hostfxr(hostfxr_dll_handle)) { + if (load_hostfxr(hostfxr_dll_handle)) { + godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized); + ERR_FAIL_NULL(godot_plugins_initialize); + } else { #if !defined(TOOLS_ENABLED) - godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle); - - if (godot_plugins_initialize != nullptr) { - is_native_aot = true; - runtime_initialized = true; + if (load_coreclr(coreclr_dll_handle)) { + godot_plugins_initialize = initialize_coreclr_and_godot_plugins(runtime_initialized); } else { + godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle); + if (godot_plugins_initialize != nullptr) { + runtime_initialized = true; + } + } + + if (godot_plugins_initialize == nullptr) { ERR_FAIL_MSG(".NET: Failed to load hostfxr"); } #else @@ -400,11 +556,6 @@ void GDMono::initialize() { #endif } - if (!is_native_aot) { - godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized); - ERR_FAIL_NULL(godot_plugins_initialize); - } - int32_t interop_funcs_size = 0; const void **interop_funcs = godotsharp::get_runtime_interop_funcs(interop_funcs_size); @@ -553,6 +704,9 @@ GDMono::~GDMono() { if (hostfxr_dll_handle) { OS::get_singleton()->close_dynamic_library(hostfxr_dll_handle); } + if (coreclr_dll_handle) { + OS::get_singleton()->close_dynamic_library(coreclr_dll_handle); + } finalizing_scripts_domain = false; runtime_initialized = false; diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 614bfc63fb..fae3421ac9 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -64,7 +64,7 @@ class GDMono { bool finalizing_scripts_domain = false; void *hostfxr_dll_handle = nullptr; - bool is_native_aot = false; + void *coreclr_dll_handle = nullptr; String project_assembly_path; uint64_t project_assembly_modified_time = 0; diff --git a/modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar b/modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar Binary files differnew file mode 100755 index 0000000000..7366030881 --- /dev/null +++ b/modules/mono/thirdparty/libSystem.Security.Cryptography.Native.Android.jar 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_base.h b/modules/navigation/nav_base.h index c28392acf7..d2308abfaf 100644 --- a/modules/navigation/nav_base.h +++ b/modules/navigation/nav_base.h @@ -64,7 +64,7 @@ public: void set_owner_id(ObjectID p_owner_id) { owner_id = p_owner_id; } ObjectID get_owner_id() const { return owner_id; } - virtual ~NavBase(){}; + virtual ~NavBase() {} }; #endif // NAV_BASE_H 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/extensions/platform/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp index de4a9e4b8e..caded14ca7 100644 --- a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp @@ -240,8 +240,8 @@ bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, in Vector<RID> texture_rids; for (uint64_t i = 0; i < swapchain_length; i++) { - RID texture_rid = texture_storage->texture_create_external( - p_array_size == 1 ? GLES3::Texture::TYPE_2D : GLES3::Texture::TYPE_LAYERED, + RID texture_rid = texture_storage->texture_create_from_native_handle( + p_array_size == 1 ? RS::TEXTURE_TYPE_2D : RS::TEXTURE_TYPE_LAYERED, format, images[i].image, p_width, diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index a19a75e722..c67be5a2b3 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"); } @@ -1245,7 +1247,7 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { return false; } - set_object_name(XR_OBJECT_TYPE_SWAPCHAIN, uint64_t(render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain()), "Main depth swapchain"); + set_object_name(XR_OBJECT_TYPE_SWAPCHAIN, uint64_t(render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].get_swapchain()), "Main depth swapchain"); } // We create our velocity swapchain if: 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/raycast/godot_update_embree.py b/modules/raycast/godot_update_embree.py index c179060365..c4fff330c9 100644 --- a/modules/raycast/godot_update_embree.py +++ b/modules/raycast/godot_update_embree.py @@ -4,8 +4,8 @@ import re import shutil import stat import subprocess -from types import TracebackType -from typing import Any, Callable, Tuple, Type +import sys +from typing import Any, Callable git_tag = "v4.3.1" @@ -100,9 +100,7 @@ subprocess.run(["git", "checkout", git_tag]) commit_hash = str(subprocess.check_output(["git", "rev-parse", "HEAD"], universal_newlines=True)).strip() -def on_rm_error( - function: Callable[..., Any], path: str, excinfo: Tuple[Type[Exception], Exception, TracebackType] -) -> None: +def on_rm_error(function: Callable[..., Any], path: str, excinfo: Exception) -> None: """ Error handler for `shutil.rmtree()`. @@ -113,10 +111,12 @@ def on_rm_error( os.unlink(path) -# 3.12 Python and beyond should replace `onerror` with `onexc`. # We remove the .git directory because it contains # a lot of read-only files that are problematic on Windows. -shutil.rmtree(".git", onerror=on_rm_error) +if sys.version_info >= (3, 12): + shutil.rmtree(".git", onexc=on_rm_error) +else: + shutil.rmtree(".git", onerror=on_rm_error) # type: ignore all_files = set(cpp_files) diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 448be9ebe4..c63389b1c6 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -705,7 +705,7 @@ class TextServerAdvanced : public TextServerExtension { }; protected: - static void _bind_methods(){}; + static void _bind_methods() {} void full_copy(ShapedTextDataAdvanced *p_shaped); void invalidate(ShapedTextDataAdvanced *p_shaped, bool p_text = false); diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index ee1f72401f..7f12ad593b 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -574,7 +574,7 @@ class TextServerFallback : public TextServerExtension { Mutex ft_mutex; protected: - static void _bind_methods(){}; + static void _bind_methods() {} void full_copy(ShapedTextDataFallback *p_shaped); void invalidate(ShapedTextDataFallback *p_shaped); 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() { diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp index 78a9db9b19..352d495dd4 100644 --- a/modules/webxr/webxr_interface_js.cpp +++ b/modules/webxr/webxr_interface_js.cpp @@ -561,8 +561,8 @@ RID WebXRInterfaceJS::_get_texture(unsigned int p_texture_id) { uint32_t view_count = godot_webxr_get_view_count(); Size2 texture_size = get_render_target_size(); - RID texture = texture_storage->texture_create_external( - view_count == 1 ? GLES3::Texture::TYPE_2D : GLES3::Texture::TYPE_LAYERED, + RID texture = texture_storage->texture_create_from_native_handle( + view_count == 1 ? RS::TEXTURE_TYPE_2D : RS::TEXTURE_TYPE_LAYERED, Image::FORMAT_RGBA8, p_texture_id, (int)texture_size.width, |