diff options
Diffstat (limited to 'modules')
269 files changed, 5255 insertions, 1078 deletions
diff --git a/modules/astcenc/config.py b/modules/astcenc/config.py index eb565b85b9..37bb65f8ae 100644 --- a/modules/astcenc/config.py +++ b/modules/astcenc/config.py @@ -1,5 +1,7 @@ def can_build(env, platform): - return env.editor_build + # Godot only uses it in the editor, but ANGLE depends on it and we had + # to remove the copy from prebuilt ANGLE libs to solve symbol clashes. + return env.editor_build or env.get("angle_libs") def configure(env): diff --git a/modules/astcenc/image_compress_astcenc.cpp b/modules/astcenc/image_compress_astcenc.cpp index 499cf739c4..1dfa7da766 100644 --- a/modules/astcenc/image_compress_astcenc.cpp +++ b/modules/astcenc/image_compress_astcenc.cpp @@ -97,7 +97,7 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) { // Initialize astcenc. - int dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); + int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); Vector<uint8_t> dest_data; dest_data.resize(dest_size); uint8_t *dest_write = dest_data.ptrw(); @@ -125,12 +125,12 @@ void _compress_astc(Image *r_img, Image::ASTCFormat p_format) { int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0; for (int i = 0; i < mip_count + 1; i++) { int src_mip_w, src_mip_h; - int src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h); + int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h); const uint8_t *slices = &image_data.ptr()[src_ofs]; int dst_mip_w, dst_mip_h; - int dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h); + int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h); // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). if (unlikely(dst_ofs % 8 != 0)) { astcenc_context_free(context); @@ -231,7 +231,7 @@ void _decompress_astc(Image *r_img) { const bool mipmaps = r_img->has_mipmaps(); int width = r_img->get_width(); int height = r_img->get_height(); - int dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); + int64_t dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); Vector<uint8_t> dest_data; dest_data.resize(dest_size); uint8_t *dest_write = dest_data.ptrw(); @@ -244,9 +244,9 @@ void _decompress_astc(Image *r_img) { for (int i = 0; i < mip_count + 1; i++) { int src_mip_w, src_mip_h; - int src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h); + int64_t src_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, r_img->get_format(), i, src_mip_w, src_mip_h); const uint8_t *src_data = &image_data.ptr()[src_ofs]; - int src_size; + int64_t src_size; if (i == mip_count) { src_size = image_data.size() - src_ofs; } else { @@ -255,7 +255,7 @@ void _decompress_astc(Image *r_img) { } int dst_mip_w, dst_mip_h; - int dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h); + int64_t dst_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dst_mip_w, dst_mip_h); // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). ERR_FAIL_COND(dst_ofs % 8 != 0); uint8_t *dest_mip_write = (uint8_t *)&dest_write[dst_ofs]; diff --git a/modules/basis_universal/image_compress_basisu.cpp b/modules/basis_universal/image_compress_basisu.cpp index 216fa6c9a2..8167fe8c73 100644 --- a/modules/basis_universal/image_compress_basisu.cpp +++ b/modules/basis_universal/image_compress_basisu.cpp @@ -120,7 +120,8 @@ Vector<uint8_t> basis_universal_packer(const Ref<Image> &p_image, Image::UsedCha Vector<uint32_t> mip_data_padded; for (int32_t i = 0; i <= image->get_mipmap_count(); i++) { - int ofs, size, width, height; + int64_t ofs, size; + int width, height; image->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height); const uint8_t *image_mip_data = image_data.ptr() + ofs; @@ -306,7 +307,7 @@ Ref<Image> basis_universal_unpacker_ptr(const uint8_t *p_data, int p_size) { transcoder.get_image_level_info(src_ptr, src_size, basisu_level, 0, i); uint32_t mip_block_or_pixel_count = Image::is_format_compressed(image_format) ? basisu_level.m_total_blocks : basisu_level.m_orig_width * basisu_level.m_orig_height; - int ofs = Image::get_image_mipmap_offset(basisu_info.m_width, basisu_info.m_height, image_format, i); + int64_t ofs = Image::get_image_mipmap_offset(basisu_info.m_width, basisu_info.m_height, image_format, i); bool result = transcoder.transcode_image_level(src_ptr, src_size, 0, i, dst + ofs, mip_block_or_pixel_count, basisu_format); diff --git a/modules/betsy/CrossPlatformSettings_piece_all.glsl b/modules/betsy/CrossPlatformSettings_piece_all.glsl new file mode 100644 index 0000000000..b7abac7fcc --- /dev/null +++ b/modules/betsy/CrossPlatformSettings_piece_all.glsl @@ -0,0 +1,76 @@ + +#define min3(a, b, c) min(a, min(b, c)) +#define max3(a, b, c) max(a, max(b, c)) + +#define float2 vec2 +#define float3 vec3 +#define float4 vec4 + +#define int2 ivec2 +#define int3 ivec3 +#define int4 ivec4 + +#define uint2 uvec2 +#define uint3 uvec3 +#define uint4 uvec4 + +#define float2x2 mat2 +#define float3x3 mat3 +#define float4x4 mat4 +#define ogre_float4x3 mat3x4 + +#define ushort uint +#define ushort3 uint3 +#define ushort4 uint4 + +//Short used for read operations. It's an int in GLSL & HLSL. An ushort in Metal +#define rshort int +#define rshort2 int2 +#define rint int +//Short used for write operations. It's an int in GLSL. An ushort in HLSL & Metal +#define wshort2 int2 +#define wshort3 int3 + +#define toFloat3x3(x) mat3(x) +#define buildFloat3x3(row0, row1, row2) mat3(row0, row1, row2) + +#define mul(x, y) ((x) * (y)) +#define saturate(x) clamp((x), 0.0, 1.0) +#define lerp mix +#define rsqrt inversesqrt +#define INLINE +#define NO_INTERPOLATION_PREFIX flat +#define NO_INTERPOLATION_SUFFIX + +#define PARAMS_ARG_DECL +#define PARAMS_ARG + +#define reversebits bitfieldReverse + +#define OGRE_Sample(tex, sampler, uv) texture(tex, uv) +#define OGRE_SampleLevel(tex, sampler, uv, lod) textureLod(tex, uv, lod) +#define OGRE_SampleArray2D(tex, sampler, uv, arrayIdx) texture(tex, vec3(uv, arrayIdx)) +#define OGRE_SampleArray2DLevel(tex, sampler, uv, arrayIdx, lod) textureLod(tex, vec3(uv, arrayIdx), lod) +#define OGRE_SampleArrayCubeLevel(tex, sampler, uv, arrayIdx, lod) textureLod(tex, vec4(uv, arrayIdx), lod) +#define OGRE_SampleGrad(tex, sampler, uv, ddx, ddy) textureGrad(tex, uv, ddx, ddy) +#define OGRE_SampleArray2DGrad(tex, sampler, uv, arrayIdx, ddx, ddy) textureGrad(tex, vec3(uv, arrayIdx), ddx, ddy) +#define OGRE_ddx(val) dFdx(val) +#define OGRE_ddy(val) dFdy(val) +#define OGRE_Load2D(tex, iuv, lod) texelFetch(tex, iuv, lod) +#define OGRE_LoadArray2D(tex, iuv, arrayIdx, lod) texelFetch(tex, ivec3(iuv, arrayIdx), lod) +#define OGRE_Load2DMS(tex, iuv, subsample) texelFetch(tex, iuv, subsample) + +#define OGRE_Load3D(tex, iuv, lod) texelFetch(tex, ivec3(iuv), lod) + +#define OGRE_GatherRed(tex, sampler, uv) textureGather(tex, uv, 0) +#define OGRE_GatherGreen(tex, sampler, uv) textureGather(tex, uv, 1) +#define OGRE_GatherBlue(tex, sampler, uv) textureGather(tex, uv, 2) + +#define bufferFetch1(buffer, idx) texelFetch(buffer, idx).x + +#define OGRE_SAMPLER_ARG_DECL(samplerName) +#define OGRE_SAMPLER_ARG(samplerName) + +#define OGRE_Texture3D_float4 sampler3D +#define OGRE_OUT_REF(declType, variableName) out declType variableName +#define OGRE_INOUT_REF(declType, variableName) inout declType variableName diff --git a/modules/betsy/SCsub b/modules/betsy/SCsub new file mode 100644 index 0000000000..9930e1f4cf --- /dev/null +++ b/modules/betsy/SCsub @@ -0,0 +1,24 @@ +# !/ usr / bin / env python +Import("env") +Import("env_modules") + +env_betsy = env_modules.Clone() +env_betsy.GLSL_HEADER("bc6h.glsl") +env_betsy.Depends(Glob("*.glsl.gen.h"), ["#glsl_builders.py"]) + +# Thirdparty source files +thirdparty_obj = [] +thirdparty_dir = "#thirdparty/betsy/" +env_betsy.Prepend(CPPPATH=[thirdparty_dir]) + +env_thirdparty = env_betsy.Clone() +env_thirdparty.disable_warnings() +env.modules_sources += thirdparty_obj + +# Godot source files +module_obj = [] +env_betsy.add_source_files(module_obj, "*.cpp") +env.modules_sources += module_obj + +# Needed to force rebuilding the module files when the thirdparty library is updated. +env.Depends(module_obj, thirdparty_obj) diff --git a/modules/betsy/UavCrossPlatform_piece_all.glsl b/modules/betsy/UavCrossPlatform_piece_all.glsl new file mode 100644 index 0000000000..30854df637 --- /dev/null +++ b/modules/betsy/UavCrossPlatform_piece_all.glsl @@ -0,0 +1,17 @@ + +#define OGRE_imageLoad2D(inImage, iuv) imageLoad(inImage, int2(iuv)) +#define OGRE_imageLoad2DArray(inImage, iuvw) imageLoad(inImage, int3(iuvw)) + +#define OGRE_imageWrite2D1(outImage, iuv, value) imageStore(outImage, int2(iuv), float4(value, 0, 0, 0)) +#define OGRE_imageWrite2D2(outImage, iuv, value) imageStore(outImage, int2(iuv), float4(value, 0, 0)) +#define OGRE_imageWrite2D4(outImage, iuv, value) imageStore(outImage, int2(iuv), value) + +#define OGRE_imageLoad3D(inImage, iuv) imageLoad(inImage, int3(iuv)) + +#define OGRE_imageWrite3D1(outImage, iuv, value) imageStore(outImage, int3(iuv), value) +#define OGRE_imageWrite3D4(outImage, iuv, value) imageStore(outImage, int3(iuv), value) + +#define OGRE_imageWrite2DArray1(outImage, iuvw, value) imageStore(outImage, int3(iuvw), value) +#define OGRE_imageWrite2DArray4(outImage, iuvw, value) imageStore(outImage, int3(iuvw), value) + +//#define sharedOnlyBarrier memoryBarrierShared();barrier(); diff --git a/modules/betsy/bc6h.glsl b/modules/betsy/bc6h.glsl new file mode 100644 index 0000000000..0d10d378fd --- /dev/null +++ b/modules/betsy/bc6h.glsl @@ -0,0 +1,653 @@ +#[versions] + +signed = "#define SIGNED"; +unsigned = ""; + +#[compute] +#version 450 + +#include "CrossPlatformSettings_piece_all.glsl" +#include "UavCrossPlatform_piece_all.glsl" + +#VERSION_DEFINES +#define QUALITY + +//SIGNED macro is WIP +//#define SIGNED + +float3 f32tof16(float3 value) { + return float3(packHalf2x16(float2(value.x, 0.0)), + packHalf2x16(float2(value.y, 0.0)), + packHalf2x16(float2(value.z, 0.0))); +} + +float3 f16tof32(uint3 value) { + return float3(unpackHalf2x16(value.x).x, + unpackHalf2x16(value.y).x, + unpackHalf2x16(value.z).x); +} + +float f32tof16(float value) { + return packHalf2x16(float2(value.x, 0.0)); +} + +float f16tof32(uint value) { + return unpackHalf2x16(value.x).x; +} + +layout(binding = 0) uniform sampler2D srcTexture; +layout(binding = 1, rgba32ui) uniform restrict writeonly uimage2D dstTexture; + +layout(push_constant, std430) uniform Params { + float2 p_textureSizeRcp; + uint padding0; + uint padding1; +} +params; + +const float HALF_MAX = 65504.0f; +const uint PATTERN_NUM = 32u; + +float CalcMSLE(float3 a, float3 b) { + float3 err = log2((b + 1.0f) / (a + 1.0f)); + err = err * err; + return err.x + err.y + err.z; +} + +uint PatternFixupID(uint i) { + uint ret = 15u; + ret = ((3441033216u >> i) & 0x1u) != 0 ? 2u : ret; + ret = ((845414400u >> i) & 0x1u) != 0 ? 8u : ret; + return ret; +} + +uint Pattern(uint p, uint i) { + uint p2 = p / 2u; + uint p3 = p - p2 * 2u; + + uint enc = 0u; + enc = p2 == 0u ? 2290666700u : enc; + enc = p2 == 1u ? 3972591342u : enc; + enc = p2 == 2u ? 4276930688u : enc; + enc = p2 == 3u ? 3967876808u : enc; + enc = p2 == 4u ? 4293707776u : enc; + enc = p2 == 5u ? 3892379264u : enc; + enc = p2 == 6u ? 4278255592u : enc; + enc = p2 == 7u ? 4026597360u : enc; + enc = p2 == 8u ? 9369360u : enc; + enc = p2 == 9u ? 147747072u : enc; + enc = p2 == 10u ? 1930428556u : enc; + enc = p2 == 11u ? 2362323200u : enc; + enc = p2 == 12u ? 823134348u : enc; + enc = p2 == 13u ? 913073766u : enc; + enc = p2 == 14u ? 267393000u : enc; + enc = p2 == 15u ? 966553998u : enc; + + enc = p3 != 0u ? enc >> 16u : enc; + uint ret = (enc >> i) & 0x1u; + return ret; +} + +#ifndef SIGNED +//UF +float3 Quantize7(float3 x) { + return (f32tof16(x) * 128.0f) / (0x7bff + 1.0f); +} + +float3 Quantize9(float3 x) { + return (f32tof16(x) * 512.0f) / (0x7bff + 1.0f); +} + +float3 Quantize10(float3 x) { + return (f32tof16(x) * 1024.0f) / (0x7bff + 1.0f); +} + +float3 Unquantize7(float3 x) { + return (x * 65536.0f + 0x8000) / 128.0f; +} + +float3 Unquantize9(float3 x) { + return (x * 65536.0f + 0x8000) / 512.0f; +} + +float3 Unquantize10(float3 x) { + return (x * 65536.0f + 0x8000) / 1024.0f; +} + +float3 FinishUnquantize(float3 endpoint0Unq, float3 endpoint1Unq, float weight) { + float3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 4096.0f); + return f16tof32(uint3(comp)); +} +#else +//SF + +float3 cmpSign(float3 value) { + float3 signVal; + signVal.x = value.x >= 0.0f ? 1.0f : -1.0f; + signVal.y = value.y >= 0.0f ? 1.0f : -1.0f; + signVal.z = value.z >= 0.0f ? 1.0f : -1.0f; + return signVal; +} + +float3 Quantize7(float3 x) { + float3 signVal = cmpSign(x); + return signVal * (f32tof16(abs(x)) * 64.0f) / (0x7bff + 1.0f); +} + +float3 Quantize9(float3 x) { + float3 signVal = cmpSign(x); + return signVal * (f32tof16(abs(x)) * 256.0f) / (0x7bff + 1.0f); +} + +float3 Quantize10(float3 x) { + float3 signVal = cmpSign(x); + return signVal * (f32tof16(abs(x)) * 512.0f) / (0x7bff + 1.0f); +} + +float3 Unquantize7(float3 x) { + float3 signVal = sign(x); + x = abs(x); + float3 finalVal = signVal * (x * 32768.0f + 0x4000) / 64.0f; + finalVal.x = x.x >= 64.0f ? 32767.0 : finalVal.x; + finalVal.y = x.y >= 64.0f ? 32767.0 : finalVal.y; + finalVal.z = x.z >= 64.0f ? 32767.0 : finalVal.z; + return finalVal; +} + +float3 Unquantize9(float3 x) { + float3 signVal = sign(x); + x = abs(x); + float3 finalVal = signVal * (x * 32768.0f + 0x4000) / 256.0f; + finalVal.x = x.x >= 256.0f ? 32767.0 : finalVal.x; + finalVal.y = x.y >= 256.0f ? 32767.0 : finalVal.y; + finalVal.z = x.z >= 256.0f ? 32767.0 : finalVal.z; + return finalVal; +} + +float3 Unquantize10(float3 x) { + float3 signVal = sign(x); + x = abs(x); + float3 finalVal = signVal * (x * 32768.0f + 0x4000) / 512.0f; + finalVal.x = x.x >= 512.0f ? 32767.0 : finalVal.x; + finalVal.y = x.y >= 512.0f ? 32767.0 : finalVal.y; + finalVal.z = x.z >= 512.0f ? 32767.0 : finalVal.z; + return finalVal; +} + +float3 FinishUnquantize(float3 endpoint0Unq, float3 endpoint1Unq, float weight) { + float3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 2048.0f); + /*float3 signVal; + signVal.x = comp.x >= 0.0f ? 0.0f : 0x8000; + signVal.y = comp.y >= 0.0f ? 0.0f : 0x8000; + signVal.z = comp.z >= 0.0f ? 0.0f : 0x8000;*/ + //return f16tof32( uint3( signVal + abs( comp ) ) ); + return f16tof32(uint3(comp)); +} +#endif + +void Swap(inout float3 a, inout float3 b) { + float3 tmp = a; + a = b; + b = tmp; +} + +void Swap(inout float a, inout float b) { + float tmp = a; + a = b; + b = tmp; +} + +uint ComputeIndex3(float texelPos, float endPoint0Pos, float endPoint1Pos) { + float r = (texelPos - endPoint0Pos) / (endPoint1Pos - endPoint0Pos); + return uint(clamp(r * 6.98182f + 0.00909f + 0.5f, 0.0f, 7.0f)); +} + +uint ComputeIndex4(float texelPos, float endPoint0Pos, float endPoint1Pos) { + float r = (texelPos - endPoint0Pos) / (endPoint1Pos - endPoint0Pos); + return uint(clamp(r * 14.93333f + 0.03333f + 0.5f, 0.0f, 15.0f)); +} + +void SignExtend(inout float3 v1, uint mask, uint signFlag) { + int3 v = int3(v1); + v.x = (v.x & int(mask)) | (v.x < 0 ? int(signFlag) : 0); + v.y = (v.y & int(mask)) | (v.y < 0 ? int(signFlag) : 0); + v.z = (v.z & int(mask)) | (v.z < 0 ? int(signFlag) : 0); + v1 = v; +} + +void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) { + // compute endpoints (min/max RGB bbox) + float3 blockMin = texels[0]; + float3 blockMax = texels[0]; + for (uint i = 1u; i < 16u; ++i) { + blockMin = min(blockMin, texels[i]); + blockMax = max(blockMax, texels[i]); + } + + // refine endpoints in log2 RGB space + float3 refinedBlockMin = blockMax; + float3 refinedBlockMax = blockMin; + for (uint i = 0u; i < 16u; ++i) { + refinedBlockMin = min(refinedBlockMin, texels[i] == blockMin ? refinedBlockMin : texels[i]); + refinedBlockMax = max(refinedBlockMax, texels[i] == blockMax ? refinedBlockMax : texels[i]); + } + + float3 logBlockMax = log2(blockMax + 1.0f); + float3 logBlockMin = log2(blockMin + 1.0f); + float3 logRefinedBlockMax = log2(refinedBlockMax + 1.0f); + float3 logRefinedBlockMin = log2(refinedBlockMin + 1.0f); + float3 logBlockMaxExt = (logBlockMax - logBlockMin) * (1.0f / 32.0f); + logBlockMin += min(logRefinedBlockMin - logBlockMin, logBlockMaxExt); + logBlockMax -= min(logBlockMax - logRefinedBlockMax, logBlockMaxExt); + blockMin = exp2(logBlockMin) - 1.0f; + blockMax = exp2(logBlockMax) - 1.0f; + + float3 blockDir = blockMax - blockMin; + blockDir = blockDir / (blockDir.x + blockDir.y + blockDir.z); + + float3 endpoint0 = Quantize10(blockMin); + float3 endpoint1 = Quantize10(blockMax); + float endPoint0Pos = f32tof16(dot(blockMin, blockDir)); + float endPoint1Pos = f32tof16(dot(blockMax, blockDir)); + + // check if endpoint swap is required + float fixupTexelPos = f32tof16(dot(texels[0], blockDir)); + uint fixupIndex = ComputeIndex4(fixupTexelPos, endPoint0Pos, endPoint1Pos); + if (fixupIndex > 7) { + Swap(endPoint0Pos, endPoint1Pos); + Swap(endpoint0, endpoint1); + } + + // compute indices + uint indices[16] = { 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u }; + for (uint i = 0u; i < 16u; ++i) { + float texelPos = f32tof16(dot(texels[i], blockDir)); + indices[i] = ComputeIndex4(texelPos, endPoint0Pos, endPoint1Pos); + } + + // compute compression error (MSLE) + float3 endpoint0Unq = Unquantize10(endpoint0); + float3 endpoint1Unq = Unquantize10(endpoint1); + float msle = 0.0f; + for (uint i = 0u; i < 16u; ++i) { + float weight = floor((indices[i] * 64.0f) / 15.0f + 0.5f); + float3 texelUnc = FinishUnquantize(endpoint0Unq, endpoint1Unq, weight); + + msle += CalcMSLE(texels[i], texelUnc); + } + + // encode block for mode 11 + blockMSLE = msle; + block.x = 0x03; + + // endpoints + block.x |= uint(endpoint0.x) << 5u; + block.x |= uint(endpoint0.y) << 15u; + block.x |= uint(endpoint0.z) << 25u; + block.y |= uint(endpoint0.z) >> 7u; + block.y |= uint(endpoint1.x) << 3u; + block.y |= uint(endpoint1.y) << 13u; + block.y |= uint(endpoint1.z) << 23u; + block.z |= uint(endpoint1.z) >> 9u; + + // indices + block.z |= indices[0] << 1u; + block.z |= indices[1] << 4u; + block.z |= indices[2] << 8u; + block.z |= indices[3] << 12u; + block.z |= indices[4] << 16u; + block.z |= indices[5] << 20u; + block.z |= indices[6] << 24u; + block.z |= indices[7] << 28u; + block.w |= indices[8] << 0u; + block.w |= indices[9] << 4u; + block.w |= indices[10] << 8u; + block.w |= indices[11] << 12u; + block.w |= indices[12] << 16u; + block.w |= indices[13] << 20u; + block.w |= indices[14] << 24u; + block.w |= indices[15] << 28u; +} + +float DistToLineSq(float3 PointOnLine, float3 LineDirection, float3 Point) { + float3 w = Point - PointOnLine; + float3 x = w - dot(w, LineDirection) * LineDirection; + + return dot(x, x); +} + +float EvaluateP2Pattern(uint pattern, float3 texels[16]) { + float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); + float3 p0BlockMax = float3(0.0f, 0.0f, 0.0f); + float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); + float3 p1BlockMax = float3(0.0f, 0.0f, 0.0f); + + for (uint i = 0; i < 16; ++i) { + uint paletteID = Pattern(pattern, i); + if (paletteID == 0) { + p0BlockMin = min(p0BlockMin, texels[i]); + p0BlockMax = max(p0BlockMax, texels[i]); + } else { + p1BlockMin = min(p1BlockMin, texels[i]); + p1BlockMax = max(p1BlockMax, texels[i]); + } + } + + float3 p0BlockDir = normalize(p0BlockMax - p0BlockMin); + float3 p1BlockDir = normalize(p1BlockMax - p1BlockMin); + + float sqDistanceFromLine = 0.0f; + + for (uint i = 0; i < 16; ++i) { + uint paletteID = Pattern(pattern, i); + if (paletteID == 0) { + sqDistanceFromLine += DistToLineSq(p0BlockMin, p0BlockDir, texels[i]); + } else { + sqDistanceFromLine += DistToLineSq(p1BlockMin, p1BlockDir, texels[i]); + } + } + + return sqDistanceFromLine; +} + +void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, float3 texels[16]) { + float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); + float3 p0BlockMax = float3(0.0f, 0.0f, 0.0f); + float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); + float3 p1BlockMax = float3(0.0f, 0.0f, 0.0f); + + for (uint i = 0u; i < 16u; ++i) { + uint paletteID = Pattern(pattern, i); + if (paletteID == 0) { + p0BlockMin = min(p0BlockMin, texels[i]); + p0BlockMax = max(p0BlockMax, texels[i]); + } else { + p1BlockMin = min(p1BlockMin, texels[i]); + p1BlockMax = max(p1BlockMax, texels[i]); + } + } + + float3 p0BlockDir = p0BlockMax - p0BlockMin; + float3 p1BlockDir = p1BlockMax - p1BlockMin; + p0BlockDir = p0BlockDir / (p0BlockDir.x + p0BlockDir.y + p0BlockDir.z); + p1BlockDir = p1BlockDir / (p1BlockDir.x + p1BlockDir.y + p1BlockDir.z); + + float p0Endpoint0Pos = f32tof16(dot(p0BlockMin, p0BlockDir)); + float p0Endpoint1Pos = f32tof16(dot(p0BlockMax, p0BlockDir)); + float p1Endpoint0Pos = f32tof16(dot(p1BlockMin, p1BlockDir)); + float p1Endpoint1Pos = f32tof16(dot(p1BlockMax, p1BlockDir)); + + uint fixupID = PatternFixupID(pattern); + float p0FixupTexelPos = f32tof16(dot(texels[0], p0BlockDir)); + float p1FixupTexelPos = f32tof16(dot(texels[fixupID], p1BlockDir)); + uint p0FixupIndex = ComputeIndex3(p0FixupTexelPos, p0Endpoint0Pos, p0Endpoint1Pos); + uint p1FixupIndex = ComputeIndex3(p1FixupTexelPos, p1Endpoint0Pos, p1Endpoint1Pos); + if (p0FixupIndex > 3u) { + Swap(p0Endpoint0Pos, p0Endpoint1Pos); + Swap(p0BlockMin, p0BlockMax); + } + if (p1FixupIndex > 3u) { + Swap(p1Endpoint0Pos, p1Endpoint1Pos); + Swap(p1BlockMin, p1BlockMax); + } + + uint indices[16] = { 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u }; + for (uint i = 0u; i < 16u; ++i) { + float p0TexelPos = f32tof16(dot(texels[i], p0BlockDir)); + float p1TexelPos = f32tof16(dot(texels[i], p1BlockDir)); + uint p0Index = ComputeIndex3(p0TexelPos, p0Endpoint0Pos, p0Endpoint1Pos); + uint p1Index = ComputeIndex3(p1TexelPos, p1Endpoint0Pos, p1Endpoint1Pos); + + uint paletteID = Pattern(pattern, i); + indices[i] = paletteID == 0u ? p0Index : p1Index; + } + + float3 endpoint760 = floor(Quantize7(p0BlockMin)); + float3 endpoint761 = floor(Quantize7(p0BlockMax)); + float3 endpoint762 = floor(Quantize7(p1BlockMin)); + float3 endpoint763 = floor(Quantize7(p1BlockMax)); + + float3 endpoint950 = floor(Quantize9(p0BlockMin)); + float3 endpoint951 = floor(Quantize9(p0BlockMax)); + float3 endpoint952 = floor(Quantize9(p1BlockMin)); + float3 endpoint953 = floor(Quantize9(p1BlockMax)); + + endpoint761 = endpoint761 - endpoint760; + endpoint762 = endpoint762 - endpoint760; + endpoint763 = endpoint763 - endpoint760; + + endpoint951 = endpoint951 - endpoint950; + endpoint952 = endpoint952 - endpoint950; + endpoint953 = endpoint953 - endpoint950; + + int maxVal76 = 0x1F; + endpoint761 = clamp(endpoint761, -maxVal76, maxVal76); + endpoint762 = clamp(endpoint762, -maxVal76, maxVal76); + endpoint763 = clamp(endpoint763, -maxVal76, maxVal76); + + int maxVal95 = 0xF; + endpoint951 = clamp(endpoint951, -maxVal95, maxVal95); + endpoint952 = clamp(endpoint952, -maxVal95, maxVal95); + endpoint953 = clamp(endpoint953, -maxVal95, maxVal95); + + float3 endpoint760Unq = Unquantize7(endpoint760); + float3 endpoint761Unq = Unquantize7(endpoint760 + endpoint761); + float3 endpoint762Unq = Unquantize7(endpoint760 + endpoint762); + float3 endpoint763Unq = Unquantize7(endpoint760 + endpoint763); + float3 endpoint950Unq = Unquantize9(endpoint950); + float3 endpoint951Unq = Unquantize9(endpoint950 + endpoint951); + float3 endpoint952Unq = Unquantize9(endpoint950 + endpoint952); + float3 endpoint953Unq = Unquantize9(endpoint950 + endpoint953); + + float msle76 = 0.0f; + float msle95 = 0.0f; + for (uint i = 0u; i < 16u; ++i) { + uint paletteID = Pattern(pattern, i); + + float3 tmp760Unq = paletteID == 0u ? endpoint760Unq : endpoint762Unq; + float3 tmp761Unq = paletteID == 0u ? endpoint761Unq : endpoint763Unq; + float3 tmp950Unq = paletteID == 0u ? endpoint950Unq : endpoint952Unq; + float3 tmp951Unq = paletteID == 0u ? endpoint951Unq : endpoint953Unq; + + float weight = floor((indices[i] * 64.0f) / 7.0f + 0.5f); + float3 texelUnc76 = FinishUnquantize(tmp760Unq, tmp761Unq, weight); + float3 texelUnc95 = FinishUnquantize(tmp950Unq, tmp951Unq, weight); + + msle76 += CalcMSLE(texels[i], texelUnc76); + msle95 += CalcMSLE(texels[i], texelUnc95); + } + + SignExtend(endpoint761, 0x1F, 0x20); + SignExtend(endpoint762, 0x1F, 0x20); + SignExtend(endpoint763, 0x1F, 0x20); + + SignExtend(endpoint951, 0xF, 0x10); + SignExtend(endpoint952, 0xF, 0x10); + SignExtend(endpoint953, 0xF, 0x10); + + // encode block + float p2MSLE = min(msle76, msle95); + if (p2MSLE < blockMSLE) { + blockMSLE = p2MSLE; + block = uint4(0u, 0u, 0u, 0u); + + if (p2MSLE == msle76) { + // 7.6 + block.x = 0x1u; + block.x |= (uint(endpoint762.y) & 0x20u) >> 3u; + block.x |= (uint(endpoint763.y) & 0x10u) >> 1u; + block.x |= (uint(endpoint763.y) & 0x20u) >> 1u; + block.x |= uint(endpoint760.x) << 5u; + block.x |= (uint(endpoint763.z) & 0x01u) << 12u; + block.x |= (uint(endpoint763.z) & 0x02u) << 12u; + block.x |= (uint(endpoint762.z) & 0x10u) << 10u; + block.x |= uint(endpoint760.y) << 15u; + block.x |= (uint(endpoint762.z) & 0x20u) << 17u; + block.x |= (uint(endpoint763.z) & 0x04u) << 21u; + block.x |= (uint(endpoint762.y) & 0x10u) << 20u; + block.x |= uint(endpoint760.z) << 25u; + block.y |= (uint(endpoint763.z) & 0x08u) >> 3u; + block.y |= (uint(endpoint763.z) & 0x20u) >> 4u; + block.y |= (uint(endpoint763.z) & 0x10u) >> 2u; + block.y |= uint(endpoint761.x) << 3u; + block.y |= (uint(endpoint762.y) & 0x0Fu) << 9u; + block.y |= uint(endpoint761.y) << 13u; + block.y |= (uint(endpoint763.y) & 0x0Fu) << 19u; + block.y |= uint(endpoint761.z) << 23u; + block.y |= (uint(endpoint762.z) & 0x07u) << 29u; + block.z |= (uint(endpoint762.z) & 0x08u) >> 3u; + block.z |= uint(endpoint762.x) << 1u; + block.z |= uint(endpoint763.x) << 7u; + } else { + // 9.5 + block.x = 0xEu; + block.x |= uint(endpoint950.x) << 5u; + block.x |= (uint(endpoint952.z) & 0x10u) << 10u; + block.x |= uint(endpoint950.y) << 15u; + block.x |= (uint(endpoint952.y) & 0x10u) << 20u; + block.x |= uint(endpoint950.z) << 25u; + block.y |= uint(endpoint950.z) >> 7u; + block.y |= (uint(endpoint953.z) & 0x10u) >> 2u; + block.y |= uint(endpoint951.x) << 3u; + block.y |= (uint(endpoint953.y) & 0x10u) << 4u; + block.y |= (uint(endpoint952.y) & 0x0Fu) << 9u; + block.y |= uint(endpoint951.y) << 13u; + block.y |= (uint(endpoint953.z) & 0x01u) << 18u; + block.y |= (uint(endpoint953.y) & 0x0Fu) << 19u; + block.y |= uint(endpoint951.z) << 23u; + block.y |= (uint(endpoint953.z) & 0x02u) << 27u; + block.y |= uint(endpoint952.z) << 29u; + block.z |= (uint(endpoint952.z) & 0x08u) >> 3u; + block.z |= uint(endpoint952.x) << 1u; + block.z |= (uint(endpoint953.z) & 0x04u) << 4u; + block.z |= uint(endpoint953.x) << 7u; + block.z |= (uint(endpoint953.z) & 0x08u) << 9u; + } + + block.z |= pattern << 13u; + uint blockFixupID = PatternFixupID(pattern); + if (blockFixupID == 15u) { + block.z |= indices[0] << 18u; + block.z |= indices[1] << 20u; + block.z |= indices[2] << 23u; + block.z |= indices[3] << 26u; + block.z |= indices[4] << 29u; + block.w |= indices[5] << 0u; + block.w |= indices[6] << 3u; + block.w |= indices[7] << 6u; + block.w |= indices[8] << 9u; + block.w |= indices[9] << 12u; + block.w |= indices[10] << 15u; + block.w |= indices[11] << 18u; + block.w |= indices[12] << 21u; + block.w |= indices[13] << 24u; + block.w |= indices[14] << 27u; + block.w |= indices[15] << 30u; + } else if (blockFixupID == 2u) { + block.z |= indices[0] << 18u; + block.z |= indices[1] << 20u; + block.z |= indices[2] << 23u; + block.z |= indices[3] << 25u; + block.z |= indices[4] << 28u; + block.z |= indices[5] << 31u; + block.w |= indices[5] >> 1u; + block.w |= indices[6] << 2u; + block.w |= indices[7] << 5u; + block.w |= indices[8] << 8u; + block.w |= indices[9] << 11u; + block.w |= indices[10] << 14u; + block.w |= indices[11] << 17u; + block.w |= indices[12] << 20u; + block.w |= indices[13] << 23u; + block.w |= indices[14] << 26u; + block.w |= indices[15] << 29u; + } else { + block.z |= indices[0] << 18u; + block.z |= indices[1] << 20u; + block.z |= indices[2] << 23u; + block.z |= indices[3] << 26u; + block.z |= indices[4] << 29u; + block.w |= indices[5] << 0u; + block.w |= indices[6] << 3u; + block.w |= indices[7] << 6u; + block.w |= indices[8] << 9u; + block.w |= indices[9] << 11u; + block.w |= indices[10] << 14u; + block.w |= indices[11] << 17u; + block.w |= indices[12] << 20u; + block.w |= indices[13] << 23u; + block.w |= indices[14] << 26u; + block.w |= indices[15] << 29u; + } + } +} + +layout(local_size_x = 8, + local_size_y = 8, + local_size_z = 1) in; + +void main() { + // gather texels for current 4x4 block + // 0 1 2 3 + // 4 5 6 7 + // 8 9 10 11 + // 12 13 14 15 + float2 uv = gl_GlobalInvocationID.xy * params.p_textureSizeRcp * 4.0f + params.p_textureSizeRcp; + float2 block0UV = uv; + float2 block1UV = uv + float2(2.0f * params.p_textureSizeRcp.x, 0.0f); + float2 block2UV = uv + float2(0.0f, 2.0f * params.p_textureSizeRcp.y); + float2 block3UV = uv + float2(2.0f * params.p_textureSizeRcp.x, 2.0f * params.p_textureSizeRcp.y); + float4 block0X = OGRE_GatherRed(srcTexture, pointSampler, block0UV); + float4 block1X = OGRE_GatherRed(srcTexture, pointSampler, block1UV); + float4 block2X = OGRE_GatherRed(srcTexture, pointSampler, block2UV); + float4 block3X = OGRE_GatherRed(srcTexture, pointSampler, block3UV); + float4 block0Y = OGRE_GatherGreen(srcTexture, pointSampler, block0UV); + float4 block1Y = OGRE_GatherGreen(srcTexture, pointSampler, block1UV); + float4 block2Y = OGRE_GatherGreen(srcTexture, pointSampler, block2UV); + float4 block3Y = OGRE_GatherGreen(srcTexture, pointSampler, block3UV); + float4 block0Z = OGRE_GatherBlue(srcTexture, pointSampler, block0UV); + float4 block1Z = OGRE_GatherBlue(srcTexture, pointSampler, block1UV); + float4 block2Z = OGRE_GatherBlue(srcTexture, pointSampler, block2UV); + float4 block3Z = OGRE_GatherBlue(srcTexture, pointSampler, block3UV); + + float3 texels[16]; + texels[0] = float3(block0X.w, block0Y.w, block0Z.w); + texels[1] = float3(block0X.z, block0Y.z, block0Z.z); + texels[2] = float3(block1X.w, block1Y.w, block1Z.w); + texels[3] = float3(block1X.z, block1Y.z, block1Z.z); + texels[4] = float3(block0X.x, block0Y.x, block0Z.x); + texels[5] = float3(block0X.y, block0Y.y, block0Z.y); + texels[6] = float3(block1X.x, block1Y.x, block1Z.x); + texels[7] = float3(block1X.y, block1Y.y, block1Z.y); + texels[8] = float3(block2X.w, block2Y.w, block2Z.w); + texels[9] = float3(block2X.z, block2Y.z, block2Z.z); + texels[10] = float3(block3X.w, block3Y.w, block3Z.w); + texels[11] = float3(block3X.z, block3Y.z, block3Z.z); + texels[12] = float3(block2X.x, block2Y.x, block2Z.x); + texels[13] = float3(block2X.y, block2Y.y, block2Z.y); + texels[14] = float3(block3X.x, block3Y.x, block3Z.x); + texels[15] = float3(block3X.y, block3Y.y, block3Z.y); + + uint4 block = uint4(0u, 0u, 0u, 0u); + float blockMSLE = 0.0f; + + EncodeP1(block, blockMSLE, texels); + +#ifdef QUALITY + float bestScore = EvaluateP2Pattern(0, texels); + uint bestPattern = 0; + + for (uint i = 1u; i < 32u; ++i) { + float score = EvaluateP2Pattern(i, texels); + + if (score < bestScore) { + bestPattern = i; + bestScore = score; + } + } + + EncodeP2Pattern(block, blockMSLE, bestPattern, texels); +#endif + + imageStore(dstTexture, int2(gl_GlobalInvocationID.xy), block); +} diff --git a/modules/betsy/config.py b/modules/betsy/config.py new file mode 100644 index 0000000000..eb565b85b9 --- /dev/null +++ b/modules/betsy/config.py @@ -0,0 +1,6 @@ +def can_build(env, platform): + return env.editor_build + + +def configure(env): + pass diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp new file mode 100644 index 0000000000..7f723826d1 --- /dev/null +++ b/modules/betsy/image_compress_betsy.cpp @@ -0,0 +1,329 @@ +/**************************************************************************/ +/* image_compress_betsy.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "image_compress_betsy.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 + +#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; + + // Create local RD. + RenderingContextDriver *rcd = nullptr; + RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device(); + + if (rd == nullptr) { +#if defined(RD_ENABLED) +#if defined(VULKAN_ENABLED) + rcd = memnew(RenderingContextDriverVulkan); + rd = memnew(RenderingDevice); +#endif +#endif + if (rcd != nullptr && rd != nullptr) { + err = rcd->initialize(); + if (err == OK) { + err = rd->initialize(rcd); + } + + if (err != OK) { + memdelete(rd); + memdelete(rcd); + rd = nullptr; + rcd = nullptr; + } + } + } + + ERR_FAIL_NULL_V_MSG(rd, err, "Unable to create a local RenderingDevice."); + + Ref<RDShaderFile> compute_shader; + compute_shader.instantiate(); + + // Destination format. + Image::Format dest_format = Image::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"; + } + + } break; + + default: + err = ERR_INVALID_PARAMETER; + break; + } + + if (err != OK) { + memdelete(rd); + if (rcd != nullptr) { + memdelete(rcd); + } + + return err; + } + + // Compile the shader, return early if invalid. + RID shader = rd->shader_create_from_spirv(compute_shader->get_spirv_stages(version)); + + if (shader.is_null()) { + memdelete(rd); + if (rcd != nullptr) { + memdelete(rcd); + } + + return err; + } + + RID pipeline = rd->compute_pipeline_create(shader); + + // src_texture format information. + RD::TextureFormat src_texture_format; + { + src_texture_format.array_layers = 1; + src_texture_format.depth = 1; + src_texture_format.mipmaps = 1; + src_texture_format.texture_type = RD::TEXTURE_TYPE_2D; + src_texture_format.usage_bits = RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT; + } + + switch (r_img->get_format()) { + case Image::FORMAT_RH: + src_texture_format.format = RD::DATA_FORMAT_R16_SFLOAT; + break; + + case Image::FORMAT_RGH: + src_texture_format.format = RD::DATA_FORMAT_R16G16_SFLOAT; + break; + + case Image::FORMAT_RGBH: + r_img->convert(Image::FORMAT_RGBAH); + src_texture_format.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT; + break; + + case Image::FORMAT_RGBAH: + src_texture_format.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT; + break; + + case Image::FORMAT_RF: + src_texture_format.format = RD::DATA_FORMAT_R32_SFLOAT; + break; + + case Image::FORMAT_RGF: + src_texture_format.format = RD::DATA_FORMAT_R32G32_SFLOAT; + break; + + case Image::FORMAT_RGBF: + r_img->convert(Image::FORMAT_RGBAF); + src_texture_format.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; + break; + + case Image::FORMAT_RGBAF: + src_texture_format.format = RD::DATA_FORMAT_R32G32B32A32_SFLOAT; + break; + + case Image::FORMAT_RGBE9995: + src_texture_format.format = RD::DATA_FORMAT_E5B9G9R9_UFLOAT_PACK32; + 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; + + const int mip_count = r_img->get_mipmap_count() + 1; + + // Container for the compressed data. + Vector<uint8_t> dst_data; + dst_data.resize(Image::get_image_data_size(r_img->get_width(), r_img->get_height(), dest_format, r_img->has_mipmaps())); + uint8_t *dst_data_ptr = dst_data.ptrw(); + + Vector<Vector<uint8_t>> src_images; + src_images.push_back(Vector<uint8_t>()); + Vector<uint8_t> *src_image_ptr = src_images.ptrw(); + + // Compress each mipmap. + for (int i = 0; i < mip_count; i++) { + int64_t ofs, size; + int width, height; + r_img->get_mipmap_offset_size_and_dimensions(i, ofs, size, width, height); + + // Set the source texture width and size. + src_texture_format.height = height; + src_texture_format.width = width; + + // Set the destination texture width and size. + dst_texture_format.height = (height + 3) >> 2; + dst_texture_format.width = (width + 3) >> 2; + + // Create a buffer filled with the source mip layer data. + src_image_ptr[0].resize(size); + 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()); + + if (dest_format == Image::FORMAT_BPTC_RGBFU || dest_format == Image::FORMAT_BPTC_RGBF) { + BC6PushConstant push_constant; + push_constant.sizeX = 1.0f / width; + push_constant.sizeY = 1.0f / height; + 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); + } + } + + RID uniform_set = rd->uniform_set_create(uniforms, shader, 0); + RD::ComputeListID compute_list = rd->compute_list_begin(); + + 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(); + } + + rd->submit(); + rd->sync(); + + // Copy data from the GPU to the buffer. + const Vector<uint8_t> texture_data = 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); + } + + src_images.clear(); + + // 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; +} + +Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels) { + Image::Format format = r_img->get_format(); + + if (format >= Image::FORMAT_RF && format <= Image::FORMAT_RGBE9995) { + return _compress_betsy(BETSY_FORMAT_BC6, r_img); + } + + return ERR_UNAVAILABLE; +} diff --git a/modules/betsy/image_compress_betsy.h b/modules/betsy/image_compress_betsy.h new file mode 100644 index 0000000000..a64e586c76 --- /dev/null +++ b/modules/betsy/image_compress_betsy.h @@ -0,0 +1,44 @@ +/**************************************************************************/ +/* image_compress_betsy.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 IMAGE_COMPRESS_BETSY_H +#define IMAGE_COMPRESS_BETSY_H + +#include "core/io/image.h" + +enum BetsyFormat { + BETSY_FORMAT_BC6, +}; + +Error _compress_betsy(BetsyFormat p_format, Image *r_img); + +Error _betsy_compress_bptc(Image *r_img, Image::UsedChannels p_channels); + +#endif // IMAGE_COMPRESS_BETSY_H diff --git a/modules/betsy/register_types.cpp b/modules/betsy/register_types.cpp new file mode 100644 index 0000000000..019099e67c --- /dev/null +++ b/modules/betsy/register_types.cpp @@ -0,0 +1,47 @@ +/**************************************************************************/ +/* register_types.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "register_types.h" + +#include "image_compress_betsy.h" + +void initialize_betsy_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + Image::_image_compress_bptc_rd_func = _betsy_compress_bptc; +} + +void uninitialize_betsy_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } +} diff --git a/modules/betsy/register_types.h b/modules/betsy/register_types.h new file mode 100644 index 0000000000..0ce6c553b6 --- /dev/null +++ b/modules/betsy/register_types.h @@ -0,0 +1,39 @@ +/**************************************************************************/ +/* register_types.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_REGISTER_TYPES_H +#define BETSY_REGISTER_TYPES_H + +#include "modules/register_module_types.h" + +void initialize_betsy_module(ModuleInitializationLevel p_level); +void uninitialize_betsy_module(ModuleInitializationLevel p_level); + +#endif // BETSY_REGISTER_TYPES_H diff --git a/modules/camera/camera_macos.mm b/modules/camera/camera_macos.mm index c0d8dc2cef..578a1d6325 100644 --- a/modules/camera/camera_macos.mm +++ b/modules/camera/camera_macos.mm @@ -307,11 +307,17 @@ MyDeviceNotifications *device_notifications = nil; // CameraMacOS - Subclass for our camera server on macOS void CameraMacOS::update_feeds() { -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 - AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; - NSArray *devices = session.devices; +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500 + AVCaptureDeviceDiscoverySession *session; +#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 140000 + // Avoid deprecated warning if the minimum SDK is 14.0. + session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternal, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; #else - NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified]; +#endif + NSArray<AVCaptureDevice *> *devices = session.devices; +#else + NSArray<AVCaptureDevice *> *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; #endif // remove devices that are gone.. diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index 296cda627a..8777651545 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -460,27 +460,31 @@ void CSGShape3D::_update_shape() { _update_collision_faces(); } -void CSGShape3D::_update_collision_faces() { - if (use_collision && is_root_shape() && root_collision_shape.is_valid()) { - CSGBrush *n = _get_brush(); - ERR_FAIL_NULL_MSG(n, "Cannot get CSGBrush."); - Vector<Vector3> physics_faces; - physics_faces.resize(n->faces.size() * 3); - Vector3 *physicsw = physics_faces.ptrw(); - - for (int i = 0; i < n->faces.size(); i++) { - int order[3] = { 0, 1, 2 }; +Vector<Vector3> CSGShape3D::_get_brush_collision_faces() { + Vector<Vector3> collision_faces; + CSGBrush *n = _get_brush(); + ERR_FAIL_NULL_V_MSG(n, collision_faces, "Cannot get CSGBrush."); + collision_faces.resize(n->faces.size() * 3); + Vector3 *collision_faces_ptrw = collision_faces.ptrw(); - if (n->faces[i].invert) { - SWAP(order[1], order[2]); - } + for (int i = 0; i < n->faces.size(); i++) { + int order[3] = { 0, 1, 2 }; - physicsw[i * 3 + 0] = n->faces[i].vertices[order[0]]; - physicsw[i * 3 + 1] = n->faces[i].vertices[order[1]]; - physicsw[i * 3 + 2] = n->faces[i].vertices[order[2]]; + if (n->faces[i].invert) { + SWAP(order[1], order[2]); } - root_collision_shape->set_faces(physics_faces); + collision_faces_ptrw[i * 3 + 0] = n->faces[i].vertices[order[0]]; + collision_faces_ptrw[i * 3 + 1] = n->faces[i].vertices[order[1]]; + collision_faces_ptrw[i * 3 + 2] = n->faces[i].vertices[order[2]]; + } + + return collision_faces; +} + +void CSGShape3D::_update_collision_faces() { + if (use_collision && is_root_shape() && root_collision_shape.is_valid()) { + root_collision_shape->set_faces(_get_brush_collision_faces()); if (_is_debug_collision_shape_visible()) { _update_debug_collision_shape(); @@ -488,6 +492,26 @@ void CSGShape3D::_update_collision_faces() { } } +Ref<ArrayMesh> CSGShape3D::bake_static_mesh() { + Ref<ArrayMesh> baked_mesh; + if (is_root_shape() && root_mesh.is_valid()) { + baked_mesh = root_mesh; + } + return baked_mesh; +} + +Ref<ConcavePolygonShape3D> CSGShape3D::bake_collision_shape() { + Ref<ConcavePolygonShape3D> baked_collision_shape; + if (is_root_shape() && root_collision_shape.is_valid()) { + baked_collision_shape.instantiate(); + baked_collision_shape->set_faces(root_collision_shape->get_faces()); + } else if (is_root_shape()) { + baked_collision_shape.instantiate(); + baked_collision_shape->set_faces(_get_brush_collision_faces()); + } + return baked_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()); } @@ -704,6 +728,9 @@ void CSGShape3D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_meshes"), &CSGShape3D::get_meshes); + ClassDB::bind_method(D_METHOD("bake_static_mesh"), &CSGShape3D::bake_static_mesh); + ClassDB::bind_method(D_METHOD("bake_collision_shape"), &CSGShape3D::bake_collision_shape); + ADD_PROPERTY(PropertyInfo(Variant::INT, "operation", PROPERTY_HINT_ENUM, "Union,Intersection,Subtraction"), "set_operation", "get_operation"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "snap", PROPERTY_HINT_RANGE, "0.000001,1,0.000001,suffix:m"), "set_snap", "get_snap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "calculate_tangents"), "set_calculate_tangents", "is_calculating_tangents"); @@ -934,7 +961,8 @@ CSGBrush *CSGMesh3D::_build_brush() { void CSGMesh3D::_mesh_changed() { _make_dirty(); - update_gizmos(); + + callable_mp((Node3D *)this, &Node3D::update_gizmos).call_deferred(); } void CSGMesh3D::set_material(const Ref<Material> &p_material) { diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index bb7c8be431..8f23ae2f9e 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -113,6 +113,7 @@ private: void _update_debug_collision_shape(); void _clear_debug_collision_shape(); void _on_transform_changed(); + Vector<Vector3> _get_brush_collision_faces(); protected: void _notification(int p_what); @@ -161,6 +162,10 @@ public: bool is_calculating_tangents() const; bool is_root_shape() const; + + Ref<ArrayMesh> bake_static_mesh(); + Ref<ConcavePolygonShape3D> bake_collision_shape(); + CSGShape3D(); ~CSGShape3D(); }; diff --git a/modules/csg/doc_classes/CSGShape3D.xml b/modules/csg/doc_classes/CSGShape3D.xml index f9017e47c7..ac62d8dd83 100644 --- a/modules/csg/doc_classes/CSGShape3D.xml +++ b/modules/csg/doc_classes/CSGShape3D.xml @@ -5,12 +5,29 @@ </brief_description> <description> This is the CSG base class that provides CSG operation support to the various CSG nodes in Godot. - [b]Note:[/b] CSG nodes are intended to be used for level prototyping. Creating CSG nodes has a significant CPU cost compared to creating a [MeshInstance3D] with a [PrimitiveMesh]. Moving a CSG node within another CSG node also has a significant CPU cost, so it should be avoided during gameplay. + [b]Performance:[/b] CSG nodes are only intended for prototyping as they have a significant CPU performance cost. + Consider baking final CSG operation results into static geometry that replaces the CSG nodes. + Individual CSG root node results can be baked to nodes with static resources with the editor menu that appears when a CSG root node is selected. + Individual CSG root nodes can also be baked to static resources with scripts by calling [method bake_static_mesh] for the visual mesh or [method bake_collision_shape] for the physics collision. + Entire scenes of CSG nodes can be baked to static geometry and exported with the editor gltf scene exporter. </description> <tutorials> <link title="Prototyping levels with CSG">$DOCS_URL/tutorials/3d/csg_tools.html</link> </tutorials> <methods> + <method name="bake_collision_shape"> + <return type="ConcavePolygonShape3D" /> + <description> + Returns a baked physics [ConcavePolygonShape3D] of this node's CSG operation result. Returns an empty shape if the node is not a CSG root node or has no valid geometry. + [b]Performance:[/b] If the CSG operation results in a very detailed geometry with many faces physics performance will be very slow. Concave shapes should in general only be used for static level geometry and not with dynamic objects that are moving. + </description> + </method> + <method name="bake_static_mesh"> + <return type="ArrayMesh" /> + <description> + Returns a baked static [ArrayMesh] of this node's CSG operation result. Materials from involved CSG nodes are added as extra mesh surfaces. Returns an empty mesh if the node is not a CSG root node or has no valid geometry. + </description> + </method> <method name="get_collision_layer_value" qualifiers="const"> <return type="bool" /> <param index="0" name="layer_number" type="int" /> diff --git a/modules/csg/editor/csg_gizmos.cpp b/modules/csg/editor/csg_gizmos.cpp index ea7b6d225e..72676f4a40 100644 --- a/modules/csg/editor/csg_gizmos.cpp +++ b/modules/csg/editor/csg_gizmos.cpp @@ -38,6 +38,135 @@ #include "editor/plugins/gizmos/gizmo_3d_helper.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/camera_3d.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/physics/collision_shape_3d.h" +#include "scene/3d/physics/static_body_3d.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/menu_button.h" + +void CSGShapeEditor::_node_removed(Node *p_node) { + if (p_node == node) { + node = nullptr; + options->hide(); + } +} + +void CSGShapeEditor::edit(CSGShape3D *p_csg_shape) { + node = p_csg_shape; + if (node) { + options->show(); + } else { + options->hide(); + } +} + +void CSGShapeEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + options->set_icon(get_editor_theme_icon(SNAME("CSGCombiner3D"))); + } break; + } +} + +void CSGShapeEditor::_menu_option(int p_option) { + Array meshes = node->get_meshes(); + if (meshes.is_empty()) { + err_dialog->set_text(TTR("CSG operation returned an empty array.")); + err_dialog->popup_centered(); + return; + } + + switch (p_option) { + case MENU_OPTION_BAKE_MESH_INSTANCE: { + _create_baked_mesh_instance(); + } break; + case MENU_OPTION_BAKE_COLLISION_SHAPE: { + _create_baked_collision_shape(); + } break; + } +} + +void CSGShapeEditor::_create_baked_mesh_instance() { + if (node == get_tree()->get_edited_scene_root()) { + err_dialog->set_text(TTR("Can not add a baked mesh as sibling for the scene root.\nMove the CSG root node below a parent node.")); + err_dialog->popup_centered(); + return; + } + + Ref<ArrayMesh> mesh = node->bake_static_mesh(); + if (mesh.is_null()) { + err_dialog->set_text(TTR("CSG operation returned an empty mesh.")); + err_dialog->popup_centered(); + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Create baked CSGShape3D Mesh Instance")); + + Node *owner = get_tree()->get_edited_scene_root(); + + MeshInstance3D *mi = memnew(MeshInstance3D); + mi->set_mesh(mesh); + mi->set_name("CSGBakedMeshInstance3D"); + mi->set_transform(node->get_transform()); + ur->add_do_method(node, "add_sibling", mi, true); + ur->add_do_method(mi, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), mi); + + ur->add_do_reference(mi); + ur->add_undo_method(node->get_parent(), "remove_child", mi); + + ur->commit_action(); +} + +void CSGShapeEditor::_create_baked_collision_shape() { + if (node == get_tree()->get_edited_scene_root()) { + err_dialog->set_text(TTR("Can not add a baked collision shape as sibling for the scene root.\nMove the CSG root node below a parent node.")); + err_dialog->popup_centered(); + return; + } + + Ref<Shape3D> shape = node->bake_collision_shape(); + if (shape.is_null()) { + err_dialog->set_text(TTR("CSG operation returned an empty shape.")); + err_dialog->popup_centered(); + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Create baked CSGShape3D Collision Shape")); + + Node *owner = get_tree()->get_edited_scene_root(); + + CollisionShape3D *cshape = memnew(CollisionShape3D); + cshape->set_shape(shape); + cshape->set_name("CSGBakedCollisionShape3D"); + cshape->set_transform(node->get_transform()); + ur->add_do_method(node, "add_sibling", cshape, true); + ur->add_do_method(cshape, "set_owner", owner); + ur->add_do_method(Node3DEditor::get_singleton(), SceneStringName(_request_gizmo), cshape); + + ur->add_do_reference(cshape); + ur->add_undo_method(node->get_parent(), "remove_child", cshape); + + ur->commit_action(); +} + +CSGShapeEditor::CSGShapeEditor() { + options = memnew(MenuButton); + options->hide(); + options->set_text(TTR("CSG")); + options->set_switch_on_hover(true); + Node3DEditor::get_singleton()->add_control_to_menu_panel(options); + + options->get_popup()->add_item(TTR("Bake Mesh Instance"), MENU_OPTION_BAKE_MESH_INSTANCE); + options->get_popup()->add_item(TTR("Bake Collision Shape"), MENU_OPTION_BAKE_COLLISION_SHAPE); + + options->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &CSGShapeEditor::_menu_option)); + + err_dialog = memnew(AcceptDialog); + add_child(err_dialog); +} /////////// @@ -393,9 +522,26 @@ void CSGShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } } +void EditorPluginCSG::edit(Object *p_object) { + CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object); + if (csg_shape && csg_shape->is_root_shape()) { + csg_shape_editor->edit(csg_shape); + } else { + csg_shape_editor->edit(nullptr); + } +} + +bool EditorPluginCSG::handles(Object *p_object) const { + CSGShape3D *csg_shape = Object::cast_to<CSGShape3D>(p_object); + return csg_shape && csg_shape->is_root_shape(); +} + EditorPluginCSG::EditorPluginCSG() { Ref<CSGShape3DGizmoPlugin> gizmo_plugin = Ref<CSGShape3DGizmoPlugin>(memnew(CSGShape3DGizmoPlugin)); 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); } #endif // TOOLS_ENABLED diff --git a/modules/csg/editor/csg_gizmos.h b/modules/csg/editor/csg_gizmos.h index de19b33e7d..c562fe9fe7 100644 --- a/modules/csg/editor/csg_gizmos.h +++ b/modules/csg/editor/csg_gizmos.h @@ -37,8 +37,11 @@ #include "editor/plugins/editor_plugin.h" #include "editor/plugins/node_3d_editor_gizmos.h" +#include "scene/gui/control.h" +class AcceptDialog; class Gizmo3DHelper; +class MenuButton; class CSGShape3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(CSGShape3DGizmoPlugin, EditorNode3DGizmoPlugin); @@ -62,10 +65,43 @@ public: ~CSGShape3DGizmoPlugin(); }; +class CSGShapeEditor : public Control { + GDCLASS(CSGShapeEditor, Control); + + enum Menu { + MENU_OPTION_BAKE_MESH_INSTANCE, + MENU_OPTION_BAKE_COLLISION_SHAPE, + }; + + CSGShape3D *node = nullptr; + MenuButton *options = nullptr; + AcceptDialog *err_dialog = nullptr; + + void _menu_option(int p_option); + + void _create_baked_mesh_instance(); + void _create_baked_collision_shape(); + +protected: + void _node_removed(Node *p_node); + + void _notification(int p_what); + +public: + void edit(CSGShape3D *p_csg_shape); + CSGShapeEditor(); +}; + class EditorPluginCSG : public EditorPlugin { GDCLASS(EditorPluginCSG, EditorPlugin); + CSGShapeEditor *csg_shape_editor = nullptr; + public: + virtual String get_name() const override { return "CSGShape3D"; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + EditorPluginCSG(); }; diff --git a/modules/cvtt/image_compress_cvtt.cpp b/modules/cvtt/image_compress_cvtt.cpp index 7335315c51..2087dde2a1 100644 --- a/modules/cvtt/image_compress_cvtt.cpp +++ b/modules/cvtt/image_compress_cvtt.cpp @@ -142,14 +142,17 @@ static void _digest_job_queue(void *p_job_queue, uint32_t p_index) { } void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { + uint64_t start_time = OS::get_singleton()->get_ticks_msec(); + if (p_image->is_compressed()) { return; //do not compress, already compressed } + int w = p_image->get_width(); int h = p_image->get_height(); bool is_ldr = (p_image->get_format() <= Image::FORMAT_RGBA8); - bool is_hdr = (p_image->get_format() >= Image::FORMAT_RH) && (p_image->get_format() <= Image::FORMAT_RGBE9995); + bool is_hdr = (p_image->get_format() >= Image::FORMAT_RF) && (p_image->get_format() <= Image::FORMAT_RGBE9995); if (!is_ldr && !is_hdr) { return; // Not a usable source format @@ -171,17 +174,7 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { p_image->convert(Image::FORMAT_RGBH); } - const uint8_t *rb = p_image->get_data().ptr(); - - const uint16_t *source_data = reinterpret_cast<const uint16_t *>(&rb[0]); - int pixel_element_count = w * h * 3; - for (int i = 0; i < pixel_element_count; i++) { - if ((source_data[i] & 0x8000) != 0 && (source_data[i] & 0x7fff) != 0) { - is_signed = true; - break; - } - } - + is_signed = p_image->detect_signed(); target_format = is_signed ? Image::FORMAT_BPTC_RGBF : Image::FORMAT_BPTC_RGBFU; } else { p_image->convert(Image::FORMAT_RGBA8); //still uses RGBA to convert @@ -190,14 +183,14 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { const uint8_t *rb = p_image->get_data().ptr(); Vector<uint8_t> data; - int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); + int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); int mm_count = p_image->has_mipmaps() ? Image::get_image_required_mipmaps(w, h, target_format) : 0; data.resize(target_size); int shift = Image::get_format_pixel_rshift(target_format); uint8_t *wb = data.ptrw(); - int dst_ofs = 0; + int64_t dst_ofs = 0; CVTTCompressionJobQueue job_queue; job_queue.job_params.is_hdr = is_hdr; @@ -219,7 +212,7 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { int bw = w % 4 != 0 ? w + (4 - w % 4) : w; int bh = h % 4 != 0 ? h + (4 - h % 4) : h; - int src_ofs = p_image->get_mipmap_offset(i); + int64_t src_ofs = p_image->get_mipmap_offset(i); const uint8_t *in_bytes = &rb[src_ofs]; uint8_t *out_bytes = &wb[dst_ofs]; @@ -250,6 +243,8 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) { WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data); + + print_verbose(vformat("CVTT: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time)); } void image_decompress_cvtt(Image *p_image) { @@ -279,7 +274,7 @@ void image_decompress_cvtt(Image *p_image) { const uint8_t *rb = p_image->get_data().ptr(); Vector<uint8_t> data; - int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); + int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); int mm_count = p_image->get_mipmap_count(); data.resize(target_size); @@ -287,10 +282,10 @@ void image_decompress_cvtt(Image *p_image) { int bytes_per_pixel = is_hdr ? 6 : 4; - int dst_ofs = 0; + int64_t dst_ofs = 0; for (int i = 0; i <= mm_count; i++) { - int src_ofs = p_image->get_mipmap_offset(i); + int64_t src_ofs = p_image->get_mipmap_offset(i); const uint8_t *in_bytes = &rb[src_ofs]; uint8_t *out_bytes = &wb[dst_ofs]; diff --git a/modules/dds/texture_loader_dds.cpp b/modules/dds/texture_loader_dds.cpp index b2de6b656e..6ea44c5fc3 100644 --- a/modules/dds/texture_loader_dds.cpp +++ b/modules/dds/texture_loader_dds.cpp @@ -42,14 +42,18 @@ enum { DDSD_PITCH = 0x00000008, DDSD_LINEARSIZE = 0x00080000, DDSD_MIPMAPCOUNT = 0x00020000, - DDPF_FOURCC = 0x00000004, DDPF_ALPHAPIXELS = 0x00000001, - DDPF_RGB = 0x00000040 + DDPF_ALPHAONLY = 0x00000002, + DDPF_FOURCC = 0x00000004, + DDPF_RGB = 0x00000040, + DDPF_RG_SNORM = 0x00080000 }; enum DDSFourCC { DDFCC_DXT1 = PF_FOURCC("DXT1"), + DDFCC_DXT2 = PF_FOURCC("DXT2"), DDFCC_DXT3 = PF_FOURCC("DXT3"), + DDFCC_DXT4 = PF_FOURCC("DXT4"), DDFCC_DXT5 = PF_FOURCC("DXT5"), DDFCC_ATI1 = PF_FOURCC("ATI1"), DDFCC_BC4U = PF_FOURCC("BC4U"), @@ -68,17 +72,25 @@ enum DDSFourCC { // Reference: https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format enum DXGIFormat { DXGI_R32G32B32A32_FLOAT = 2, + DXGI_R32G32B32_FLOAT = 6, DXGI_R16G16B16A16_FLOAT = 10, DXGI_R32G32_FLOAT = 16, DXGI_R10G10B10A2_UNORM = 24, DXGI_R8G8B8A8_UNORM = 28, + DXGI_R8G8B8A8_UNORM_SRGB = 29, DXGI_R16G16_FLOAT = 34, DXGI_R32_FLOAT = 41, + DXGI_R8G8_UNORM = 49, DXGI_R16_FLOAT = 54, + DXGI_R8_UNORM = 61, + DXGI_A8_UNORM = 65, DXGI_R9G9B9E5 = 67, DXGI_BC1_UNORM = 71, + DXGI_BC1_UNORM_SRGB = 72, DXGI_BC2_UNORM = 74, + DXGI_BC2_UNORM_SRGB = 75, DXGI_BC3_UNORM = 77, + DXGI_BC3_UNORM_SRGB = 78, DXGI_BC4_UNORM = 80, DXGI_BC5_UNORM = 83, DXGI_B5G6R5_UNORM = 85, @@ -87,6 +99,7 @@ enum DXGIFormat { DXGI_BC6H_UF16 = 95, DXGI_BC6H_SF16 = 96, DXGI_BC7_UNORM = 98, + DXGI_BC7_UNORM_SRGB = 99, DXGI_B4G4R4A4_UNORM = 115 }; @@ -100,25 +113,29 @@ enum DDSFormat { DDS_ATI2, DDS_BC6U, DDS_BC6S, - DDS_BC7U, + DDS_BC7, DDS_R16F, DDS_RG16F, DDS_RGBA16F, DDS_R32F, DDS_RG32F, + DDS_RGB32F, DDS_RGBA32F, DDS_RGB9E5, - DDS_BGRA8, - DDS_BGR8, - DDS_RGBA8, DDS_RGB8, + DDS_RGBA8, + DDS_BGR8, + DDS_BGRA8, DDS_BGR5A1, DDS_BGR565, + DDS_B2GR3, + DDS_B2GR3A8, DDS_BGR10A2, DDS_RGB10A2, DDS_BGRA4, DDS_LUMINANCE, DDS_LUMINANCE_ALPHA, + DDS_LUMINANCE_ALPHA_4, DDS_MAX }; @@ -132,31 +149,35 @@ struct DDSFormatInfo { static const DDSFormatInfo dds_format_info[DDS_MAX] = { { "DXT1/BC1", true, 4, 8, Image::FORMAT_DXT1 }, - { "DXT3/BC2", true, 4, 16, Image::FORMAT_DXT3 }, - { "DXT5/BC3", true, 4, 16, Image::FORMAT_DXT5 }, + { "DXT2/DXT3/BC2", true, 4, 16, Image::FORMAT_DXT3 }, + { "DXT4/DXT5/BC3", true, 4, 16, Image::FORMAT_DXT5 }, { "ATI1/BC4", true, 4, 8, Image::FORMAT_RGTC_R }, { "ATI2/A2XY/BC5", true, 4, 16, Image::FORMAT_RGTC_RG }, - { "BC6U", true, 4, 16, Image::FORMAT_BPTC_RGBFU }, - { "BC6S", true, 4, 16, Image::FORMAT_BPTC_RGBF }, - { "BC7U", true, 4, 16, Image::FORMAT_BPTC_RGBA }, + { "BC6UF", true, 4, 16, Image::FORMAT_BPTC_RGBFU }, + { "BC6SF", true, 4, 16, Image::FORMAT_BPTC_RGBF }, + { "BC7", true, 4, 16, Image::FORMAT_BPTC_RGBA }, { "R16F", false, 1, 2, Image::FORMAT_RH }, { "RG16F", false, 1, 4, Image::FORMAT_RGH }, { "RGBA16F", false, 1, 8, Image::FORMAT_RGBAH }, { "R32F", false, 1, 4, Image::FORMAT_RF }, { "RG32F", false, 1, 8, Image::FORMAT_RGF }, + { "RGB32F", false, 1, 12, Image::FORMAT_RGBF }, { "RGBA32F", false, 1, 16, Image::FORMAT_RGBAF }, { "RGB9E5", false, 1, 4, Image::FORMAT_RGBE9995 }, - { "BGRA8", false, 1, 4, Image::FORMAT_RGBA8 }, - { "BGR8", false, 1, 3, Image::FORMAT_RGB8 }, - { "RGBA8", false, 1, 4, Image::FORMAT_RGBA8 }, { "RGB8", false, 1, 3, Image::FORMAT_RGB8 }, + { "RGBA8", false, 1, 4, Image::FORMAT_RGBA8 }, + { "BGR8", false, 1, 3, Image::FORMAT_RGB8 }, + { "BGRA8", false, 1, 4, Image::FORMAT_RGBA8 }, { "BGR5A1", false, 1, 2, Image::FORMAT_RGBA8 }, { "BGR565", false, 1, 2, Image::FORMAT_RGB8 }, + { "B2GR3", false, 1, 1, Image::FORMAT_RGB8 }, + { "B2GR3A8", false, 1, 2, Image::FORMAT_RGBA8 }, { "BGR10A2", false, 1, 4, Image::FORMAT_RGBA8 }, { "RGB10A2", false, 1, 4, Image::FORMAT_RGBA8 }, { "BGRA4", false, 1, 2, Image::FORMAT_RGBA8 }, { "GRAYSCALE", false, 1, 1, Image::FORMAT_L8 }, - { "GRAYSCALE_ALPHA", false, 1, 2, Image::FORMAT_LA8 } + { "GRAYSCALE_ALPHA", false, 1, 2, Image::FORMAT_LA8 }, + { "GRAYSCALE_ALPHA_4", false, 1, 1, Image::FORMAT_LA8 } }; static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) { @@ -164,6 +185,9 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) { case DXGI_R32G32B32A32_FLOAT: { return DDS_RGBA32F; } + case DXGI_R32G32B32_FLOAT: { + return DDS_RGB32F; + } case DXGI_R16G16B16A16_FLOAT: { return DDS_RGBA16F; } @@ -173,7 +197,8 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) { case DXGI_R10G10B10A2_UNORM: { return DDS_RGB10A2; } - case DXGI_R8G8B8A8_UNORM: { + case DXGI_R8G8B8A8_UNORM: + case DXGI_R8G8B8A8_UNORM_SRGB: { return DDS_RGBA8; } case DXGI_R16G16_FLOAT: { @@ -182,19 +207,29 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) { case DXGI_R32_FLOAT: { return DDS_R32F; } + case DXGI_R8_UNORM: + case DXGI_A8_UNORM: { + return DDS_LUMINANCE; + } case DXGI_R16_FLOAT: { return DDS_R16F; } + case DXGI_R8G8_UNORM: { + return DDS_LUMINANCE_ALPHA; + } case DXGI_R9G9B9E5: { return DDS_RGB9E5; } - case DXGI_BC1_UNORM: { + case DXGI_BC1_UNORM: + case DXGI_BC1_UNORM_SRGB: { return DDS_DXT1; } - case DXGI_BC2_UNORM: { + case DXGI_BC2_UNORM: + case DXGI_BC2_UNORM_SRGB: { return DDS_DXT3; } - case DXGI_BC3_UNORM: { + case DXGI_BC3_UNORM: + case DXGI_BC3_UNORM_SRGB: { return DDS_DXT5; } case DXGI_BC4_UNORM: { @@ -218,8 +253,9 @@ static DDSFormat dxgi_to_dds_format(uint32_t p_dxgi_format) { case DXGI_BC6H_SF16: { return DDS_BC6S; } - case DXGI_BC7_UNORM: { - return DDS_BC7U; + case DXGI_BC7_UNORM: + case DXGI_BC7_UNORM_SRGB: { + return DDS_BC7; } case DXGI_B4G4R4A4_UNORM: { return DDS_BGRA4; @@ -299,9 +335,11 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig case DDFCC_DXT1: { dds_format = DDS_DXT1; } break; + case DDFCC_DXT2: case DDFCC_DXT3: { dds_format = DDS_DXT3; } break; + case DDFCC_DXT4: case DDFCC_DXT5: { dds_format = DDS_DXT5; } break; @@ -363,6 +401,8 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig dds_format = DDS_RGB10A2; } else if (format_rgb_bits == 16 && format_red_mask == 0xf00 && format_green_mask == 0xf0 && format_blue_mask == 0xf && format_alpha_mask == 0xf000) { dds_format = DDS_BGRA4; + } else if (format_rgb_bits == 16 && format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3 && format_alpha_mask == 0xff00) { + dds_format = DDS_B2GR3A8; } } else { @@ -373,18 +413,38 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig dds_format = DDS_RGB8; } else if (format_rgb_bits == 16 && format_red_mask == 0x0000f800 && format_green_mask == 0x000007e0 && format_blue_mask == 0x0000001f) { dds_format = DDS_BGR565; + } else if (format_rgb_bits == 8 && format_red_mask == 0xe0 && format_green_mask == 0x1c && format_blue_mask == 0x3) { + dds_format = DDS_B2GR3; } } } else { // Other formats. - if (format_flags & DDPF_ALPHAPIXELS && format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) { - dds_format = DDS_LUMINANCE_ALPHA; - } else if (!(format_flags & DDPF_ALPHAPIXELS) && format_rgb_bits == 8 && format_red_mask == 0xff) { + if (format_flags & DDPF_ALPHAONLY && format_rgb_bits == 8 && format_alpha_mask == 0xff) { + // Alpha only. dds_format = DDS_LUMINANCE; } } + // Depending on the writer, luminance formats may or may not have the DDPF_RGB or DDPF_LUMINANCE flags defined, + // so we check for these formats after everything else failed. + if (dds_format == DDS_MAX) { + if (format_flags & DDPF_ALPHAPIXELS) { + // With alpha. + if (format_rgb_bits == 16 && format_red_mask == 0xff && format_alpha_mask == 0xff00) { + dds_format = DDS_LUMINANCE_ALPHA; + } else if (format_rgb_bits == 8 && format_red_mask == 0xf && format_alpha_mask == 0xf0) { + dds_format = DDS_LUMINANCE_ALPHA_4; + } + + } else { + // Without alpha. + if (format_rgb_bits == 8 && format_red_mask == 0xff) { + dds_format = DDS_LUMINANCE; + } + } + } + // No format detected, error. if (dds_format == DDS_MAX) { ERR_FAIL_V_MSG(Ref<Resource>(), "Unrecognized or unsupported color layout in DDS '" + p_path + "'."); @@ -433,10 +493,24 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig } // Calculate the space these formats will take up after decoding. - if (dds_format == DDS_BGR565) { - size = size * 3 / 2; - } else if (dds_format == DDS_BGR5A1 || dds_format == DDS_BGRA4) { - size = size * 2; + switch (dds_format) { + case DDS_BGR565: + size = size * 3 / 2; + break; + + case DDS_BGR5A1: + case DDS_BGRA4: + case DDS_B2GR3A8: + case DDS_LUMINANCE_ALPHA_4: + size = size * 2; + break; + + case DDS_B2GR3: + size = size * 3; + break; + + default: + break; } src_data.resize(size); @@ -503,6 +577,44 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig } } break; + case DDS_B2GR3: { + // To RGB8. + int colcount = size / 3; + + for (int i = colcount - 1; i >= 0; i--) { + int src_ofs = i; + int dst_ofs = i * 3; + + uint8_t b = (wb[src_ofs] & 0x3) << 6; + uint8_t g = (wb[src_ofs] & 0x1C) << 3; + uint8_t r = (wb[src_ofs] & 0xE0); + + wb[dst_ofs] = r; + wb[dst_ofs + 1] = g; + wb[dst_ofs + 2] = b; + } + + } break; + case DDS_B2GR3A8: { + // To RGBA8. + int colcount = size / 4; + + for (int i = colcount - 1; i >= 0; i--) { + int src_ofs = i * 2; + int dst_ofs = i * 4; + + uint8_t b = (wb[src_ofs] & 0x3) << 6; + uint8_t g = (wb[src_ofs] & 0x1C) << 3; + uint8_t r = (wb[src_ofs] & 0xE0); + uint8_t a = wb[src_ofs + 1]; + + wb[dst_ofs] = r; + wb[dst_ofs + 1] = g; + wb[dst_ofs + 2] = b; + wb[dst_ofs + 3] = a; + } + + } break; case DDS_RGB10A2: { // To RGBA8. int colcount = size / 4; @@ -549,6 +661,8 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig } } break; + + // Channel-swapped. case DDS_BGRA8: { // To RGBA8. int colcount = size / 4; @@ -568,6 +682,24 @@ Ref<Resource> ResourceFormatDDS::load(const String &p_path, const String &p_orig } break; + // Grayscale. + case DDS_LUMINANCE_ALPHA_4: { + // To LA8. + int colcount = size / 2; + + for (int i = colcount - 1; i >= 0; i--) { + int src_ofs = i; + int dst_ofs = i * 2; + + uint8_t l = wb[src_ofs] & 0x0F; + uint8_t a = wb[src_ofs] & 0xF0; + + wb[dst_ofs] = (l << 4) | l; + wb[dst_ofs + 1] = a | (a >> 4); + } + + } break; + default: { } } diff --git a/modules/enet/doc_classes/ENetConnection.xml b/modules/enet/doc_classes/ENetConnection.xml index ebd1577172..84534fec06 100644 --- a/modules/enet/doc_classes/ENetConnection.xml +++ b/modules/enet/doc_classes/ENetConnection.xml @@ -51,7 +51,7 @@ <param index="3" name="data" type="int" default="0" /> <description> Initiates a connection to a foreign [param address] using the specified [param port] and allocating the requested [param channels]. Optional [param data] can be passed during connection in the form of a 32 bit integer. - [b]Note:[/b] You must call either [method create_host] or [method create_host_bound] before calling this method. + [b]Note:[/b] You must call either [method create_host] or [method create_host_bound] on both ends before calling this method. </description> </method> <method name="create_host"> @@ -61,7 +61,9 @@ <param index="2" name="in_bandwidth" type="int" default="0" /> <param index="3" name="out_bandwidth" type="int" default="0" /> <description> - Create an ENetHost that will allow up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth]. + Creates an ENetHost that allows up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth] (if greater than zero). + This method binds a random available dynamic UDP port on the host machine at the [i]unspecified[/i] address. Use [method create_host_bound] to specify the address and port. + [b]Note:[/b] It is necessary to create a host in both client and server in order to establish a connection. </description> </method> <method name="create_host_bound"> @@ -73,7 +75,8 @@ <param index="4" name="in_bandwidth" type="int" default="0" /> <param index="5" name="out_bandwidth" type="int" default="0" /> <description> - Create an ENetHost like [method create_host] which is also bound to the given [param bind_address] and [param bind_port]. + Creates an ENetHost bound to the given [param bind_address] and [param bind_port] that allows up to [param max_peers] connected peers, each allocating up to [param max_channels] channels, optionally limiting bandwidth to [param in_bandwidth] and [param out_bandwidth] (if greater than zero). + [b]Note:[/b] It is necessary to create a host in both client and server in order to establish a connection. </description> </method> <method name="destroy"> @@ -141,8 +144,9 @@ <return type="Array" /> <param index="0" name="timeout" type="int" default="0" /> <description> - Waits for events on the specified host and shuttles packets between the host and its peers, with the given [param timeout] (in milliseconds). The returned [Array] will have 4 elements. An [enum EventType], the [ENetPacketPeer] which generated the event, the event associated data (if any), the event associated channel (if any). If the generated event is [constant EVENT_RECEIVE], the received packet will be queued to the associated [ENetPacketPeer]. + Waits for events on this connection and shuttles packets between the host and its peers, with the given [param timeout] (in milliseconds). The returned [Array] will have 4 elements. An [enum EventType], the [ENetPacketPeer] which generated the event, the event associated data (if any), the event associated channel (if any). If the generated event is [constant EVENT_RECEIVE], the received packet will be queued to the associated [ENetPacketPeer]. Call this function regularly to handle connections, disconnections, and to receive new packets. + [b]Note:[/b] This method must be called on both ends involved in the event (sending and receiving hosts). </description> </method> <method name="socket_send"> diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index 4ce0cf50d9..14887ce469 100644 --- a/modules/etcpak/image_compress_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -50,6 +50,7 @@ EtcpakType _determine_etc_type(Image::UsedChannels p_channels) { return EtcpakType::ETCPAK_TYPE_ETC2; case Image::USED_CHANNELS_RGBA: return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA; + default: return EtcpakType::ETCPAK_TYPE_ETC2_ALPHA; } @@ -69,6 +70,7 @@ EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) { return EtcpakType::ETCPAK_TYPE_DXT1; case Image::USED_CHANNELS_RGBA: return EtcpakType::ETCPAK_TYPE_DXT5; + default: return EtcpakType::ETCPAK_TYPE_DXT5; } @@ -79,71 +81,86 @@ void _compress_etc1(Image *r_img) { } void _compress_etc2(Image *r_img, Image::UsedChannels p_channels) { - EtcpakType type = _determine_etc_type(p_channels); - _compress_etcpak(type, r_img); + _compress_etcpak(_determine_etc_type(p_channels), r_img); } void _compress_bc(Image *r_img, Image::UsedChannels p_channels) { - EtcpakType type = _determine_dxt_type(p_channels); - _compress_etcpak(type, r_img); + _compress_etcpak(_determine_dxt_type(p_channels), r_img); } -void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { +void _compress_etcpak(EtcpakType p_compress_type, Image *r_img) { uint64_t start_time = OS::get_singleton()->get_ticks_msec(); - Image::Format img_format = r_img->get_format(); - if (Image::is_format_compressed(img_format)) { - return; // Do not compress, already compressed. - } - if (img_format > Image::FORMAT_RGBA8) { - // TODO: we should be able to handle FORMAT_RGBA4444 and FORMAT_RGBA5551 eventually + // The image is already compressed, return. + if (r_img->is_compressed()) { return; } - // Use RGBA8 to convert. - if (img_format != Image::FORMAT_RGBA8) { - r_img->convert(Image::FORMAT_RGBA8); - } + // Convert to RGBA8 for compression. + r_img->convert(Image::FORMAT_RGBA8); // Determine output format based on Etcpak type. Image::Format target_format = Image::FORMAT_RGBA8; - if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) { - target_format = Image::FORMAT_ETC; - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) { - target_format = Image::FORMAT_ETC2_RGB8; - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_R) { - target_format = Image::FORMAT_ETC2_R11; - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RG) { - target_format = Image::FORMAT_ETC2_RG11; - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { - target_format = Image::FORMAT_ETC2_RA_AS_RG; - r_img->convert_rg_to_ra_rgba8(); - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) { - target_format = Image::FORMAT_ETC2_RGBA8; - r_img->convert_rgba8_to_bgra8(); // It's badly documented but ETCPAK seems to be expected BGRA8 for ETC. - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) { - target_format = Image::FORMAT_DXT1; - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) { - target_format = Image::FORMAT_DXT5_RA_AS_RG; - r_img->convert_rg_to_ra_rgba8(); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) { - target_format = Image::FORMAT_DXT5; - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_R) { - target_format = Image::FORMAT_RGTC_R; - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_RGTC_RG) { - target_format = Image::FORMAT_RGTC_RG; - } else { - ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT."); + + switch (p_compress_type) { + case EtcpakType::ETCPAK_TYPE_ETC1: + target_format = Image::FORMAT_ETC; + break; + + case EtcpakType::ETCPAK_TYPE_ETC2: + target_format = Image::FORMAT_ETC2_RGB8; + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA: + target_format = Image::FORMAT_ETC2_RGBA8; + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_R: + target_format = Image::FORMAT_ETC2_R11; + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_RG: + target_format = Image::FORMAT_ETC2_RG11; + break; + + case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG: + target_format = Image::FORMAT_ETC2_RA_AS_RG; + r_img->convert_rg_to_ra_rgba8(); + break; + + case EtcpakType::ETCPAK_TYPE_DXT1: + target_format = Image::FORMAT_DXT1; + break; + + case EtcpakType::ETCPAK_TYPE_DXT5: + target_format = Image::FORMAT_DXT5; + break; + + case EtcpakType::ETCPAK_TYPE_RGTC_R: + target_format = Image::FORMAT_RGTC_R; + break; + + case EtcpakType::ETCPAK_TYPE_RGTC_RG: + target_format = Image::FORMAT_RGTC_RG; + break; + + case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG: + target_format = Image::FORMAT_DXT5_RA_AS_RG; + r_img->convert_rg_to_ra_rgba8(); + break; + + default: + ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT."); + break; } - // Compress image data and (if required) mipmaps. + // It's badly documented but ETCPAK seems to expect BGRA8 for ETC formats. + if (p_compress_type < EtcpakType::ETCPAK_TYPE_DXT1) { + r_img->convert_rgba8_to_bgra8(); + } - const bool mipmaps = r_img->has_mipmaps(); + // Compress image data and (if required) mipmaps. + const bool has_mipmaps = r_img->has_mipmaps(); int width = r_img->get_width(); int height = r_img->get_height(); @@ -164,109 +181,115 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { are used for a 2x2 map, and texel 'a' is used for 1x1. Note that this is similar to, but distinct from, the surface pitch, which can encompass additional padding beyond the physical surface size. */ - int next_width = width <= 2 ? width : (width + 3) & ~3; - int next_height = height <= 2 ? height : (height + 3) & ~3; - if (next_width != width || next_height != height) { - r_img->resize(next_width, next_height, Image::INTERPOLATE_LANCZOS); - width = r_img->get_width(); - height = r_img->get_height(); + + if (width % 4 != 0 || height % 4 != 0) { + width = width <= 2 ? width : (width + 3) & ~3; + height = height <= 2 ? height : (height + 3) & ~3; } - // ERR_FAIL_COND(width % 4 != 0 || height % 4 != 0); // FIXME: No longer guaranteed. + // Multiple-of-4 should be guaranteed by above. // However, power-of-two 3d textures will create Nx2 and Nx1 mipmap levels, // which are individually compressed Image objects that violate the above rule. // Hence, we allow Nx1 and Nx2 images through without forcing to multiple-of-4. - const uint8_t *src_read = r_img->get_data().ptr(); - - print_verbose(vformat("etcpak: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : "")); - - int dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); + // Create the buffer for compressed image data. Vector<uint8_t> dest_data; - dest_data.resize(dest_size); + dest_data.resize(Image::get_image_data_size(width, height, target_format, has_mipmaps)); uint8_t *dest_write = dest_data.ptrw(); - int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0; + const uint8_t *src_read = r_img->get_data().ptr(); + + const int mip_count = has_mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0; Vector<uint32_t> padded_src; for (int i = 0; i < mip_count + 1; i++) { // Get write mip metrics for target image. - int orig_mip_w, orig_mip_h; - int mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, orig_mip_w, orig_mip_h); + int dest_mip_w, dest_mip_h; + int64_t dest_mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, dest_mip_w, dest_mip_h); + // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). - ERR_FAIL_COND(mip_ofs % 8 != 0); - uint64_t *dest_mip_write = (uint64_t *)&dest_write[mip_ofs]; + ERR_FAIL_COND(dest_mip_ofs % 8 != 0); + uint64_t *dest_mip_write = reinterpret_cast<uint64_t *>(dest_write + dest_mip_ofs); - // Block size. Align stride to multiple of 4 (RGBA8). - int mip_w = (orig_mip_w + 3) & ~3; - int mip_h = (orig_mip_h + 3) & ~3; - const uint32_t blocks = mip_w * mip_h / 16; + // Block size. + dest_mip_w = (dest_mip_w + 3) & ~3; + dest_mip_h = (dest_mip_h + 3) & ~3; + const uint32_t blocks = dest_mip_w * dest_mip_h / 16; // Get mip data from source image for reading. - int src_mip_ofs = r_img->get_mipmap_offset(i); - const uint32_t *src_mip_read = (const uint32_t *)&src_read[src_mip_ofs]; + int64_t src_mip_ofs, src_mip_size; + int src_mip_w, src_mip_h; + + r_img->get_mipmap_offset_size_and_dimensions(i, src_mip_ofs, src_mip_size, src_mip_w, src_mip_h); + + const uint32_t *src_mip_read = reinterpret_cast<const uint32_t *>(src_read + src_mip_ofs); // Pad textures to nearest block by smearing. - if (mip_w != orig_mip_w || mip_h != orig_mip_h) { - padded_src.resize(mip_w * mip_h); + if (dest_mip_w != src_mip_w || dest_mip_h != src_mip_h) { + // Reserve the buffer for padded image data. + padded_src.resize(dest_mip_w * dest_mip_h); uint32_t *ptrw = padded_src.ptrw(); + int x = 0, y = 0; - for (y = 0; y < orig_mip_h; y++) { - for (x = 0; x < orig_mip_w; x++) { - ptrw[mip_w * y + x] = src_mip_read[orig_mip_w * y + x]; + for (y = 0; y < src_mip_h; y++) { + for (x = 0; x < src_mip_w; x++) { + ptrw[dest_mip_w * y + x] = src_mip_read[src_mip_w * y + x]; } + // First, smear in x. - for (; x < mip_w; x++) { - ptrw[mip_w * y + x] = ptrw[mip_w * y + x - 1]; + for (; x < dest_mip_w; x++) { + ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - 1]; } } + // Then, smear in y. - for (; y < mip_h; y++) { - for (x = 0; x < mip_w; x++) { - ptrw[mip_w * y + x] = ptrw[mip_w * y + x - mip_w]; + for (; y < dest_mip_h; y++) { + for (x = 0; x < dest_mip_w; x++) { + ptrw[dest_mip_w * y + x] = ptrw[dest_mip_w * y + x - dest_mip_w]; } } + // Override the src_mip_read pointer to our temporary Vector. src_mip_read = padded_src.ptr(); } - switch (p_compresstype) { + switch (p_compress_type) { case EtcpakType::ETCPAK_TYPE_ETC1: - CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w); + CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_ETC2: - CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w, true); + CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, dest_mip_w, true); break; case EtcpakType::ETCPAK_TYPE_ETC2_ALPHA: case EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG: - CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w, true); + CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, dest_mip_w, true); break; case EtcpakType::ETCPAK_TYPE_ETC2_R: - CompressEacR(src_mip_read, dest_mip_write, blocks, mip_w); + CompressEacR(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_ETC2_RG: - CompressEacRg(src_mip_read, dest_mip_write, blocks, mip_w); + CompressEacRg(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_DXT1: - CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w); + CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_DXT5: case EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG: - CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w); + CompressDxt5(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_RGTC_R: - CompressBc4(src_mip_read, dest_mip_write, blocks, mip_w); + CompressBc4(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; case EtcpakType::ETCPAK_TYPE_RGTC_RG: - CompressBc5(src_mip_read, dest_mip_write, blocks, mip_w); + CompressBc5(src_mip_read, dest_mip_write, blocks, dest_mip_w); break; default: @@ -276,7 +299,7 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img) { } // Replace original image with compressed one. - r_img->set_data(width, height, mipmaps, target_format, dest_data); + r_img->set_data(width, height, has_mipmaps, target_format, dest_data); print_verbose(vformat("etcpak: Encoding took %d ms.", OS::get_singleton()->get_ticks_msec() - start_time)); } diff --git a/modules/etcpak/image_compress_etcpak.h b/modules/etcpak/image_compress_etcpak.h index 9d5343740b..d50b322fe4 100644 --- a/modules/etcpak/image_compress_etcpak.h +++ b/modules/etcpak/image_compress_etcpak.h @@ -51,6 +51,6 @@ void _compress_etc1(Image *r_img); void _compress_etc2(Image *r_img, Image::UsedChannels p_channels); void _compress_bc(Image *r_img, Image::UsedChannels p_channels); -void _compress_etcpak(EtcpakType p_compresstype, Image *r_img); +void _compress_etcpak(EtcpakType p_compress_type, Image *r_img); #endif // IMAGE_COMPRESS_ETCPAK_H diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp index afb63246e4..f5b19f803a 100644 --- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp +++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.cpp @@ -124,12 +124,11 @@ Node *EditorSceneFormatImporterFBX2GLTF::import_scene(const String &p_path, uint #endif } -Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, bool p_for_animation, +Variant EditorSceneFormatImporterFBX2GLTF::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) { - if (p_option == "fbx/embedded_image_handling") { - return false; - } - if (p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == EditorSceneFormatImporterUFBX::FBX_IMPORTER_FBX2GLTF && p_option == "fbx/embedded_image_handling") { + // Remove all the FBX options except for 'fbx/importer' if the importer is fbx2gltf. + // These options are available only for ufbx. + if (p_option.begins_with("fbx/") && p_option != "fbx/importer" && p_options.has("fbx/importer") && int(p_options["fbx/importer"]) == EditorSceneFormatImporterUFBX::FBX_IMPORTER_FBX2GLTF) { return false; } return true; diff --git a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h index c68e37f0d8..ce2bac6fcf 100644 --- a/modules/fbx/editor/editor_scene_importer_fbx2gltf.h +++ b/modules/fbx/editor/editor_scene_importer_fbx2gltf.h @@ -49,7 +49,7 @@ public: List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; - virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, + virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) override; virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override; }; diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.cpp b/modules/fbx/editor/editor_scene_importer_ufbx.cpp index fb5e324390..64075c0664 100644 --- a/modules/fbx/editor/editor_scene_importer_ufbx.cpp +++ b/modules/fbx/editor/editor_scene_importer_ufbx.cpp @@ -76,9 +76,6 @@ Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { state->set_import_as_skeleton_bones(true); } - if (p_options.has(SNAME("nodes/import_as_skeleton_bones")) ? (bool)p_options[SNAME("nodes/import_as_skeleton_bones")] : false) { - state->set_import_as_skeleton_bones(true); - } p_flags |= EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS; state->set_bake_fps(p_options["animation/fps"]); Error err = fbx->append_from_file(path, state, p_flags, p_path.get_base_dir()); @@ -91,23 +88,19 @@ Node *EditorSceneFormatImporterUFBX::import_scene(const String &p_path, uint32_t return fbx->generate_scene(state, state->get_bake_fps(), (bool)p_options["animation/trimming"], false); } -Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, bool p_for_animation, +Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) { - String file_extension = p_path.get_extension().to_lower(); - if (file_extension != "fbx" && p_option.begins_with("fbx/")) { - return false; - } - if ((file_extension != "gltf" && file_extension != "glb") && p_option.begins_with("gltf/")) { - return false; - } return true; } void EditorSceneFormatImporterUFBX::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/importer", PROPERTY_HINT_ENUM, "ufbx,FBX2glTF"), FBX_IMPORTER_UFBX)); - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::BOOL, "fbx/allow_geometry_helper_nodes"), false)); - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), FBXState::HANDLE_BINARY_EXTRACT_TEXTURES)); + // Returns all the options when path is empty because that means it's for the Project Settings. + if (p_path.is_empty() || p_path.get_extension().to_lower() == "fbx") { + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/importer", PROPERTY_HINT_ENUM, "ufbx,FBX2glTF"), FBX_IMPORTER_UFBX)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::BOOL, "fbx/allow_geometry_helper_nodes"), false)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), FBXState::HANDLE_BINARY_EXTRACT_TEXTURES)); + } } void EditorSceneFormatImporterUFBX::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const { diff --git a/modules/fbx/editor/editor_scene_importer_ufbx.h b/modules/fbx/editor/editor_scene_importer_ufbx.h index b81b8df4c1..6e3eafc100 100644 --- a/modules/fbx/editor/editor_scene_importer_ufbx.h +++ b/modules/fbx/editor/editor_scene_importer_ufbx.h @@ -53,7 +53,7 @@ public: List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; - virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, + virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) override; virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override; }; diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index b9d9ec7b6c..4e1a00cad6 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -2017,6 +2017,7 @@ void FBXDocument::_process_mesh_instances(Ref<FBXState> p_state, Node *p_scene_r ERR_CONTINUE_MSG(skeleton == nullptr, vformat("Unable to find Skeleton for node %d skin %d", node_i, skin_i)); mi->get_parent()->remove_child(mi); + mi->set_owner(nullptr); skeleton->add_child(mi, true); mi->set_owner(skeleton->get_owner()); @@ -2114,9 +2115,6 @@ Error FBXDocument::_parse(Ref<FBXState> p_state, String p_path, Ref<FileAccess> return OK; } -void FBXDocument::_bind_methods() { -} - Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) { Ref<FBXState> state = p_state; ERR_FAIL_COND_V(state.is_null(), nullptr); diff --git a/modules/fbx/fbx_document.h b/modules/fbx/fbx_document.h index 4a3bb176c2..96f1905881 100644 --- a/modules/fbx/fbx_document.h +++ b/modules/fbx/fbx_document.h @@ -61,9 +61,6 @@ public: PackedByteArray generate_buffer(Ref<GLTFState> p_state) override; Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) override; -protected: - static void _bind_methods(); - private: String _get_texture_path(const String &p_base_directory, const String &p_source_file_path) const; void _process_uv_set(PackedVector2Array &uv_array); diff --git a/modules/gdscript/config.py b/modules/gdscript/config.py index a7d5c406e9..ecd33a5dac 100644 --- a/modules/gdscript/config.py +++ b/modules/gdscript/config.py @@ -11,6 +11,7 @@ def get_doc_classes(): return [ "@GDScript", "GDScript", + "GDScriptSyntaxHighlighter", ] diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 6e7ac0dec9..f539f27848 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -52,9 +52,9 @@ <description> Returns a single character (as a [String]) of the given Unicode code point (which is compatible with ASCII code). [codeblock] - a = char(65) # a is "A" - a = char(65 + 32) # a is "a" - a = char(8364) # a is "€" + var upper = char(65) # upper is "A" + var lower = char(65 + 32) # lower is "a" + var euro = char(8364) # euro is "€" [/codeblock] </description> </method> @@ -133,7 +133,7 @@ - An [Object]-derived class which exists in [ClassDB], for example [Node]. - A [Script] (you can use any class, including inner one). Unlike the right operand of the [code]is[/code] operator, [param type] can be a non-constant value. The [code]is[/code] operator supports more features (such as typed arrays). Use the operator instead of this method if you do not need dynamic type checking. - Examples: + [b]Examples:[/b] [codeblock] print(is_instance_of(a, TYPE_INT)) print(is_instance_of(a, Node)) @@ -150,10 +150,10 @@ <description> Returns the length of the given Variant [param var]. The length can be the character count of a [String] or [StringName], the element count of any array type, or the size of a [Dictionary]. For every other Variant type, a run-time error is generated and execution is stopped. [codeblock] - a = [1, 2, 3, 4] + var a = [1, 2, 3, 4] len(a) # Returns 4 - b = "Hello!" + var b = "Hello!" len(b) # Returns 6 [/codeblock] </description> @@ -220,7 +220,7 @@ [code]range(b: int, n: int, s: int)[/code]: Starts from [code]b[/code], increases/decreases by steps of [code]s[/code], and stops [i]before[/i] [code]n[/code]. The arguments [code]b[/code] and [code]n[/code] are [b]inclusive[/b] and [b]exclusive[/b], respectively. The argument [code]s[/code] [b]can[/b] be negative, but not [code]0[/code]. If [code]s[/code] is [code]0[/code], an error message is printed. [method range] converts all arguments to [int] before processing. [b]Note:[/b] Returns an empty array if no value meets the value constraint (e.g. [code]range(2, 5, -1)[/code] or [code]range(5, 5, 1)[/code]). - Examples: + [b]Examples:[/b] [codeblock] print(range(4)) # Prints [0, 1, 2, 3] print(range(2, 5)) # Prints [2, 3, 4] diff --git a/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml b/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml new file mode 100644 index 0000000000..63a9222901 --- /dev/null +++ b/modules/gdscript/doc_classes/GDScriptSyntaxHighlighter.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="GDScriptSyntaxHighlighter" inherits="EditorSyntaxHighlighter" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + A GDScript syntax highlighter that can be used with [TextEdit] and [CodeEdit] nodes. + </brief_description> + <description> + [b]Note:[/b] This class can only be used for editor plugins because it relies on editor settings. + [codeblocks] + [gdscript] + var code_preview = TextEdit.new() + var highlighter = GDScriptSyntaxHighlighter.new() + code_preview.syntax_highlighter = highlighter + [/gdscript] + [csharp] + var codePreview = new TextEdit(); + var highlighter = new GDScriptSyntaxHighlighter(); + codePreview.SyntaxHighlighter = highlighter; + [/csharp] + [/codeblocks] + </description> + <tutorials> + </tutorials> +</class> diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index f06bcb823d..c72755642b 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1538,10 +1538,14 @@ void GDScript::clear(ClearData *p_clear_data) { } } - RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); - for (GDScript *E : must_clear_dependencies) { - clear_data->scripts.insert(E); - E->clear(clear_data); + // If we're in the process of shutting things down then every single script will be cleared + // anyway, so we can safely skip this very costly operation. + if (!GDScriptLanguage::singleton->finishing) { + RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies(); + for (GDScript *E : must_clear_dependencies) { + clear_data->scripts.insert(E); + E->clear(clear_data); + } } for (const KeyValue<StringName, GDScriptFunction *> &E : member_functions) { @@ -2247,6 +2251,11 @@ String GDScriptLanguage::get_extension() const { } void GDScriptLanguage::finish() { + if (finishing) { + return; + } + finishing = true; + _call_stack.free(); // Clear the cache before parsing the script_list @@ -2282,6 +2291,8 @@ void GDScriptLanguage::finish() { } script_list.clear(); function_list.clear(); + + finishing = false; } void GDScriptLanguage::profiling_start() { @@ -2859,8 +2870,11 @@ GDScriptLanguage::GDScriptLanguage() { _debug_parse_err_line = -1; _debug_parse_err_file = ""; +#ifdef DEBUG_ENABLED profiling = false; + profile_native_calls = false; script_frame_time = 0; +#endif int dmcs = GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/gdscript/max_call_stack", PROPERTY_HINT_RANGE, "512," + itos(GDScriptFunction::MAX_CALL_DEPTH - 1) + ",1"), 1024); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index d097cb193b..6527a0ea4d 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -411,6 +411,8 @@ class GDScriptLanguage : public ScriptLanguage { static GDScriptLanguage *singleton; + bool finishing = false; + Variant *_global_array = nullptr; Vector<Variant> global_array; HashMap<StringName, int> globals; @@ -457,9 +459,11 @@ class GDScriptLanguage : public ScriptLanguage { friend class GDScriptFunction; SelfList<GDScriptFunction>::List function_list; +#ifdef DEBUG_ENABLED bool profiling; bool profile_native_calls; uint64_t script_frame_time; +#endif HashMap<String, ObjectID> orphan_subclasses; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index a6b4bce000..e98cae765b 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -285,7 +285,7 @@ Error GDScriptAnalyzer::check_class_member_name_conflict(const GDScriptParser::C return OK; } -void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list) { +void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list, GDScriptParser::Node *p_source) { ERR_FAIL_NULL(p_node); ERR_FAIL_NULL(p_list); @@ -299,11 +299,15 @@ void GDScriptAnalyzer::get_class_node_current_scope_classes(GDScriptParser::Clas // Prioritize node base type over its outer class if (p_node->base_type.class_type != nullptr) { - get_class_node_current_scope_classes(p_node->base_type.class_type, p_list); + // TODO: 'ensure_cached_external_parser_for_class()' is only necessary because 'resolve_class_inheritance()' is not getting called here. + ensure_cached_external_parser_for_class(p_node->base_type.class_type, p_node, "Trying to fetch classes in the current scope", p_source); + get_class_node_current_scope_classes(p_node->base_type.class_type, p_list, p_source); } if (p_node->outer != nullptr) { - get_class_node_current_scope_classes(p_node->outer, p_list); + // TODO: 'ensure_cached_external_parser_for_class()' is only necessary because 'resolve_class_inheritance()' is not getting called here. + ensure_cached_external_parser_for_class(p_node->outer, p_node, "Trying to fetch classes in the current scope", p_source); + get_class_node_current_scope_classes(p_node->outer, p_list, p_source); } } @@ -312,6 +316,13 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c p_source = p_class; } + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class inheritance", p_source); + Finally finally([&]() { + for (GDScriptParser::ClassNode *look_class = p_class; look_class != nullptr; look_class = look_class->base_type.class_type) { + ensure_cached_external_parser_for_class(look_class->base_type.class_type, look_class, "Trying to resolve class inheritance", p_source); + } + }); + if (p_class->base_type.is_resolving()) { push_error(vformat(R"(Could not resolve class "%s": Cyclic reference.)", type_from_metatype(p_class->get_datatype()).to_string()), p_source); return ERR_PARSE_ERROR; @@ -323,21 +334,17 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c } if (!parser->has_class(p_class)) { - String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { - push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + // Error already pushed. return ERR_PARSE_ERROR; } Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); if (err) { - push_error(vformat(R"(Could not parse script "%s": %s.)", script_path, error_names[err]), p_source); + push_error(vformat(R"(Could not parse script "%s": %s.)", p_class->get_datatype().script_path, error_names[err]), p_source); return ERR_PARSE_ERROR; } - ERR_FAIL_COND_V_MSG(!parser_ref->get_parser()->has_class(p_class), ERR_PARSE_ERROR, R"(Parser bug: Mismatched external parser.)"); - GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); GDScriptParser *other_parser = parser_ref->get_parser(); @@ -471,7 +478,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c // Look for other classes in script. bool found = false; List<GDScriptParser::ClassNode *> script_classes; - get_class_node_current_scope_classes(p_class, &script_classes); + get_class_node_current_scope_classes(p_class, &script_classes, id); for (GDScriptParser::ClassNode *look_class : script_classes) { if (look_class->identifier && look_class->identifier->name == name) { if (!look_class->get_datatype().is_set()) { @@ -763,7 +770,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type // Classes in current scope. List<GDScriptParser::ClassNode *> script_classes; bool found = false; - get_class_node_current_scope_classes(parser->current_class, &script_classes); + get_class_node_current_scope_classes(parser->current_class, &script_classes, p_type); for (GDScriptParser::ClassNode *script_class : script_classes) { if (found) { break; @@ -883,6 +890,15 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, p_source = member.get_source_node(); } + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class member", p_source); + Finally finally([&]() { + ensure_cached_external_parser_for_class(member.get_datatype().class_type, p_class, "Trying to resolve datatype of class member", p_source); + GDScriptParser::DataType member_type = member.get_datatype(); + if (member_type.has_container_element_type(0)) { + ensure_cached_external_parser_for_class(member_type.get_container_element_type(0).class_type, p_class, "Trying to resolve datatype of class member", p_source); + } + }); + if (member.get_datatype().is_resolving()) { push_error(vformat(R"(Could not resolve member "%s": Cyclic reference.)", member.get_name()), p_source); return; @@ -892,42 +908,39 @@ void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, return; } + // If it's already resolving, that's ok. + if (!p_class->base_type.is_resolving()) { + Error err = resolve_class_inheritance(p_class); + if (err) { + return; + } + } + if (!parser->has_class(p_class)) { - String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { - push_error(vformat(R"(Could not find script "%s" (While resolving "%s").)", script_path, member.get_name()), p_source); + // Error already pushed. return; } Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); if (err) { - push_error(vformat(R"(Could not resolve script "%s": %s (While resolving "%s").)", script_path, error_names[err], member.get_name()), p_source); + push_error(vformat(R"(Could not parse script "%s": %s (While resolving external class member "%s").)", p_class->get_datatype().script_path, error_names[err], member.get_name()), p_source); return; } - ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); - GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); GDScriptParser *other_parser = parser_ref->get_parser(); int error_count = other_parser->errors.size(); other_analyzer->resolve_class_member(p_class, p_index); if (other_parser->errors.size() > error_count) { - push_error(vformat(R"(Could not resolve member "%s".)", member.get_name()), p_source); + push_error(vformat(R"(Could not resolve external class member "%s".)", member.get_name()), p_source); + return; } return; } - // If it's already resolving, that's ok. - if (!p_class->base_type.is_resolving()) { - Error err = resolve_class_inheritance(p_class); - if (err) { - return; - } - } - GDScriptParser::ClassNode *previous_class = parser->current_class; parser->current_class = p_class; @@ -1170,27 +1183,25 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas p_source = p_class; } + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class interface", p_source); + if (!p_class->resolved_interface) { #ifdef DEBUG_ENABLED bool has_static_data = p_class->has_static_data; #endif if (!parser->has_class(p_class)) { - String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { - push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + // Error already pushed. return; } Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); if (err) { - push_error(vformat(R"(Could not resolve script "%s": %s.)", script_path, error_names[err]), p_source); + push_error(vformat(R"(Could not parse script "%s": %s.)", p_class->get_datatype().script_path, error_names[err]), p_source); return; } - ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); - GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); GDScriptParser *other_parser = parser_ref->get_parser(); @@ -1198,6 +1209,7 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas other_analyzer->resolve_class_interface(p_class); if (other_parser->errors.size() > error_count) { push_error(vformat(R"(Could not resolve class "%s".)", p_class->fqcn), p_source); + return; } return; @@ -1261,26 +1273,24 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co p_source = p_class; } + Ref<GDScriptParserRef> parser_ref = ensure_cached_external_parser_for_class(p_class, nullptr, "Trying to resolve class body", p_source); + if (p_class->resolved_body) { return; } if (!parser->has_class(p_class)) { - String script_path = p_class->get_datatype().script_path; - Ref<GDScriptParserRef> parser_ref = parser->get_depended_parser_for(script_path); if (parser_ref.is_null()) { - push_error(vformat(R"(Could not find script "%s".)", script_path), p_source); + // Error already pushed. return; } Error err = parser_ref->raise_status(GDScriptParserRef::PARSED); if (err) { - push_error(vformat(R"(Could not resolve script "%s": %s.)", script_path, error_names[err]), p_source); + push_error(vformat(R"(Could not parse script "%s": %s.)", p_class->get_datatype().script_path, error_names[err]), p_source); return; } - ERR_FAIL_COND_MSG(!parser_ref->get_parser()->has_class(p_class), R"(Parser bug: Mismatched external parser.)"); - GDScriptAnalyzer *other_analyzer = parser_ref->get_analyzer(); GDScriptParser *other_parser = parser_ref->get_parser(); @@ -1288,6 +1298,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co other_analyzer->resolve_class_body(p_class); if (other_parser->errors.size() > error_count) { push_error(vformat(R"(Could not resolve class "%s".)", p_class->fqcn), p_source); + return; } return; @@ -1967,8 +1978,8 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } #ifdef DEBUG_ENABLED + const bool is_parameter = p_assignable->type == GDScriptParser::Node::PARAMETER; if (!has_specified_type) { - const bool is_parameter = p_assignable->type == GDScriptParser::Node::PARAMETER; const String declaration_type = is_constant ? "Constant" : (is_parameter ? "Parameter" : "Variable"); if (p_assignable->infer_datatype || is_constant) { // Do not produce the `INFERRED_DECLARATION` warning on type import because there is no way to specify the true type. @@ -1980,7 +1991,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else { parser->push_warning(p_assignable, GDScriptWarning::UNTYPED_DECLARATION, declaration_type, p_assignable->identifier->name); } - } else if (specified_type.kind == GDScriptParser::DataType::ENUM && p_assignable->initializer == nullptr) { + } else if (!is_parameter && specified_type.kind == GDScriptParser::DataType::ENUM && p_assignable->initializer == nullptr) { // Warn about enum variables without default value. Unless the enum defines the "0" value, then it's fine. bool has_zero_value = false; for (const KeyValue<StringName, int64_t> &kv : specified_type.enum_values) { @@ -3645,12 +3656,116 @@ GDScriptParser::DataType GDScriptAnalyzer::make_global_class_meta_type(const Str } } +Ref<GDScriptParserRef> GDScriptAnalyzer::ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source) { + if (p_class == nullptr) { + return nullptr; + } + + if (HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>>::Iterator E = external_class_parser_cache.find(p_class)) { + return E->value; + } + + if (parser->has_class(p_class)) { + return nullptr; + } + + if (p_from_class == nullptr) { + p_from_class = parser->head; + } + + String script_path = p_class->get_datatype().script_path; + + Ref<GDScriptParserRef> parser_ref; + for (const GDScriptParser::ClassNode *look_class = p_from_class; look_class != nullptr; look_class = look_class->base_type.class_type) { + if (parser->has_class(look_class)) { + parser_ref = find_cached_external_parser_for_class(p_class, parser); + if (parser_ref.is_valid()) { + break; + } + } + + if (HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>>::Iterator E = external_class_parser_cache.find(look_class)) { + parser_ref = find_cached_external_parser_for_class(p_class, E->value); + if (parser_ref.is_valid()) { + break; + } + } + + String look_class_script_path = look_class->get_datatype().script_path; + if (HashMap<String, Ref<GDScriptParserRef>>::Iterator E = parser->depended_parsers.find(look_class_script_path)) { + parser_ref = find_cached_external_parser_for_class(p_class, E->value); + if (parser_ref.is_valid()) { + break; + } + } + } + + if (parser_ref.is_null()) { + push_error(vformat(R"(Parser bug (please report): Could not find external parser for class "%s". (%s))", p_class->fqcn, p_context), p_source); + // A null parser will be inserted into the cache, so this error won't spam for the same class. + // This is ok, the values of external_class_parser_cache are not assumed to be valid references. + } + + external_class_parser_cache.insert(p_class, parser_ref); + return parser_ref; +} + +Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser) { + if (p_dependant_parser.is_null()) { + return nullptr; + } + + if (HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>>::Iterator E = p_dependant_parser->get_analyzer()->external_class_parser_cache.find(p_class)) { + if (E->value.is_valid()) { + // Silently ensure it's parsed. + E->value->raise_status(GDScriptParserRef::PARSED); + if (E->value->get_parser()->has_class(p_class)) { + return E->value; + } + } + } + + if (p_dependant_parser->get_parser()->has_class(p_class)) { + return p_dependant_parser; + } + + // Silently ensure it's parsed. + p_dependant_parser->raise_status(GDScriptParserRef::PARSED); + return find_cached_external_parser_for_class(p_class, p_dependant_parser->get_parser()); +} + +Ref<GDScriptParserRef> GDScriptAnalyzer::find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser) { + if (p_dependant_parser == nullptr) { + return nullptr; + } + + String script_path = p_class->get_datatype().script_path; + if (HashMap<String, Ref<GDScriptParserRef>>::Iterator E = p_dependant_parser->depended_parsers.find(script_path)) { + if (E->value.is_valid()) { + // Silently ensure it's parsed. + E->value->raise_status(GDScriptParserRef::PARSED); + if (E->value->get_parser()->has_class(p_class)) { + return E->value; + } + } + } + + return nullptr; +} + +Ref<GDScript> GDScriptAnalyzer::get_depended_shallow_script(const String &p_path, Error &r_error) { + // To keep a local cache of the parser for resolving external nodes later. + parser->get_depended_parser_for(p_path); + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_path, r_error, parser->script_path); + return scr; +} + void GDScriptAnalyzer::reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype) { ERR_FAIL_NULL(p_identifier); p_identifier->set_datatype(p_identifier_datatype); Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_identifier_datatype.script_path, err, parser->script_path); + Ref<GDScript> scr = get_depended_shallow_script(p_identifier_datatype.script_path, err); if (err) { push_error(vformat(R"(Error while getting cache for script "%s".)", p_identifier_datatype.script_path), p_identifier); return; @@ -3767,7 +3882,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod bool is_base = true; if (base_class != nullptr) { - get_class_node_current_scope_classes(base_class, &script_classes); + get_class_node_current_scope_classes(base_class, &script_classes, p_identifier); } bool is_constructor = base.is_meta_type && p_identifier->name == SNAME("new"); @@ -4340,7 +4455,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { const String &res_type = ResourceLoader::get_resource_type(p_preload->resolved_path); if (res_type == "GDScript") { Error err = OK; - Ref<GDScript> res = GDScriptCache::get_shallow_script(p_preload->resolved_path, err, parser->script_path); + Ref<GDScript> res = get_depended_shallow_script(p_preload->resolved_path, err); p_preload->resource = res; if (err != OK) { push_error(vformat(R"(Could not preload resource script "%s".)", p_preload->resolved_path), p_preload->path); @@ -4361,6 +4476,11 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { p_preload->is_constant = true; p_preload->reduced_value = p_preload->resource; p_preload->set_datatype(type_from_variant(p_preload->reduced_value, p_preload)); + + // TODO: Not sure if this is necessary anymore. + // 'type_from_variant()' should call 'resolve_class_inheritance()' which would call 'ensure_cached_external_parser_for_class()' + // Better safe than sorry. + ensure_cached_external_parser_for_class(p_preload->get_datatype().class_type, nullptr, "Trying to resolve preload", p_preload); } void GDScriptAnalyzer::reduce_self(GDScriptParser::SelfNode *p_self) { @@ -4916,7 +5036,7 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D Ref<Script> script_type = p_element_datatype.script_type; if (p_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { Error err = OK; - Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_element_datatype.script_path, err, parser->script_path); + Ref<GDScript> scr = get_depended_shallow_script(p_element_datatype.script_path, err); if (err) { push_error(vformat(R"(Error while getting cache for script "%s".)", p_element_datatype.script_path), p_source_node); return array; @@ -5180,7 +5300,7 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::Node *p_source, bo if (!class_exists(base_native)) { push_error(vformat("Native class %s used in script doesn't exist or isn't exposed.", base_native), p_source); return false; - } else if (p_is_constructor && !ClassDB::can_instantiate(base_native)) { + } else if (p_is_constructor && ClassDB::is_abstract(base_native)) { if (p_base_type.kind == GDScriptParser::DataType::CLASS) { push_error(vformat(R"(Class "%s" cannot be constructed as it is based on abstract native class "%s".)", p_base_type.class_type->fqcn.get_file(), base_native), p_source); } else if (p_base_type.kind == GDScriptParser::DataType::SCRIPT) { diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 922000df52..25e5aa9a2c 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -41,9 +41,22 @@ class GDScriptAnalyzer { GDScriptParser *parser = nullptr; + template <typename Fn> + class Finally { + Fn fn; + + public: + Finally(Fn p_fn) : + fn(p_fn) {} + ~Finally() { + fn(); + } + }; + const GDScriptParser::EnumNode *current_enum = nullptr; GDScriptParser::LambdaNode *current_lambda = nullptr; List<GDScriptParser::LambdaNode *> pending_body_resolution_lambdas; + HashMap<const GDScriptParser::ClassNode *, Ref<GDScriptParserRef>> external_class_parser_cache; bool static_context = false; // Tests for detecting invalid overloading of script members @@ -52,7 +65,7 @@ class GDScriptAnalyzer { Error check_native_member_name_conflict(const StringName &p_member_name, const GDScriptParser::Node *p_member_node, const StringName &p_native_type_string); Error check_class_member_name_conflict(const GDScriptParser::ClassNode *p_class_node, const StringName &p_member_name, const GDScriptParser::Node *p_member_node); - void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list); + void get_class_node_current_scope_classes(GDScriptParser::ClassNode *p_node, List<GDScriptParser::ClassNode *> *p_list, GDScriptParser::Node *p_source); Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr); Error resolve_class_inheritance(GDScriptParser::ClassNode *p_class, bool p_recursive); @@ -132,6 +145,10 @@ class GDScriptAnalyzer { void resolve_pending_lambda_bodies(); bool class_exists(const StringName &p_class) const; void reduce_identifier_from_base_set_class(GDScriptParser::IdentifierNode *p_identifier, GDScriptParser::DataType p_identifier_datatype); + Ref<GDScriptParserRef> ensure_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const GDScriptParser::ClassNode *p_from_class, const char *p_context, const GDScriptParser::Node *p_source); + Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, const Ref<GDScriptParserRef> &p_dependant_parser); + Ref<GDScriptParserRef> find_cached_external_parser_for_class(const GDScriptParser::ClassNode *p_class, GDScriptParser *p_dependant_parser); + Ref<GDScript> get_depended_shallow_script(const String &p_path, Error &r_error); #ifdef DEBUG_ENABLED void is_shadowing(GDScriptParser::IdentifierNode *p_identifier, const String &p_context, const bool p_in_local_scope); #endif diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 7c9fba799d..b3c0744bdf 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -135,11 +135,23 @@ void GDScriptParserRef::clear() { GDScriptParserRef::~GDScriptParserRef() { clear(); - GDScriptCache::remove_parser(path); + + if (!abandoned) { + MutexLock lock(GDScriptCache::singleton->mutex); + GDScriptCache::singleton->parser_map.erase(path); + } } GDScriptCache *GDScriptCache::singleton = nullptr; +SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG> &_get_gdscript_cache_mutex() { + return GDScriptCache::mutex; +} + +template <> +thread_local SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG>::TLSData SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG>::tls_data(_get_gdscript_cache_mutex()); +SafeBinaryMutex<GDScriptCache::BINARY_MUTEX_TAG> GDScriptCache::mutex; + void GDScriptCache::move_script(const String &p_from, const String &p_to) { if (singleton == nullptr || p_from == p_to) { return; @@ -151,15 +163,7 @@ void GDScriptCache::move_script(const String &p_from, const String &p_to) { return; } - if (singleton->parser_map.has(p_from) && !p_from.is_empty()) { - singleton->parser_map[p_to] = singleton->parser_map[p_from]; - } - singleton->parser_map.erase(p_from); - - if (singleton->parser_inverse_dependencies.has(p_from) && !p_from.is_empty()) { - singleton->parser_inverse_dependencies[p_to] = singleton->parser_inverse_dependencies[p_from]; - } - singleton->parser_inverse_dependencies.erase(p_from); + remove_parser(p_from); if (singleton->shallow_gdscript_cache.has(p_from) && !p_from.is_empty()) { singleton->shallow_gdscript_cache[p_to] = singleton->shallow_gdscript_cache[p_from]; @@ -183,6 +187,17 @@ void GDScriptCache::remove_script(const String &p_path) { return; } + if (HashMap<String, Vector<ObjectID>>::Iterator E = singleton->abandoned_parser_map.find(p_path)) { + for (ObjectID parser_ref_id : E->value) { + Ref<GDScriptParserRef> parser_ref{ ObjectDB::get_instance(parser_ref_id) }; + if (parser_ref.is_valid()) { + parser_ref->clear(); + } + } + } + + singleton->abandoned_parser_map.erase(p_path); + if (singleton->parser_map.has(p_path)) { singleton->parser_map[p_path]->clear(); } @@ -229,6 +244,13 @@ bool GDScriptCache::has_parser(const String &p_path) { void GDScriptCache::remove_parser(const String &p_path) { MutexLock lock(singleton->mutex); + + if (singleton->parser_map.has(p_path)) { + GDScriptParserRef *parser_ref = singleton->parser_map[p_path]; + parser_ref->abandoned = true; + singleton->abandoned_parser_map[p_path].push_back(parser_ref->get_instance_id()); + } + // Can't clear the parser because some other parser might be currently using it in the chain of calls. singleton->parser_map.erase(p_path); @@ -355,7 +377,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro // Allowing lifting the lock might cause a script to be reloaded multiple times, // which, as a last resort deadlock prevention strategy, is a good tradeoff. - uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(&singleton->mutex); + uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(singleton->mutex); r_error = script->reload(true); WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id); if (r_error) { @@ -432,6 +454,17 @@ void GDScriptCache::clear() { singleton->parser_inverse_dependencies.clear(); + for (const KeyValue<String, Vector<ObjectID>> &KV : singleton->abandoned_parser_map) { + for (ObjectID parser_ref_id : KV.value) { + Ref<GDScriptParserRef> parser_ref{ ObjectDB::get_instance(parser_ref_id) }; + if (parser_ref.is_valid()) { + parser_ref->clear(); + } + } + } + + singleton->abandoned_parser_map.clear(); + RBSet<Ref<GDScriptParserRef>> parser_map_refs; for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) { parser_map_refs.insert(E.value); diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index c927317e19..4903da92b4 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -34,7 +34,7 @@ #include "gdscript.h" #include "core/object/ref_counted.h" -#include "core/os/mutex.h" +#include "core/os/safe_binary_mutex.h" #include "core/templates/hash_map.h" #include "core/templates/hash_set.h" @@ -59,6 +59,7 @@ private: String path; uint32_t source_hash = 0; bool clearing = false; + bool abandoned = false; friend class GDScriptCache; friend class GDScript; @@ -79,6 +80,7 @@ public: class GDScriptCache { // String key is full path. HashMap<String, GDScriptParserRef *> parser_map; + HashMap<String, Vector<ObjectID>> abandoned_parser_map; HashMap<String, Ref<GDScript>> shallow_gdscript_cache; HashMap<String, Ref<GDScript>> full_gdscript_cache; HashMap<String, Ref<GDScript>> static_gdscript_cache; @@ -93,7 +95,12 @@ class GDScriptCache { bool cleared = false; - Mutex mutex; +public: + static const int BINARY_MUTEX_TAG = 2; + +private: + static SafeBinaryMutex<BINARY_MUTEX_TAG> mutex; + friend SafeBinaryMutex<BINARY_MUTEX_TAG> &_get_gdscript_cache_mutex(); public: static void move_script(const String &p_from, const String &p_to); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 5469dad3f7..d8b44a558f 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1064,12 +1064,24 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code // Get at (potential) root stack pos, so it can be returned. GDScriptCodeGenerator::Address base = _parse_expression(codegen, r_error, chain.back()->get()->base); + const bool base_known_type = base.type.has_type; + const bool base_is_shared = Variant::is_type_shared(base.type.builtin_type); + if (r_error) { return GDScriptCodeGenerator::Address(); } GDScriptCodeGenerator::Address prev_base = base; + // In case the base has a setter, don't use the address directly, as we want to call that setter. + // So use a temp value instead and call the setter at the end. + GDScriptCodeGenerator::Address base_temp; + if ((!base_known_type || !base_is_shared) && base.mode == GDScriptCodeGenerator::Address::MEMBER && member_property_has_setter && !member_property_is_in_setter) { + base_temp = codegen.add_temporary(base.type); + gen->write_assign(base_temp, base); + prev_base = base_temp; + } + struct ChainInfo { bool is_named = false; GDScriptCodeGenerator::Address base; @@ -1218,6 +1230,15 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code gen->write_end_jump_if_shared(); } } + } else if (base_temp.mode == GDScriptCodeGenerator::Address::TEMPORARY) { + if (!base_known_type) { + gen->write_jump_if_shared(base); + } + // Save the temp value back to the base by calling its setter. + gen->write_call(GDScriptCodeGenerator::Address(), base, member_property_setter_function, { assigned }); + if (!base_known_type) { + gen->write_end_jump_if_shared(); + } } if (assigned.mode == GDScriptCodeGenerator::Address::TEMPORARY) { @@ -1880,7 +1901,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui case GDScriptParser::Node::MATCH: { const GDScriptParser::MatchNode *match = static_cast<const GDScriptParser::MatchNode *>(s); - codegen.start_block(); // Add an extra block, since the binding pattern and @special variables belong to the branch scope. + codegen.start_block(); // Add an extra block, since @special locals belong to the match scope. // Evaluate the match expression. GDScriptCodeGenerator::Address value = codegen.add_local("@match_value", _gdtype_from_datatype(match->test->get_datatype(), codegen.script)); @@ -1918,7 +1939,7 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui const GDScriptParser::MatchBranchNode *branch = match->branches[j]; - codegen.start_block(); // Create an extra block around for binds. + codegen.start_block(); // Add an extra block, since binds belong to the match branch scope. // Add locals in block before patterns, so temporaries don't use the stack address for binds. List<GDScriptCodeGenerator::Address> branch_locals = _add_block_locals(codegen, branch->block); @@ -1970,13 +1991,15 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui _clear_block_locals(codegen, branch_locals); - codegen.end_block(); // Get out of extra block. + codegen.end_block(); // Get out of extra block for binds. } // End all nested `if`s. for (int j = 0; j < match->branches.size(); j++) { gen->write_endif(); } + + codegen.end_block(); // Get out of extra block for match's @special locals. } break; case GDScriptParser::Node::IF: { const GDScriptParser::IfNode *if_n = static_cast<const GDScriptParser::IfNode *>(s); @@ -2010,7 +2033,9 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui case GDScriptParser::Node::FOR: { const GDScriptParser::ForNode *for_n = static_cast<const GDScriptParser::ForNode *>(s); - codegen.start_block(); // Add an extra block, since the iterator and @special variables belong to the loop scope. + // Add an extra block, since the iterator and @special locals belong to the loop scope. + // Also we use custom logic to clear block locals. + codegen.start_block(); GDScriptCodeGenerator::Address iterator = codegen.add_local(for_n->variable->name, _gdtype_from_datatype(for_n->variable->get_datatype(), codegen.script)); @@ -2043,11 +2068,13 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui _clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit. - codegen.end_block(); // Get out of extra block. + codegen.end_block(); // Get out of extra block for loop iterator, @special locals, and custom locals clearing. } break; case GDScriptParser::Node::WHILE: { const GDScriptParser::WhileNode *while_n = static_cast<const GDScriptParser::WhileNode *>(s); + codegen.start_block(); // Add an extra block, since we use custom logic to clear block locals. + gen->start_while_condition(); GDScriptCodeGenerator::Address condition = _parse_expression(codegen, err, while_n->condition); @@ -2074,6 +2101,8 @@ Error GDScriptCompiler::_parse_block(CodeGen &codegen, const GDScriptParser::Sui gen->write_endwhile(); _clear_block_locals(codegen, loop_locals); // Outside loop, after block - for `break` and normal exit. + + codegen.end_block(); // Get out of extra block for custom locals clearing. } break; case GDScriptParser::Node::BREAK: { gen->write_break(); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f557727718..636339ef1d 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -97,8 +97,8 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri } processed_template = processed_template.replace("_BASE_", p_base_class_name) - .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_identifier()) - .replace("_CLASS_", p_class_name.to_pascal_case().validate_identifier()) + .replace("_CLASS_SNAKE_CASE_", p_class_name.to_snake_case().validate_ascii_identifier()) + .replace("_CLASS_", p_class_name.to_pascal_case().validate_ascii_identifier()) .replace("_TS_", _get_indentation()); scr->set_source_code(processed_template); return scr; @@ -402,7 +402,9 @@ void GDScriptLanguage::debug_get_globals(List<String> *p_globals, List<Variant> } const Variant &var = gl_array[E.value]; - if (Object *obj = var) { + bool freed = false; + const Object *obj = var.get_validated_object_with_check(freed); + if (obj && !freed) { if (Object::cast_to<GDScriptNativeClass>(obj)) { continue; } @@ -796,7 +798,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::CALL: { const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(par->initializer); if (call->is_constant && call->reduced) { - def_val = call->function_name.operator String() + call->reduced_value.operator String(); + def_val = call->reduced_value.get_construct_string(); } else { def_val = call->function_name.operator String() + (call->arguments.is_empty() ? "()" : "(...)"); } @@ -804,7 +806,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::ARRAY: { const GDScriptParser::ArrayNode *arr = static_cast<const GDScriptParser::ArrayNode *>(par->initializer); if (arr->is_constant && arr->reduced) { - def_val = arr->reduced_value.operator String(); + def_val = arr->reduced_value.get_construct_string(); } else { def_val = arr->elements.is_empty() ? "[]" : "[...]"; } @@ -812,24 +814,17 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio case GDScriptParser::Node::DICTIONARY: { const GDScriptParser::DictionaryNode *dict = static_cast<const GDScriptParser::DictionaryNode *>(par->initializer); if (dict->is_constant && dict->reduced) { - def_val = dict->reduced_value.operator String(); + def_val = dict->reduced_value.get_construct_string(); } else { def_val = dict->elements.is_empty() ? "{}" : "{...}"; } } break; case GDScriptParser::Node::SUBSCRIPT: { const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->initializer); - if (sub->is_constant) { - if (sub->datatype.kind == GDScriptParser::DataType::ENUM) { - def_val = sub->get_datatype().to_string(); - } else if (sub->reduced) { - const Variant::Type vt = sub->reduced_value.get_type(); - if (vt == Variant::Type::NIL || vt == Variant::Type::FLOAT || vt == Variant::Type::INT || vt == Variant::Type::STRING || vt == Variant::Type::STRING_NAME || vt == Variant::Type::BOOL || vt == Variant::Type::NODE_PATH) { - def_val = sub->reduced_value.operator String(); - } else { - def_val = sub->get_datatype().to_string() + sub->reduced_value.operator String(); - } - } + if (sub->is_attribute && sub->datatype.kind == GDScriptParser::DataType::ENUM && !sub->datatype.is_meta_type) { + def_val = sub->get_datatype().to_string() + "." + sub->attribute->name; + } else if (sub->is_constant && sub->reduced) { + def_val = sub->reduced_value.get_construct_string(); } } break; default: @@ -1521,22 +1516,19 @@ static GDScriptCompletionIdentifier _type_from_variant(const Variant &p_value, G } if (scr.is_valid()) { ci.type.script_path = scr->get_path(); + ci.type.script_type = scr; + ci.type.native_type = scr->get_instance_base_type(); + ci.type.kind = GDScriptParser::DataType::SCRIPT; if (scr->get_path().ends_with(".gd")) { - Error err; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(scr->get_path(), GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(scr->get_path()); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { ci.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; ci.type.class_type = parser->get_parser()->get_tree(); ci.type.kind = GDScriptParser::DataType::CLASS; - p_context.dependent_parsers.push_back(parser); return ci; } } - - ci.type.kind = GDScriptParser::DataType::SCRIPT; - ci.type.script_type = scr; - ci.type.native_type = scr->get_instance_base_type(); } else { ci.type.kind = GDScriptParser::DataType::NATIVE; } @@ -1811,8 +1803,6 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, if (mb && mb->is_const()) { bool all_is_const = true; Vector<Variant> args; - GDScriptParser::CompletionContext c2 = p_context; - c2.current_line = call->start_line; for (int i = 0; all_is_const && i < call->arguments.size(); i++) { GDScriptCompletionIdentifier arg; @@ -1849,16 +1839,14 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } if (FileAccess::exists(script)) { - Error err = OK; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(script); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); - p_context.dependent_parsers.push_back(parser); found = true; } } @@ -1959,11 +1947,14 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, break; } - if (base.value.in(index.value)) { - Variant value = base.value.get(index.value); - r_type = _type_from_variant(value, p_context); - found = true; - break; + { + bool valid; + Variant value = base.value.get(index.value, &valid); + if (valid) { + r_type = _type_from_variant(value, p_context); + found = true; + break; + } } // Look if it is a dictionary node. @@ -2307,9 +2298,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (ScriptServer::is_global_class(p_identifier->name)) { String script = ScriptServer::get_global_class_path(p_identifier->name); if (script.to_lower().ends_with(".gd")) { - Error err = OK; - Ref<GDScriptParserRef> parser = GDScriptCache::get_parser(script, GDScriptParserRef::INTERFACE_SOLVED, err); - if (err == OK) { + Ref<GDScriptParserRef> parser = p_context.parser->get_depended_parser_for(script); + if (parser.is_valid() && parser->raise_status(GDScriptParserRef::INTERFACE_SOLVED) == OK) { r_type.type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; r_type.type.script_path = script; r_type.type.class_type = parser->get_parser()->get_tree(); @@ -2317,7 +2307,6 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, r_type.type.is_constant = false; r_type.type.kind = GDScriptParser::DataType::CLASS; r_type.value = Variant(); - p_context.dependent_parsers.push_back(parser); return true; } } else { @@ -2765,6 +2754,20 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c base_type = base_type.class_type->base_type; } break; + case GDScriptParser::DataType::SCRIPT: { + if (base_type.script_type->is_valid() && base_type.script_type->has_method(p_method)) { + r_arghint = _make_arguments_hint(base_type.script_type->get_method_info(p_method), p_argidx); + return; + } + Ref<Script> base_script = base_type.script_type->get_base_script(); + if (base_script.is_valid()) { + base_type.script_type = base_script; + } else { + base_type.kind = GDScriptParser::DataType::NATIVE; + base_type.builtin_type = Variant::OBJECT; + base_type.native_type = base_type.script_type->get_instance_base_type(); + } + } break; case GDScriptParser::DataType::NATIVE: { StringName class_name = base_type.native_type; if (!ClassDB::class_exists(class_name)) { @@ -3300,11 +3303,36 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c case GDScriptParser::COMPLETION_SUBSCRIPT: { const GDScriptParser::SubscriptNode *subscript = static_cast<const GDScriptParser::SubscriptNode *>(completion_context.node); GDScriptCompletionIdentifier base; - if (!_guess_expression_type(completion_context, subscript->base, base)) { - break; - } + const bool res = _guess_expression_type(completion_context, subscript->base, base); + + // If the type is not known, we assume it is BUILTIN, since indices on arrays is the most common use case. + if (!subscript->is_attribute && (!res || base.type.kind == GDScriptParser::DataType::BUILTIN || base.type.is_variant())) { + if (base.value.get_type() == Variant::DICTIONARY) { + List<PropertyInfo> members; + base.value.get_property_list(&members); - _find_identifiers_in_base(base, false, false, options, 0); + for (const PropertyInfo &E : members) { + ScriptLanguage::CodeCompletionOption option(E.name.quote(quote_style), ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, ScriptLanguage::LOCATION_LOCAL); + options.insert(option.display, option); + } + } + if (!subscript->index || subscript->index->type != GDScriptParser::Node::LITERAL) { + _find_identifiers(completion_context, false, options, 0); + } + } else if (res) { + if (!subscript->is_attribute) { + // Quote the options if they are not accessed as attribute. + + HashMap<String, ScriptLanguage::CodeCompletionOption> opt; + _find_identifiers_in_base(base, false, false, opt, 0); + for (const KeyValue<String, CodeCompletionOption> &E : opt) { + ScriptLanguage::CodeCompletionOption option(E.value.insert_text.quote(quote_style), E.value.kind, E.value.location); + options.insert(option.display, option); + } + } else { + _find_identifiers_in_base(base, false, false, options, 0); + } + } } break; case GDScriptParser::COMPLETION_TYPE_ATTRIBUTE: { if (!completion_context.current_class) { @@ -3458,7 +3486,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c // is not a valid identifier. bool path_needs_quote = false; for (const String &part : opt.split("/")) { - if (!part.is_valid_identifier()) { + if (!part.is_valid_ascii_identifier()) { path_needs_quote = true; break; } diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index 759e92d68c..ac4bab6d84 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -509,7 +509,7 @@ private: } profile; #endif - _FORCE_INLINE_ String _get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const; + _FORCE_INLINE_ String _get_call_error(const String &p_where, const Variant **p_argptrs, const Variant &p_ret, const Callable::CallError &p_err) const; Variant _get_default_variant_for_data_type(const GDScriptDataType &p_data_type); public: diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 42b0b066e1..2162a727b3 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -198,6 +198,10 @@ ObjectID GDScriptLambdaSelfCallable::get_object() const { return object->get_instance_id(); } +StringName GDScriptLambdaSelfCallable::get_method() const { + return function->get_name(); +} + int GDScriptLambdaSelfCallable::get_argument_count(bool &r_is_valid) const { if (function == nullptr) { r_is_valid = false; diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index 45c0235913..2d27b8d679 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -87,6 +87,7 @@ public: CompareEqualFunc get_compare_equal_func() const override; CompareLessFunc get_compare_less_func() const override; ObjectID get_object() const override; + StringName get_method() const override; int get_argument_count(bool &r_is_valid) const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index a1ea94667d..6e32733faa 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -245,8 +245,26 @@ void GDScriptParser::apply_pending_warnings() { } #endif -void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) { - if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { +void GDScriptParser::override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument) { + if (!for_completion) { + return; + } + if (completion_context.node != p_for_node) { + return; + } + CompletionContext context; + context.type = p_type; + context.current_class = current_class; + context.current_function = current_function; + context.current_suite = current_suite; + context.current_line = tokenizer->get_cursor_line(); + context.current_argument = p_argument; + context.node = p_node; + completion_context = context; +} + +void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument) { + if (!for_completion) { return; } if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) { @@ -260,11 +278,12 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node context.current_line = tokenizer->get_cursor_line(); context.current_argument = p_argument; context.node = p_node; + context.parser = this; completion_context = context; } -void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) { - if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { +void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type) { + if (!for_completion) { return; } if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) { @@ -277,6 +296,7 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Typ context.current_suite = current_suite; context.current_line = tokenizer->get_cursor_line(); context.builtin_type = p_builtin_type; + context.parser = this; completion_context = context; } @@ -413,7 +433,7 @@ Error GDScriptParser::parse_binary(const Vector<uint8_t> &p_binary, const String } tokenizer = buffer_tokenizer; - script_path = p_script_path; + script_path = p_script_path.simplify_path(); current = tokenizer->scan(); // Avoid error or newline as the first token. // The latter can mess with the parser when opening files filled exclusively with comments and newlines. @@ -1618,7 +1638,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali advance(); // Arguments. push_completion_call(annotation); - make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0, true); + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, 0); int argument_index = 0; do { if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { @@ -1626,7 +1646,7 @@ GDScriptParser::AnnotationNode *GDScriptParser::parse_annotation(uint32_t p_vali break; } - make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index, true); + make_completion_context(COMPLETION_ANNOTATION_ARGUMENTS, annotation, argument_index); set_last_completion_call_arg(argument_index++); ExpressionNode *argument = parse_expression(false); if (argument == nullptr) { @@ -2567,8 +2587,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_literal(ExpressionNode *p_ } LiteralNode *literal = alloc_node<LiteralNode>(); - complete_extents(literal); literal->value = previous.literal; + reset_extents(literal, p_previous_operand); + update_extents(literal); + make_completion_context(COMPLETION_NONE, literal, -1); + complete_extents(literal); return literal; } @@ -3063,12 +3086,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_attribute(ExpressionNode * const IdentifierNode *id = static_cast<const IdentifierNode *>(p_previous_operand); Variant::Type builtin_type = get_builtin_type(id->name); if (builtin_type < Variant::VARIANT_MAX) { - make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type, true); + make_completion_context(COMPLETION_BUILT_IN_TYPE_CONSTANT_OR_STATIC_METHOD, builtin_type); is_builtin = true; } } if (!is_builtin) { - make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1, true); + make_completion_context(COMPLETION_ATTRIBUTE, attribute, -1); } } @@ -3099,6 +3122,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_subscript(ExpressionNode * subscript->base = p_previous_operand; subscript->index = parse_expression(false); +#ifdef TOOLS_ENABLED + if (subscript->index != nullptr && subscript->index->type == Node::LITERAL) { + override_completion_context(subscript->index, COMPLETION_SUBSCRIPT, subscript); + } +#endif + if (subscript->index == nullptr) { push_error(R"(Expected expression after "[".)"); } @@ -3193,23 +3222,24 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre push_completion_call(call); int argument_index = 0; do { - make_completion_context(ct, call, argument_index++, true); + make_completion_context(ct, call, argument_index); if (check(GDScriptTokenizer::Token::PARENTHESIS_CLOSE)) { // Allow for trailing comma. break; } - bool use_identifier_completion = current.cursor_place == GDScriptTokenizerText::CURSOR_END || current.cursor_place == GDScriptTokenizerText::CURSOR_MIDDLE; ExpressionNode *argument = parse_expression(false); if (argument == nullptr) { push_error(R"(Expected expression as the function argument.)"); } else { call->arguments.push_back(argument); - if (argument->type == Node::IDENTIFIER && use_identifier_completion) { - completion_context.type = COMPLETION_IDENTIFIER; + if (argument->type == Node::LITERAL) { + override_completion_context(argument, ct, call, argument_index); } } + ct = COMPLETION_CALL_ARGUMENTS; + argument_index++; } while (match(GDScriptTokenizer::Token::COMMA)); pop_completion_call(); @@ -3222,7 +3252,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_call(ExpressionNode *p_pre GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p_previous_operand, bool p_can_assign) { // We want code completion after a DOLLAR even if the current code is invalid. - make_completion_context(COMPLETION_GET_NODE, nullptr, -1, true); + make_completion_context(COMPLETION_GET_NODE, nullptr, -1); if (!current.is_node_name() && !check(GDScriptTokenizer::Token::LITERAL) && !check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) { push_error(vformat(R"(Expected node path as string or identifier after "%s".)", previous.get_name())); @@ -3279,7 +3309,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p path_state = PATH_STATE_SLASH; } - make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++, true); + make_completion_context(COMPLETION_GET_NODE, get_node, context_argument++); if (match(GDScriptTokenizer::Token::LITERAL)) { if (previous.literal.get_type() != Variant::STRING) { diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 21942222cf..2999fb11e4 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1314,7 +1314,7 @@ public: Variant::Type builtin_type = Variant::VARIANT_MAX; Node *node = nullptr; Object *base = nullptr; - List<Ref<GDScriptParserRef>> dependent_parsers; + GDScriptParser *parser = nullptr; }; struct CompletionCall { @@ -1455,9 +1455,11 @@ private: } void apply_pending_warnings(); #endif - - void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = false); - void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = false); + void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1); + void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type); + // In some cases it might become necessary to alter the completion context after parsing a subexpression. + // For example to not override COMPLETE_CALL_ARGUMENTS with COMPLETION_NONE from string literals. + void override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument = -1); void push_completion_call(Node *p_call); void pop_completion_call(); void set_last_completion_call_arg(int p_argument); diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp index 5b1639e250..404b61fb40 100644 --- a/modules/gdscript/gdscript_tokenizer.cpp +++ b/modules/gdscript/gdscript_tokenizer.cpp @@ -574,7 +574,9 @@ GDScriptTokenizer::Token GDScriptTokenizerText::potential_identifier() { if (len == 1 && _peek(-1) == '_') { // Lone underscore. - return make_token(Token::UNDERSCORE); + Token token = make_token(Token::UNDERSCORE); + token.literal = "_"; + return token; } String name(_start, len); diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 5d1805696d..ddb0cf9502 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -134,38 +134,36 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT return Variant(); } -String GDScriptFunction::_get_call_error(const Callable::CallError &p_err, const String &p_where, const Variant **argptrs) const { - String err_text; - - if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_ARGUMENT) { - int errorarg = p_err.argument; - ERR_FAIL_COND_V_MSG(errorarg < 0 || argptrs[errorarg] == nullptr, "GDScript bug (please report): Invalid CallError argument index or null pointer.", "Invalid CallError argument index or null pointer."); - // Handle the Object to Object case separately as we don't have further class details. +String GDScriptFunction::_get_call_error(const String &p_where, const Variant **p_argptrs, const Variant &p_ret, const Callable::CallError &p_err) const { + switch (p_err.error) { + case Callable::CallError::CALL_OK: + return String(); + case Callable::CallError::CALL_ERROR_INVALID_METHOD: + if (p_ret.get_type() == Variant::STRING && !p_ret.operator String().is_empty()) { + return "Invalid call " + p_where + ": " + p_ret.operator String(); + } + return "Invalid call. Nonexistent " + p_where + "."; + case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: + ERR_FAIL_COND_V_MSG(p_err.argument < 0 || p_argptrs[p_err.argument] == nullptr, "Bug: Invalid CallError argument index or null pointer.", "Bug: Invalid CallError argument index or null pointer."); + // Handle the Object to Object case separately as we don't have further class details. #ifdef DEBUG_ENABLED - if (p_err.expected == Variant::OBJECT && argptrs[errorarg]->get_type() == p_err.expected) { - err_text = "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") is not a subclass of the expected argument class."; - } else if (p_err.expected == Variant::ARRAY && argptrs[errorarg]->get_type() == p_err.expected) { - err_text = "Invalid type in " + p_where + ". The array of argument " + itos(errorarg + 1) + " (" + _get_var_type(argptrs[errorarg]) + ") does not have the same element type as the expected typed array argument."; - } else + if (p_err.expected == Variant::OBJECT && p_argptrs[p_err.argument]->get_type() == p_err.expected) { + return "Invalid type in " + p_where + ". The Object-derived class of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") is not a subclass of the expected argument class."; + } + if (p_err.expected == Variant::ARRAY && p_argptrs[p_err.argument]->get_type() == p_err.expected) { + return "Invalid type in " + p_where + ". The array of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed array argument."; + } #endif // DEBUG_ENABLED - { - err_text = "Invalid type in " + p_where + ". Cannot convert argument " + itos(errorarg + 1) + " from " + Variant::get_type_name(argptrs[errorarg]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; - } - } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) { - err_text = "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { - err_text = "Invalid call. Nonexistent " + p_where + "."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) { - err_text = "Attempt to call " + p_where + " on a null instance."; - } else if (p_err.error == Callable::CallError::CALL_ERROR_METHOD_NOT_CONST) { - err_text = "Attempt to call " + p_where + " on a const instance."; - } else { - err_text = "Bug, call error: #" + itos(p_err.error); + return "Invalid type in " + p_where + ". Cannot convert argument " + itos(p_err.argument + 1) + " from " + Variant::get_type_name(p_argptrs[p_err.argument]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; + case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: + case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: + return "Invalid call to " + p_where + ". Expected " + itos(p_err.expected) + " arguments."; + case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL: + return "Attempt to call " + p_where + " on a null instance."; + case Callable::CallError::CALL_ERROR_METHOD_NOT_CONST: + return "Attempt to call " + p_where + " on a const instance."; } - - return err_text; + return "Bug: Invalid call error code " + itos(p_err.error) + "."; } void (*type_init_function_table[])(Variant *) = { @@ -550,9 +548,22 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a return _get_default_variant_for_data_type(return_type); } if (argument_types[i].kind == GDScriptDataType::BUILTIN) { - Variant arg; - Variant::construct(argument_types[i].builtin_type, arg, &p_args[i], 1, r_err); - memnew_placement(&stack[i + 3], Variant(arg)); + if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) { + const GDScriptDataType &arg_type = argument_types[i].container_element_types[0]; + Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type); + memnew_placement(&stack[i + 3], Variant(array)); + } else { + Variant variant; + Variant::construct(argument_types[i].builtin_type, variant, &p_args[i], 1, r_err); + if (unlikely(r_err.error)) { + r_err.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_err.argument = i; + r_err.expected = argument_types[i].builtin_type; + call_depth--; + return _get_default_variant_for_data_type(return_type); + } + memnew_placement(&stack[i + 3], Variant(variant)); + } } else { memnew_placement(&stack[i + 3], Variant(*p_args[i])); } @@ -1595,7 +1606,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (err.error != Callable::CallError::CALL_OK) { - err_text = _get_call_error(err, "'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs); + err_text = _get_call_error("'" + Variant::get_type_name(t) + "' constructor", (const Variant **)argptrs, *dst, err); OPCODE_BREAK; } #endif @@ -1731,10 +1742,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a StringName base_class = base_obj ? base_obj->get_class_name() : StringName(); #endif + Variant temp_ret; Callable::CallError err; if (call_ret) { GET_INSTRUCTION_ARG(ret, argc + 1); - base->callp(*methodname, (const Variant **)argptrs, argc, *ret, err); + base->callp(*methodname, (const Variant **)argptrs, argc, temp_ret, err); + *ret = temp_ret; #ifdef DEBUG_ENABLED if (ret->get_type() == Variant::NIL) { if (base_type == Variant::OBJECT) { @@ -1763,8 +1776,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } #endif } else { - Variant ret; - base->callp(*methodname, (const Variant **)argptrs, argc, ret, err); + base->callp(*methodname, (const Variant **)argptrs, argc, temp_ret, err); } #ifdef DEBUG_ENABLED @@ -1794,7 +1806,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { if (base->is_ref_counted()) { - err_text = "Attempted to free a reference."; + err_text = "Attempted to free a RefCounted object."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { err_text = "Attempted to free a locked object (calling or emitting)."; @@ -1809,7 +1821,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } } - err_text = _get_call_error(err, "function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs); + err_text = _get_call_error("function '" + methodstr + (is_callable ? "" : "' in base '" + basestr) + "'", (const Variant **)argptrs, temp_ret, err); OPCODE_BREAK; } #endif @@ -1855,12 +1867,14 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } #endif + Variant temp_ret; Callable::CallError err; if (call_ret) { GET_INSTRUCTION_ARG(ret, argc + 1); - *ret = method->call(base_obj, (const Variant **)argptrs, argc, err); + temp_ret = method->call(base_obj, (const Variant **)argptrs, argc, err); + *ret = temp_ret; } else { - method->call(base_obj, (const Variant **)argptrs, argc, err); + temp_ret = method->call(base_obj, (const Variant **)argptrs, argc, err); } #ifdef DEBUG_ENABLED @@ -1885,7 +1899,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else if (methodstr == "free") { if (err.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) { if (base->is_ref_counted()) { - err_text = "Attempted to free a reference."; + err_text = "Attempted to free a RefCounted object."; OPCODE_BREAK; } else if (base->get_type() == Variant::OBJECT) { err_text = "Attempted to free a locked object (calling or emitting)."; @@ -1893,7 +1907,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } } } - err_text = _get_call_error(err, "function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs); + err_text = _get_call_error("function '" + methodstr + "' in base '" + basestr + "'", (const Variant **)argptrs, temp_ret, err); OPCODE_BREAK; } #endif @@ -1926,7 +1940,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #ifdef DEBUG_ENABLED if (err.error != Callable::CallError::CALL_OK) { - err_text = _get_call_error(err, "static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs); + err_text = _get_call_error("static function '" + methodname->operator String() + "' in type '" + Variant::get_type_name(builtin_type) + "'", argptrs, *ret, err); OPCODE_BREAK; } #endif @@ -1970,7 +1984,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif if (err.error != Callable::CallError::CALL_OK) { - err_text = _get_call_error(err, "static function '" + method->get_name().operator String() + "' in type '" + method->get_instance_class().operator String() + "'", argptrs); + err_text = _get_call_error("static function '" + method->get_name().operator String() + "' in type '" + method->get_instance_class().operator String() + "'", argptrs, *ret, err); OPCODE_BREAK; } @@ -2201,7 +2215,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a // Call provided error string. err_text = vformat(R"*(Error calling utility function "%s()": %s)*", methodstr, *dst); } else { - err_text = _get_call_error(err, vformat(R"*(utility function "%s()")*", methodstr), (const Variant **)argptrs); + err_text = _get_call_error(vformat(R"*(utility function "%s()")*", methodstr), (const Variant **)argptrs, *dst, err); } OPCODE_BREAK; } @@ -2258,7 +2272,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a // Call provided error string. err_text = vformat(R"*(Error calling GDScript utility function "%s()": %s)*", methodstr, *dst); } else { - err_text = _get_call_error(err, vformat(R"*(GDScript utility function "%s()")*", methodstr), (const Variant **)argptrs); + err_text = _get_call_error(vformat(R"*(GDScript utility function "%s()")*", methodstr), (const Variant **)argptrs, *dst, err); } OPCODE_BREAK; } @@ -2325,7 +2339,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (err.error != Callable::CallError::CALL_OK) { String methodstr = *methodname; - err_text = _get_call_error(err, "function '" + methodstr + "'", (const Variant **)argptrs); + err_text = _get_call_error("function '" + methodstr + "'", (const Variant **)argptrs, *dst, err); OPCODE_BREAK; } diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 59e387eece..055f8e4110 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -165,6 +165,13 @@ void initialize_gdscript_module(ModuleInitializationLevel p_level) { gdscript_translation_parser_plugin.instantiate(); EditorTranslationParser::get_singleton()->add_parser(gdscript_translation_parser_plugin, EditorTranslationParser::STANDARD); + } else if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { + ClassDB::APIType prev_api = ClassDB::get_current_api(); + ClassDB::set_current_api(ClassDB::API_EDITOR); + + GDREGISTER_CLASS(GDScriptSyntaxHighlighter); + + ClassDB::set_current_api(prev_api); } #endif // TOOLS_ENABLED } diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index fbfa4a0a79..025fcbd32a 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -622,7 +622,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { enable_stdout(); result.status = GDTEST_COMPILER_ERROR; result.output = get_text_for_status(result.status) + "\n"; - result.output = compiler.get_error(); + result.output += compiler.get_error() + "\n"; if (!p_is_generating) { result.passed = check_output(result.output); } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out index 64a6bd417d..878e827d04 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/cyclic_ref_external.out @@ -1,2 +1,2 @@ GDTEST_ANALYZER_ERROR -Could not resolve member "v". +Could not resolve external class member "v". diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser.gd new file mode 100644 index 0000000000..ad265a7365 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser.gd @@ -0,0 +1,17 @@ +extends "external_parser_base1.notest.gd" + +const External1 = preload("external_parser_script1.notest.gd") + +func baz(e1: External1) -> void: + print(e1.e1c.bar) + print(e1.baz) + +func test_external_base_parser_type_resolve(_v: TypeFromBase): + pass + +func test(): + var ext := External1.new() + print(ext.array[0].test2) + print(ext.get_external2().get_external3().test3) + # TODO: This actually produces a runtime error, but we're testing the analyzer here + #baz(ext) diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser.out b/modules/gdscript/tests/scripts/analyzer/features/external_parser.out new file mode 100644 index 0000000000..26c2527fbc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser.out @@ -0,0 +1,3 @@ +GDTEST_OK +test2 +test3 diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_base1.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base1.notest.gd new file mode 100644 index 0000000000..d27d8cef3b --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base1.notest.gd @@ -0,0 +1 @@ +extends "external_parser_base2.notest.gd" diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_base2.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base2.notest.gd new file mode 100644 index 0000000000..f8de818485 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_base2.notest.gd @@ -0,0 +1,2 @@ +class TypeFromBase: + pass diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1.notest.gd new file mode 100644 index 0000000000..e1221bb138 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1.notest.gd @@ -0,0 +1,12 @@ +extends "external_parser_script1_base.notest.gd" + +const External2 = preload("external_parser_script2.notest.gd") +const External1c = preload("external_parser_script1c.notest.gd") + +@export var e1c: External1c + +var array: Array[External2] = [ External2.new() ] +var baz: int + +func get_external2() -> External2: + return External2.new() diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1_base.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1_base.notest.gd new file mode 100644 index 0000000000..4249efa1f9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1_base.notest.gd @@ -0,0 +1 @@ +extends Resource diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1c.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1c.notest.gd new file mode 100644 index 0000000000..34a48cba6f --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1c.notest.gd @@ -0,0 +1 @@ +extends "external_parser_script1d.notest.gd" diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1d.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1d.notest.gd new file mode 100644 index 0000000000..43bc6f3e53 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1d.notest.gd @@ -0,0 +1,5 @@ +extends "external_parser_script1f.notest.gd" + +const External1e = preload("external_parser_script1e.notest.gd") + +var bar: Array[External1e] diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1e.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1e.notest.gd new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1e.notest.gd diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1f.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1f.notest.gd new file mode 100644 index 0000000000..4249efa1f9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script1f.notest.gd @@ -0,0 +1 @@ +extends Resource diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script2.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script2.notest.gd new file mode 100644 index 0000000000..885f813503 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script2.notest.gd @@ -0,0 +1,6 @@ +const External3 = preload("external_parser_script3.notest.gd") + +var test2 := "test2" + +func get_external3() -> External3: + return External3.new() diff --git a/modules/gdscript/tests/scripts/analyzer/features/external_parser_script3.notest.gd b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script3.notest.gd new file mode 100644 index 0000000000..3e62974256 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/external_parser_script3.notest.gd @@ -0,0 +1 @@ +var test3 := "test3" diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd index 13e3edf93f..f3a8661acf 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/enum_without_default_value.gd @@ -7,3 +7,17 @@ var has_no_zero: HasNoZero # Warning, because there is no `0` in the enum. func test(): print(has_zero) print(has_no_zero) + + +# GH-94634. A parameter is either mandatory or has a default value. +func test_no_exec(param: HasNoZero) -> void: + print(param) + + # Loop iterator always has a value. + for i: HasNoZero in HasNoZero.values(): + print(i) + + match param: + # Pattern bind always has a value. + var x: + print(x) diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg new file mode 100644 index 0000000000..be9bd510e1 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.cfg @@ -0,0 +1,5 @@ +[output] +include=[ + {"insert_text": "\"property_of_a\""}, + {"insert_text": "\"name\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd new file mode 100644 index 0000000000..a8e04a62a7 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/argument_options/string_literals/argument_options_inside_string_literal.gd @@ -0,0 +1,8 @@ +extends Node + +const A = preload ("res://completion/class_a.notest.gd") + +func _ready() -> void: + var a := A.new() + var tween := get_tree().create_tween() + tween.tween_property(a, "➡") diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.cfg index 871a404e3a..5f08f9c265 100644 --- a/modules/gdscript/tests/scripts/completion/common/identifiers.cfg +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.cfg @@ -11,11 +11,15 @@ include=[ {"display": "func_of_a"}, {"display": "signal_of_a"}, - ; GDScript: self.gd + ; GDScript: identifiers.gd {"display": "test_signal_1"}, {"display": "test_signal_2"}, {"display": "test_var_1"}, {"display": "test_var_2"}, {"display": "test_func_1"}, {"display": "test_func_2"}, + {"display": "test_parameter_1"}, + {"display": "test_parameter_2"}, + {"display": "local_test_var_1"}, + {"display": "local_test_var_2"}, ] diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd new file mode 100644 index 0000000000..91488c25aa --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_call.gd @@ -0,0 +1,18 @@ +extends "res://completion/class_a.notest.gd" + +signal test_signal_1(a) +signal test_signal_2(a: int) + +var test_var_1 +var test_var_2: int + +func test_func_1(t): + pass + +func test_func_2(t: int) -> void: + pass + +func _init(test_parameter_1, test_parameter_2: String): + var local_test_var_1 + var local_test_var_2: int + print(t➡) diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg new file mode 100644 index 0000000000..5f08f9c265 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.cfg @@ -0,0 +1,25 @@ +scene="res://completion/get_node/get_node.tscn" +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, + + ; GDScript: identifiers.gd + {"display": "test_signal_1"}, + {"display": "test_signal_2"}, + {"display": "test_var_1"}, + {"display": "test_var_2"}, + {"display": "test_func_1"}, + {"display": "test_func_2"}, + {"display": "test_parameter_1"}, + {"display": "test_parameter_2"}, + {"display": "local_test_var_1"}, + {"display": "local_test_var_2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.gd index efbafbee8e..a2f5b7bc23 100644 --- a/modules/gdscript/tests/scripts/completion/common/identifiers.gd +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_function_body.gd @@ -12,5 +12,7 @@ func test_func_1(t): func test_func_2(t: int) -> void: pass -func _init(): +func _init(test_parameter_1, test_parameter_2: String): + var local_test_var_1 + var local_test_var_2: int t➡ diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg new file mode 100644 index 0000000000..5f08f9c265 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.cfg @@ -0,0 +1,25 @@ +scene="res://completion/get_node/get_node.tscn" +[output] +include=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, + + ; GDScript: identifiers.gd + {"display": "test_signal_1"}, + {"display": "test_signal_2"}, + {"display": "test_var_1"}, + {"display": "test_var_2"}, + {"display": "test_func_1"}, + {"display": "test_func_2"}, + {"display": "test_parameter_1"}, + {"display": "test_parameter_2"}, + {"display": "local_test_var_1"}, + {"display": "local_test_var_2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd new file mode 100644 index 0000000000..fed0b869c4 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/identifiers_in_unclosed_call.gd @@ -0,0 +1,22 @@ +# godotengine/godot#92226 +extends "res://completion/class_a.notest.gd" + +signal test_signal_1(a) +signal test_signal_2(a: int) + +var test_var_1 +var test_var_2: int + +func test_func_1(t): + pass + +func test_func_2(t: int) -> void: + pass + +func _init(test_parameter_1, test_parameter_2: String): + var local_test_var_1 + var local_test_var_2: int + print(t➡ + + if true: + pass diff --git a/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg new file mode 100644 index 0000000000..462846c9b2 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.cfg @@ -0,0 +1,26 @@ +scene="res://completion/get_node/get_node.tscn" +[output] +exclude=[ + ; Node + {"display": "add_child"}, + {"display": "owner"}, + {"display": "child_entered_tree"}, + {"display": "add_child"}, + + ; GDScript: class_a.notest.gd + {"display": "property_of_a"}, + {"display": "func_of_a"}, + {"display": "signal_of_a"}, + + ; GDScript: no_completion_in_string.gd + {"display": "test_signal_1"}, + {"display": "test_signal_2"}, + {"display": "test_var_1"}, + {"display": "test_var_2"}, + {"display": "test_func_1"}, + {"display": "test_func_2"}, + {"display": "test_parameter_1"}, + {"display": "test_parameter_2"}, + {"display": "local_test_var_1"}, + {"display": "local_test_var_2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd new file mode 100644 index 0000000000..da52af9fe3 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/common/no_completion_in_string.gd @@ -0,0 +1,19 @@ +# godotengine/godot#62945 +extends "res://completion/class_a.notest.gd" + +signal test_signal_1(a) +signal test_signal_2(a: int) + +var test_var_1 +var test_var_2: int + +func test_func_1(t): + pass + +func test_func_2(t: int) -> void: + pass + +func _init(test_parameter_1, test_parameter_2: String): + var local_test_var_1 + var local_test_var_2: int + var a = "➡" diff --git a/modules/gdscript/tests/scripts/completion/common/self.gd b/modules/gdscript/tests/scripts/completion/common/self.gd index 9ad2fbea51..ed181af0c5 100644 --- a/modules/gdscript/tests/scripts/completion/common/self.gd +++ b/modules/gdscript/tests/scripts/completion/common/self.gd @@ -14,3 +14,4 @@ func test_func_2(t: int) -> void: func _init(): self.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/filter/organized_export.gd b/modules/gdscript/tests/scripts/completion/filter/organized_export.gd index 189608904c..9fa9618cee 100644 --- a/modules/gdscript/tests/scripts/completion/filter/organized_export.gd +++ b/modules/gdscript/tests/scripts/completion/filter/organized_export.gd @@ -5,4 +5,5 @@ extends CPUParticles2D @export_subgroup("Test Subgroup") func _init(): - ➡ + t➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.cfg index ae7d34d87d..ae7d34d87d 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.gd index 7710c2d13b..7710c2d13b 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered/local_interfered.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered/local_infered.gd diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.cfg index 9c580b711d..9c580b711d 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.gd index 6b29bf5526..6b29bf5526 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/class_local_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/class_local_infered_scene.gd diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.cfg index 446198dd35..446198dd35 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.gd index 7710c2d13b..7710c2d13b 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/local_interfered_scene/native_local_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/local_infered_scene/native_local_infered_scene.gd diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.cfg index ae7d34d87d..ae7d34d87d 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.gd index 97b288334e..97b288334e 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered/member_interfered.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered/member_infered.gd diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.cfg index 9c580b711d..9c580b711d 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.gd index 402fd1d275..402fd1d275 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/class_member_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/class_member_infered_scene.gd diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.cfg b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.cfg index 446198dd35..446198dd35 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.cfg +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.cfg diff --git a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.gd index 97b288334e..97b288334e 100644 --- a/modules/gdscript/tests/scripts/completion/get_node/member_interfered_scene/native_member_interfered_scene.gd +++ b/modules/gdscript/tests/scripts/completion/get_node/member_infered_scene/native_member_infered_scene.gd diff --git a/modules/gdscript/tests/scripts/completion/index/array_type.cfg b/modules/gdscript/tests/scripts/completion/index/array_type.cfg new file mode 100644 index 0000000000..5cd5565d00 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/array_type.cfg @@ -0,0 +1,9 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] +exclude=[ + {"display": "append"}, + {"display": "\"append\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/array_type.gd b/modules/gdscript/tests/scripts/completion/index/array_type.gd new file mode 100644 index 0000000000..e0a15da556 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/array_type.gd @@ -0,0 +1,10 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var array: Array + + array[i➡] diff --git a/modules/gdscript/tests/scripts/completion/index/array_value.cfg b/modules/gdscript/tests/scripts/completion/index/array_value.cfg new file mode 100644 index 0000000000..5cd5565d00 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/array_value.cfg @@ -0,0 +1,9 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] +exclude=[ + {"display": "append"}, + {"display": "\"append\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/array_value.gd b/modules/gdscript/tests/scripts/completion/index/array_value.gd new file mode 100644 index 0000000000..17451725bc --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/array_value.gd @@ -0,0 +1,10 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var array = [] + + array[i➡] diff --git a/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.cfg b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.cfg new file mode 100644 index 0000000000..ecea284b5d --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.cfg @@ -0,0 +1,11 @@ +[output] +include=[ + {"display": "\"key1\""}, + {"display": "\"key2\""}, +] +exclude=[ + {"display": "keys"}, + {"display": "\"keys\""}, + {"display": "key1"}, + {"display": "key2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.gd b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.gd new file mode 100644 index 0000000000..06498c57a6 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/const_dictionary_keys.gd @@ -0,0 +1,13 @@ +extends Node + +var outer + +const dict = { + "key1": "value", + "key2": null, +} + +func _ready() -> void: + var inner + + dict["➡"] diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_type.cfg b/modules/gdscript/tests/scripts/completion/index/dictionary_type.cfg new file mode 100644 index 0000000000..cddf7b8cc8 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/dictionary_type.cfg @@ -0,0 +1,9 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] +exclude=[ + {"display": "keys"}, + {"display": "\"keys\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_type.gd b/modules/gdscript/tests/scripts/completion/index/dictionary_type.gd new file mode 100644 index 0000000000..b02c62eea5 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/dictionary_type.gd @@ -0,0 +1,10 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var dict: Dictionary + + dict[i➡] diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_value.cfg b/modules/gdscript/tests/scripts/completion/index/dictionary_value.cfg new file mode 100644 index 0000000000..cddf7b8cc8 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/dictionary_value.cfg @@ -0,0 +1,9 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] +exclude=[ + {"display": "keys"}, + {"display": "\"keys\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/dictionary_value.gd b/modules/gdscript/tests/scripts/completion/index/dictionary_value.gd new file mode 100644 index 0000000000..60bf391716 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/dictionary_value.gd @@ -0,0 +1,10 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var dict = {} + + dict[i➡] diff --git a/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.cfg b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.cfg new file mode 100644 index 0000000000..ecea284b5d --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.cfg @@ -0,0 +1,11 @@ +[output] +include=[ + {"display": "\"key1\""}, + {"display": "\"key2\""}, +] +exclude=[ + {"display": "keys"}, + {"display": "\"keys\""}, + {"display": "key1"}, + {"display": "key2"}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.gd b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.gd new file mode 100644 index 0000000000..2220cdcc59 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/local_dictionary_keys.gd @@ -0,0 +1,13 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var dict: Dictionary = { + "key1": "value", + "key2": null, + } + + dict["➡"] diff --git a/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.cfg b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.cfg new file mode 100644 index 0000000000..8da525bff8 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.cfg @@ -0,0 +1,9 @@ +[output] +exclude=[ + {"display": "keys"}, + {"display": "\"keys\""}, + {"display": "key1"}, + {"display": "key2"}, + {"display": "\"key1\""}, + {"display": "\"key2\""}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.gd b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.gd new file mode 100644 index 0000000000..ba8d7f76fd --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/property_dictionary_keys.gd @@ -0,0 +1,13 @@ +extends Node + +var outer + +var dict = { + "key1": "value", + "key2": null, +} + +func _ready() -> void: + var inner + + dict["➡"] diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_local.cfg b/modules/gdscript/tests/scripts/completion/index/untyped_local.cfg new file mode 100644 index 0000000000..1173043f94 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/untyped_local.cfg @@ -0,0 +1,5 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_local.gd b/modules/gdscript/tests/scripts/completion/index/untyped_local.gd new file mode 100644 index 0000000000..1a1157af02 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/untyped_local.gd @@ -0,0 +1,10 @@ +extends Node + +var outer + +func _ready() -> void: + var inner + + var array + + array[i➡] diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_property.cfg b/modules/gdscript/tests/scripts/completion/index/untyped_property.cfg new file mode 100644 index 0000000000..1173043f94 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/untyped_property.cfg @@ -0,0 +1,5 @@ +[output] +include=[ + {"display": "outer"}, + {"display": "inner"}, +] diff --git a/modules/gdscript/tests/scripts/completion/index/untyped_property.gd b/modules/gdscript/tests/scripts/completion/index/untyped_property.gd new file mode 100644 index 0000000000..9fa23da504 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/index/untyped_property.gd @@ -0,0 +1,9 @@ +extends Node + +var outer +var array + +func _ready() -> void: + var inner + + array[i➡] diff --git a/modules/gdscript/tests/scripts/completion/types/local/interfered.cfg b/modules/gdscript/tests/scripts/completion/types/local/infered.cfg index 8b68d51a89..8b68d51a89 100644 --- a/modules/gdscript/tests/scripts/completion/types/local/interfered.cfg +++ b/modules/gdscript/tests/scripts/completion/types/local/infered.cfg diff --git a/modules/gdscript/tests/scripts/completion/types/local/interfered.gd b/modules/gdscript/tests/scripts/completion/types/local/infered.gd index f003c366a4..f003c366a4 100644 --- a/modules/gdscript/tests/scripts/completion/types/local/interfered.gd +++ b/modules/gdscript/tests/scripts/completion/types/local/infered.gd diff --git a/modules/gdscript/tests/scripts/completion/types/member/interfered.cfg b/modules/gdscript/tests/scripts/completion/types/member/infered.cfg index 8b68d51a89..8b68d51a89 100644 --- a/modules/gdscript/tests/scripts/completion/types/member/interfered.cfg +++ b/modules/gdscript/tests/scripts/completion/types/member/infered.cfg diff --git a/modules/gdscript/tests/scripts/completion/types/member/interfered.gd b/modules/gdscript/tests/scripts/completion/types/member/infered.gd index 069abd7891..069abd7891 100644 --- a/modules/gdscript/tests/scripts/completion/types/member/interfered.gd +++ b/modules/gdscript/tests/scripts/completion/types/member/infered.gd diff --git a/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd new file mode 100644 index 0000000000..3724c8c713 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.gd @@ -0,0 +1,9 @@ +# https://github.com/godotengine/godot/issues/90086 + +class MyObj: + var obj: WeakRef + +func test(): + var obj_1 = MyObj.new() + var obj_2 = MyObj.new() + obj_1.obj = obj_2 diff --git a/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out new file mode 100644 index 0000000000..dfca5b1eca --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/invalid_property_assignment.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/invalid_property_assignment.gd +>> 9 +>> Invalid assignment of property or key 'obj' with value of type 'RefCounted (MyObj)' on a base object of type 'RefCounted (MyObj)'. diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out index 7b9f1066b0..9b38957101 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_array_assign_wrong_to_typed.out @@ -1,4 +1,5 @@ GDTEST_RUNTIME_ERROR >> ERROR >> Method/function failed. +>> Unable to convert array index 0 from "Object" to "Object". not ok diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd new file mode 100644 index 0000000000..160e43a797 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.gd @@ -0,0 +1,21 @@ +# https://github.com/godotengine/godot/issues/94074 + +func foo(): + pass + +func test(): + var lambda_self := func test() -> void: + foo() + var anon_lambda_self := func() -> void: + foo() + + print(lambda_self.get_method()) # Should print "test". + print(anon_lambda_self.get_method()) # Should print "<anonymous lambda>". + + var lambda_non_self := func test() -> void: + pass + var anon_lambda_non_self := func() -> void: + pass + + print(lambda_non_self.get_method()) # Should print "test". + print(anon_lambda_non_self.get_method()) # Should print "<anonymous lambda>". diff --git a/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out new file mode 100644 index 0000000000..17ee47fca2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/lambda_get_method.out @@ -0,0 +1,5 @@ +GDTEST_OK +test +<anonymous lambda> +test +<anonymous lambda> diff --git a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd index c774ebf83c..df639a7b4d 100644 --- a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd +++ b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.gd @@ -1,6 +1,5 @@ # GH-77666 - -func test(): +func test_exit_if(): var ref := RefCounted.new() print(ref.get_reference_count()) @@ -8,3 +7,20 @@ func test(): var _temp := ref print(ref.get_reference_count()) + +# GH-94654 +func test_exit_while(): + var slots_data := [] + + while true: + @warning_ignore("confusable_local_declaration") + var slot = 42 + slots_data.append(slot) + break + + var slot: int = slots_data[0] + print(slot) + +func test(): + test_exit_if() + test_exit_while() diff --git a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out index 04b4638adf..164eb24963 100644 --- a/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out +++ b/modules/gdscript/tests/scripts/runtime/features/reset_local_var_on_exit_block.out @@ -1,3 +1,4 @@ GDTEST_OK 1 1 +42 diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd new file mode 100644 index 0000000000..e1aba83507 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd @@ -0,0 +1,11 @@ +# https://github.com/godotengine/godot/issues/90086 + +class MyObj: + var obj : WeakRef + +func test(): + var obj_1 = MyObj.new() + var obj_2 = MyObj.new() + assert(obj_2.get_reference_count() == 1) + obj_1.set(&"obj", obj_2) + assert(obj_2.get_reference_count() == 1) diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd new file mode 100644 index 0000000000..f70b521e1a --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.gd @@ -0,0 +1,62 @@ +# GH-94667 + +class Inner: + var subprop: Vector2: + set(value): + prints("subprop setter", value) + subprop = value + get: + print("subprop getter") + return subprop + + func _to_string() -> String: + return "<Inner>" + +var prop1: + set(value): + prints("prop1 setter", value) + prop1 = value + +var prop2: Inner: + set(value): + prints("prop2 setter", value) + prop2 = value + +var prop3: + set(value): + prints("prop3 setter", value) + prop3 = value + get: + print("prop3 getter") + return prop3 + +var prop4: Inner: + set(value): + prints("prop4 setter", value) + prop4 = value + get: + print("prop4 getter") + return prop4 + +func test(): + print("===") + prop1 = Vector2() + prop1.x = 1.0 + print("---") + prop1 = Inner.new() + prop1.subprop.x = 1.0 + + print("===") + prop2 = Inner.new() + prop2.subprop.x = 1.0 + + print("===") + prop3 = Vector2() + prop3.x = 1.0 + print("---") + prop3 = Inner.new() + prop3.subprop.x = 1.0 + + print("===") + prop4 = Inner.new() + prop4.subprop.x = 1.0 diff --git a/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out new file mode 100644 index 0000000000..c51759f481 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/setter_chain_shared_types.out @@ -0,0 +1,26 @@ +GDTEST_OK +=== +prop1 setter (0, 0) +prop1 setter (1, 0) +--- +prop1 setter <Inner> +subprop getter +subprop setter (1, 0) +=== +prop2 setter <Inner> +subprop getter +subprop setter (1, 0) +=== +prop3 setter (0, 0) +prop3 getter +prop3 setter (1, 0) +--- +prop3 setter <Inner> +prop3 getter +subprop getter +subprop setter (1, 0) +=== +prop4 setter <Inner> +prop4 getter +subprop getter +subprop setter (1, 0) diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd new file mode 100644 index 0000000000..9e27a500bf --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.gd @@ -0,0 +1,13 @@ +# https://github.com/godotengine/godot/issues/85952 + +var vec: Vector2 = Vector2.ZERO: + set(new_vec): + prints("setting vec from", vec, "to", new_vec) + if new_vec == Vector2(1, 1): + vec = new_vec + +func test(): + vec.x = 2 + vec.y = 2 + + prints("vec is", vec) diff --git a/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out new file mode 100644 index 0000000000..31b3b3a3a8 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/simple_setter_chain_call_setter.out @@ -0,0 +1,4 @@ +GDTEST_OK +setting vec from (0, 0) to (2, 0) +setting vec from (0, 0) to (0, 2) +vec is (0, 0) diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd new file mode 100644 index 0000000000..b07c40b6da --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd @@ -0,0 +1,15 @@ +extends Node + +func test() -> void: + var node1 := Node.new() + node1.name = "_" + var node2 := Node.new() + node2.name = "Child" + var node3 := Node.new() + node3.name = "Child" + + add_child(node1) + node1.add_child(node2) + add_child(node3) + + assert(get_node("_/Child") == $_/Child) diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out new file mode 100644 index 0000000000..d73c5eb7cd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.out @@ -0,0 +1 @@ +GDTEST_OK diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd new file mode 100644 index 0000000000..13f2c3b956 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.gd @@ -0,0 +1,7 @@ +# GH-93990 + +func test_param(array: Array[String]) -> void: + print(array.get_typed_builtin() == TYPE_STRING) + +func test() -> void: + test_param(PackedStringArray()) diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out new file mode 100644 index 0000000000..55482c2b52 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_implicit_cast_param.out @@ -0,0 +1,2 @@ +GDTEST_OK +true diff --git a/modules/glslang/config.py b/modules/glslang/config.py index 1169776a73..a024dcf984 100644 --- a/modules/glslang/config.py +++ b/modules/glslang/config.py @@ -1,7 +1,7 @@ def can_build(env, platform): - # glslang is only needed when Vulkan or Direct3D 12-based renderers are available, + # glslang is only needed when Vulkan, Direct3D 12 or Metal-based renderers are available, # as OpenGL doesn't use glslang. - return env["vulkan"] or env["d3d12"] + return env["vulkan"] or env["d3d12"] or env["metal"] def configure(env): diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp index b5f70fb98b..81505f716a 100644 --- a/modules/glslang/register_types.cpp +++ b/modules/glslang/register_types.cpp @@ -73,6 +73,9 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage // - SPIRV-Reflect won't be able to parse the compute workgroup size. // - We want to play it safe with NIR-DXIL. TargetVersion = glslang::EShTargetSpv_1_3; + } else if (capabilities.device_family == RDD::DEVICE_METAL) { + ClientVersion = glslang::EShTargetVulkan_1_1; + TargetVersion = glslang::EShTargetSpv_1_6; } else { // once we support other backends we'll need to do something here if (r_error) { diff --git a/modules/gltf/README.md b/modules/gltf/README.md index 5d8966b201..2d1e92e070 100644 --- a/modules/gltf/README.md +++ b/modules/gltf/README.md @@ -1,11 +1,11 @@ -# Godot GLTF import and export module +# Godot glTF import and export module -In a nutshell, the GLTF module works like this: +In a nutshell, the glTF module works like this: -* The [`structures/`](structures/) folder contains GLTF structures, the - small pieces that make up a GLTF file, represented as C++ classes. -* The [`extensions/`](extensions/) folder contains GLTF extensions, which - are optional features that build on top of the base GLTF spec. +* The [`structures/`](structures/) folder contains glTF structures, the + small pieces that make up a glTF file, represented as C++ classes. +* The [`extensions/`](extensions/) folder contains glTF extensions, which + are optional features that build on top of the base glTF spec. * [`GLTFState`](gltf_state.h) holds collections of structures and extensions. * [`GLTFDocument`](gltf_document.h) operates on GLTFState and its elements. * The [`editor/`](editor/) folder uses GLTFDocument to import and export 3D models. diff --git a/modules/gltf/doc_classes/GLTFAccessor.xml b/modules/gltf/doc_classes/GLTFAccessor.xml index dd059e6b79..bc142797a3 100644 --- a/modules/gltf/doc_classes/GLTFAccessor.xml +++ b/modules/gltf/doc_classes/GLTFAccessor.xml @@ -1,11 +1,11 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFAccessor" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF accessor. + Represents a glTF accessor. </brief_description> <description> - GLTFAccessor is a data structure representing GLTF a [code]accessor[/code] that would be found in the [code]"accessors"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer. An accessor is a typed interpretation of the data in a buffer view. - Most custom data stored in GLTF does not need accessors, only buffer views (see [GLTFBufferView]). Accessors are for more advanced use cases such as interleaved mesh data encoded for the GPU. + GLTFAccessor is a data structure representing a glTF [code]accessor[/code] that would be found in the [code]"accessors"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer. An accessor is a typed interpretation of the data in a buffer view. + Most custom data stored in glTF does not need accessors, only buffer views (see [GLTFBufferView]). Accessors are for more advanced use cases such as interleaved mesh data encoded for the GPU. </description> <tutorials> <link title="Buffers, BufferViews, and Accessors in Khronos glTF specification">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_005_BuffersBufferViewsAccessors.md</link> @@ -13,7 +13,7 @@ </tutorials> <members> <member name="accessor_type" type="int" setter="set_accessor_type" getter="get_accessor_type" enum="GLTFAccessor.GLTFAccessorType" default="0"> - The GLTF accessor type as an enum. Possible values are 0 for "SCALAR", 1 for "VEC2", 2 for "VEC3", 3 for "VEC4", 4 for "MAT2", 5 for "MAT3", and 6 for "MAT4". + The glTF accessor type as an enum. Possible values are 0 for "SCALAR", 1 for "VEC2", 2 for "VEC3", 3 for "VEC4", 4 for "MAT2", 5 for "MAT3", and 6 for "MAT4". </member> <member name="buffer_view" type="int" setter="set_buffer_view" getter="get_buffer_view" default="-1"> The index of the buffer view this accessor is referencing. If [code]-1[/code], this accessor is not referencing any buffer view. @@ -22,7 +22,7 @@ The offset relative to the start of the buffer view in bytes. </member> <member name="component_type" type="int" setter="set_component_type" getter="get_component_type" default="0"> - The GLTF component type as an enum. Possible values are 5120 for "BYTE", 5121 for "UNSIGNED_BYTE", 5122 for "SHORT", 5123 for "UNSIGNED_SHORT", 5125 for "UNSIGNED_INT", and 5126 for "FLOAT". A value of 5125 or "UNSIGNED_INT" must not be used for any accessor that is not referenced by mesh.primitive.indices. + The glTF component type as an enum. Possible values are 5120 for "BYTE", 5121 for "UNSIGNED_BYTE", 5122 for "SHORT", 5123 for "UNSIGNED_SHORT", 5125 for "UNSIGNED_INT", and 5126 for "FLOAT". A value of 5125 or "UNSIGNED_INT" must not be used for any accessor that is not referenced by mesh.primitive.indices. </member> <member name="count" type="int" setter="set_count" getter="get_count" default="0"> The number of elements referenced by this accessor. @@ -55,7 +55,7 @@ The offset relative to the start of the bufferView in bytes. </member> <member name="type" type="int" setter="set_type" getter="get_type" deprecated="Use [member accessor_type] instead."> - The GLTF accessor type as an enum. Use [member accessor_type] instead. + The glTF accessor type as an enum. Use [member accessor_type] instead. </member> </members> <constants> diff --git a/modules/gltf/doc_classes/GLTFAnimation.xml b/modules/gltf/doc_classes/GLTFAnimation.xml index 2c70f4461f..d269145bbd 100644 --- a/modules/gltf/doc_classes/GLTFAnimation.xml +++ b/modules/gltf/doc_classes/GLTFAnimation.xml @@ -13,7 +13,7 @@ <param index="0" name="extension_name" type="StringName" /> <description> Gets additional arbitrary data in this [GLTFAnimation] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null. </description> </method> <method name="set_additional_data"> @@ -22,7 +22,7 @@ <param index="1" name="additional_data" type="Variant" /> <description> Sets additional arbitrary data in this [GLTFAnimation] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want. </description> </method> </methods> diff --git a/modules/gltf/doc_classes/GLTFBufferView.xml b/modules/gltf/doc_classes/GLTFBufferView.xml index e191935fc9..b7f499ad72 100644 --- a/modules/gltf/doc_classes/GLTFBufferView.xml +++ b/modules/gltf/doc_classes/GLTFBufferView.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFBufferView" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF buffer view. + Represents a glTF buffer view. </brief_description> <description> - GLTFBufferView is a data structure representing GLTF a [code]bufferView[/code] that would be found in the [code]"bufferViews"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer that can be used to identify and extract data from the buffer. + GLTFBufferView is a data structure representing a glTF [code]bufferView[/code] that would be found in the [code]"bufferViews"[/code] array. A buffer is a blob of binary data. A buffer view is a slice of a buffer that can be used to identify and extract data from the buffer. Most custom uses of buffers only need to use the [member buffer], [member byte_length], and [member byte_offset]. The [member byte_stride] and [member indices] properties are for more advanced use cases such as interleaved mesh data encoded for the GPU. </description> <tutorials> diff --git a/modules/gltf/doc_classes/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml index b334bf2867..9fce21659c 100644 --- a/modules/gltf/doc_classes/GLTFCamera.xml +++ b/modules/gltf/doc_classes/GLTFCamera.xml @@ -1,15 +1,15 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFCamera" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF camera. + Represents a glTF camera. </brief_description> <description> - Represents a camera as defined by the base GLTF spec. + Represents a camera as defined by the base glTF spec. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="GLTF camera detailed specification">https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-camera</link> - <link title="GLTF camera spec and example file">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md</link> + <link title="glTF camera detailed specification">https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-camera</link> + <link title="glTF camera spec and example file">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md</link> </tutorials> <methods> <method name="from_dictionary" qualifiers="static"> @@ -41,19 +41,19 @@ </methods> <members> <member name="depth_far" type="float" setter="set_depth_far" getter="get_depth_far" default="4000.0"> - The distance to the far culling boundary for this camera relative to its local Z axis, in meters. This maps to GLTF's [code]zfar[/code] property. + The distance to the far culling boundary for this camera relative to its local Z axis, in meters. This maps to glTF's [code]zfar[/code] property. </member> <member name="depth_near" type="float" setter="set_depth_near" getter="get_depth_near" default="0.05"> - The distance to the near culling boundary for this camera relative to its local Z axis, in meters. This maps to GLTF's [code]znear[/code] property. + The distance to the near culling boundary for this camera relative to its local Z axis, in meters. This maps to glTF's [code]znear[/code] property. </member> <member name="fov" type="float" setter="set_fov" getter="get_fov" default="1.309"> - The FOV of the camera. This class and GLTF define the camera FOV in radians, while Godot uses degrees. This maps to GLTF's [code]yfov[/code] property. This value is only used for perspective cameras, when [member perspective] is true. + The FOV of the camera. This class and glTF define the camera FOV in radians, while Godot uses degrees. This maps to glTF's [code]yfov[/code] property. This value is only used for perspective cameras, when [member perspective] is true. </member> <member name="perspective" type="bool" setter="set_perspective" getter="get_perspective" default="true"> - Whether or not the camera is in perspective mode. If false, the camera is in orthographic/orthogonal mode. This maps to GLTF's camera [code]type[/code] property. See [member Camera3D.projection] and the GLTF spec for more information. + Whether or not the camera is in perspective mode. If false, the camera is in orthographic/orthogonal mode. This maps to glTF's camera [code]type[/code] property. See [member Camera3D.projection] and the glTF spec for more information. </member> <member name="size_mag" type="float" setter="set_size_mag" getter="get_size_mag" default="0.5"> - The size of the camera. This class and GLTF define the camera size magnitude as a radius in meters, while Godot defines it as a diameter in meters. This maps to GLTF's [code]ymag[/code] property. This value is only used for orthographic/orthogonal cameras, when [member perspective] is false. + The size of the camera. This class and glTF define the camera size magnitude as a radius in meters, while Godot defines it as a diameter in meters. This maps to glTF's [code]ymag[/code] property. This value is only used for orthographic/orthogonal cameras, when [member perspective] is false. </member> </members> </class> diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index 1b51def28e..ebeed015e9 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -5,7 +5,7 @@ </brief_description> <description> GLTFDocument supports reading data from a glTF file, buffer, or Godot scene. This data can then be written to the filesystem, buffer, or used to create a Godot scene. - All of the data in a GLTF scene is stored in the [GLTFState] class. GLTFDocument processes state objects, but does not contain any scene data itself. GLTFDocument has member variables to store export configuration settings such as the image format, but is otherwise stateless. Multiple scenes can be processed with the same settings using the same GLTFDocument object and different [GLTFState] objects. + All of the data in a glTF scene is stored in the [GLTFState] class. GLTFDocument processes state objects, but does not contain any scene data itself. GLTFDocument has member variables to store export configuration settings such as the image format, but is otherwise stateless. Multiple scenes can be processed with the same settings using the same GLTFDocument object and different [GLTFState] objects. GLTFDocument can be extended with arbitrary functionality by extending the [GLTFDocumentExtension] class and registering it with GLTFDocument via [method register_gltf_document_extension]. This allows for custom data to be imported and exported. </description> <tutorials> @@ -21,7 +21,7 @@ <param index="2" name="state" type="GLTFState" /> <param index="3" name="flags" type="int" default="0" /> <description> - Takes a [PackedByteArray] defining a GLTF and imports the data to the given [GLTFState] object through the [param state] parameter. + Takes a [PackedByteArray] defining a glTF and imports the data to the given [GLTFState] object through the [param state] parameter. [b]Note:[/b] The [param base_path] tells [method append_from_buffer] where to find dependencies and can be empty. </description> </method> @@ -32,7 +32,7 @@ <param index="2" name="flags" type="int" default="0" /> <param index="3" name="base_path" type="String" default="""" /> <description> - Takes a path to a GLTF file and imports the data at that file path to the given [GLTFState] object through the [param state] parameter. + Takes a path to a glTF file and imports the data at that file path to the given [GLTFState] object through the [param state] parameter. [b]Note:[/b] The [param base_path] tells [method append_from_file] where to find dependencies and can be empty. </description> </method> @@ -49,7 +49,7 @@ <return type="PackedByteArray" /> <param index="0" name="state" type="GLTFState" /> <description> - Takes a [GLTFState] object through the [param state] parameter and returns a GLTF [PackedByteArray]. + Takes a [GLTFState] object through the [param state] parameter and returns a glTF [PackedByteArray]. </description> </method> <method name="generate_scene"> @@ -91,7 +91,7 @@ </methods> <members> <member name="image_format" type="String" setter="set_image_format" getter="get_image_format" default=""PNG""> - The user-friendly name of the export image format. This is used when exporting the GLTF file, including writing to a file and writing to a byte array. + The user-friendly name of the export image format. This is used when exporting the glTF file, including writing to a file and writing to a byte array. By default, Godot allows the following options: "None", "PNG", "JPEG", "Lossless WebP", and "Lossy WebP". Support for more image formats can be added in [GLTFDocumentExtension] classes. </member> <member name="lossy_quality" type="float" setter="set_lossy_quality" getter="get_lossy_quality" default="0.75"> diff --git a/modules/gltf/doc_classes/GLTFDocumentExtension.xml b/modules/gltf/doc_classes/GLTFDocumentExtension.xml index 0eabcb5022..5c548c472f 100644 --- a/modules/gltf/doc_classes/GLTFDocumentExtension.xml +++ b/modules/gltf/doc_classes/GLTFDocumentExtension.xml @@ -4,7 +4,7 @@ [GLTFDocument] extension class. </brief_description> <description> - Extends the functionality of the [GLTFDocument] class by allowing you to run arbitrary code at various stages of GLTF import or export. + Extends the functionality of the [GLTFDocument] class by allowing you to run arbitrary code at various stages of glTF import or export. To use, make a new class extending GLTFDocumentExtension, override any methods you need, make an instance of your class, and register it using [method GLTFDocument.register_gltf_document_extension]. [b]Note:[/b] Like GLTFDocument itself, all GLTFDocumentExtension classes must be stateless in order to function properly. If you need to store data, use the [code]set_additional_data[/code] and [code]get_additional_data[/code] methods in [GLTFState] or [GLTFNode]. </description> @@ -30,7 +30,7 @@ <param index="3" name="node" type="Node" /> <description> Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _export_post]. If this [GLTFDocumentExtension] is used for exporting images, this runs after [method _serialize_texture_json]. - This method can be used to modify the final JSON of each node. Data should be primarily stored in [param gltf_node] prior to serializing the JSON, but the original Godot [param node] is also provided if available. The node may be null if not available, such as when exporting GLTF data not generated from a Godot scene. + This method can be used to modify the final JSON of each node. Data should be primarily stored in [param gltf_node] prior to serializing the JSON, but the original Godot [param node] is also provided if available. The node may be null if not available, such as when exporting glTF data not generated from a Godot scene. </description> </method> <method name="_export_post" qualifiers="virtual"> @@ -38,7 +38,7 @@ <param index="0" name="state" type="GLTFState" /> <description> Part of the export process. This method is run last, after all other parts of the export process. - This method can be used to modify the final JSON of the generated GLTF file. + This method can be used to modify the final JSON of the generated glTF file. </description> </method> <method name="_export_preflight" qualifiers="virtual"> @@ -47,7 +47,7 @@ <param index="1" name="root" type="Node" /> <description> Part of the export process. This method is run first, before all other parts of the export process. - The return value is used to determine if this [GLTFDocumentExtension] instance should be used for exporting a given GLTF file. If [constant OK], the export will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. + The return value is used to determine if this [GLTFDocumentExtension] instance should be used for exporting a given glTF file. If [constant OK], the export will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. </description> </method> <method name="_export_preserialize" qualifiers="virtual"> @@ -86,7 +86,7 @@ <return type="PackedStringArray" /> <description> Part of the import process. This method is run after [method _import_preflight] and before [method _parse_node_extensions]. - Returns an array of the GLTF extensions supported by this GLTFDocumentExtension class. This is used to validate if a GLTF file with required extensions can be loaded. + Returns an array of the glTF extensions supported by this GLTFDocumentExtension class. This is used to validate if a glTF file with required extensions can be loaded. </description> </method> <method name="_import_node" qualifiers="virtual"> @@ -123,7 +123,7 @@ <param index="1" name="extensions" type="PackedStringArray" /> <description> Part of the import process. This method is run first, before all other parts of the import process. - The return value is used to determine if this [GLTFDocumentExtension] instance should be used for importing a given GLTF file. If [constant OK], the import will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. + The return value is used to determine if this [GLTFDocumentExtension] instance should be used for importing a given glTF file. If [constant OK], the import will use this [GLTFDocumentExtension] instance. If not overridden, [constant OK] is returned. </description> </method> <method name="_parse_image_data" qualifiers="virtual"> @@ -134,7 +134,7 @@ <param index="3" name="ret_image" type="Image" /> <description> Part of the import process. This method is run after [method _parse_node_extensions] and before [method _parse_texture_json]. - Runs when parsing image data from a GLTF file. The data could be sourced from a separate file, a URI, or a buffer, and then is passed as a byte array. + Runs when parsing image data from a glTF file. The data could be sourced from a separate file, a URI, or a buffer, and then is passed as a byte array. </description> </method> <method name="_parse_node_extensions" qualifiers="virtual"> @@ -154,7 +154,7 @@ <param index="2" name="ret_gltf_texture" type="GLTFTexture" /> <description> Part of the import process. This method is run after [method _parse_image_data] and before [method _generate_scene_node]. - Runs when parsing the texture JSON from the GLTF textures array. This can be used to set the source image index to use as the texture. + Runs when parsing the texture JSON from the glTF textures array. This can be used to set the source image index to use as the texture. </description> </method> <method name="_save_image_at_path" qualifiers="virtual"> @@ -166,7 +166,7 @@ <param index="4" name="lossy_quality" type="float" /> <description> Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _serialize_texture_json]. - This method is run when saving images separately from the GLTF file. When images are embedded, [method _serialize_image_to_bytes] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter. + This method is run when saving images separately from the glTF file. When images are embedded, [method _serialize_image_to_bytes] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter. </description> </method> <method name="_serialize_image_to_bytes" qualifiers="virtual"> @@ -178,7 +178,7 @@ <param index="4" name="lossy_quality" type="float" /> <description> Part of the export process. This method is run after [method _get_saveable_image_formats] and before [method _serialize_texture_json]. - This method is run when embedding images in the GLTF file. When images are saved separately, [method _save_image_at_path] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter. + This method is run when embedding images in the glTF file. When images are saved separately, [method _save_image_at_path] runs instead. Note that these methods only run when this [GLTFDocumentExtension] is selected as the image exporter. This method must set the image MIME type in the [param image_dict] with the [code]"mimeType"[/code] key. For example, for a PNG image, it would be set to [code]"image/png"[/code]. The return value must be a [PackedByteArray] containing the image data. </description> </method> diff --git a/modules/gltf/doc_classes/GLTFLight.xml b/modules/gltf/doc_classes/GLTFLight.xml index 87ea159e7c..e07d24a144 100644 --- a/modules/gltf/doc_classes/GLTFLight.xml +++ b/modules/gltf/doc_classes/GLTFLight.xml @@ -1,14 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFLight" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF light. + Represents a glTF light. </brief_description> <description> - Represents a light as defined by the [code]KHR_lights_punctual[/code] GLTF extension. + Represents a light as defined by the [code]KHR_lights_punctual[/code] glTF extension. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="KHR_lights_punctual GLTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual</link> + <link title="KHR_lights_punctual glTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual</link> </tutorials> <methods> <method name="from_dictionary" qualifiers="static"> @@ -70,7 +70,7 @@ At this angle, the light drops off to zero brightness. Between the inner and outer cone angles, there is a transition from full brightness to zero brightness. If this angle is a half turn, then the spotlight emits in all directions. When creating a Godot [SpotLight3D], the outer cone angle is used as the angle of the spotlight. </member> <member name="range" type="float" setter="set_range" getter="get_range" default="inf"> - The range of the light, beyond which the light has no effect. GLTF lights with no range defined behave like physical lights (which have infinite range). When creating a Godot light, the range is clamped to 4096. + The range of the light, beyond which the light has no effect. glTF lights with no range defined behave like physical lights (which have infinite range). When creating a Godot light, the range is clamped to 4096. </member> </members> </class> diff --git a/modules/gltf/doc_classes/GLTFMesh.xml b/modules/gltf/doc_classes/GLTFMesh.xml index b4c3db7618..da73c20c1d 100644 --- a/modules/gltf/doc_classes/GLTFMesh.xml +++ b/modules/gltf/doc_classes/GLTFMesh.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFMesh" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - GLTFMesh represents a GLTF mesh. + GLTFMesh represents a glTF mesh. </brief_description> <description> - GLTFMesh handles 3D mesh data imported from GLTF files. It includes properties for blend channels, blend weights, instance materials, and the mesh itself. + GLTFMesh handles 3D mesh data imported from glTF files. It includes properties for blend channels, blend weights, instance materials, and the mesh itself. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> @@ -15,7 +15,7 @@ <param index="0" name="extension_name" type="StringName" /> <description> Gets additional arbitrary data in this [GLTFMesh] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null. </description> </method> <method name="set_additional_data"> @@ -24,7 +24,7 @@ <param index="1" name="additional_data" type="Variant" /> <description> Sets additional arbitrary data in this [GLTFMesh] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want. </description> </method> </methods> diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml index 4a7570e4bc..2786c25e9a 100644 --- a/modules/gltf/doc_classes/GLTFNode.xml +++ b/modules/gltf/doc_classes/GLTFNode.xml @@ -1,15 +1,15 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFNode" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - GLTF node class. + glTF node class. </brief_description> <description> - Represents a GLTF node. GLTF nodes may have names, transforms, children (other GLTF nodes), and more specialized properties (represented by their own classes). - GLTF nodes generally exist inside of [GLTFState] which represents all data of a GLTF file. Most of GLTFNode's properties are indices of other data in the GLTF file. You can extend a GLTF node with additional properties by using [method get_additional_data] and [method set_additional_data]. + Represents a glTF node. glTF nodes may have names, transforms, children (other glTF nodes), and more specialized properties (represented by their own classes). + glTF nodes generally exist inside of [GLTFState] which represents all data of a glTF file. Most of GLTFNode's properties are indices of other data in the glTF file. You can extend a glTF node with additional properties by using [method get_additional_data] and [method set_additional_data]. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="GLTF scene and node spec">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md"</link> + <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="get_additional_data"> @@ -17,7 +17,7 @@ <param index="0" name="extension_name" type="StringName" /> <description> Gets additional arbitrary data in this [GLTFNode] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null. </description> </method> <method name="set_additional_data"> @@ -26,25 +26,25 @@ <param index="1" name="additional_data" type="Variant" /> <description> Sets additional arbitrary data in this [GLTFNode] instance. This can be used to keep per-node state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want. </description> </method> </methods> <members> <member name="camera" type="int" setter="set_camera" getter="get_camera" default="-1"> - If this GLTF node is a camera, the index of the [GLTFCamera] in the [GLTFState] that describes the camera's properties. If -1, this node is not a camera. + If this glTF node is a camera, the index of the [GLTFCamera] in the [GLTFState] that describes the camera's properties. If -1, this node is not a camera. </member> <member name="children" type="PackedInt32Array" setter="set_children" getter="get_children" default="PackedInt32Array()"> - The indices of the child nodes in the [GLTFState]. If this GLTF node has no children, this will be an empty array. + The indices of the child nodes in the [GLTFState]. If this glTF node has no children, this will be an empty array. </member> <member name="height" type="int" setter="set_height" getter="get_height" default="-1"> How deep into the node hierarchy this node is. A root node will have a height of 0, its children will have a height of 1, and so on. If -1, the height has not been calculated. </member> <member name="light" type="int" setter="set_light" getter="get_light" default="-1"> - If this GLTF node is a light, the index of the [GLTFLight] in the [GLTFState] that describes the light's properties. If -1, this node is not a light. + If this glTF node is a light, the index of the [GLTFLight] in the [GLTFState] that describes the light's properties. If -1, this node is not a light. </member> <member name="mesh" type="int" setter="set_mesh" getter="get_mesh" default="-1"> - If this GLTF node is a mesh, the index of the [GLTFMesh] in the [GLTFState] that describes the mesh's properties. If -1, this node is not a mesh. + If this glTF node is a mesh, the index of the [GLTFMesh] in the [GLTFState] that describes the mesh's properties. If -1, this node is not a mesh. </member> <member name="original_name" type="String" setter="set_original_name" getter="get_original_name" default=""""> The original name of the node. @@ -53,22 +53,22 @@ The index of the parent node in the [GLTFState]. If -1, this node is a root node. </member> <member name="position" type="Vector3" setter="set_position" getter="get_position" default="Vector3(0, 0, 0)"> - The position of the GLTF node relative to its parent. + The position of the glTF node relative to its parent. </member> <member name="rotation" type="Quaternion" setter="set_rotation" getter="get_rotation" default="Quaternion(0, 0, 0, 1)"> - The rotation of the GLTF node relative to its parent. + The rotation of the glTF node relative to its parent. </member> <member name="scale" type="Vector3" setter="set_scale" getter="get_scale" default="Vector3(1, 1, 1)"> - The scale of the GLTF node relative to its parent. + The scale of the glTF node relative to its parent. </member> <member name="skeleton" type="int" setter="set_skeleton" getter="get_skeleton" default="-1"> - If this GLTF node has a skeleton, the index of the [GLTFSkeleton] in the [GLTFState] that describes the skeleton's properties. If -1, this node does not have a skeleton. + If this glTF node has a skeleton, the index of the [GLTFSkeleton] in the [GLTFState] that describes the skeleton's properties. If -1, this node does not have a skeleton. </member> <member name="skin" type="int" setter="set_skin" getter="get_skin" default="-1"> - If this GLTF node has a skin, the index of the [GLTFSkin] in the [GLTFState] that describes the skin's properties. If -1, this node does not have a skin. + If this glTF node has a skin, the index of the [GLTFSkin] in the [GLTFState] that describes the skin's properties. If -1, this node does not have a skin. </member> <member name="xform" type="Transform3D" setter="set_xform" getter="get_xform" default="Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)"> - The transform of the GLTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred. + The transform of the glTF node relative to its parent. This property is usually unused since the position, rotation, and scale properties are preferred. </member> </members> </class> diff --git a/modules/gltf/doc_classes/GLTFPhysicsBody.xml b/modules/gltf/doc_classes/GLTFPhysicsBody.xml index cd701e2f2f..1a76b190ba 100644 --- a/modules/gltf/doc_classes/GLTFPhysicsBody.xml +++ b/modules/gltf/doc_classes/GLTFPhysicsBody.xml @@ -1,21 +1,21 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFPhysicsBody" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF physics body. + Represents a glTF physics body. </brief_description> <description> - Represents a physics body as an intermediary between the [code]OMI_physics_body[/code] GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future. + Represents a physics body as an intermediary between the [code]OMI_physics_body[/code] glTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different glTF physics extensions in the future. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="OMI_physics_body GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body</link> + <link title="OMI_physics_body glTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_body</link> </tutorials> <methods> <method name="from_dictionary" qualifiers="static"> <return type="GLTFPhysicsBody" /> <param index="0" name="dictionary" type="Dictionary" /> <description> - Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary] in the [code]OMI_physics_body[/code] GLTF extension format. + Creates a new GLTFPhysicsBody instance by parsing the given [Dictionary] in the [code]OMI_physics_body[/code] glTF extension format. </description> </method> <method name="from_node" qualifiers="static"> @@ -28,7 +28,7 @@ <method name="to_dictionary" qualifiers="const"> <return type="Dictionary" /> <description> - Serializes this GLTFPhysicsBody instance into a [Dictionary]. It will be in the format expected by the [code]OMI_physics_body[/code] GLTF extension. + Serializes this GLTFPhysicsBody instance into a [Dictionary]. It will be in the format expected by the [code]OMI_physics_body[/code] glTF extension. </description> </method> <method name="to_node" qualifiers="const"> diff --git a/modules/gltf/doc_classes/GLTFPhysicsShape.xml b/modules/gltf/doc_classes/GLTFPhysicsShape.xml index a4aaf9415c..53872a942f 100644 --- a/modules/gltf/doc_classes/GLTFPhysicsShape.xml +++ b/modules/gltf/doc_classes/GLTFPhysicsShape.xml @@ -1,15 +1,15 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFPhysicsShape" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF physics shape. + Represents a glTF physics shape. </brief_description> <description> - Represents a physics shape as defined by the [code]OMI_physics_shape[/code] or [code]OMI_collider[/code] GLTF extensions. This class is an intermediary between the GLTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different GLTF physics extensions in the future. + Represents a physics shape as defined by the [code]OMI_physics_shape[/code] or [code]OMI_collider[/code] glTF extensions. This class is an intermediary between the glTF data and Godot's nodes, and it's abstracted in a way that allows adding support for different glTF physics extensions in the future. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="OMI_physics_shape GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape</link> - <link title="OMI_collider GLTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/Archived/OMI_collider</link> + <link title="OMI_physics_shape glTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/OMI_physics_shape</link> + <link title="OMI_collider glTF extension">https://github.com/omigroup/gltf-extensions/tree/main/extensions/2.0/Archived/OMI_collider</link> </tutorials> <methods> <method name="from_dictionary" qualifiers="static"> @@ -66,7 +66,7 @@ This is the only variable not used in the [method to_node] method, it's intended to be used alongside when deciding where to add the generated node as a child. </member> <member name="mesh_index" type="int" setter="set_mesh_index" getter="get_mesh_index" default="-1"> - The index of the shape's mesh in the GLTF file. This is only used when the shape type is "hull" (convex hull) or "trimesh" (concave trimesh). + The index of the shape's mesh in the glTF file. This is only used when the shape type is "hull" (convex hull) or "trimesh" (concave trimesh). </member> <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5"> The radius of the shape, in meters. This is only used when the shape type is "capsule", "cylinder", or "sphere". This value should not be negative. diff --git a/modules/gltf/doc_classes/GLTFSkeleton.xml b/modules/gltf/doc_classes/GLTFSkeleton.xml index ac03a6ee9e..2dd3a37413 100644 --- a/modules/gltf/doc_classes/GLTFSkeleton.xml +++ b/modules/gltf/doc_classes/GLTFSkeleton.xml @@ -22,7 +22,7 @@ <method name="get_godot_bone_node"> <return type="Dictionary" /> <description> - Returns a [Dictionary] that maps skeleton bone indices to the indices of GLTF nodes. This property is unused during import, and only set during export. In a GLTF file, a bone is a node, so Godot converts skeleton bones to GLTF nodes. + Returns a [Dictionary] that maps skeleton bone indices to the indices of glTF nodes. This property is unused during import, and only set during export. In a glTF file, a bone is a node, so Godot converts skeleton bones to glTF nodes. </description> </method> <method name="get_godot_skeleton"> @@ -39,7 +39,7 @@ <return type="void" /> <param index="0" name="godot_bone_node" type="Dictionary" /> <description> - Sets a [Dictionary] that maps skeleton bone indices to the indices of GLTF nodes. This property is unused during import, and only set during export. In a GLTF file, a bone is a node, so Godot converts skeleton bones to GLTF nodes. + Sets a [Dictionary] that maps skeleton bone indices to the indices of glTF nodes. This property is unused during import, and only set during export. In a glTF file, a bone is a node, so Godot converts skeleton bones to glTF nodes. </description> </method> <method name="set_unique_names"> diff --git a/modules/gltf/doc_classes/GLTFSpecGloss.xml b/modules/gltf/doc_classes/GLTFSpecGloss.xml index 722fa5e9ae..11151f53d0 100644 --- a/modules/gltf/doc_classes/GLTFSpecGloss.xml +++ b/modules/gltf/doc_classes/GLTFSpecGloss.xml @@ -1,14 +1,14 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFSpecGloss" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Archived GLTF extension for specular/glossy materials. + Archived glTF extension for specular/glossy materials. </brief_description> <description> - KHR_materials_pbrSpecularGlossiness is an archived GLTF extension. This means that it is deprecated and not recommended for new files. However, it is still supported for loading old files. + KHR_materials_pbrSpecularGlossiness is an archived glTF extension. This means that it is deprecated and not recommended for new files. However, it is still supported for loading old files. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="KHR_materials_pbrSpecularGlossiness GLTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness</link> + <link title="KHR_materials_pbrSpecularGlossiness glTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness</link> </tutorials> <members> <member name="diffuse_factor" type="Color" setter="set_diffuse_factor" getter="get_diffuse_factor" default="Color(1, 1, 1, 1)"> diff --git a/modules/gltf/doc_classes/GLTFState.xml b/modules/gltf/doc_classes/GLTFState.xml index 21a0527813..c049acf557 100644 --- a/modules/gltf/doc_classes/GLTFState.xml +++ b/modules/gltf/doc_classes/GLTFState.xml @@ -1,15 +1,15 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFState" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents all data of a GLTF file. + Represents all data of a glTF file. </brief_description> <description> - Contains all nodes and resources of a GLTF file. This is used by [GLTFDocument] as data storage, which allows [GLTFDocument] and all [GLTFDocumentExtension] classes to remain stateless. - GLTFState can be populated by [GLTFDocument] reading a file or by converting a Godot scene. Then the data can either be used to create a Godot scene or save to a GLTF file. The code that converts to/from a Godot scene can be intercepted at arbitrary points by [GLTFDocumentExtension] classes. This allows for custom data to be stored in the GLTF file or for custom data to be converted to/from Godot nodes. + Contains all nodes and resources of a glTF file. This is used by [GLTFDocument] as data storage, which allows [GLTFDocument] and all [GLTFDocumentExtension] classes to remain stateless. + GLTFState can be populated by [GLTFDocument] reading a file or by converting a Godot scene. Then the data can either be used to create a Godot scene or save to a glTF file. The code that converts to/from a Godot scene can be intercepted at arbitrary points by [GLTFDocumentExtension] classes. This allows for custom data to be stored in the glTF file or for custom data to be converted to/from Godot nodes. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> - <link title="GLTF asset header schema">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/schema/asset.schema.json"</link> + <link title="glTF asset header schema">https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/schema/asset.schema.json"</link> </tutorials> <methods> <method name="add_used_extension"> @@ -17,7 +17,7 @@ <param index="0" name="extension_name" type="String" /> <param index="1" name="required" type="bool" /> <description> - Appends an extension to the list of extensions used by this GLTF file during serialization. If [param required] is true, the extension will also be added to the list of required extensions. Do not run this in [method GLTFDocumentExtension._export_post], as that stage is too late to add extensions. The final list is sorted alphabetically. + Appends an extension to the list of extensions used by this glTF file during serialization. If [param required] is true, the extension will also be added to the list of required extensions. Do not run this in [method GLTFDocumentExtension._export_post], as that stage is too late to add extensions. The final list is sorted alphabetically. </description> </method> <method name="append_data_to_buffers"> @@ -38,27 +38,27 @@ <param index="0" name="extension_name" type="StringName" /> <description> Gets additional arbitrary data in this [GLTFState] instance. This can be used to keep per-file state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the return value can be anything you set. If nothing was set, the return value is null. + The argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the return value can be anything you set. If nothing was set, the return value is null. </description> </method> <method name="get_animation_player"> <return type="AnimationPlayer" /> <param index="0" name="idx" type="int" /> <description> - Returns the [AnimationPlayer] node with the given index. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to GLTF animations. + Returns the [AnimationPlayer] node with the given index. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to glTF animations. </description> </method> <method name="get_animation_players_count"> <return type="int" /> <param index="0" name="idx" type="int" /> <description> - Returns the number of [AnimationPlayer] nodes in this [GLTFState]. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to GLTF animations. + Returns the number of [AnimationPlayer] nodes in this [GLTFState]. These nodes are only used during the export process when converting Godot [AnimationPlayer] nodes to glTF animations. </description> </method> <method name="get_animations"> <return type="GLTFAnimation[]" /> <description> - Returns an array of all [GLTFAnimation]s in the GLTF file. When importing, these will be generated as animations in an [AnimationPlayer] node. When exporting, these will be generated from Godot [AnimationPlayer] nodes. + Returns an array of all [GLTFAnimation]s in the glTF file. When importing, these will be generated as animations in an [AnimationPlayer] node. When exporting, these will be generated from Godot [AnimationPlayer] nodes. </description> </method> <method name="get_buffer_views"> @@ -69,7 +69,7 @@ <method name="get_cameras"> <return type="GLTFCamera[]" /> <description> - Returns an array of all [GLTFCamera]s in the GLTF file. These are the cameras that the [member GLTFNode.camera] index refers to. + Returns an array of all [GLTFCamera]s in the glTF file. These are the cameras that the [member GLTFNode.camera] index refers to. </description> </method> <method name="get_handle_binary_image"> @@ -80,13 +80,13 @@ <method name="get_images"> <return type="Texture2D[]" /> <description> - Gets the images of the GLTF file as an array of [Texture2D]s. These are the images that the [member GLTFTexture.src_image] index refers to. + Gets the images of the glTF file as an array of [Texture2D]s. These are the images that the [member GLTFTexture.src_image] index refers to. </description> </method> <method name="get_lights"> <return type="GLTFLight[]" /> <description> - Returns an array of all [GLTFLight]s in the GLTF file. These are the lights that the [member GLTFNode.light] index refers to. + Returns an array of all [GLTFLight]s in the glTF file. These are the lights that the [member GLTFNode.light] index refers to. </description> </method> <method name="get_materials"> @@ -97,7 +97,7 @@ <method name="get_meshes"> <return type="GLTFMesh[]" /> <description> - Returns an array of all [GLTFMesh]es in the GLTF file. These are the meshes that the [member GLTFNode.mesh] index refers to. + Returns an array of all [GLTFMesh]es in the glTF file. These are the meshes that the [member GLTFNode.mesh] index refers to. </description> </method> <method name="get_node_index"> @@ -111,7 +111,7 @@ <method name="get_nodes"> <return type="GLTFNode[]" /> <description> - Returns an array of all [GLTFNode]s in the GLTF file. These are the nodes that [member GLTFNode.children] and [member root_nodes] refer to. This includes nodes that may not be generated in the Godot scene, or nodes that may generate multiple Godot scene nodes. + Returns an array of all [GLTFNode]s in the glTF file. These are the nodes that [member GLTFNode.children] and [member root_nodes] refer to. This includes nodes that may not be generated in the Godot scene, or nodes that may generate multiple Godot scene nodes. </description> </method> <method name="get_scene_node"> @@ -125,19 +125,19 @@ <method name="get_skeletons"> <return type="GLTFSkeleton[]" /> <description> - Returns an array of all [GLTFSkeleton]s in the GLTF file. These are the skeletons that the [member GLTFNode.skeleton] index refers to. + Returns an array of all [GLTFSkeleton]s in the glTF file. These are the skeletons that the [member GLTFNode.skeleton] index refers to. </description> </method> <method name="get_skins"> <return type="GLTFSkin[]" /> <description> - Returns an array of all [GLTFSkin]s in the GLTF file. These are the skins that the [member GLTFNode.skin] index refers to. + Returns an array of all [GLTFSkin]s in the glTF file. These are the skins that the [member GLTFNode.skin] index refers to. </description> </method> <method name="get_texture_samplers"> <return type="GLTFTextureSampler[]" /> <description> - Retrieves the array of texture samplers that are used by the textures contained in the GLTF. + Retrieves the array of texture samplers that are used by the textures contained in the glTF. </description> </method> <method name="get_textures"> @@ -169,7 +169,7 @@ <param index="1" name="additional_data" type="Variant" /> <description> Sets additional arbitrary data in this [GLTFState] instance. This can be used to keep per-file state data in [GLTFDocumentExtension] classes, which is important because they are stateless. - The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the GLTF file), and the second argument can be anything you want. + The first argument should be the [GLTFDocumentExtension] name (does not have to match the extension name in the glTF file), and the second argument can be anything you want. </description> </method> <method name="set_animations"> @@ -250,7 +250,7 @@ <return type="void" /> <param index="0" name="texture_samplers" type="GLTFTextureSampler[]" /> <description> - Sets the array of texture samplers that are used by the textures contained in the GLTF. + Sets the array of texture samplers that are used by the textures contained in the glTF. </description> </method> <method name="set_textures"> @@ -279,17 +279,17 @@ The baking fps of the animation for either import or export. </member> <member name="base_path" type="String" setter="set_base_path" getter="get_base_path" default=""""> - The folder path associated with this GLTF data. This is used to find other files the GLTF file references, like images or binary buffers. This will be set during import when appending from a file, and will be set during export when writing to a file. + The folder path associated with this glTF data. This is used to find other files the glTF file references, like images or binary buffers. This will be set during import when appending from a file, and will be set during export when writing to a file. </member> <member name="buffers" type="PackedByteArray[]" setter="set_buffers" getter="get_buffers" default="[]"> </member> <member name="copyright" type="String" setter="set_copyright" getter="get_copyright" default=""""> - The copyright string in the asset header of the GLTF file. This is set during import if present and export if non-empty. See the GLTF asset header documentation for more information. + The copyright string in the asset header of the glTF file. This is set during import if present and export if non-empty. See the glTF asset header documentation for more information. </member> <member name="create_animations" type="bool" setter="set_create_animations" getter="get_create_animations" default="true"> </member> <member name="filename" type="String" setter="set_filename" getter="get_filename" default=""""> - The file name associated with this GLTF data. If it ends with [code].gltf[/code], this is text-based GLTF, otherwise this is binary GLB. This will be set during import when appending from a file, and will be set during export when writing to a file. If writing to a buffer, this will be an empty string. + The file name associated with this glTF data. If it ends with [code].gltf[/code], this is text-based glTF, otherwise this is binary GLB. This will be set during import when appending from a file, and will be set during export when writing to a file. If writing to a buffer, this will be an empty string. </member> <member name="glb_data" type="PackedByteArray" setter="set_glb_data" getter="get_glb_data" default="PackedByteArray()"> The binary buffer attached to a .glb file. @@ -305,10 +305,10 @@ <member name="minor_version" type="int" setter="set_minor_version" getter="get_minor_version" default="0"> </member> <member name="root_nodes" type="PackedInt32Array" setter="set_root_nodes" getter="get_root_nodes" default="PackedInt32Array()"> - The root nodes of the GLTF file. Typically, a GLTF file will only have one scene, and therefore one root node. However, a GLTF file may have multiple scenes and therefore multiple root nodes, which will be generated as siblings of each other and as children of the root node of the generated Godot scene. + The root nodes of the glTF file. Typically, a glTF file will only have one scene, and therefore one root node. However, a glTF file may have multiple scenes and therefore multiple root nodes, which will be generated as siblings of each other and as children of the root node of the generated Godot scene. </member> <member name="scene_name" type="String" setter="set_scene_name" getter="get_scene_name" default=""""> - The name of the scene. When importing, if not specified, this will be the file name. When exporting, if specified, the scene name will be saved to the GLTF file. + The name of the scene. When importing, if not specified, this will be the file name. When exporting, if specified, the scene name will be saved to the glTF file. </member> <member name="use_named_skin_binds" type="bool" setter="set_use_named_skin_binds" getter="get_use_named_skin_binds" default="false"> </member> diff --git a/modules/gltf/doc_classes/GLTFTexture.xml b/modules/gltf/doc_classes/GLTFTexture.xml index 9ad7c0f4c6..2a868a8ba3 100644 --- a/modules/gltf/doc_classes/GLTFTexture.xml +++ b/modules/gltf/doc_classes/GLTFTexture.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFTexture" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - GLTFTexture represents a texture in a GLTF file. + GLTFTexture represents a texture in a glTF file. </brief_description> <description> </description> diff --git a/modules/gltf/doc_classes/GLTFTextureSampler.xml b/modules/gltf/doc_classes/GLTFTextureSampler.xml index 2b5bad6724..d00ab463c2 100644 --- a/modules/gltf/doc_classes/GLTFTextureSampler.xml +++ b/modules/gltf/doc_classes/GLTFTextureSampler.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="GLTFTextureSampler" inherits="Resource" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> - Represents a GLTF texture sampler + Represents a glTF texture sampler </brief_description> <description> - Represents a texture sampler as defined by the base GLTF spec. Texture samplers in GLTF specify how to sample data from the texture's base image, when rendering the texture on an object. + Represents a texture sampler as defined by the base glTF spec. Texture samplers in glTF specify how to sample data from the texture's base image, when rendering the texture on an object. </description> <tutorials> <link title="Runtime file loading and saving">$DOCS_URL/tutorials/io/runtime_file_loading_and_saving.html</link> diff --git a/modules/gltf/editor/editor_import_blend_runner.cpp b/modules/gltf/editor/editor_import_blend_runner.cpp index 330310d92a..22c8adfe88 100644 --- a/modules/gltf/editor/editor_import_blend_runner.cpp +++ b/modules/gltf/editor/editor_import_blend_runner.cpp @@ -43,6 +43,7 @@ from xmlrpc.server import SimpleXMLRPCServer req = threading.Condition() res = threading.Condition() info = None +export_err = None def xmlrpc_server(): server = SimpleXMLRPCServer(('127.0.0.1', %d)) server.register_function(export_gltf) @@ -54,6 +55,10 @@ def export_gltf(opts): req.notify() with res: res.wait() + if export_err: + raise export_err + # Important to return a value to prevent the error 'cannot marshal None unless allow_none is enabled'. + return 'BLENDER_GODOT_EXPORT_SUCCESSFUL' if bpy.app.version < (3, 0, 0): print('Blender 3.0 or higher is required.', file=sys.stderr) threading.Thread(target=xmlrpc_server).start() @@ -64,12 +69,13 @@ while True: method, opts = info if method == 'export_gltf': try: + export_err = None bpy.ops.wm.open_mainfile(filepath=opts['path']) if opts['unpack_all']: bpy.ops.file.unpack_all(method='USE_LOCAL') bpy.ops.export_scene.gltf(**opts['gltf_options']) - except: - pass + except Exception as e: + export_err = e info = None with res: res.notify() @@ -184,7 +190,9 @@ Error EditorImportBlendRunner::do_import(const Dictionary &p_options) { EditorSettings::get_singleton()->set_manually("filesystem/import/blender/rpc_port", 0); rpc_port = 0; } - err = do_import_direct(p_options); + if (err != ERR_QUERY_FAILED) { + err = do_import_direct(p_options); + } } return err; } else { @@ -259,6 +267,7 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { // Wait for response. bool done = false; + PackedByteArray response; while (!done) { status = client->get_status(); switch (status) { @@ -268,7 +277,10 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { } case HTTPClient::STATUS_BODY: { client->poll(); - // Parse response here if needed. For now we can just ignore it. + response.append_array(client->read_response_body_chunk()); + break; + } + case HTTPClient::STATUS_CONNECTED: { done = true; break; } @@ -278,9 +290,56 @@ Error EditorImportBlendRunner::do_import_rpc(const Dictionary &p_options) { } } + String response_text = "No response from Blender."; + if (response.size() > 0) { + response_text = String::utf8((const char *)response.ptr(), response.size()); + } + + if (client->get_response_code() != HTTPClient::RESPONSE_OK) { + ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Error received from Blender - status code: %s, error: %s", client->get_response_code(), response_text)); + } else if (response_text.find("BLENDER_GODOT_EXPORT_SUCCESSFUL") < 0) { + // Previous versions of Godot used a Python script where the RPC function did not return + // a value, causing the error 'cannot marshal None unless allow_none is enabled'. + // If an older version of Godot is running and has started Blender with this script, + // we will receive the error, but there's a good chance that the import was successful. + // We are discarding this error to maintain backward compatibility and prevent situations + // where the user needs to close the older version of Godot or kill Blender. + if (response_text.find("cannot marshal None unless allow_none is enabled") < 0) { + String error_message; + if (_extract_error_message_xml(response, error_message)) { + ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Blender exportation failed: %s", error_message)); + } else { + ERR_FAIL_V_MSG(ERR_QUERY_FAILED, vformat("Blender exportation failed: %s", response_text)); + } + } + } + return OK; } +bool EditorImportBlendRunner::_extract_error_message_xml(const Vector<uint8_t> &p_response_data, String &r_error_message) { + // Based on RPC Xml spec from: https://xmlrpc.com/spec.md + Ref<XMLParser> parser = memnew(XMLParser); + Error err = parser->open_buffer(p_response_data); + if (err) { + return false; + } + + r_error_message = String(); + while (parser->read() == OK) { + if (parser->get_node_type() == XMLParser::NODE_TEXT) { + if (parser->get_node_data().size()) { + if (r_error_message.size()) { + r_error_message += " "; + } + r_error_message += parser->get_node_data().trim_suffix("\n"); + } + } + } + + return r_error_message.size(); +} + Error EditorImportBlendRunner::do_import_direct(const Dictionary &p_options) { // Export glTF directly. String python = vformat(PYTHON_SCRIPT_DIRECT, dict_to_python(p_options)); diff --git a/modules/gltf/editor/editor_import_blend_runner.h b/modules/gltf/editor/editor_import_blend_runner.h index 626f3c9eba..b3b49ebfb2 100644 --- a/modules/gltf/editor/editor_import_blend_runner.h +++ b/modules/gltf/editor/editor_import_blend_runner.h @@ -47,6 +47,7 @@ class EditorImportBlendRunner : public Node { void _resources_reimported(const PackedStringArray &p_files); void _kill_blender(); void _notification(int p_what); + bool _extract_error_message_xml(const Vector<uint8_t> &p_response_data, String &r_error_message); protected: int rpc_port = 0; diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 79a2184745..8e5a992bd4 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -57,7 +57,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) { if (!FileAccess::exists(p_path)) { if (r_err) { - *r_err = TTR("Path does not contain a Blender installation."); + *r_err = TTR("Path does not point to a valid executable."); } return false; } @@ -67,14 +67,14 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino Error err = OS::get_singleton()->execute(p_path, args, &pipe); if (err != OK) { if (r_err) { - *r_err = TTR("Can't execute Blender binary."); + *r_err = TTR("Couldn't run Blender executable."); } return false; } int bl = pipe.find("Blender "); if (bl == -1) { if (r_err) { - *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), p_path); + *r_err = vformat(TTR("Unexpected --version output from Blender executable at: %s."), p_path); } return false; } @@ -83,7 +83,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino int pp = pipe.find("."); if (pp == -1) { if (r_err) { - *r_err = TTR("Path supplied lacks a Blender binary."); + *r_err = vformat(TTR("Couldn't extract version information from Blender executable at: %s."), p_path); } return false; } @@ -91,7 +91,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino r_major = v.to_int(); if (r_major < 3) { if (r_err) { - *r_err = TTR("This Blender installation is too old for this importer (not 3.0+)."); + *r_err = vformat(TTR("Found Blender version %d.x, which is too old for this importer (3.0+ is required)."), r_major); } return false; } @@ -115,8 +115,15 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ List<String> *r_missing_deps, Error *r_err) { String blender_path = EDITOR_GET("filesystem/import/blender/blender_path"); - if (blender_major_version == -1 || blender_minor_version == -1) { - _get_blender_version(blender_path, blender_major_version, blender_minor_version, nullptr); + ERR_FAIL_COND_V_MSG(blender_path.is_empty(), nullptr, "Blender path is empty, check your Editor Settings."); + ERR_FAIL_COND_V_MSG(!FileAccess::exists(blender_path), nullptr, vformat("Invalid Blender path: %s, check your Editor Settings.", blender_path)); + + if (blender_major_version == -1 || blender_minor_version == -1 || last_tested_blender_path != blender_path) { + String error; + if (!_get_blender_version(blender_path, blender_major_version, blender_minor_version, &error)) { + ERR_FAIL_V_MSG(nullptr, error); + } + last_tested_blender_path = blender_path; } // Get global paths for source and sink. @@ -132,12 +139,10 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ } #endif - source_global = source_global.c_escape(); - const String blend_basename = p_path.get_file().get_basename(); const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join( vformat("%s-%s.gltf", blend_basename, p_path.md5_text())); - const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink).c_escape(); + const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink); // Handle configuration options. @@ -188,10 +193,18 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ } else { parameters_map["export_lights"] = false; } - if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) { - parameters_map["export_colors"] = true; + if (blender_major_version > 4 || (blender_major_version == 4 && blender_minor_version >= 2)) { + if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) { + parameters_map["export_vertex_color"] = "MATERIAL"; + } else { + parameters_map["export_vertex_color"] = "NONE"; + } } else { - parameters_map["export_colors"] = false; + if (p_options.has(SNAME("blender/meshes/colors")) && p_options[SNAME("blender/meshes/colors")]) { + parameters_map["export_colors"] = true; + } else { + parameters_map["export_colors"] = false; + } } if (p_options.has(SNAME("blender/nodes/visible"))) { int32_t visible = p_options["blender/nodes/visible"]; @@ -221,6 +234,18 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ } else { parameters_map["export_normals"] = false; } + + if (blender_major_version > 4 || (blender_major_version == 4 && blender_minor_version >= 1)) { + if (p_options.has(SNAME("blender/meshes/export_geometry_nodes_instances")) && p_options[SNAME("blender/meshes/export_geometry_nodes_instances")]) { + parameters_map["export_gn_mesh"] = true; + if (blender_major_version == 4 && blender_minor_version == 1) { + // There is a bug in Blender 4.1 where it can't export lights and geometry nodes at the same time, one must be disabled. + parameters_map["export_lights"] = false; + } + } else { + parameters_map["export_gn_mesh"] = false; + } + } if (p_options.has(SNAME("blender/meshes/tangents")) && p_options[SNAME("blender/meshes/tangents")]) { parameters_map["export_tangents"] = true; } else { @@ -311,7 +336,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ #endif } -Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, +Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) { if (p_path.get_extension().to_lower() != "blend") { return true; @@ -326,7 +351,8 @@ Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_pa } void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { - if (p_path.get_extension().to_lower() != "blend") { + // Returns all the options when path is empty because that means it's for the Project Settings. + if (!p_path.is_empty() && p_path.get_extension().to_lower() != "blend") { return; } #define ADD_OPTION_BOOL(PATH, VALUE) \ @@ -343,6 +369,7 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li ADD_OPTION_BOOL("blender/meshes/colors", false); ADD_OPTION_BOOL("blender/meshes/uvs", true); ADD_OPTION_BOOL("blender/meshes/normals", true); + ADD_OPTION_BOOL("blender/meshes/export_geometry_nodes_instances", false); ADD_OPTION_BOOL("blender/meshes/tangents", true); ADD_OPTION_ENUM("blender/meshes/skins", "None,4 Influences (Compatible),All Influences", BLEND_BONE_INFLUENCES_ALL); ADD_OPTION_BOOL("blender/meshes/export_bones_deforming_mesh_only", false); @@ -385,9 +412,9 @@ void EditorFileSystemImportFormatSupportQueryBlend::_validate_path(String p_path if (_test_blender_path(p_path, &error)) { success = true; if (auto_detected_path == p_path) { - error = TTR("Path to Blender installation is valid (Autodetected)."); + error = TTR("Path to Blender executable is valid (Autodetected)."); } else { - error = TTR("Path to Blender installation is valid."); + error = TTR("Path to Blender executable is valid."); } } } @@ -483,11 +510,15 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { if (!configure_blender_dialog) { configure_blender_dialog = memnew(ConfirmationDialog); configure_blender_dialog->set_title(TTR("Configure Blender Importer")); - configure_blender_dialog->set_flag(Window::FLAG_BORDERLESS, true); // Avoid closing accidentally . + configure_blender_dialog->set_flag(Window::FLAG_BORDERLESS, true); // Avoid closing accidentally. configure_blender_dialog->set_close_on_escape(false); + String select_exec_label = TTR("Blender 3.0+ is required to import '.blend' files.\nPlease provide a valid path to a Blender executable."); +#ifdef MACOS_ENABLED + select_exec_label += "\n" + TTR("On macOS, this should be the `Contents/MacOS/blender` file within the Blender `.app` folder."); +#endif VBoxContainer *vb = memnew(VBoxContainer); - vb->add_child(memnew(Label(TTR("Blender 3.0+ is required to import '.blend' files.\nPlease provide a valid path to a Blender installation:")))); + vb->add_child(memnew(Label(select_exec_label))); HBoxContainer *hb = memnew(HBoxContainer); @@ -521,8 +552,8 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { browse_dialog = memnew(EditorFileDialog); browse_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); - browse_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); - browse_dialog->connect("dir_selected", callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_select_install)); + browse_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + browse_dialog->connect("file_selected", callable_mp(this, &EditorFileSystemImportFormatSupportQueryBlend::_select_install)); EditorNode::get_singleton()->get_gui_base()->add_child(browse_dialog); diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h index 8a6c65a624..17eb9e5709 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.h +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -45,6 +45,7 @@ class EditorSceneFormatImporterBlend : public EditorSceneFormatImporter { int blender_major_version = -1; int blender_minor_version = -1; + String last_tested_blender_path; public: enum { @@ -73,7 +74,7 @@ public: List<String> *r_missing_deps, Error *r_err = nullptr) override; virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; - virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option, + virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) override; }; diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index b38c64de01..41e294cfc6 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -84,8 +84,12 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t void EditorSceneFormatImporterGLTF::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) { - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/naming_version", PROPERTY_HINT_ENUM, "Godot 4.1 or 4.0,Godot 4.2 or later"), 1)); - r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); + String file_extension = p_path.get_extension().to_lower(); + // Returns all the options when path is empty because that means it's for the Project Settings. + if (p_path.is_empty() || file_extension == "gltf" || file_extension == "glb") { + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/naming_version", PROPERTY_HINT_ENUM, "Godot 4.1 or 4.0,Godot 4.2 or later"), 1)); + r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "gltf/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), GLTFState::HANDLE_BINARY_EXTRACT_TEXTURES)); + } } void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const { @@ -96,12 +100,8 @@ void EditorSceneFormatImporterGLTF::handle_compatibility_options(HashMap<StringN } } -Variant EditorSceneFormatImporterGLTF::get_option_visibility(const String &p_path, bool p_for_animation, +Variant EditorSceneFormatImporterGLTF::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) { - String file_extension = p_path.get_extension().to_lower(); - if ((file_extension != "gltf" && file_extension != "glb") && p_option.begins_with("gltf/")) { - return false; - } return true; } diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h index d0a9aaf05a..e17b6f3f2e 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.h +++ b/modules/gltf/editor/editor_scene_importer_gltf.h @@ -50,7 +50,7 @@ public: virtual void get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) override; virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const override; - virtual Variant get_option_visibility(const String &p_path, bool p_for_animation, + virtual Variant get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option, const HashMap<StringName, Variant> &p_options) override; }; diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp index 07faee3dfc..64117349e0 100644 --- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp +++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp @@ -34,9 +34,6 @@ #include "scene/3d/mesh_instance_3d.h" #include "scene/resources/3d/importer_mesh.h" -void GLTFDocumentExtensionConvertImporterMesh::_bind_methods() { -} - void GLTFDocumentExtensionConvertImporterMesh::_copy_meta(Object *p_src_object, Object *p_dst_object) { List<StringName> meta_list; p_src_object->get_meta_list(&meta_list); @@ -55,24 +52,24 @@ Error GLTFDocumentExtensionConvertImporterMesh::import_post(Ref<GLTFState> p_sta while (!queue.is_empty()) { List<Node *>::Element *E = queue.front(); Node *node = E->get(); - ImporterMeshInstance3D *mesh_3d = cast_to<ImporterMeshInstance3D>(node); - if (mesh_3d) { - MeshInstance3D *mesh_instance_node_3d = memnew(MeshInstance3D); - Ref<ImporterMesh> mesh = mesh_3d->get_mesh(); + ImporterMeshInstance3D *importer_mesh_3d = Object::cast_to<ImporterMeshInstance3D>(node); + if (importer_mesh_3d) { + Ref<ImporterMesh> mesh = importer_mesh_3d->get_mesh(); if (mesh.is_valid()) { + MeshInstance3D *mesh_instance_node_3d = memnew(MeshInstance3D); Ref<ArrayMesh> array_mesh = mesh->get_mesh(); mesh_instance_node_3d->set_name(node->get_name()); - mesh_instance_node_3d->set_transform(mesh_3d->get_transform()); + mesh_instance_node_3d->set_transform(importer_mesh_3d->get_transform()); mesh_instance_node_3d->set_mesh(array_mesh); - mesh_instance_node_3d->set_skin(mesh_3d->get_skin()); - mesh_instance_node_3d->set_skeleton_path(mesh_3d->get_skeleton_path()); + mesh_instance_node_3d->set_skin(importer_mesh_3d->get_skin()); + mesh_instance_node_3d->set_skeleton_path(importer_mesh_3d->get_skeleton_path()); node->replace_by(mesh_instance_node_3d); - _copy_meta(mesh_3d, mesh_instance_node_3d); + _copy_meta(importer_mesh_3d, mesh_instance_node_3d); _copy_meta(mesh.ptr(), array_mesh.ptr()); delete_queue.push_back(node); node = mesh_instance_node_3d; } else { - memdelete(mesh_instance_node_3d); + WARN_PRINT("glTF: ImporterMeshInstance3D does not have a valid mesh. This should not happen. Continuing anyway."); } } int child_count = node->get_child_count(); diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h index ca10444eb5..b216a47a7f 100644 --- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h +++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.h @@ -37,7 +37,6 @@ class GLTFDocumentExtensionConvertImporterMesh : public GLTFDocumentExtension { GDCLASS(GLTFDocumentExtensionConvertImporterMesh, GLTFDocumentExtension); protected: - static void _bind_methods(); static void _copy_meta(Object *p_src_object, Object *p_dst_object); public: diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp index c1d2fea98b..f6e91c1635 100644 --- a/modules/gltf/extensions/gltf_light.cpp +++ b/modules/gltf/extensions/gltf_light.cpp @@ -170,7 +170,7 @@ Light3D *GLTFLight::to_node() const { } Ref<GLTFLight> GLTFLight::from_dictionary(const Dictionary p_dictionary) { - ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFLight>(), "Failed to parse GLTF light, missing required field 'type'."); + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFLight>(), "Failed to parse glTF light, missing required field 'type'."); Ref<GLTFLight> light; light.instantiate(); const String &type = p_dictionary["type"]; @@ -181,7 +181,7 @@ Ref<GLTFLight> GLTFLight::from_dictionary(const Dictionary p_dictionary) { if (arr.size() == 3) { light->color = Color(arr[0], arr[1], arr[2]).linear_to_srgb(); } else { - ERR_PRINT("Error parsing GLTF light: The color must have exactly 3 numbers."); + ERR_PRINT("Error parsing glTF light: The color must have exactly 3 numbers."); } } if (p_dictionary.has("intensity")) { @@ -195,10 +195,10 @@ Ref<GLTFLight> GLTFLight::from_dictionary(const Dictionary p_dictionary) { light->inner_cone_angle = spot["innerConeAngle"]; light->outer_cone_angle = spot["outerConeAngle"]; if (light->inner_cone_angle >= light->outer_cone_angle) { - ERR_PRINT("Error parsing GLTF light: The inner angle must be smaller than the outer angle."); + ERR_PRINT("Error parsing glTF light: The inner angle must be smaller than the outer angle."); } } else if (type != "point" && type != "directional") { - ERR_PRINT("Error parsing GLTF light: Light type '" + type + "' is unknown."); + ERR_PRINT("Error parsing glTF light: Light type '" + type + "' is unknown."); } return light; } diff --git a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp index 7e52cde059..5c26a1686b 100644 --- a/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp +++ b/modules/gltf/extensions/physics/gltf_document_extension_physics.cpp @@ -88,7 +88,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state // "collider" is the index of the collider in the state colliders array. int node_collider_index = node_collider_ext["collider"]; Array state_colliders = p_state->get_additional_data(StringName("GLTFPhysicsShapes")); - ERR_FAIL_INDEX_V_MSG(node_collider_index, state_colliders.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the collider index " + itos(node_collider_index) + " is not in the state colliders (size: " + itos(state_colliders.size()) + ")."); + ERR_FAIL_INDEX_V_MSG(node_collider_index, state_colliders.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the collider index " + itos(node_collider_index) + " is not in the state colliders (size: " + itos(state_colliders.size()) + ")."); p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), state_colliders[node_collider_index]); } else { p_gltf_node->set_additional_data(StringName("GLTFPhysicsShape"), GLTFPhysicsShape::from_dictionary(node_collider_ext)); @@ -103,7 +103,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state int node_shape_index = node_collider.get("shape", -1); if (node_shape_index != -1) { Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes")); - ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); + ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); p_gltf_node->set_additional_data(StringName("GLTFPhysicsColliderShape"), state_shapes[node_shape_index]); } else { // If this node is a collider but does not have a collider @@ -117,7 +117,7 @@ Error GLTFDocumentExtensionPhysics::parse_node_extensions(Ref<GLTFState> p_state int node_shape_index = node_trigger.get("shape", -1); if (node_shape_index != -1) { Array state_shapes = p_state->get_additional_data(StringName("GLTFPhysicsShapes")); - ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "GLTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); + ERR_FAIL_INDEX_V_MSG(node_shape_index, state_shapes.size(), Error::ERR_FILE_CORRUPT, "glTF Physics: On node " + p_gltf_node->get_name() + ", the shape index " + itos(node_shape_index) + " is not in the state shapes (size: " + itos(state_shapes.size()) + ")."); p_gltf_node->set_additional_data(StringName("GLTFPhysicsTriggerShape"), state_shapes[node_shape_index]); } else { // If this node is a trigger but does not have a trigger shape, @@ -150,7 +150,7 @@ void _setup_shape_mesh_resource_from_index_if_needed(Ref<GLTFState> p_state, Ref return; // The mesh resource is already set up. } TypedArray<GLTFMesh> state_meshes = p_state->get_meshes(); - ERR_FAIL_INDEX_MSG(shape_mesh_index, state_meshes.size(), "GLTF Physics: When importing '" + p_state->get_scene_name() + "', the shape mesh index " + itos(shape_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ")."); + ERR_FAIL_INDEX_MSG(shape_mesh_index, state_meshes.size(), "glTF Physics: When importing '" + p_state->get_scene_name() + "', the shape mesh index " + itos(shape_mesh_index) + " is not in the state meshes (size: " + itos(state_meshes.size()) + ")."); Ref<GLTFMesh> gltf_mesh = state_meshes[shape_mesh_index]; ERR_FAIL_COND(gltf_mesh.is_null()); importer_mesh = gltf_mesh->get_mesh(); @@ -164,12 +164,12 @@ CollisionObject3D *_generate_shape_with_body(Ref<GLTFState> p_state, Ref<GLTFNod bool is_trigger = p_physics_shape->get_is_trigger(); // This method is used for the case where we must generate a parent body. // This is can happen for multiple reasons. One possibility is that this - // GLTF file is using OMI_collider but not OMI_physics_body, or at least + // glTF file is using OMI_collider but not OMI_physics_body, or at least // this particular node is not using it. Another possibility is that the - // physics body information is set up on the same GLTF node, not a parent. + // physics body information is set up on the same glTF node, not a parent. CollisionObject3D *body; if (p_physics_body.is_valid()) { - // This code is run when the physics body is on the same GLTF node. + // This code is run when the physics body is on the same glTF node. body = p_physics_body->to_node(); if (is_trigger && (p_physics_body->get_body_type() != "trigger")) { // Edge case: If the body's trigger and the collider's trigger @@ -266,7 +266,7 @@ Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state Ref<GLTFPhysicsShape> gltf_physics_shape = p_gltf_node->get_additional_data(StringName("GLTFPhysicsShape")); if (gltf_physics_shape.is_valid()) { _setup_shape_mesh_resource_from_index_if_needed(p_state, gltf_physics_shape); - // If this GLTF node specifies both a shape and a body, generate both. + // If this glTF node specifies both a shape and a body, generate both. if (gltf_physics_body.is_valid()) { return _generate_shape_with_body(p_state, p_gltf_node, gltf_physics_shape, gltf_physics_body); } @@ -309,7 +309,7 @@ Node3D *GLTFDocumentExtensionPhysics::generate_scene_node(Ref<GLTFState> p_state } } else if (!Object::cast_to<PhysicsBody3D>(ancestor_col_obj)) { if (p_gltf_node->get_additional_data(StringName("GLTFPhysicsCompoundCollider"))) { - // If the GLTF file wants this node to group solid shapes together, + // If the glTF file wants this node to group solid shapes together, // and there is no parent body, we need to create a static body. ancestor_col_obj = memnew(StaticBody3D); ret = ancestor_col_obj; @@ -386,7 +386,7 @@ void GLTFDocumentExtensionPhysics::convert_scene_node(Ref<GLTFState> p_state, Re if (cast_to<CollisionShape3D>(p_scene_node)) { CollisionShape3D *godot_shape = Object::cast_to<CollisionShape3D>(p_scene_node); Ref<GLTFPhysicsShape> gltf_shape = GLTFPhysicsShape::from_node(godot_shape); - ERR_FAIL_COND_MSG(gltf_shape.is_null(), "GLTF Physics: Could not convert CollisionShape3D to GLTFPhysicsShape. Does it have a valid Shape3D?"); + ERR_FAIL_COND_MSG(gltf_shape.is_null(), "glTF Physics: Could not convert CollisionShape3D to GLTFPhysicsShape. Does it have a valid Shape3D?"); { Ref<ImporterMesh> importer_mesh = gltf_shape->get_importer_mesh(); if (importer_mesh.is_valid()) { diff --git a/modules/gltf/extensions/physics/gltf_physics_body.cpp b/modules/gltf/extensions/physics/gltf_physics_body.cpp index 7929b46542..c11aa5d2ff 100644 --- a/modules/gltf/extensions/physics/gltf_physics_body.cpp +++ b/modules/gltf/extensions/physics/gltf_physics_body.cpp @@ -108,7 +108,7 @@ void GLTFPhysicsBody::set_body_type(String p_body_type) { } else if (p_body_type == "trigger") { body_type = PhysicsBodyType::TRIGGER; } else { - ERR_PRINT("Error setting GLTF physics body type: The body type must be one of \"static\", \"animatable\", \"character\", \"rigid\", \"vehicle\", or \"trigger\"."); + ERR_PRINT("Error setting glTF physics body type: The body type must be one of \"static\", \"animatable\", \"character\", \"rigid\", \"vehicle\", or \"trigger\"."); } } @@ -194,7 +194,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_node(const CollisionObject3D *p_body_ physics_body->center_of_mass = body->get_center_of_mass(); physics_body->inertia_diagonal = body->get_inertia(); if (body->get_center_of_mass() != Vector3()) { - WARN_PRINT("GLTFPhysicsBody: This rigid body has a center of mass offset from the origin, which will be ignored when exporting to GLTF."); + WARN_PRINT("GLTFPhysicsBody: This rigid body has a center of mass offset from the origin, which will be ignored when exporting to glTF."); } if (cast_to<VehicleBody3D>(p_body_node)) { physics_body->body_type = PhysicsBodyType::VEHICLE; @@ -289,7 +289,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction physics_body->body_type = PhysicsBodyType::TRIGGER; #endif // DISABLE_DEPRECATED } else { - ERR_PRINT("Error parsing GLTF physics body: The body type in the GLTF file \"" + body_type_string + "\" was not recognized."); + ERR_PRINT("Error parsing glTF physics body: The body type in the glTF file \"" + body_type_string + "\" was not recognized."); } } if (motion.has("mass")) { @@ -300,7 +300,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction if (arr.size() == 3) { physics_body->set_linear_velocity(Vector3(arr[0], arr[1], arr[2])); } else { - ERR_PRINT("Error parsing GLTF physics body: The linear velocity vector must have exactly 3 numbers."); + ERR_PRINT("Error parsing glTF physics body: The linear velocity vector must have exactly 3 numbers."); } } if (motion.has("angularVelocity")) { @@ -308,7 +308,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction if (arr.size() == 3) { physics_body->set_angular_velocity(Vector3(arr[0], arr[1], arr[2])); } else { - ERR_PRINT("Error parsing GLTF physics body: The angular velocity vector must have exactly 3 numbers."); + ERR_PRINT("Error parsing glTF physics body: The angular velocity vector must have exactly 3 numbers."); } } if (motion.has("centerOfMass")) { @@ -316,7 +316,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction if (arr.size() == 3) { physics_body->set_center_of_mass(Vector3(arr[0], arr[1], arr[2])); } else { - ERR_PRINT("Error parsing GLTF physics body: The center of mass vector must have exactly 3 numbers."); + ERR_PRINT("Error parsing glTF physics body: The center of mass vector must have exactly 3 numbers."); } } if (motion.has("inertiaDiagonal")) { @@ -324,7 +324,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction if (arr.size() == 3) { physics_body->set_inertia_diagonal(Vector3(arr[0], arr[1], arr[2])); } else { - ERR_PRINT("Error parsing GLTF physics body: The inertia diagonal vector must have exactly 3 numbers."); + ERR_PRINT("Error parsing glTF physics body: The inertia diagonal vector must have exactly 3 numbers."); } } if (motion.has("inertiaOrientation")) { @@ -332,7 +332,7 @@ Ref<GLTFPhysicsBody> GLTFPhysicsBody::from_dictionary(const Dictionary p_diction if (arr.size() == 4) { physics_body->set_inertia_orientation(Quaternion(arr[0], arr[1], arr[2], arr[3])); } else { - ERR_PRINT("Error parsing GLTF physics body: The inertia orientation quaternion must have exactly 4 numbers."); + ERR_PRINT("Error parsing glTF physics body: The inertia orientation quaternion must have exactly 4 numbers."); } } return physics_body; diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.cpp b/modules/gltf/extensions/physics/gltf_physics_shape.cpp index 6897bdbd3a..0340eb11b5 100644 --- a/modules/gltf/extensions/physics/gltf_physics_shape.cpp +++ b/modules/gltf/extensions/physics/gltf_physics_shape.cpp @@ -134,7 +134,7 @@ void GLTFPhysicsShape::set_importer_mesh(Ref<ImporterMesh> p_importer_mesh) { Ref<ImporterMesh> _convert_hull_points_to_mesh(const Vector<Vector3> &p_hull_points) { Ref<ImporterMesh> importer_mesh; - ERR_FAIL_COND_V_MSG(p_hull_points.size() < 3, importer_mesh, "GLTFPhysicsShape: Convex hull has fewer points (" + itos(p_hull_points.size()) + ") than the minimum of 3. At least 3 points are required in order to save to GLTF, since it uses a mesh to represent convex hulls."); + ERR_FAIL_COND_V_MSG(p_hull_points.size() < 3, importer_mesh, "GLTFPhysicsShape: Convex hull has fewer points (" + itos(p_hull_points.size()) + ") than the minimum of 3. At least 3 points are required in order to save to glTF, since it uses a mesh to represent convex hulls."); if (p_hull_points.size() > 255) { WARN_PRINT("GLTFPhysicsShape: Convex hull has more points (" + itos(p_hull_points.size()) + ") than the recommended maximum of 255. This may not load correctly in other engines."); } diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index c0232e6d0c..cd25b93e6c 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -620,7 +620,7 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) { for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); Error err = ext->parse_node_extensions(p_state, node, extensions); - ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing node extensions for node " + node->get_name() + " in file " + p_state->filename + ". Continuing."); + ERR_CONTINUE_MSG(err != OK, "glTF: Encountered error " + itos(err) + " when parsing node extensions for node " + node->get_name() + " in file " + p_state->filename + ". Continuing."); } } @@ -789,8 +789,9 @@ Error GLTFDocument::_parse_buffers(Ref<GLTFState> p_state, const String &p_base_ ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. + ERR_FAIL_COND_V_MSG(!FileAccess::exists(uri), ERR_FILE_NOT_FOUND, "glTF: Binary file not found: " + uri); buffer_data = FileAccess::get_file_as_bytes(uri); - ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); + ERR_FAIL_COND_V_MSG(buffer_data.is_empty(), ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); } ERR_FAIL_COND_V(!buffer.has("byteLength"), ERR_PARSE_ERROR); @@ -3352,7 +3353,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { ERR_CONTINUE(image.is_null()); if (image->is_compressed()) { image->decompress(); - ERR_FAIL_COND_V_MSG(image->is_compressed(), ERR_INVALID_DATA, "GLTF: Image was compressed, but could not be decompressed."); + ERR_FAIL_COND_V_MSG(image->is_compressed(), ERR_INVALID_DATA, "glTF: Image was compressed, but could not be decompressed."); } if (p_state->filename.to_lower().ends_with("gltf")) { @@ -3373,7 +3374,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { if (_image_save_extension.is_valid()) { img_name = img_name + _image_save_extension->get_image_file_extension(); Error err = _image_save_extension->save_image_at_path(p_state, image, full_texture_dir.path_join(img_name), _image_format, _lossy_quality); - ERR_FAIL_COND_V_MSG(err != OK, err, "GLTF: Failed to save image in '" + _image_format + "' format as a separate file."); + ERR_FAIL_COND_V_MSG(err != OK, err, "glTF: Failed to save image in '" + _image_format + "' format as a separate file."); } else if (_image_format == "PNG") { img_name = img_name + ".png"; image->save_png(full_texture_dir.path_join(img_name)); @@ -3381,7 +3382,7 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { img_name = img_name + ".jpg"; image->save_jpg(full_texture_dir.path_join(img_name), _lossy_quality); } else { - ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'."); + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Unknown image format '" + _image_format + "'."); } image_dict["uri"] = relative_texture_dir.path_join(img_name).uri_encode(); } else { @@ -3411,9 +3412,9 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> p_state) { buffer = image->save_jpg_to_buffer(_lossy_quality); image_dict["mimeType"] = "image/jpeg"; } else { - ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "GLTF: Unknown image format '" + _image_format + "'."); + ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "glTF: Unknown image format '" + _image_format + "'."); } - ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_INVALID_DATA, "GLTF: Failed to save image in '" + _image_format + "' format."); + ERR_FAIL_COND_V_MSG(buffer.is_empty(), ERR_INVALID_DATA, "glTF: Failed to save image in '" + _image_format + "' format."); bv->byte_length = buffer.size(); p_state->buffers.write[bi].resize(p_state->buffers[bi].size() + bv->byte_length); @@ -3444,7 +3445,7 @@ Ref<Image> GLTFDocument::_parse_image_bytes_into_image(Ref<GLTFState> p_state, c for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); Error err = ext->parse_image_data(p_state, p_bytes, p_mime_type, r_image); - ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing image " + itos(p_index) + " in file " + p_state->filename + ". Continuing."); + ERR_CONTINUE_MSG(err != OK, "glTF: Encountered error " + itos(err) + " when parsing image " + itos(p_index) + " in file " + p_state->filename + ". Continuing."); if (!r_image->is_empty()) { r_file_extension = ext->get_image_file_extension(); return r_image; @@ -3735,13 +3736,13 @@ Error GLTFDocument::_parse_textures(Ref<GLTFState> p_state) { for (Ref<GLTFDocumentExtension> ext : document_extensions) { ERR_CONTINUE(ext.is_null()); Error err = ext->parse_texture_json(p_state, texture_dict, gltf_texture); - ERR_CONTINUE_MSG(err != OK, "GLTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(texture_dict)) + " in file " + p_state->filename + ". Continuing."); + ERR_CONTINUE_MSG(err != OK, "glTF: Encountered error " + itos(err) + " when parsing texture JSON " + String(Variant(texture_dict)) + " in file " + p_state->filename + ". Continuing."); if (gltf_texture->get_src_image() != -1) { break; } } if (gltf_texture->get_src_image() == -1) { - // No extensions handled it, so use the base GLTF source. + // No extensions handled it, so use the base glTF source. // This may be the fallback, or the only option anyway. ERR_FAIL_COND_V(!texture_dict.has("source"), ERR_PARSE_ERROR); gltf_texture->set_src_image(texture_dict["source"]); @@ -5630,7 +5631,7 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn // If none of our GLTFDocumentExtension classes generated us a node, we generate one. if (!current_node) { if (gltf_node->skin >= 0 && gltf_node->mesh >= 0 && !gltf_node->children.is_empty()) { - // GLTF specifies that skinned meshes should ignore their node transforms, + // glTF specifies that skinned meshes should ignore their node transforms, // only being controlled by the skeleton, so Godot will reparent a skinned // mesh to its skeleton. However, we still need to ensure any child nodes // keep their place in the tree, so if there are any child nodes, the skinned @@ -5657,6 +5658,15 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn if (p_scene_root == nullptr) { // If the root node argument is null, this is the root node. p_scene_root = current_node; + // If multiple nodes were generated under the root node, ensure they have the owner set. + if (unlikely(current_node->get_child_count() > 0)) { + Array args; + args.append(p_scene_root); + for (int i = 0; i < current_node->get_child_count(); i++) { + Node *child = current_node->get_child(i); + child->propagate_call(StringName("set_owner"), args); + } + } } else { // Add the node we generated and set the owner to the scene root. p_scene_parent->add_child(current_node, true); @@ -7137,9 +7147,9 @@ Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) { HashMap<ObjectID, SkinSkeletonIndex> skeleton_map; Error err = SkinTool::_create_skeletons(p_state->unique_names, p_state->skins, p_state->nodes, skeleton_map, p_state->skeletons, p_state->scene_nodes); - ERR_FAIL_COND_V_MSG(err != OK, nullptr, "GLTF: Failed to create skeletons."); + 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."); + ERR_FAIL_COND_V_MSG(err != OK, nullptr, "glTF: Failed to create skins."); // Generate the node tree. Node *single_root; if (p_state->extensions_used.has("GODOT_single_root")) { @@ -7458,7 +7468,7 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) { Error ret = OK; for (int i = 0; i < p_state->extensions_required.size(); i++) { if (!supported_extensions.has(p_state->extensions_required[i])) { - ERR_PRINT("GLTF: Can't import file '" + p_state->filename + "', required extension '" + String(p_state->extensions_required[i]) + "' is not supported. Are you missing a GLTFDocumentExtension plugin?"); + ERR_PRINT("glTF: Can't import file '" + p_state->filename + "', required extension '" + String(p_state->extensions_required[i]) + "' is not supported. Are you missing a GLTFDocumentExtension plugin?"); ret = ERR_UNAVAILABLE; } } diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp index d56f67a092..863e1df967 100644 --- a/modules/gltf/structures/gltf_camera.cpp +++ b/modules/gltf/structures/gltf_camera.cpp @@ -62,9 +62,9 @@ Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) { c.instantiate(); ERR_FAIL_NULL_V_MSG(p_camera, c, "Tried to create a GLTFCamera from a Camera3D node, but the given node was null."); c->set_perspective(p_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); - // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. + // glTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. c->set_fov(Math::deg_to_rad(p_camera->get_fov())); - // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. + // glTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. c->set_size_mag(p_camera->get_size() * 0.5f); c->set_depth_far(p_camera->get_far()); c->set_depth_near(p_camera->get_near()); @@ -74,9 +74,9 @@ Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) { Camera3D *GLTFCamera::to_node() const { Camera3D *camera = memnew(Camera3D); camera->set_projection(perspective ? Camera3D::PROJECTION_PERSPECTIVE : Camera3D::PROJECTION_ORTHOGONAL); - // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. + // glTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. camera->set_fov(Math::rad_to_deg(fov)); - // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. + // glTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. camera->set_size(size_mag * 2.0f); camera->set_near(depth_near); camera->set_far(depth_far); @@ -84,7 +84,7 @@ Camera3D *GLTFCamera::to_node() const { } Ref<GLTFCamera> GLTFCamera::from_dictionary(const Dictionary p_dictionary) { - ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFCamera>(), "Failed to parse GLTF camera, missing required field 'type'."); + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFCamera>(), "Failed to parse glTF camera, missing required field 'type'."); Ref<GLTFCamera> camera; camera.instantiate(); const String &type = p_dictionary["type"]; @@ -107,7 +107,7 @@ Ref<GLTFCamera> GLTFCamera::from_dictionary(const Dictionary p_dictionary) { camera->set_depth_near(ortho["znear"]); } } else { - ERR_PRINT("Error parsing GLTF camera: Camera type '" + type + "' is unknown, should be perspective or orthographic."); + ERR_PRINT("Error parsing glTF camera: Camera type '" + type + "' is unknown, should be perspective or orthographic."); } return camera; } diff --git a/modules/gltf/structures/gltf_camera.h b/modules/gltf/structures/gltf_camera.h index ef55b06a76..1a583c82cc 100644 --- a/modules/gltf/structures/gltf_camera.h +++ b/modules/gltf/structures/gltf_camera.h @@ -42,8 +42,8 @@ class GLTFCamera : public Resource { GDCLASS(GLTFCamera, Resource); private: - // GLTF has no default camera values, they should always be specified in - // the GLTF file. Here we default to Godot's default camera settings. + // glTF has no default camera values, they should always be specified in + // the glTF file. Here we default to Godot's default camera settings. bool perspective = true; real_t fov = Math::deg_to_rad(75.0); real_t size_mag = 0.5; diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index f402e2a583..ea63e07104 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -643,6 +643,7 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D _do_paste(); input_action = INPUT_NONE; _update_paste_indicator(); + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (mb->is_shift_pressed() && can_edit) { input_action = INPUT_SELECT; last_selection = selection; diff --git a/modules/hdr/image_loader_hdr.cpp b/modules/hdr/image_loader_hdr.cpp index c49c62a08b..ba59bb25ee 100644 --- a/modules/hdr/image_loader_hdr.cpp +++ b/modules/hdr/image_loader_hdr.cpp @@ -68,9 +68,11 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField imgdata.resize(height * width * (int)sizeof(uint32_t)); { - uint8_t *w = imgdata.ptrw(); + uint8_t *ptr = imgdata.ptrw(); - uint8_t *ptr = (uint8_t *)w; + Vector<uint8_t> temp_read_data; + temp_read_data.resize(128); + uint8_t *temp_read_ptr = temp_read_data.ptrw(); if (width < 8 || width >= 32768) { // Read flat data @@ -113,8 +115,9 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField } } else { // Dump + f->get_buffer(temp_read_ptr, count); for (int z = 0; z < count; ++z) { - ptr[(j * width + i++) * 4 + k] = f->get_8(); + ptr[(j * width + i++) * 4 + k] = temp_read_ptr[z]; } } } @@ -122,20 +125,27 @@ Error ImageLoaderHDR::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField } } + const bool force_linear = p_flags & FLAG_FORCE_LINEAR; + //convert for (int i = 0; i < width * height; i++) { - float exp = pow(2.0f, ptr[3] - 128.0f); + int e = ptr[3] - 128; + + if (force_linear || (e < -15 || e > 15)) { + float exp = pow(2.0f, e); + Color c(ptr[0] * exp / 255.0, ptr[1] * exp / 255.0, ptr[2] * exp / 255.0); - Color c( - ptr[0] * exp / 255.0, - ptr[1] * exp / 255.0, - ptr[2] * exp / 255.0); + if (force_linear) { + c = c.srgb_to_linear(); + } - if (p_flags & FLAG_FORCE_LINEAR) { - c = c.srgb_to_linear(); + *(uint32_t *)ptr = c.to_rgbe9995(); + } else { + // https://github.com/george-steel/rgbe-rs/blob/e7cc33b7f42b4eb3272c166dac75385e48687c92/src/types.rs#L123-L129 + uint32_t e5 = (uint32_t)(e + 15); + *(uint32_t *)ptr = ((e5 << 27) | ((uint32_t)ptr[2] << 19) | ((uint32_t)ptr[1] << 10) | ((uint32_t)ptr[0] << 1)); } - *(uint32_t *)ptr = c.to_rgbe9995(); ptr += 4; } } diff --git a/modules/hdr/image_loader_hdr.h b/modules/hdr/image_loader_hdr.h index 9821db059e..0a8e91fb9e 100644 --- a/modules/hdr/image_loader_hdr.h +++ b/modules/hdr/image_loader_hdr.h @@ -37,6 +37,7 @@ class ImageLoaderHDR : public ImageFormatLoader { public: virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField<ImageFormatLoader::LoaderFlags> p_flags, float p_scale); virtual void get_recognized_extensions(List<String> *p_extensions) const; + ImageLoaderHDR(); }; diff --git a/modules/interactive_music/audio_stream_interactive.cpp b/modules/interactive_music/audio_stream_interactive.cpp index 01764d66ed..8472c4e352 100644 --- a/modules/interactive_music/audio_stream_interactive.cpp +++ b/modules/interactive_music/audio_stream_interactive.cpp @@ -858,10 +858,7 @@ int AudioStreamPlaybackInteractive::mix(AudioFrame *p_buffer, float p_rate_scale } if (!active) { - for (int i = 0; i < p_frames; i++) { - p_buffer[i] = AudioFrame(0.0, 0.0); - } - return p_frames; + return 0; } int todo = p_frames; @@ -976,6 +973,8 @@ void AudioStreamPlaybackInteractive::switch_to_clip_by_name(const StringName &p_ return; } + ERR_FAIL_COND_MSG(stream.is_null(), "Attempted to switch while not playing back any stream."); + for (int i = 0; i < stream->get_clip_count(); i++) { if (stream->get_clip_name(i) == p_name) { switch_request = i; diff --git a/modules/interactive_music/audio_stream_playlist.cpp b/modules/interactive_music/audio_stream_playlist.cpp index f47035b30c..eb77a4a8d3 100644 --- a/modules/interactive_music/audio_stream_playlist.cpp +++ b/modules/interactive_music/audio_stream_playlist.cpp @@ -259,10 +259,7 @@ void AudioStreamPlaybackPlaylist::seek(double p_time) { int AudioStreamPlaybackPlaylist::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { if (!active) { - for (int i = 0; i < p_frames; i++) { - p_buffer[i] = AudioFrame(0.0, 0.0); - } - return p_frames; + return 0; } double time_dec = (1.0 / AudioServer::get_singleton()->get_mix_rate()); diff --git a/modules/interactive_music/audio_stream_synchronized.cpp b/modules/interactive_music/audio_stream_synchronized.cpp index d0d58fac16..e38a57ba75 100644 --- a/modules/interactive_music/audio_stream_synchronized.cpp +++ b/modules/interactive_music/audio_stream_synchronized.cpp @@ -204,11 +204,8 @@ void AudioStreamPlaybackSynchronized::seek(double p_time) { } int AudioStreamPlaybackSynchronized::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { - if (active != true) { - for (int i = 0; i < p_frames; i++) { - p_buffer[i] = AudioFrame(0.0, 0.0); - } - return p_frames; + if (!active) { + return 0; } int todo = p_frames; diff --git a/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml b/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml index ea914715a3..5353dc7376 100644 --- a/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml +++ b/modules/interactive_music/doc_classes/AudioStreamSynchronized.xml @@ -47,7 +47,7 @@ </members> <constants> <constant name="MAX_STREAMS" value="32"> - Maximum amount of streams that can be synchrohized. + Maximum amount of streams that can be synchronized. </constant> </constants> </class> diff --git a/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp b/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp index e29cc753c9..fcb477995f 100644 --- a/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp +++ b/modules/interactive_music/editor/audio_stream_interactive_editor_plugin.cpp @@ -34,6 +34,7 @@ #include "editor/editor_node.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/themes/editor_scale.h" #include "scene/gui/check_box.h" #include "scene/gui/option_button.h" #include "scene/gui/spin_box.h" @@ -290,7 +291,6 @@ void AudioStreamInteractiveTransitionEditor::edit(Object *p_obj) { } int min_w = header_font->get_string_size(name + "XX").width; - tree->set_column_expand(cell_index, false); tree->set_column_custom_minimum_width(cell_index, min_w); max_w = MAX(max_w, min_w); @@ -314,11 +314,10 @@ void AudioStreamInteractiveTransitionEditor::edit(Object *p_obj) { } } - tree->set_column_expand(header_index, false); tree->set_column_custom_minimum_width(header_index, max_w); selection_order.clear(); _update_selection(); - popup_centered_ratio(0.6); + popup_centered_clamped(Size2(900, 450) * EDSCALE); updating = false; _update_transitions(); } @@ -332,6 +331,7 @@ AudioStreamInteractiveTransitionEditor::AudioStreamInteractiveTransitionEditor() tree->set_hide_root(true); tree->add_theme_constant_override("draw_guides", 1); tree->set_select_mode(Tree::SELECT_MULTI); + tree->set_custom_minimum_size(Size2(400, 0) * EDSCALE); split->add_child(tree); tree->set_h_size_flags(Control::SIZE_EXPAND_FILL); diff --git a/modules/interactive_music/register_types.cpp b/modules/interactive_music/register_types.cpp index 5baea13f81..6175ea6493 100644 --- a/modules/interactive_music/register_types.cpp +++ b/modules/interactive_music/register_types.cpp @@ -42,11 +42,11 @@ void initialize_interactive_music_module(ModuleInitializationLevel p_level) { if (p_level == MODULE_INITIALIZATION_LEVEL_SCENE) { GDREGISTER_CLASS(AudioStreamPlaylist); - GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackPlaylist); + GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackPlaylist); GDREGISTER_CLASS(AudioStreamInteractive); - GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackInteractive); + GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackInteractive); GDREGISTER_CLASS(AudioStreamSynchronized); - GDREGISTER_VIRTUAL_CLASS(AudioStreamPlaybackSynchronized); + GDREGISTER_ABSTRACT_CLASS(AudioStreamPlaybackSynchronized); } #ifdef TOOLS_ENABLED if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) { diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index 7ac7bd8088..ddc51bcf6b 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -233,14 +233,14 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_ MeshInstance &mi = mesh_instances.write[m_i]; Size2i s = Size2i(mi.data.albedo_on_uv2->get_width(), mi.data.albedo_on_uv2->get_height()); sizes.push_back(s); - atlas_size = atlas_size.max(s + Size2i(2, 2)); + atlas_size = atlas_size.max(s + Size2i(2, 2).maxi(p_denoiser_range)); } int max = nearest_power_of_2_templated(atlas_size.width); max = MAX(max, nearest_power_of_2_templated(atlas_size.height)); if (max > p_max_texture_size) { - return BAKE_ERROR_LIGHTMAP_TOO_SMALL; + return BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE; } if (p_step_function) { @@ -254,19 +254,27 @@ Lightmapper::BakeError LightmapperRD::_blit_meshes_into_atlas(int p_max_texture_ int best_atlas_memory = 0x7FFFFFFF; Vector<Vector3i> best_atlas_offsets; - //determine best texture array atlas size by bruteforce fitting + // Determine best texture array atlas size by bruteforce fitting. while (atlas_size.x <= p_max_texture_size && atlas_size.y <= p_max_texture_size) { Vector<Vector2i> source_sizes; Vector<int> source_indices; source_sizes.resize(sizes.size()); source_indices.resize(sizes.size()); for (int i = 0; i < source_indices.size(); i++) { - source_sizes.write[i] = sizes[i] + Vector2i(2, 2).maxi(p_denoiser_range); // Add padding between lightmaps + source_sizes.write[i] = sizes[i] + Vector2i(2, 2).maxi(p_denoiser_range); // Add padding between lightmaps. source_indices.write[i] = i; } Vector<Vector3i> atlas_offsets; atlas_offsets.resize(source_sizes.size()); + // Ensure the sizes can all fit into a single atlas layer. + // This should always happen, and this check is only in place to prevent an infinite loop. + for (int i = 0; i < source_sizes.size(); i++) { + if (source_sizes[i] > atlas_size) { + return BAKE_ERROR_ATLAS_TOO_SMALL; + } + } + int slices = 0; while (source_sizes.size() > 0) { @@ -707,7 +715,7 @@ void LightmapperRD::_raster_geometry(RenderingDevice *rd, Size2i atlas_size, int raster_push_constant.uv_offset[0] = -0.5f / float(atlas_size.x); raster_push_constant.uv_offset[1] = -0.5f / float(atlas_size.y); - RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 1.0, 0, Rect2(), RDD::BreadcrumbMarker::LIGHTMAPPER_PASS); //draw opaque rd->draw_list_bind_render_pipeline(draw_list, raster_pipeline); rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0); @@ -907,7 +915,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID return BAKE_OK; } -LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function) { +LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function, void *p_bake_userdata) { RID denoise_params_buffer = p_rd->uniform_buffer_create(sizeof(DenoiseParams)); DenoiseParams denoise_params; denoise_params.spatial_bandwidth = 5.0f; @@ -970,6 +978,11 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh p_rd->sync(); } } + if (p_step_function) { + int percent = (s + 1) * 100 / p_atlas_slices; + float p = float(s) / p_atlas_slices * 0.1; + p_step_function(0.8 + p, vformat(RTR("Denoising %d%%"), percent), p_bake_userdata, false); + } } p_rd->free(compute_shader_denoise); @@ -1573,12 +1586,17 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); img->save_exr("res://2_light_primary_" + itos(i) + ".exr", false); } + + if (p_bake_sh) { + for (int i = 0; i < atlas_slices * 4; i++) { + Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i); + Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); + img->save_exr("res://2_light_primary_accum_" + itos(i) + ".exr", false); + } + } #endif /* SECONDARY (indirect) LIGHT PASS(ES) */ - if (p_step_function) { - p_step_function(0.6, RTR("Integrate indirect lighting"), p_bake_userdata, true); - } if (p_bounces > 0) { Vector<RD::Uniform> uniforms; @@ -1642,6 +1660,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d rd->submit(); rd->sync(); + if (p_step_function) { + p_step_function(0.6, RTR("Integrate indirect lighting"), p_bake_userdata, true); + } + int count = 0; for (int s = 0; s < atlas_slices; s++) { push_constant.atlas_slice = s; @@ -1795,7 +1817,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d } else { // JNLM (built-in). SWAP(light_accum_tex, light_accum_tex2); - error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, p_denoiser_range, atlas_size, atlas_slices, p_bake_sh, p_step_function); + error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, normal_tex, light_accum_tex, p_denoiser_strength, p_denoiser_range, atlas_size, atlas_slices, p_bake_sh, p_step_function, p_bake_userdata); } if (unlikely(error != BAKE_OK)) { return error; @@ -1910,7 +1932,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d seams_push_constant.slice = uint32_t(i * subslices + k); seams_push_constant.debug = debug; - RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i * subslices + k], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + // Store the current subslice in the breadcrumb. + RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i * subslices + k], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 1.0, 0, Rect2(), RDD::BreadcrumbMarker::LIGHTMAPPER_PASS | seams_push_constant.slice); rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0); rd->draw_list_bind_uniform_set(draw_list, blendseams_raster_uniform, 1); diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h index 487c44a480..59c2d52e69 100644 --- a/modules/lightmapper_rd/lightmapper_rd.h +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -272,7 +272,7 @@ class LightmapperRD : public Lightmapper { void _raster_geometry(RenderingDevice *rd, Size2i atlas_size, int atlas_slices, int grid_size, AABB bounds, float p_bias, Vector<int> slice_triangle_count, RID position_tex, RID unocclude_tex, RID normal_tex, RID raster_depth_buffer, RID rasterize_shader, RID raster_base_uniform); BakeError _dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices); - BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function); + BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function, void *p_bake_userdata); Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name); Ref<Image> _read_pfm(const String &p_name); diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index 9424d5a4c1..88fc316679 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -359,7 +359,36 @@ float get_omni_attenuation(float distance, float inv_range, float decay) { return nd * pow(max(distance, 0.0001), -decay); } -void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise) { +const int AA_SAMPLES = 16; + +const vec2 halton_map[AA_SAMPLES] = vec2[]( + vec2(0.5, 0.33333333), + vec2(0.25, 0.66666667), + vec2(0.75, 0.11111111), + vec2(0.125, 0.44444444), + vec2(0.625, 0.77777778), + vec2(0.375, 0.22222222), + vec2(0.875, 0.55555556), + vec2(0.0625, 0.88888889), + vec2(0.5625, 0.03703704), + vec2(0.3125, 0.37037037), + vec2(0.8125, 0.7037037), + vec2(0.1875, 0.14814815), + vec2(0.6875, 0.48148148), + vec2(0.4375, 0.81481481), + vec2(0.9375, 0.25925926), + vec2(0.03125, 0.59259259)); + +vec2 get_vogel_disk(float p_i, float p_rotation, float p_sample_count_sqrt) { + const float golden_angle = 2.4; + + float r = sqrt(p_i + 0.5) / p_sample_count_sqrt; + float theta = p_i * golden_angle + p_rotation; + + return vec2(cos(theta), sin(theta)) * r; +} + +void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise, float p_texel_size) { r_light = vec3(0.0f); vec3 light_pos; @@ -407,46 +436,70 @@ void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool } float penumbra = 0.0; - if ((light_data.size > 0.0) && p_soft_shadowing) { + if (p_soft_shadowing) { + const bool use_soft_shadows = (light_data.size > 0.0); + const uint ray_count = AA_SAMPLES; + const uint total_ray_count = use_soft_shadows ? params.ray_count : ray_count; + const uint shadowing_rays_check_penumbra_denom = 2; + const uint shadowing_ray_count = max(1, params.ray_count / ray_count); + const float shadowing_ray_count_sqrt = sqrt(float(total_ray_count)); + + // Setup tangent pass to calculate AA samples over the current texel. + vec3 aux = p_normal.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(p_normal, aux)); + vec3 bitan = normalize(cross(p_normal, tangent)); + + // Setup light tangent pass to calculate samples over disk aligned towards the light vec3 light_to_point = -r_light_dir; - vec3 aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); - vec3 light_to_point_tan = normalize(cross(light_to_point, aux)); + vec3 light_aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); + vec3 light_to_point_tan = normalize(cross(light_to_point, light_aux)); vec3 light_to_point_bitan = normalize(cross(light_to_point, light_to_point_tan)); - const uint shadowing_rays_check_penumbra_denom = 2; - uint shadowing_ray_count = p_soft_shadowing ? params.ray_count : 1; - uint hits = 0; - vec3 light_disk_to_point = light_to_point; - for (uint j = 0; j < shadowing_ray_count; j++) { - // Optimization: - // Once already traced an important proportion of rays, if all are hits or misses, - // assume we're not in the penumbra so we can infer the rest would have the same result - if (p_soft_shadowing) { - if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) { - if (hits == j) { - // Assume totally lit - hits = shadowing_ray_count; - break; - } else if (hits == 0) { - // Assume totally dark - hits = 0; - break; + for (uint i = 0; i < ray_count; i++) { + // Create a random sample within the texel. + vec2 disk_sample = (halton_map[i] - vec2(0.5)) * p_texel_size * light_data.shadow_blur; + // Align the sample to world space. + vec3 disk_aligned = (disk_sample.x * tangent + disk_sample.y * bitan); + vec3 origin = p_position - disk_aligned; + vec3 light_dir = normalize(light_pos - origin); + + if (use_soft_shadows) { + uint soft_shadow_hits = 0; + for (uint j = 0; j < shadowing_ray_count; j++) { + // Optimization: + // Once already traced an important proportion of rays, if all are hits or misses, + // assume we're not in the penumbra so we can infer the rest would have the same result. + if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) { + if (soft_shadow_hits == j) { + // Assume totally lit + soft_shadow_hits = shadowing_ray_count; + break; + } else if (soft_shadow_hits == 0) { + // Assume totally dark + soft_shadow_hits = 0; + break; + } } - } - } - - float r = randomize(r_noise); - float a = randomize(r_noise) * 2.0 * PI; - vec2 disk_sample = (r * vec2(cos(a), sin(a))) * soft_shadowing_disk_size * light_data.shadow_blur; - light_disk_to_point = normalize(light_to_point + disk_sample.x * light_to_point_tan + disk_sample.y * light_to_point_bitan); - if (trace_ray_any_hit(p_position - light_disk_to_point * bake_params.bias, p_position - light_disk_to_point * dist) == RAY_MISS) { - hits++; + float a = randomize(r_noise) * 2.0 * PI; + float vogel_index = float(total_ray_count - 1 - (i * shadowing_ray_count + j)); // Start from (total_ray_count - 1) so we check the outer points first. + vec2 light_disk_sample = (get_vogel_disk(vogel_index, a, shadowing_ray_count_sqrt)) * soft_shadowing_disk_size * light_data.shadow_blur; + vec3 light_disk_to_point = normalize(light_to_point + light_disk_sample.x * light_to_point_tan + light_disk_sample.y * light_to_point_bitan); + // Offset the ray origin for AA, offset the light position for soft shadows. + if (trace_ray_any_hit(origin - light_disk_to_point * (bake_params.bias + length(disk_sample)), p_position - light_disk_to_point * dist) == RAY_MISS) { + soft_shadow_hits++; + } + } + hits += soft_shadow_hits; + } else { + // Offset the ray origin based on the disk. Also increase the bias for further samples to avoid bleeding. + if (trace_ray_any_hit(origin + light_dir * (bake_params.bias + length(disk_sample)), light_pos) == RAY_MISS) { + hits++; + } } } - - penumbra = float(hits) / float(shadowing_ray_count); + penumbra = float(hits) / float(total_ray_count); } else { if (trace_ray_any_hit(p_position + r_light_dir * bake_params.bias, light_pos) == RAY_MISS) { penumbra = 1.0; @@ -470,7 +523,7 @@ vec3 trace_environment_color(vec3 ray_dir) { return textureLod(sampler2D(environment, linear_sampler), st / vec2(PI * 2.0, PI), 0.0).rgb; } -vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise) { +vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise, float p_texel_size) { // The lower limit considers the case where the lightmapper might have bounces disabled but light probes are requested. vec3 position = p_position; vec3 ray_dir = p_ray_dir; @@ -502,7 +555,7 @@ vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise) { for (uint i = 0; i < bake_params.light_count; i++) { vec3 light; vec3 light_dir; - trace_direct_light(position, normal, i, false, light, light_dir, r_noise); + trace_direct_light(position, normal, i, false, light, light_dir, r_noise, p_texel_size); direct_light += light * lights.data[i].indirect_energy; } @@ -566,6 +619,14 @@ void main() { return; //empty texel, no process } vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + vec4 neighbor_position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos + ivec2(1, 0), params.atlas_slice), 0).xyzw; + + if (neighbor_position.w < 0.001) { + // Empty texel, try again. + neighbor_position.xyz = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos + ivec2(-1, 0), params.atlas_slice), 0).xyz; + } + float texel_size_world_space = distance(position, neighbor_position.xyz); + vec3 light_for_texture = vec3(0.0); vec3 light_for_bounces = vec3(0.0); @@ -582,21 +643,26 @@ void main() { for (uint i = 0; i < bake_params.light_count; i++) { vec3 light; vec3 light_dir; - trace_direct_light(position, normal, i, true, light, light_dir, noise); + trace_direct_light(position, normal, i, true, light, light_dir, noise, texel_size_world_space); if (lights.data[i].static_bake) { light_for_texture += light; #ifdef USE_SH_LIGHTMAPS + // These coefficients include the factored out SH evaluation, diffuse convolution, and final application, as well as the BRDF 1/PI and the spherical monte carlo factor. + // LO: 1/(2*sqrtPI) * 1/(2*sqrtPI) * PI * PI * 1/PI = 0.25 + // L1: sqrt(3/(4*pi)) * sqrt(3/(4*pi)) * (PI*2/3) * (2 * PI) * 1/PI = 1.0 + // Note: This only works because we aren't scaling, rotating, or combing harmonics, we are just directing applying them in the shader. + float c[4] = float[]( - 0.282095, //l0 - 0.488603 * light_dir.y, //l1n1 - 0.488603 * light_dir.z, //l1n0 - 0.488603 * light_dir.x //l1p1 + 0.25, //l0 + light_dir.y, //l1n1 + light_dir.z, //l1n0 + light_dir.x //l1p1 ); for (uint j = 0; j < 4; j++) { - sh_accum[j].rgb += light * c[j] * 8.0; + sh_accum[j].rgb += light * c[j] * bake_params.exposure_normalization; } #endif } @@ -640,21 +706,29 @@ void main() { } vec3 position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz; + int neighbor_offset = atlas_pos.x < bake_params.atlas_size.x - 1 ? 1 : -1; + vec3 neighbor_position = texelFetch(sampler2DArray(source_position, linear_sampler), ivec3(atlas_pos + ivec2(neighbor_offset, 0), params.atlas_slice), 0).xyz; + float texel_size_world_space = distance(position, neighbor_position); uint noise = random_seed(ivec3(params.ray_from, atlas_pos)); for (uint i = params.ray_from; i < params.ray_to; i++) { vec3 ray_dir = generate_ray_dir_from_normal(normal, noise); - vec3 light = trace_indirect_light(position, ray_dir, noise); + vec3 light = trace_indirect_light(position, ray_dir, noise, texel_size_world_space); #ifdef USE_SH_LIGHTMAPS + // These coefficients include the factored out SH evaluation, diffuse convolution, and final application, as well as the BRDF 1/PI and the spherical monte carlo factor. + // LO: 1/(2*sqrtPI) * 1/(2*sqrtPI) * PI * PI * 1/PI = 0.25 + // L1: sqrt(3/(4*pi)) * sqrt(3/(4*pi)) * (PI*2/3) * (2 * PI) * 1/PI = 1.0 + // Note: This only works because we aren't scaling, rotating, or combing harmonics, we are just directing applying them in the shader. + float c[4] = float[]( - 0.282095, //l0 - 0.488603 * ray_dir.y, //l1n1 - 0.488603 * ray_dir.z, //l1n0 - 0.488603 * ray_dir.x //l1p1 + 0.25, //l0 + ray_dir.y, //l1n1 + ray_dir.z, //l1n0 + ray_dir.x //l1p1 ); for (uint j = 0; j < 4; j++) { - sh_accum[j].rgb += light * c[j] * 8.0; + sh_accum[j].rgb += light * c[j]; } #else light_accum += light; @@ -737,7 +811,7 @@ void main() { uint noise = random_seed(ivec3(params.ray_from, probe_index, 49502741 /* some prime */)); for (uint i = params.ray_from; i < params.ray_to; i++) { vec3 ray_dir = generate_sphere_uniform_direction(noise); - vec3 light = trace_indirect_light(position, ray_dir, noise); + vec3 light = trace_indirect_light(position, ray_dir, noise, 0.0); float c[9] = float[]( 0.282095, //l0 diff --git a/modules/mbedtls/crypto_mbedtls.cpp b/modules/mbedtls/crypto_mbedtls.cpp index e910627b32..0d97b5fc1a 100644 --- a/modules/mbedtls/crypto_mbedtls.cpp +++ b/modules/mbedtls/crypto_mbedtls.cpp @@ -49,8 +49,8 @@ #define PEM_END_CRT "-----END CERTIFICATE-----\n" #define PEM_MIN_SIZE 54 -CryptoKey *CryptoKeyMbedTLS::create() { - return memnew(CryptoKeyMbedTLS); +CryptoKey *CryptoKeyMbedTLS::create(bool p_notify_postinitialize) { + return static_cast<CryptoKey *>(ClassDB::creator<CryptoKeyMbedTLS>(p_notify_postinitialize)); } Error CryptoKeyMbedTLS::load(const String &p_path, bool p_public_only) { @@ -153,8 +153,8 @@ int CryptoKeyMbedTLS::_parse_key(const uint8_t *p_buf, int p_size) { #endif } -X509Certificate *X509CertificateMbedTLS::create() { - return memnew(X509CertificateMbedTLS); +X509Certificate *X509CertificateMbedTLS::create(bool p_notify_postinitialize) { + return static_cast<X509Certificate *>(ClassDB::creator<X509CertificateMbedTLS>(p_notify_postinitialize)); } Error X509CertificateMbedTLS::load(const String &p_path) { @@ -250,8 +250,8 @@ bool HMACContextMbedTLS::is_md_type_allowed(mbedtls_md_type_t p_md_type) { } } -HMACContext *HMACContextMbedTLS::create() { - return memnew(HMACContextMbedTLS); +HMACContext *HMACContextMbedTLS::create(bool p_notify_postinitialize) { + return static_cast<HMACContext *>(ClassDB::creator<HMACContextMbedTLS>(p_notify_postinitialize)); } Error HMACContextMbedTLS::start(HashingContext::HashType p_hash_type, const PackedByteArray &p_key) { @@ -309,8 +309,8 @@ HMACContextMbedTLS::~HMACContextMbedTLS() { } } -Crypto *CryptoMbedTLS::create() { - return memnew(CryptoMbedTLS); +Crypto *CryptoMbedTLS::create(bool p_notify_postinitialize) { + return static_cast<Crypto *>(ClassDB::creator<CryptoMbedTLS>(p_notify_postinitialize)); } void CryptoMbedTLS::initialize_crypto() { diff --git a/modules/mbedtls/crypto_mbedtls.h b/modules/mbedtls/crypto_mbedtls.h index 52918cedf0..5e1da550d7 100644 --- a/modules/mbedtls/crypto_mbedtls.h +++ b/modules/mbedtls/crypto_mbedtls.h @@ -49,7 +49,7 @@ private: int _parse_key(const uint8_t *p_buf, int p_size); public: - static CryptoKey *create(); + static CryptoKey *create(bool p_notify_postinitialize = true); static void make_default() { CryptoKey::_create = create; } static void finalize() { CryptoKey::_create = nullptr; } @@ -80,7 +80,7 @@ private: int locks; public: - static X509Certificate *create(); + static X509Certificate *create(bool p_notify_postinitialize = true); static void make_default() { X509Certificate::_create = create; } static void finalize() { X509Certificate::_create = nullptr; } @@ -112,7 +112,7 @@ private: void *ctx = nullptr; public: - static HMACContext *create(); + static HMACContext *create(bool p_notify_postinitialize = true); static void make_default() { HMACContext::_create = create; } static void finalize() { HMACContext::_create = nullptr; } @@ -133,7 +133,7 @@ private: static X509CertificateMbedTLS *default_certs; public: - static Crypto *create(); + static Crypto *create(bool p_notify_postinitialize = true); static void initialize_crypto(); static void finalize_crypto(); static X509CertificateMbedTLS *get_default_certificates(); diff --git a/modules/mbedtls/dtls_server_mbedtls.cpp b/modules/mbedtls/dtls_server_mbedtls.cpp index e466fe15d6..b64bdcb192 100644 --- a/modules/mbedtls/dtls_server_mbedtls.cpp +++ b/modules/mbedtls/dtls_server_mbedtls.cpp @@ -54,8 +54,8 @@ Ref<PacketPeerDTLS> DTLSServerMbedTLS::take_connection(Ref<PacketPeerUDP> p_udp_ return out; } -DTLSServer *DTLSServerMbedTLS::_create_func() { - return memnew(DTLSServerMbedTLS); +DTLSServer *DTLSServerMbedTLS::_create_func(bool p_notify_postinitialize) { + return static_cast<DTLSServer *>(ClassDB::creator<DTLSServerMbedTLS>(p_notify_postinitialize)); } void DTLSServerMbedTLS::initialize() { diff --git a/modules/mbedtls/dtls_server_mbedtls.h b/modules/mbedtls/dtls_server_mbedtls.h index 59befecf43..18661bf505 100644 --- a/modules/mbedtls/dtls_server_mbedtls.h +++ b/modules/mbedtls/dtls_server_mbedtls.h @@ -37,7 +37,7 @@ class DTLSServerMbedTLS : public DTLSServer { private: - static DTLSServer *_create_func(); + static DTLSServer *_create_func(bool p_notify_postinitialize); Ref<TLSOptions> tls_options; Ref<CookieContextMbedTLS> cookies; diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp index c7373481ca..62d27405d8 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.cpp +++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp @@ -270,8 +270,8 @@ PacketPeerMbedDTLS::Status PacketPeerMbedDTLS::get_status() const { return status; } -PacketPeerDTLS *PacketPeerMbedDTLS::_create_func() { - return memnew(PacketPeerMbedDTLS); +PacketPeerDTLS *PacketPeerMbedDTLS::_create_func(bool p_notify_postinitialize) { + return static_cast<PacketPeerDTLS *>(ClassDB::creator<PacketPeerMbedDTLS>(p_notify_postinitialize)); } void PacketPeerMbedDTLS::initialize_dtls() { diff --git a/modules/mbedtls/packet_peer_mbed_dtls.h b/modules/mbedtls/packet_peer_mbed_dtls.h index 2cff7a3589..881a5fdd0e 100644 --- a/modules/mbedtls/packet_peer_mbed_dtls.h +++ b/modules/mbedtls/packet_peer_mbed_dtls.h @@ -50,7 +50,7 @@ private: Ref<PacketPeerUDP> base; - static PacketPeerDTLS *_create_func(); + static PacketPeerDTLS *_create_func(bool p_notify_postinitialize); static int bio_recv(void *ctx, unsigned char *buf, size_t len); static int bio_send(void *ctx, const unsigned char *buf, size_t len); diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp index a359b42041..b4200410fb 100644 --- a/modules/mbedtls/stream_peer_mbedtls.cpp +++ b/modules/mbedtls/stream_peer_mbedtls.cpp @@ -295,8 +295,8 @@ Ref<StreamPeer> StreamPeerMbedTLS::get_stream() const { return base; } -StreamPeerTLS *StreamPeerMbedTLS::_create_func() { - return memnew(StreamPeerMbedTLS); +StreamPeerTLS *StreamPeerMbedTLS::_create_func(bool p_notify_postinitialize) { + return static_cast<StreamPeerTLS *>(ClassDB::creator<StreamPeerMbedTLS>(p_notify_postinitialize)); } void StreamPeerMbedTLS::initialize_tls() { diff --git a/modules/mbedtls/stream_peer_mbedtls.h b/modules/mbedtls/stream_peer_mbedtls.h index a8080f0960..b4f80b614c 100644 --- a/modules/mbedtls/stream_peer_mbedtls.h +++ b/modules/mbedtls/stream_peer_mbedtls.h @@ -42,7 +42,7 @@ private: Ref<StreamPeer> base; - static StreamPeerTLS *_create_func(); + static StreamPeerTLS *_create_func(bool p_notify_postinitialize); static int bio_recv(void *ctx, unsigned char *buf, size_t len); static int bio_send(void *ctx, const unsigned char *buf, size_t len); diff --git a/modules/mbedtls/tls_context_mbedtls.cpp b/modules/mbedtls/tls_context_mbedtls.cpp index eaea7b9293..f5c196596e 100644 --- a/modules/mbedtls/tls_context_mbedtls.cpp +++ b/modules/mbedtls/tls_context_mbedtls.cpp @@ -153,7 +153,7 @@ Error TLSContextMbedTLS::init_client(int p_transport, const String &p_hostname, int authmode = MBEDTLS_SSL_VERIFY_REQUIRED; bool unsafe = p_options->is_unsafe_client(); - if (unsafe && p_options->get_trusted_ca_chain().is_valid()) { + if (unsafe && p_options->get_trusted_ca_chain().is_null()) { authmode = MBEDTLS_SSL_VERIFY_NONE; } diff --git a/modules/mobile_vr/doc_classes/MobileVRInterface.xml b/modules/mobile_vr/doc_classes/MobileVRInterface.xml index 0dbe06d220..61d802aea3 100644 --- a/modules/mobile_vr/doc_classes/MobileVRInterface.xml +++ b/modules/mobile_vr/doc_classes/MobileVRInterface.xml @@ -10,7 +10,7 @@ [codeblock] var interface = XRServer.find_interface("Native mobile") if interface and interface.initialize(): - get_viewport().xr = true + get_viewport().use_xr = true [/codeblock] </description> <tutorials> diff --git a/modules/mono/config.py b/modules/mono/config.py index 5ebdb83b36..0573fc662a 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -11,9 +11,11 @@ def can_build(env, platform): def configure(env): # Check if the platform has marked mono as supported. supported = env.get("supported", []) - if "mono" not in supported: - raise RuntimeError("This module does not currently support building for this platform") + import sys + + print("The 'mono' module does not currently support building for this platform. Aborting.") + sys.exit(255) env.add_module_version_string("mono") diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 36c8a40ed9..6d561c1566 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -148,7 +148,7 @@ void CSharpLanguage::finalize() { finalizing = true; - // Make sure all script binding gchandles are released before finalizing GDMono + // Make sure all script binding gchandles are released before finalizing GDMono. for (KeyValue<Object *, CSharpScriptBinding> &E : script_bindings) { CSharpScriptBinding &script_binding = E.value; @@ -156,6 +156,10 @@ void CSharpLanguage::finalize() { script_binding.gchandle.release(); script_binding.inited = false; } + + // Make sure we clear all the instance binding callbacks so they don't get called + // after finalizing the C# language. + script_binding.owner->free_instance_binding(this); } if (gdmono) { @@ -1227,6 +1231,11 @@ void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_bin } GDExtensionBool CSharpLanguage::_instance_binding_reference_callback(void *p_token, void *p_binding, GDExtensionBool p_reference) { + // Instance bindings callbacks can only be called if the C# language is available. + // Failing this assert usually means that we didn't clear the instance binding in some Object + // and the C# language has already been finalized. + DEV_ASSERT(CSharpLanguage::get_singleton() != nullptr); + CRASH_COND(!p_binding); CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)p_binding)->get(); @@ -1662,7 +1671,7 @@ bool CSharpInstance::_reference_owner_unsafe() { // but the managed instance is alive, the refcount will be 1 instead of 0. // See: _unreference_owner_unsafe() - // May not me referenced yet, so we must use init_ref() instead of reference() + // May not be referenced yet, so we must use init_ref() instead of reference() if (static_cast<RefCounted *>(owner)->init_ref()) { CSharpLanguage::get_singleton()->post_unsafe_reference(owner); unsafe_referenced = true; @@ -2351,8 +2360,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg if (!ok) { // Important to clear this before destroying the script instance here instance->script = Ref<CSharpScript>(); - instance->owner = nullptr; p_owner->set_script_instance(nullptr); + instance->owner = nullptr; return nullptr; } 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 74623a60ba..ee624a443d 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 @@ -7,7 +7,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>Godot.NET.Sdk</PackageId> - <Version>4.3.0</Version> + <Version>4.4.0</Version> <PackageVersion>$(PackageVersion_Godot_NET_Sdk)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs index 217f467637..9a8b3ea846 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportDiagnostics_GD0107_OK_ScriptPropertyDefVal.generated.cs @@ -11,11 +11,27 @@ partial class ExportDiagnostics_GD0107_OK [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] internal new static global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant> GetGodotPropertyDefaultValues() { - var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(2); + var values = new global::System.Collections.Generic.Dictionary<global::Godot.StringName, global::Godot.Variant>(10); global::Godot.Node __NodeProperty_default_value = default; values.Add(PropertyName.@NodeProperty, global::Godot.Variant.From<global::Godot.Node>(__NodeProperty_default_value)); + global::Godot.Node[] __SystemArrayOfNodesProperty_default_value = default; + values.Add(PropertyName.@SystemArrayOfNodesProperty, global::Godot.Variant.CreateFrom(__SystemArrayOfNodesProperty_default_value)); + global::Godot.Collections.Array<global::Godot.Node> __GodotArrayOfNodesProperty_default_value = default; + values.Add(PropertyName.@GodotArrayOfNodesProperty, global::Godot.Variant.CreateFrom(__GodotArrayOfNodesProperty_default_value)); + global::Godot.Collections.Dictionary<global::Godot.Node, string> __GodotDictionaryWithNodeAsKeyProperty_default_value = default; + values.Add(PropertyName.@GodotDictionaryWithNodeAsKeyProperty, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsKeyProperty_default_value)); + global::Godot.Collections.Dictionary<string, global::Godot.Node> __GodotDictionaryWithNodeAsValueProperty_default_value = default; + values.Add(PropertyName.@GodotDictionaryWithNodeAsValueProperty, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsValueProperty_default_value)); global::Godot.Node __NodeField_default_value = default; values.Add(PropertyName.@NodeField, global::Godot.Variant.From<global::Godot.Node>(__NodeField_default_value)); + global::Godot.Node[] __SystemArrayOfNodesField_default_value = default; + values.Add(PropertyName.@SystemArrayOfNodesField, global::Godot.Variant.CreateFrom(__SystemArrayOfNodesField_default_value)); + global::Godot.Collections.Array<global::Godot.Node> __GodotArrayOfNodesField_default_value = default; + values.Add(PropertyName.@GodotArrayOfNodesField, global::Godot.Variant.CreateFrom(__GodotArrayOfNodesField_default_value)); + global::Godot.Collections.Dictionary<global::Godot.Node, string> __GodotDictionaryWithNodeAsKeyField_default_value = default; + values.Add(PropertyName.@GodotDictionaryWithNodeAsKeyField, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsKeyField_default_value)); + global::Godot.Collections.Dictionary<string, global::Godot.Node> __GodotDictionaryWithNodeAsValueField_default_value = default; + values.Add(PropertyName.@GodotDictionaryWithNodeAsValueField, global::Godot.Variant.CreateFrom(__GodotDictionaryWithNodeAsValueField_default_value)); return values; } #endif // TOOLS diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs index 067783ea66..4613d883c2 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/Sources/ExportDiagnostics_GD0107.cs @@ -1,4 +1,5 @@ using Godot; +using Godot.Collections; public partial class ExportDiagnostics_GD0107_OK : Node { @@ -6,7 +7,31 @@ public partial class ExportDiagnostics_GD0107_OK : Node public Node NodeField; [Export] + public Node[] SystemArrayOfNodesField; + + [Export] + public Array<Node> GodotArrayOfNodesField; + + [Export] + public Dictionary<Node, string> GodotDictionaryWithNodeAsKeyField; + + [Export] + public Dictionary<string, Node> GodotDictionaryWithNodeAsValueField; + + [Export] public Node NodeProperty { get; set; } + + [Export] + public Node[] SystemArrayOfNodesProperty { get; set; } + + [Export] + public Array<Node> GodotArrayOfNodesProperty { get; set; } + + [Export] + public Dictionary<Node, string> GodotDictionaryWithNodeAsKeyProperty { get; set; } + + [Export] + public Dictionary<string, Node> GodotDictionaryWithNodeAsValueProperty { get; set; } } public partial class ExportDiagnostics_GD0107_KO : Resource @@ -15,5 +40,29 @@ public partial class ExportDiagnostics_GD0107_KO : Resource public Node {|GD0107:NodeField|}; [Export] + public Node[] {|GD0107:SystemArrayOfNodesField|}; + + [Export] + public Array<Node> {|GD0107:GodotArrayOfNodesField|}; + + [Export] + public Dictionary<Node, string> {|GD0107:GodotDictionaryWithNodeAsKeyField|}; + + [Export] + public Dictionary<string, Node> {|GD0107:GodotDictionaryWithNodeAsValueField|}; + + [Export] public Node {|GD0107:NodeProperty|} { get; set; } + + [Export] + public Node[] {|GD0107:SystemArrayOfNodesProperty|} { get; set; } + + [Export] + public Array<Node> {|GD0107:GodotArrayOfNodesProperty|} { get; set; } + + [Export] + public Dictionary<Node, string> {|GD0107:GodotDictionaryWithNodeAsKeyProperty|} { get; set; } + + [Export] + public Dictionary<string, Node> {|GD0107:GodotDictionaryWithNodeAsValueProperty|} { get; set; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj index 1aa2979e76..8e407da7a6 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj @@ -9,7 +9,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>Godot.SourceGenerators</PackageId> - <Version>4.3.0</Version> + <Version>4.4.0</Version> <PackageVersion>$(PackageVersion_Godot_SourceGenerators)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs index efe88d8468..626f51ecae 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -196,16 +196,13 @@ namespace Godot.SourceGenerators continue; } - if (marshalType == MarshalType.GodotObjectOrDerived) + if (!isNode && MemberHasNodeType(propertyType, marshalType.Value)) { - if (!isNode && propertyType.InheritsFrom("GodotSharp", GodotClasses.Node)) - { - context.ReportDiagnostic(Diagnostic.Create( - Common.OnlyNodesShouldExportNodesRule, - property.Locations.FirstLocationWithSourceTreeOrDefault() - )); - continue; - } + context.ReportDiagnostic(Diagnostic.Create( + Common.OnlyNodesShouldExportNodesRule, + property.Locations.FirstLocationWithSourceTreeOrDefault() + )); + continue; } var propertyDeclarationSyntax = property.DeclaringSyntaxReferences @@ -315,16 +312,13 @@ namespace Godot.SourceGenerators continue; } - if (marshalType == MarshalType.GodotObjectOrDerived) + if (!isNode && MemberHasNodeType(fieldType, marshalType.Value)) { - if (!isNode && fieldType.InheritsFrom("GodotSharp", GodotClasses.Node)) - { - context.ReportDiagnostic(Diagnostic.Create( - Common.OnlyNodesShouldExportNodesRule, - field.Locations.FirstLocationWithSourceTreeOrDefault() - )); - continue; - } + context.ReportDiagnostic(Diagnostic.Create( + Common.OnlyNodesShouldExportNodesRule, + field.Locations.FirstLocationWithSourceTreeOrDefault() + )); + continue; } EqualsValueClauseSyntax? initializer = field.DeclaringSyntaxReferences @@ -424,6 +418,27 @@ namespace Godot.SourceGenerators context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); } + private static bool MemberHasNodeType(ITypeSymbol memberType, MarshalType marshalType) + { + if (marshalType == MarshalType.GodotObjectOrDerived) + { + return memberType.InheritsFrom("GodotSharp", GodotClasses.Node); + } + if (marshalType == MarshalType.GodotObjectOrDerivedArray) + { + var elementType = ((IArrayTypeSymbol)memberType).ElementType; + return elementType.InheritsFrom("GodotSharp", GodotClasses.Node); + } + if (memberType is INamedTypeSymbol { IsGenericType: true } genericType) + { + return genericType.TypeArguments + .Any(static typeArgument + => typeArgument.InheritsFrom("GodotSharp", GodotClasses.Node)); + } + + return false; + } + private struct ExportedPropertyMetadata { public ExportedPropertyMetadata(string name, MarshalType type, ITypeSymbol typeSymbol, string? value) diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index d3720dcb72..ede0600ac1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -355,24 +355,23 @@ namespace GodotTools.Export if (outputPaths.Count > 2) { // lipo the simulator binaries together - // TODO: Move this to the native lipo implementation we have in the macos export plugin. - var lipoArgs = new List<string>(); - lipoArgs.Add("-create"); - lipoArgs.AddRange(outputPaths.Skip(1).Select(x => Path.Combine(x, $"{GodotSharpDirs.ProjectAssemblyName}.dylib"))); - lipoArgs.Add("-output"); - lipoArgs.Add(Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib")); - int lipoExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("lipo"), lipoArgs); - if (lipoExitCode != 0) - throw new InvalidOperationException($"Command 'lipo' exited with code: {lipoExitCode}."); + string outputPath = Path.Combine(outputPaths[1], $"{GodotSharpDirs.ProjectAssemblyName}.dylib"); + string[] files = outputPaths + .Skip(1) + .Select(path => Path.Combine(path, $"{GodotSharpDirs.ProjectAssemblyName}.dylib")) + .ToArray(); + + if (!Internal.LipOCreateFile(outputPath, files)) + { + throw new InvalidOperationException($"Failed to 'lipo' simulator binaries."); + } outputPaths.RemoveRange(2, outputPaths.Count - 2); } - var xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, - $"{GodotSharpDirs.ProjectAssemblyName}_aot.xcframework"); - if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths, - Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, xcFrameworkPath))) + string xcFrameworkPath = Path.Combine(GodotSharpDirs.ProjectBaseOutputPath, publishConfig.BuildConfig, $"{GodotSharpDirs.ProjectAssemblyName}_aot.xcframework"); + if (!BuildManager.GenerateXCFrameworkBlocking(outputPaths, xcFrameworkPath)) { throw new InvalidOperationException("Failed to generate xcframework."); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs b/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs deleted file mode 100644 index 023f46b685..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Export/XcodeHelper.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.IO; - -namespace GodotTools.Export -{ - public static class XcodeHelper - { - private static string? _XcodePath = null; - - public static string XcodePath - { - get - { - if (_XcodePath == null) - { - _XcodePath = FindXcode(); - - if (_XcodePath == null) - throw new FileNotFoundException("Could not find Xcode."); - } - - return _XcodePath; - } - } - - private static string? FindSelectedXcode() - { - var outputWrapper = new Godot.Collections.Array(); - - int exitCode = Godot.OS.Execute("xcode-select", new string[] { "--print-path" }, output: outputWrapper); - - if (exitCode == 0) - { - string output = (string)outputWrapper[0]; - return output.Trim(); - } - - Console.Error.WriteLine($"'xcode-select --print-path' exited with code: {exitCode}"); - - return null; - } - - public static string? FindXcode() - { - string? selectedXcode = FindSelectedXcode(); - if (selectedXcode != null) - { - if (Directory.Exists(Path.Combine(selectedXcode, "Contents", "Developer"))) - return selectedXcode; - - // The path already pointed to Contents/Developer - var dirInfo = new DirectoryInfo(selectedXcode); - if (dirInfo is not { Parent.Name: "Contents", Name: "Developer" }) - { - Console.WriteLine(Path.GetDirectoryName(selectedXcode)); - Console.WriteLine(System.IO.Directory.GetParent(selectedXcode)?.Name); - Console.Error.WriteLine("Unrecognized path for selected Xcode"); - } - else - { - return System.IO.Path.GetFullPath($"{selectedXcode}/../.."); - } - } - else - { - Console.Error.WriteLine("Could not find the selected Xcode; trying with a hint path"); - } - - const string XcodeHintPath = "/Applications/Xcode.app"; - - if (Directory.Exists(XcodeHintPath)) - { - if (Directory.Exists(Path.Combine(XcodeHintPath, "Contents", "Developer"))) - return XcodeHintPath; - - Console.Error.WriteLine($"Found Xcode at '{XcodeHintPath}' but it's missing the 'Contents/Developer' sub-directory"); - } - - return null; - } - - public static string FindXcodeTool(string toolName) - { - string XcodeDefaultToolchain = Path.Combine(XcodePath, "Contents", "Developer", "Toolchains", "XcodeDefault.xctoolchain"); - - string path = Path.Combine(XcodeDefaultToolchain, "usr", "bin", toolName); - if (File.Exists(path)) - return path; - - throw new FileNotFoundException($"Cannot find Xcode tool: {toolName}"); - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 175bb78051..225ac4073b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -35,6 +35,13 @@ namespace GodotTools.Internals return godot_icall_Internal_IsMacOSAppBundleInstalled(bundleIdIn); } + public static bool LipOCreateFile(string outputPath, string[] files) + { + using godot_string outputPathIn = Marshaling.ConvertStringToNative(outputPath); + using godot_packed_string_array filesIn = Marshaling.ConvertSystemArrayToNativePackedStringArray(files); + return godot_icall_Internal_LipOCreateFile(outputPathIn, filesIn); + } + public static bool GodotIs32Bits() => godot_icall_Internal_GodotIs32Bits(); public static bool GodotIsRealTDouble() => godot_icall_Internal_GodotIsRealTDouble(); @@ -121,6 +128,8 @@ namespace GodotTools.Internals private static partial bool godot_icall_Internal_IsMacOSAppBundleInstalled(in godot_string bundleId); + private static partial bool godot_icall_Internal_LipOCreateFile(in godot_string outputPath, in godot_packed_string_array files); + private static partial bool godot_icall_Internal_GodotIs32Bits(); private static partial bool godot_icall_Internal_GodotIsRealTDouble(); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 9a76a25639..b26f6d1bbf 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -2184,7 +2184,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Add native constructor static field output << MEMBER_BEGIN << "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" - << INDENT1 "private static readonly unsafe delegate* unmanaged<IntPtr> " + << INDENT1 "private static readonly unsafe delegate* unmanaged<godot_bool, IntPtr> " << CS_STATIC_FIELD_NATIVE_CTOR " = " ICALL_CLASSDB_GET_CONSTRUCTOR << "(" BINDINGS_NATIVE_NAME_FIELD ");\n"; } diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 03d8b4eab6..7322a47630 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -44,6 +44,7 @@ #include "editor/editor_node.h" #include "editor/editor_paths.h" #include "editor/editor_settings.h" +#include "editor/export/lipo.h" #include "editor/gui/editor_run_bar.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/themes/editor_scale.h" @@ -117,6 +118,13 @@ bool godot_icall_Internal_IsMacOSAppBundleInstalled(const godot_string *p_bundle #endif } +bool godot_icall_Internal_LipOCreateFile(const godot_string *p_output_path, const godot_packed_array *p_files) { + String output_path = *reinterpret_cast<const String *>(p_output_path); + PackedStringArray files = *reinterpret_cast<const PackedStringArray *>(p_files); + LipO lip; + return lip.create_file(output_path, files); +} + bool godot_icall_Internal_GodotIs32Bits() { return sizeof(void *) == 4; } @@ -258,6 +266,7 @@ static const void *unmanaged_callbacks[]{ (void *)godot_icall_EditorProgress_Step, (void *)godot_icall_Internal_FullExportTemplatesDir, (void *)godot_icall_Internal_IsMacOSAppBundleInstalled, + (void *)godot_icall_Internal_LipOCreateFile, (void *)godot_icall_Internal_GodotIs32Bits, (void *)godot_icall_Internal_GodotIsRealTDouble, (void *)godot_icall_Internal_GodotMainIteration, diff --git a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs index cd335934db..ece1ab44a2 100644 --- a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs +++ b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs @@ -3,6 +3,8 @@ using _BINDINGS_NAMESPACE_; using System; +[Tool] +[GlobalClass] public partial class VisualShaderNode_CLASS_ : _BASE_ { public override string _GetName() @@ -20,37 +22,37 @@ public partial class VisualShaderNode_CLASS_ : _BASE_ return ""; } - public override long _GetReturnIconType() + public override VisualShaderNode.PortType _GetReturnIconType() { return 0; } - public override long _GetInputPortCount() + public override int _GetInputPortCount() { return 0; } - public override string _GetInputPortName(long port) + public override string _GetInputPortName(int port) { return ""; } - public override long _GetInputPortType(long port) + public override VisualShaderNode.PortType _GetInputPortType(int port) { return 0; } - public override long _GetOutputPortCount() + public override int _GetOutputPortCount() { return 1; } - public override string _GetOutputPortName(long port) + public override string _GetOutputPortName(int port) { return "result"; } - public override long _GetOutputPortType(long port) + public override VisualShaderNode.PortType _GetOutputPortType(int port) { return 0; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs index 33f0850a8d..ef550eab0d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs @@ -314,13 +314,20 @@ namespace Godot /// <returns>A vector representing the support.</returns> public readonly Vector3 GetSupport(Vector3 dir) { - Vector3 halfExtents = _size * 0.5f; - Vector3 ofs = _position + halfExtents; - - return ofs + new Vector3( - dir.X > 0f ? halfExtents.X : -halfExtents.X, - dir.Y > 0f ? halfExtents.Y : -halfExtents.Y, - dir.Z > 0f ? halfExtents.Z : -halfExtents.Z); + Vector3 support = _position; + if (dir.X > 0.0f) + { + support.X += _size.X; + } + if (dir.Y > 0.0f) + { + support.Y += _size.Y; + } + if (dir.Z > 0.0f) + { + support.Z += _size.Z; + } + return support; } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index 5cc2a8026e..901700067d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -241,11 +241,17 @@ namespace Godot.Bridge if (outIconPath != null) { - var iconAttr = scriptType.GetCustomAttributes(inherit: false) + IconAttribute? iconAttr = scriptType.GetCustomAttributes(inherit: false) .OfType<IconAttribute>() .FirstOrDefault(); - *outIconPath = Marshaling.ConvertStringToNative(iconAttr?.Path); + if (!string.IsNullOrEmpty(iconAttr?.Path)) + { + string iconPath = iconAttr.Path.IsAbsolutePath() + ? iconAttr.Path.SimplifyPath() + : scriptPathStr.GetBaseDir().PathJoin(iconAttr.Path).SimplifyPath(); + *outIconPath = Marshaling.ConvertStringToNative(iconPath); + } } if (outBaseType != null) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs index 563a6abe9b..1fc6e54e09 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/GodotObjectExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Godot.NativeInterop; #nullable enable @@ -51,7 +52,7 @@ namespace Godot /// </summary> /// <param name="instance">The instance to check.</param> /// <returns>If the instance is a valid object.</returns> - public static bool IsInstanceValid(GodotObject? instance) + public static bool IsInstanceValid([NotNullWhen(true)] GodotObject? instance) { return instance != null && instance.NativeInstance != IntPtr.Zero; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs index 0be9cdc953..c094eaed77 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotObject.base.cs @@ -30,7 +30,7 @@ namespace Godot } internal unsafe void ConstructAndInitialize( - delegate* unmanaged<IntPtr> nativeCtor, + delegate* unmanaged<godot_bool, IntPtr> nativeCtor, StringName nativeName, Type cachedType, bool refCounted @@ -40,7 +40,8 @@ namespace Godot { Debug.Assert(nativeCtor != null); - NativePtr = nativeCtor(); + // Need postinitialization. + NativePtr = nativeCtor(godot_bool.True); InteropUtils.TieManagedToUnmanaged(this, NativePtr, nativeName, refCounted, GetType(), cachedType); @@ -260,7 +261,7 @@ namespace Godot return methodBind; } - internal static unsafe delegate* unmanaged<IntPtr> ClassDB_get_constructor(StringName type) + internal static unsafe delegate* unmanaged<godot_bool, IntPtr> ClassDB_get_constructor(StringName type) { // for some reason the '??' operator doesn't support 'delegate*' var typeSelf = (godot_string_name)type.NativeValue; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index cfd9ed7acc..6a643833f6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -47,7 +47,7 @@ namespace Godot.NativeInterop public static partial IntPtr godotsharp_method_bind_get_method_with_compatibility( in godot_string_name p_classname, in godot_string_name p_methodname, ulong p_hash); - public static partial delegate* unmanaged<IntPtr> godotsharp_get_class_constructor( + public static partial delegate* unmanaged<godot_bool, IntPtr> godotsharp_get_class_constructor( in godot_string_name p_classname); public static partial IntPtr godotsharp_engine_get_singleton(in godot_string p_name); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index 19721b6cca..4f606cc6b3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -172,6 +172,26 @@ namespace Godot } /// <summary> + /// Returns the support point in a given direction. + /// This is useful for collision detection algorithms. + /// </summary> + /// <param name="direction">The direction to find support for.</param> + /// <returns>A vector representing the support.</returns> + public readonly Vector2 GetSupport(Vector2 direction) + { + Vector2 support = _position; + if (direction.X > 0.0f) + { + support.X += _size.X; + } + if (direction.Y > 0.0f) + { + support.Y += _size.Y; + } + return support; + } + + /// <summary> /// Returns a copy of the <see cref="Rect2"/> grown by the specified amount /// on all sides. /// </summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index a5fa89d3bf..f943a3049d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -47,7 +47,7 @@ namespace Godot { get { - real_t detSign = Mathf.Sign(BasisDeterminant()); + real_t detSign = Mathf.Sign(Determinant()); return new Vector2(X.Length(), detSign * Y.Length()); } } @@ -59,7 +59,7 @@ namespace Godot { get { - real_t detSign = Mathf.Sign(BasisDeterminant()); + real_t detSign = Mathf.Sign(Determinant()); return Mathf.Acos(X.Normalized().Dot(detSign * Y.Normalized())) - Mathf.Pi * 0.5f; } } @@ -135,7 +135,7 @@ namespace Godot /// <returns>The inverse transformation matrix.</returns> public readonly Transform2D AffineInverse() { - real_t det = BasisDeterminant(); + real_t det = Determinant(); if (det == 0) throw new InvalidOperationException("Matrix determinant is zero and cannot be inverted."); @@ -157,15 +157,16 @@ namespace Godot /// <summary> /// Returns the determinant of the basis matrix. If the basis is - /// uniformly scaled, its determinant is the square of the scale. + /// uniformly scaled, then its determinant equals the square of the + /// scale factor. /// - /// A negative determinant means the Y scale is negative. - /// A zero determinant means the basis isn't invertible, - /// and is usually considered invalid. + /// A negative determinant means the basis was flipped, so one part of + /// the scale is negative. A zero determinant means the basis isn't + /// invertible, and is usually considered invalid. /// </summary> /// <returns>The determinant of the basis matrix.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - private readonly real_t BasisDeterminant() + public readonly real_t Determinant() { return (X.X * Y.Y) - (X.Y * Y.X); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 50bf56d832..f5b64ff81b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -869,7 +869,7 @@ namespace Godot } /// <summary> - /// Multiplies each component of the <see cref="Vector2"/> + /// Divides each component of the <see cref="Vector2"/> /// by the given <see cref="real_t"/>. /// </summary> /// <param name="vec">The dividend vector.</param> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 27f2713efa..ef66d5bbe0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -692,10 +692,18 @@ namespace Godot // Zero length vectors have no angle, so the best we can do is either lerp or throw an error. return Lerp(to, weight); } + Vector3 axis = Cross(to); + real_t axisLengthSquared = axis.LengthSquared(); + if (axisLengthSquared == 0.0) + { + // Colinear vectors have no rotation axis or angle between them, so the best we can do is lerp. + return Lerp(to, weight); + } + axis /= Mathf.Sqrt(axisLengthSquared); real_t startLength = Mathf.Sqrt(startLengthSquared); real_t resultLength = Mathf.Lerp(startLength, Mathf.Sqrt(endLengthSquared), weight); real_t angle = AngleTo(to); - return Rotated(Cross(to).Normalized(), angle * weight) * (resultLength / startLength); + return Rotated(axis, angle * weight) * (resultLength / startLength); } /// <summary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 6b25087c93..b838f8eac7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -17,7 +17,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>GodotSharp</PackageId> - <Version>4.3.0</Version> + <Version>4.4.0</Version> <PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharp</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj index 4561fdaf2b..65b4824f94 100644 --- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj @@ -15,7 +15,7 @@ <Authors>Godot Engine contributors</Authors> <PackageId>GodotSharpEditor</PackageId> - <Version>4.3.0</Version> + <Version>4.4.0</Version> <PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion> <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharpEditor</RepositoryUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 80e9fdf77f..73c10eba83 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -58,7 +58,7 @@ extern "C" { // For ArrayPrivate and DictionaryPrivate static_assert(sizeof(SafeRefCount) == sizeof(uint32_t)); -typedef Object *(*godotsharp_class_creation_func)(); +typedef Object *(*godotsharp_class_creation_func)(bool); bool godotsharp_dotnet_module_is_initialized() { return GDMono::get_singleton()->is_initialized(); diff --git a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml index 3da245f806..b620292519 100644 --- a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml @@ -48,11 +48,11 @@ </methods> <members> <member name="spawn_function" type="Callable" setter="set_spawn_function" getter="get_spawn_function"> - Method called on all peers when for every custom [method spawn] requested by the authority. Will receive the [code]data[/code] parameter, and should return a [Node] that is not in the scene tree. + Method called on all peers when a custom [method spawn] is requested by the authority. Will receive the [code]data[/code] parameter, and should return a [Node] that is not in the scene tree. [b]Note:[/b] The returned node should [b]not[/b] be added to the scene with [method Node.add_child]. This is done automatically. </member> <member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0"> - Maximum nodes that is allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns. + Maximum number of nodes allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns. When set to [code]0[/code] (the default), there is no limit. </member> <member name="spawn_path" type="NodePath" setter="set_spawn_path" getter="get_spawn_path" default="NodePath("")"> diff --git a/modules/multiplayer/editor/replication_editor.cpp b/modules/multiplayer/editor/replication_editor.cpp index d242b06c29..851ad85876 100644 --- a/modules/multiplayer/editor/replication_editor.cpp +++ b/modules/multiplayer/editor/replication_editor.cpp @@ -430,7 +430,7 @@ void ReplicationEditor::_tree_item_edited() { undo_redo->add_do_method(config.ptr(), "property_set_spawn", prop, value); undo_redo->add_undo_method(config.ptr(), "property_set_spawn", prop, !value); undo_redo->add_do_method(this, "_update_value", prop, column, value ? 1 : 0); - undo_redo->add_undo_method(this, "_update_value", prop, column, value ? 1 : 0); + undo_redo->add_undo_method(this, "_update_value", prop, column, value ? 0 : 1); undo_redo->commit_action(); } else if (column == 2) { undo_redo->create_action(TTR("Set sync property")); diff --git a/modules/multiplayer/scene_cache_interface.cpp b/modules/multiplayer/scene_cache_interface.cpp index af43123b29..99c8930e92 100644 --- a/modules/multiplayer/scene_cache_interface.cpp +++ b/modules/multiplayer/scene_cache_interface.cpp @@ -54,11 +54,14 @@ void SceneCacheInterface::_remove_node_cache(ObjectID p_oid) { if (nc->cache_id) { assigned_ids.erase(nc->cache_id); } +#if 0 + // TODO: Find a way to cleanup recv_nodes without breaking visibility and RPCs interactions. for (KeyValue<int, int> &E : nc->recv_ids) { PeerInfo *pinfo = peers_info.getptr(E.key); ERR_CONTINUE(!pinfo); pinfo->recv_nodes.erase(E.value); } +#endif for (KeyValue<int, bool> &E : nc->confirmed_peers) { PeerInfo *pinfo = peers_info.getptr(E.key); ERR_CONTINUE(!pinfo); @@ -73,9 +76,12 @@ void SceneCacheInterface::on_peer_change(int p_id, bool p_connected) { } else { PeerInfo *pinfo = peers_info.getptr(p_id); ERR_FAIL_NULL(pinfo); // Bug. - for (KeyValue<int, ObjectID> E : pinfo->recv_nodes) { - NodeCache *nc = nodes_cache.getptr(E.value); - ERR_CONTINUE(!nc); + for (KeyValue<int, RecvNode> E : pinfo->recv_nodes) { + NodeCache *nc = nodes_cache.getptr(E.value.oid); + if (!nc) { + // Node might have already been deleted locally. + continue; + } nc->recv_ids.erase(p_id); } for (const ObjectID &oid : pinfo->sent_nodes) { @@ -115,7 +121,7 @@ void SceneCacheInterface::process_simplify_path(int p_from, const uint8_t *p_pac ERR_PRINT("The rpc node checksum failed. Make sure to have the same methods on both nodes. Node path: " + path); } - peers_info[p_from].recv_nodes.insert(id, node->get_instance_id()); + peers_info[p_from].recv_nodes.insert(id, RecvNode(node->get_instance_id(), path)); NodeCache &cache = _track(node); cache.recv_ids.insert(p_from, id); @@ -269,14 +275,21 @@ bool SceneCacheInterface::send_object_cache(Object *p_obj, int p_peer_id, int &r } Object *SceneCacheInterface::get_cached_object(int p_from, uint32_t p_cache_id) { - Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); - ERR_FAIL_NULL_V(root_node, nullptr); PeerInfo *pinfo = peers_info.getptr(p_from); ERR_FAIL_NULL_V(pinfo, nullptr); - const ObjectID *oid = pinfo->recv_nodes.getptr(p_cache_id); - ERR_FAIL_NULL_V_MSG(oid, nullptr, vformat("ID %d not found in cache of peer %d.", p_cache_id, p_from)); - Node *node = Object::cast_to<Node>(ObjectDB::get_instance(*oid)); + RecvNode *recv_node = pinfo->recv_nodes.getptr(p_cache_id); + ERR_FAIL_NULL_V_MSG(recv_node, nullptr, vformat("ID %d not found in cache of peer %d.", p_cache_id, p_from)); + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(recv_node->oid)); + if (!node) { + // Fallback to path lookup. + Node *root_node = SceneTree::get_singleton()->get_root()->get_node(multiplayer->get_root_path()); + ERR_FAIL_NULL_V(root_node, nullptr); + node = root_node->get_node(recv_node->path); + if (node) { + recv_node->oid = node->get_instance_id(); + } + } ERR_FAIL_NULL_V_MSG(node, nullptr, vformat("Failed to get cached node from peer %d with cache ID %d.", p_from, p_cache_id)); return node; } diff --git a/modules/multiplayer/scene_cache_interface.h b/modules/multiplayer/scene_cache_interface.h index 73d6bde6ef..fbe618f4ad 100644 --- a/modules/multiplayer/scene_cache_interface.h +++ b/modules/multiplayer/scene_cache_interface.h @@ -49,8 +49,18 @@ private: HashMap<int, bool> confirmed_peers; // peer id, confirmed }; + struct RecvNode { + ObjectID oid; + NodePath path; + + RecvNode(const ObjectID &p_oid, const NodePath &p_path) { + oid = p_oid; + path = p_path; + } + }; + struct PeerInfo { - HashMap<int, ObjectID> recv_nodes; // remote cache id, ObjectID + HashMap<int, RecvNode> recv_nodes; // remote cache id, (ObjectID, NodePath) HashSet<ObjectID> sent_nodes; }; diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp index 99aba680cc..e245101eeb 100644 --- a/modules/multiplayer/scene_multiplayer.cpp +++ b/modules/multiplayer/scene_multiplayer.cpp @@ -307,8 +307,10 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p int len = p_packet_len - SYS_CMD_SIZE; bool should_process = false; if (get_unique_id() == 1) { // I am the server. - // Direct messages to server should not go through relay. - ERR_FAIL_COND(peer > 0 && !connected_peers.has(peer)); + // The requested target might have disconnected while the packet was in transit. + if (unlikely(peer > 0 && !connected_peers.has(peer))) { + return; + } // Send relay packet. relay_buffer->seek(0); relay_buffer->put_u8(NETWORK_COMMAND_SYS); @@ -319,21 +321,24 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p multiplayer_peer->set_transfer_mode(p_mode); multiplayer_peer->set_transfer_channel(p_channel); if (peer > 0) { + // Single destination. multiplayer_peer->set_target_peer(peer); _send(data.ptr(), relay_buffer->get_position()); } else { + // Multiple destinations. for (const int &P : connected_peers) { // Not to sender, nor excluded. - if (P == p_from || (peer < 0 && P != -peer)) { + if (P == p_from || P == -peer) { continue; } multiplayer_peer->set_target_peer(P); _send(data.ptr(), relay_buffer->get_position()); } - } - if (peer == 0 || peer == -1) { - should_process = true; - peer = p_from; // Process as the source. + if (peer != -1) { + // The server is one of the targets, process the packet with sender as source. + should_process = true; + peer = p_from; + } } } else { ERR_FAIL_COND(p_from != 1); // Bug. @@ -425,11 +430,11 @@ void SceneMultiplayer::_del_peer(int p_id) { void SceneMultiplayer::disconnect_peer(int p_id) { ERR_FAIL_COND(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED); - if (pending_peers.has(p_id)) { - pending_peers.erase(p_id); - } else if (connected_peers.has(p_id)) { - connected_peers.erase(p_id); - } + // Block signals to avoid emitting peer_disconnected. + bool blocking = is_blocking_signals(); + set_block_signals(true); + _del_peer(p_id); + set_block_signals(blocking); multiplayer_peer->disconnect_peer(p_id); } diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 8c2fb42463..33b92f6266 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -1042,10 +1042,32 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation } TPPLPartition tpart; - if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { //failed! - ERR_PRINT("NavigationPolygon Convex partition failed. Unable to create a valid NavigationMesh from defined polygon outline paths."); - p_navigation_mesh->clear(); - return; + + NavigationPolygon::SamplePartitionType sample_partition_type = p_navigation_mesh->get_sample_partition_type(); + + switch (sample_partition_type) { + case NavigationPolygon::SamplePartitionType::SAMPLE_PARTITION_CONVEX_PARTITION: + if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { + ERR_PRINT("NavigationPolygon polygon convex partition failed. Unable to create a valid navigation mesh polygon layout from provided source geometry."); + p_navigation_mesh->set_vertices(Vector<Vector2>()); + p_navigation_mesh->clear_polygons(); + return; + } + break; + case NavigationPolygon::SamplePartitionType::SAMPLE_PARTITION_TRIANGULATE: + if (tpart.Triangulate_EC(&tppl_in_polygon, &tppl_out_polygon) == 0) { + ERR_PRINT("NavigationPolygon polygon triangulation failed. Unable to create a valid navigation mesh polygon layout from provided source geometry."); + p_navigation_mesh->set_vertices(Vector<Vector2>()); + p_navigation_mesh->clear_polygons(); + return; + } + break; + default: { + ERR_PRINT("NavigationPolygon polygon partitioning failed. Unrecognized partition type."); + p_navigation_mesh->set_vertices(Vector<Vector2>()); + p_navigation_mesh->clear_polygons(); + return; + } } Vector<Vector2> new_vertices; diff --git a/modules/navigation/3d/godot_navigation_server_3d.cpp b/modules/navigation/3d/godot_navigation_server_3d.cpp index 430d527844..11a5de608b 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.cpp +++ b/modules/navigation/3d/godot_navigation_server_3d.cpp @@ -509,22 +509,31 @@ void GodotNavigationServer3D::region_bake_navigation_mesh(Ref<NavigationMesh> p_ int GodotNavigationServer3D::region_get_connections_count(RID p_region) const { NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_NULL_V(region, 0); - - return region->get_connections_count(); + NavMap *map = region->get_map(); + if (map) { + return map->get_region_connections_count(region); + } + return 0; } Vector3 GodotNavigationServer3D::region_get_connection_pathway_start(RID p_region, int p_connection_id) const { NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_NULL_V(region, Vector3()); - - return region->get_connection_pathway_start(p_connection_id); + NavMap *map = region->get_map(); + if (map) { + return map->get_region_connection_pathway_start(region, p_connection_id); + } + return Vector3(); } Vector3 GodotNavigationServer3D::region_get_connection_pathway_end(RID p_region, int p_connection_id) const { NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_NULL_V(region, Vector3()); - - return region->get_connection_pathway_end(p_connection_id); + NavMap *map = region->get_map(); + if (map) { + return map->get_region_connection_pathway_end(region, p_connection_id); + } + return Vector3(); } Vector3 GodotNavigationServer3D::region_get_random_point(RID p_region, uint32_t p_navigation_layers, bool p_uniformly) const { @@ -1298,6 +1307,7 @@ void GodotNavigationServer3D::process(real_t p_delta_time) { int _new_pm_edge_merge_count = 0; int _new_pm_edge_connection_count = 0; int _new_pm_edge_free_count = 0; + int _new_pm_obstacle_count = 0; // In c++ we can't be sure that this is performed in the main thread // even with mutable functions. @@ -1315,6 +1325,7 @@ void GodotNavigationServer3D::process(real_t p_delta_time) { _new_pm_edge_merge_count += active_maps[i]->get_pm_edge_merge_count(); _new_pm_edge_connection_count += active_maps[i]->get_pm_edge_connection_count(); _new_pm_edge_free_count += active_maps[i]->get_pm_edge_free_count(); + _new_pm_obstacle_count += active_maps[i]->get_pm_obstacle_count(); // Emit a signal if a map changed. const uint32_t new_map_iteration_id = active_maps[i]->get_iteration_id(); @@ -1332,6 +1343,7 @@ void GodotNavigationServer3D::process(real_t p_delta_time) { pm_edge_merge_count = _new_pm_edge_merge_count; pm_edge_connection_count = _new_pm_edge_connection_count; pm_edge_free_count = _new_pm_edge_free_count; + pm_obstacle_count = _new_pm_obstacle_count; } void GodotNavigationServer3D::init() { @@ -1566,6 +1578,9 @@ int GodotNavigationServer3D::get_process_info(ProcessInfo p_info) const { case INFO_EDGE_FREE_COUNT: { return pm_edge_free_count; } break; + case INFO_OBSTACLE_COUNT: { + return pm_obstacle_count; + } break; } return 0; diff --git a/modules/navigation/3d/godot_navigation_server_3d.h b/modules/navigation/3d/godot_navigation_server_3d.h index 5ba7ed1088..12a1132f07 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.h +++ b/modules/navigation/3d/godot_navigation_server_3d.h @@ -95,6 +95,7 @@ class GodotNavigationServer3D : public NavigationServer3D { int pm_edge_merge_count = 0; int pm_edge_connection_count = 0; int pm_edge_free_count = 0; + int pm_obstacle_count = 0; public: GodotNavigationServer3D(); diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp index d07d3cdff5..f37ed9b168 100644 --- a/modules/navigation/editor/navigation_mesh_editor_plugin.cpp +++ b/modules/navigation/editor/navigation_mesh_editor_plugin.cpp @@ -126,9 +126,6 @@ void NavigationMeshEditor::edit(NavigationRegion3D *p_nav_region) { node = p_nav_region; } -void NavigationMeshEditor::_bind_methods() { -} - NavigationMeshEditor::NavigationMeshEditor() { bake_hbox = memnew(HBoxContainer); diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.h b/modules/navigation/editor/navigation_mesh_editor_plugin.h index 6114c62ebf..f5a471d531 100644 --- a/modules/navigation/editor/navigation_mesh_editor_plugin.h +++ b/modules/navigation/editor/navigation_mesh_editor_plugin.h @@ -60,7 +60,6 @@ class NavigationMeshEditor : public Control { protected: void _node_removed(Node *p_node); - static void _bind_methods(); void _notification(int p_what); public: diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index f917c988ea..0c91e8dea3 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -907,6 +907,7 @@ void NavMap::sync() { int _new_pm_edge_merge_count = pm_edge_merge_count; int _new_pm_edge_connection_count = pm_edge_connection_count; int _new_pm_edge_free_count = pm_edge_free_count; + int _new_pm_obstacle_count = obstacles.size(); // Check if we need to update the links. if (regenerate_polygons) { @@ -936,8 +937,9 @@ void NavMap::sync() { _new_pm_edge_free_count = 0; // Remove regions connections. + region_external_connections.clear(); for (NavRegion *region : regions) { - region->get_connections().clear(); + region_external_connections[region] = LocalVector<gd::Edge::Connection>(); } // Resize the polygon count. @@ -1071,7 +1073,7 @@ void NavMap::sync() { free_edge.polygon->edges[free_edge.edge].connections.push_back(new_connection); // Add the connection to the region_connection map. - ((NavRegion *)free_edge.polygon->owner)->get_connections().push_back(new_connection); + region_external_connections[(NavRegion *)free_edge.polygon->owner].push_back(new_connection); _new_pm_edge_connection_count += 1; } } @@ -1219,6 +1221,7 @@ void NavMap::sync() { pm_edge_merge_count = _new_pm_edge_merge_count; pm_edge_connection_count = _new_pm_edge_connection_count; pm_edge_free_count = _new_pm_edge_free_count; + pm_obstacle_count = _new_pm_obstacle_count; } void NavMap::_update_rvo_obstacles_tree_2d() { @@ -1426,6 +1429,40 @@ void NavMap::_update_merge_rasterizer_cell_dimensions() { merge_rasterizer_cell_height = cell_height * merge_rasterizer_cell_scale; } +int NavMap::get_region_connections_count(NavRegion *p_region) const { + ERR_FAIL_NULL_V(p_region, 0); + + HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region); + if (found_connections) { + return found_connections->value.size(); + } + return 0; +} + +Vector3 NavMap::get_region_connection_pathway_start(NavRegion *p_region, int p_connection_id) const { + ERR_FAIL_NULL_V(p_region, Vector3()); + + HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region); + if (found_connections) { + ERR_FAIL_INDEX_V(p_connection_id, int(found_connections->value.size()), Vector3()); + return found_connections->value[p_connection_id].pathway_start; + } + + return Vector3(); +} + +Vector3 NavMap::get_region_connection_pathway_end(NavRegion *p_region, int p_connection_id) const { + ERR_FAIL_NULL_V(p_region, Vector3()); + + HashMap<NavRegion *, LocalVector<gd::Edge::Connection>>::ConstIterator found_connections = region_external_connections.find(p_region); + if (found_connections) { + ERR_FAIL_INDEX_V(p_connection_id, int(found_connections->value.size()), Vector3()); + return found_connections->value[p_connection_id].pathway_end; + } + + return Vector3(); +} + NavMap::NavMap() { avoidance_use_multiple_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_multiple_threads"); avoidance_use_high_priority_threads = GLOBAL_GET("navigation/avoidance/thread_model/avoidance_use_high_priority_threads"); diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h index d6215ea57f..82e8854b7a 100644 --- a/modules/navigation/nav_map.h +++ b/modules/navigation/nav_map.h @@ -123,6 +123,9 @@ class NavMap : public NavRid { int pm_edge_merge_count = 0; int pm_edge_connection_count = 0; int pm_edge_free_count = 0; + int pm_obstacle_count = 0; + + HashMap<NavRegion *, LocalVector<gd::Edge::Connection>> region_external_connections; public: NavMap(); @@ -216,6 +219,11 @@ public: int get_pm_edge_merge_count() const { return pm_edge_merge_count; } int get_pm_edge_connection_count() const { return pm_edge_connection_count; } int get_pm_edge_free_count() const { return pm_edge_free_count; } + int get_pm_obstacle_count() const { return pm_obstacle_count; } + + int get_region_connections_count(NavRegion *p_region) const; + Vector3 get_region_connection_pathway_start(NavRegion *p_region, int p_connection_id) const; + Vector3 get_region_connection_pathway_end(NavRegion *p_region, int p_connection_id) const; private: void compute_single_step(uint32_t index, NavAgent **agent); diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp index f30855d697..85510bd416 100644 --- a/modules/navigation/nav_region.cpp +++ b/modules/navigation/nav_region.cpp @@ -44,8 +44,6 @@ void NavRegion::set_map(NavMap *p_map) { map = p_map; polygons_dirty = true; - connections.clear(); - if (map) { map->add_region(this); } @@ -105,25 +103,6 @@ void NavRegion::set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) { polygons_dirty = true; } -int NavRegion::get_connections_count() const { - if (!map) { - return 0; - } - return connections.size(); -} - -Vector3 NavRegion::get_connection_pathway_start(int p_connection_id) const { - ERR_FAIL_NULL_V(map, Vector3()); - ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3()); - return connections[p_connection_id].pathway_start; -} - -Vector3 NavRegion::get_connection_pathway_end(int p_connection_id) const { - ERR_FAIL_NULL_V(map, Vector3()); - ERR_FAIL_INDEX_V(p_connection_id, connections.size(), Vector3()); - return connections[p_connection_id].pathway_end; -} - Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniformly) const { if (!get_enabled()) { return Vector3(); diff --git a/modules/navigation/nav_region.h b/modules/navigation/nav_region.h index ebc082bd2f..662a32c47a 100644 --- a/modules/navigation/nav_region.h +++ b/modules/navigation/nav_region.h @@ -40,7 +40,6 @@ class NavRegion : public NavBase { NavMap *map = nullptr; Transform3D transform; - Vector<gd::Edge::Connection> connections; bool enabled = true; bool use_edge_connections = true; @@ -85,13 +84,6 @@ public: void set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh); - Vector<gd::Edge::Connection> &get_connections() { - return connections; - } - int get_connections_count() const; - Vector3 get_connection_pathway_start(int p_connection_id) const; - Vector3 get_connection_pathway_end(int p_connection_id) const; - LocalVector<gd::Polygon> const &get_polygons() const { return polygons; } diff --git a/modules/noise/noise_texture_3d.cpp b/modules/noise/noise_texture_3d.cpp index 1e929e6f63..9047491344 100644 --- a/modules/noise/noise_texture_3d.cpp +++ b/modules/noise/noise_texture_3d.cpp @@ -331,6 +331,10 @@ int NoiseTexture3D::get_depth() const { return depth; } +bool NoiseTexture3D::has_mipmaps() const { + return false; +} + RID NoiseTexture3D::get_rid() const { if (!texture.is_valid()) { texture = RS::get_singleton()->texture_3d_placeholder_create(); diff --git a/modules/noise/noise_texture_3d.h b/modules/noise/noise_texture_3d.h index 13125efe7f..d55b78a2ba 100644 --- a/modules/noise/noise_texture_3d.h +++ b/modules/noise/noise_texture_3d.h @@ -103,6 +103,8 @@ public: virtual int get_height() const override; virtual int get_depth() const override; + virtual bool has_mipmaps() const override; + virtual RID get_rid() const override; virtual Vector<Ref<Image>> get_data() const override; diff --git a/modules/openxr/action_map/openxr_interaction_profile.cpp b/modules/openxr/action_map/openxr_interaction_profile.cpp index 2579697d05..1266457113 100644 --- a/modules/openxr/action_map/openxr_interaction_profile.cpp +++ b/modules/openxr/action_map/openxr_interaction_profile.cpp @@ -127,9 +127,12 @@ Ref<OpenXRInteractionProfile> OpenXRInteractionProfile::new_profile(const char * void OpenXRInteractionProfile::set_interaction_profile_path(const String p_input_profile_path) { OpenXRInteractionProfileMetadata *pmd = OpenXRInteractionProfileMetadata::get_singleton(); - ERR_FAIL_NULL(pmd); - - interaction_profile_path = pmd->check_profile_name(p_input_profile_path); + if (pmd) { + interaction_profile_path = pmd->check_profile_name(p_input_profile_path); + } else { + // OpenXR module not enabled, ignore checks. + interaction_profile_path = p_input_profile_path; + } emit_changed(); } diff --git a/modules/openxr/config.py b/modules/openxr/config.py index 6c6b9fdb8b..559aa2acf6 100644 --- a/modules/openxr/config.py +++ b/modules/openxr/config.py @@ -22,6 +22,7 @@ def get_doc_classes(): "OpenXRInteractionProfileMetadata", "OpenXRIPBinding", "OpenXRHand", + "OpenXRVisibilityMask", "OpenXRCompositionLayer", "OpenXRCompositionLayerQuad", "OpenXRCompositionLayerCylinder", diff --git a/modules/openxr/doc_classes/OpenXRAPIExtension.xml b/modules/openxr/doc_classes/OpenXRAPIExtension.xml index 4419d24dd3..432b331eec 100644 --- a/modules/openxr/doc_classes/OpenXRAPIExtension.xml +++ b/modules/openxr/doc_classes/OpenXRAPIExtension.xml @@ -17,12 +17,25 @@ <link title="XrPosef documentation">https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrPosef.html</link> </tutorials> <methods> + <method name="begin_debug_label_region"> + <return type="void" /> + <param index="0" name="label_name" type="String" /> + <description> + Begins a new debug label region, this label will be reported in debug messages for any calls following this until [method end_debug_label_region] is called. Debug labels can be stacked. + </description> + </method> <method name="can_render"> <return type="bool" /> <description> Returns [code]true[/code] if OpenXR is initialized for rendering with an XR viewport. </description> </method> + <method name="end_debug_label_region"> + <return type="void" /> + <description> + Marks the end of a debug label region. Removes the latest debug label region added by calling [method begin_debug_label_region]. + </description> + </method> <method name="get_error_string"> <return type="String" /> <param index="0" name="result" type="int" /> @@ -88,6 +101,13 @@ Returns the id of the system, which is a [url=https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrSystemId.html]XrSystemId[/url] cast to an integer. </description> </method> + <method name="insert_debug_label"> + <return type="void" /> + <param index="0" name="label_name" type="String" /> + <description> + Inserts a debug label, this label is reported in any debug message resulting from the OpenXR calls that follows, until any of [method begin_debug_label_region], [method end_debug_label_region], or [method insert_debug_label] is called. + </description> + </method> <method name="is_environment_blend_mode_alpha_supported"> <return type="int" enum="OpenXRAPIExtension.OpenXRAlphaBlendModeSupport" /> <description> @@ -127,6 +147,15 @@ If set to [code]true[/code], an OpenXR extension is loaded which is capable of emulating the [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND] blend mode. </description> </method> + <method name="set_object_name"> + <return type="void" /> + <param index="0" name="object_type" type="int" /> + <param index="1" name="object_handle" type="int" /> + <param index="2" name="object_name" type="String" /> + <description> + Set the object name of an OpenXR object, used for debug output. [param object_type] must be a valid OpenXR [code]XrObjectType[/code] enum and [param object_handle] must be a valid OpenXR object handle. + </description> + </method> <method name="transform_from_pose"> <return type="Transform3D" /> <param index="0" name="pose" type="const void*" /> diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml index 309cbe0d72..ed5810da3c 100644 --- a/modules/openxr/doc_classes/OpenXRInterface.xml +++ b/modules/openxr/doc_classes/OpenXRInterface.xml @@ -176,7 +176,7 @@ <param index="0" name="refresh_rate" type="float" /> <description> Informs the user the HMD refresh rate has changed. - [b]Node:[/b] Only emitted if XR runtime supports the refresh rate extension. + [b]Note:[/b] Only emitted if XR runtime supports the refresh rate extension. </description> </signal> <signal name="session_begun"> diff --git a/modules/openxr/doc_classes/OpenXRVisibilityMask.xml b/modules/openxr/doc_classes/OpenXRVisibilityMask.xml new file mode 100644 index 0000000000..90ee889c7c --- /dev/null +++ b/modules/openxr/doc_classes/OpenXRVisibilityMask.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="OpenXRVisibilityMask" inherits="VisualInstance3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> + <brief_description> + Draws a stereo correct visibility mask. + </brief_description> + <description> + The visibility mask allows us to black out the part of the render result that is invisible due to lens distortion. + As this is rendered first, it prevents fragments with expensive lighting calculations to be processed as they are discarded through z-checking. + </description> + <tutorials> + </tutorials> +</class> diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp index 937973f388..a353073f21 100644 --- a/modules/openxr/editor/openxr_action_map_editor.cpp +++ b/modules/openxr/editor/openxr_action_map_editor.cpp @@ -248,26 +248,27 @@ void OpenXRActionMapEditor::_on_interaction_profile_selected(const String p_path void OpenXRActionMapEditor::_load_action_map(const String p_path, bool p_create_new_if_missing) { Error err = OK; - action_map = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE, &err); - if (err != OK) { - if ((err == ERR_FILE_NOT_FOUND || err == ERR_CANT_OPEN) && p_create_new_if_missing) { - action_map.instantiate(); - action_map->create_default_action_sets(); - - // Save it immediately - err = ResourceSaver::save(action_map, p_path); - if (err != OK) { - // show warning but continue - EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file %s: %s"), edited_path, error_names[err])); - } - - } else { + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (da->file_exists(p_path)) { + action_map = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); + if (err != OK) { EditorNode::get_singleton()->show_warning(vformat(TTR("Error loading %s: %s."), edited_path, error_names[err])); edited_path = ""; header_label->set_text(""); return; } + } else if (p_create_new_if_missing) { + action_map.instantiate(); + action_map->create_default_action_sets(); + action_map->set_path(p_path); + + // Save it immediately + err = ResourceSaver::save(action_map, p_path); + if (err != OK) { + // Show warning but continue. + EditorNode::get_singleton()->show_warning(vformat(TTR("Error saving file %s: %s"), edited_path, error_names[err])); + } } edited_path = p_path; diff --git a/modules/openxr/editor/openxr_select_runtime.cpp b/modules/openxr/editor/openxr_select_runtime.cpp index 026797c6e0..4d95b079e2 100644 --- a/modules/openxr/editor/openxr_select_runtime.cpp +++ b/modules/openxr/editor/openxr_select_runtime.cpp @@ -35,9 +35,6 @@ #include "editor/editor_settings.h" #include "editor/editor_string_names.h" -void OpenXRSelectRuntime::_bind_methods() { -} - void OpenXRSelectRuntime::_update_items() { Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); OS *os = OS::get_singleton(); diff --git a/modules/openxr/editor/openxr_select_runtime.h b/modules/openxr/editor/openxr_select_runtime.h index 60b5137f67..9a3487439c 100644 --- a/modules/openxr/editor/openxr_select_runtime.h +++ b/modules/openxr/editor/openxr_select_runtime.h @@ -40,7 +40,6 @@ public: OpenXRSelectRuntime(); protected: - static void _bind_methods(); void _notification(int p_notification); private: diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.cpp b/modules/openxr/extensions/openxr_composition_layer_extension.cpp index 994b08af53..8a448afc08 100644 --- a/modules/openxr/extensions/openxr_composition_layer_extension.cpp +++ b/modules/openxr/extensions/openxr_composition_layer_extension.cpp @@ -58,7 +58,7 @@ HashMap<String, bool *> OpenXRCompositionLayerExtension::get_requested_extension return request_extensions; } -void OpenXRCompositionLayerExtension::on_session_created(const XrSession p_instance) { +void OpenXRCompositionLayerExtension::on_session_created(const XrSession p_session) { OpenXRAPI::get_singleton()->register_composition_layer_provider(this); } diff --git a/modules/openxr/extensions/openxr_composition_layer_extension.h b/modules/openxr/extensions/openxr_composition_layer_extension.h index 4fefc416e6..34e330a60a 100644 --- a/modules/openxr/extensions/openxr_composition_layer_extension.h +++ b/modules/openxr/extensions/openxr_composition_layer_extension.h @@ -49,7 +49,7 @@ public: virtual ~OpenXRCompositionLayerExtension() override; virtual HashMap<String, bool *> get_requested_extensions() override; - virtual void on_session_created(const XrSession 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; diff --git a/modules/openxr/extensions/openxr_debug_utils_extension.cpp b/modules/openxr/extensions/openxr_debug_utils_extension.cpp new file mode 100644 index 0000000000..10dbe629f7 --- /dev/null +++ b/modules/openxr/extensions/openxr_debug_utils_extension.cpp @@ -0,0 +1,287 @@ +/**************************************************************************/ +/* openxr_debug_utils_extension.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "openxr_debug_utils_extension.h" + +#include "../openxr_api.h" +#include "core/config/project_settings.h" +#include "core/string/print_string.h" + +#include <openxr/openxr.h> + +OpenXRDebugUtilsExtension *OpenXRDebugUtilsExtension::singleton = nullptr; + +OpenXRDebugUtilsExtension *OpenXRDebugUtilsExtension::get_singleton() { + return singleton; +} + +OpenXRDebugUtilsExtension::OpenXRDebugUtilsExtension() { + singleton = this; +} + +OpenXRDebugUtilsExtension::~OpenXRDebugUtilsExtension() { + singleton = nullptr; +} + +HashMap<String, bool *> OpenXRDebugUtilsExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_EXT_DEBUG_UTILS_EXTENSION_NAME] = &debug_utils_ext; + + return request_extensions; +} + +void OpenXRDebugUtilsExtension::on_instance_created(const XrInstance p_instance) { + if (debug_utils_ext) { + EXT_INIT_XR_FUNC(xrCreateDebugUtilsMessengerEXT); + EXT_INIT_XR_FUNC(xrDestroyDebugUtilsMessengerEXT); + EXT_INIT_XR_FUNC(xrSetDebugUtilsObjectNameEXT); + EXT_INIT_XR_FUNC(xrSessionBeginDebugUtilsLabelRegionEXT); + EXT_INIT_XR_FUNC(xrSessionEndDebugUtilsLabelRegionEXT); + EXT_INIT_XR_FUNC(xrSessionInsertDebugUtilsLabelEXT); + + debug_utils_ext = xrCreateDebugUtilsMessengerEXT_ptr && xrDestroyDebugUtilsMessengerEXT_ptr && xrSetDebugUtilsObjectNameEXT_ptr && xrSessionBeginDebugUtilsLabelRegionEXT_ptr && xrSessionEndDebugUtilsLabelRegionEXT_ptr && xrSessionInsertDebugUtilsLabelEXT_ptr; + } else { + WARN_PRINT("OpenXR: The debug utils extension is not available on this runtime. Debug logging is not enabled!"); + } + + // On successful init, setup our default messenger. + if (debug_utils_ext) { + int max_severity = GLOBAL_GET("xr/openxr/extensions/debug_utils"); + int types = GLOBAL_GET("xr/openxr/extensions/debug_message_types"); + + XrDebugUtilsMessageSeverityFlagsEXT message_severities = 0; + + if (max_severity >= 1) { + message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + } + if (max_severity >= 2) { + message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; + } + if (max_severity >= 3) { + message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; + } + if (max_severity >= 4) { + message_severities |= XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; + } + + XrDebugUtilsMessageTypeFlagsEXT message_types = 0; + + // These should match up but just to be safe and future proof... + if (types & 1) { + message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT; + } + if (types & 2) { + message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; + } + if (types & 4) { + message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + } + if (types & 8) { + message_types |= XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT; + } + + XrDebugUtilsMessengerCreateInfoEXT callback_info = { + XR_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, // type + nullptr, // next + message_severities, // messageSeverities + message_types, // messageTypes + &OpenXRDebugUtilsExtension::_debug_callback, // userCallback + nullptr, // userData + }; + + XrResult result = xrCreateDebugUtilsMessengerEXT(p_instance, &callback_info, &default_messenger); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to create debug callback [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + set_object_name(XR_OBJECT_TYPE_INSTANCE, uint64_t(p_instance), "Main Godot OpenXR Instance"); + } +} + +void OpenXRDebugUtilsExtension::on_instance_destroyed() { + if (default_messenger != XR_NULL_HANDLE) { + XrResult result = xrDestroyDebugUtilsMessengerEXT(default_messenger); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to destroy debug callback [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } + + default_messenger = XR_NULL_HANDLE; + } + + xrCreateDebugUtilsMessengerEXT_ptr = nullptr; + xrDestroyDebugUtilsMessengerEXT_ptr = nullptr; + xrSetDebugUtilsObjectNameEXT_ptr = nullptr; + xrSessionBeginDebugUtilsLabelRegionEXT_ptr = nullptr; + xrSessionEndDebugUtilsLabelRegionEXT_ptr = nullptr; + xrSessionInsertDebugUtilsLabelEXT_ptr = nullptr; + debug_utils_ext = false; +} + +bool OpenXRDebugUtilsExtension::get_active() { + return debug_utils_ext; +} + +void OpenXRDebugUtilsExtension::set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const char *p_object_name) { + ERR_FAIL_COND(!debug_utils_ext); + ERR_FAIL_NULL(xrSetDebugUtilsObjectNameEXT_ptr); + + const XrDebugUtilsObjectNameInfoEXT space_name_info = { + XR_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, // type + nullptr, // next + p_object_type, // objectType + p_object_handle, // objectHandle + p_object_name, // objectName + }; + + XrResult result = xrSetDebugUtilsObjectNameEXT_ptr(OpenXRAPI::get_singleton()->get_instance(), &space_name_info); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to set object name [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } +} + +void OpenXRDebugUtilsExtension::begin_debug_label_region(const char *p_label_name) { + ERR_FAIL_COND(!debug_utils_ext); + ERR_FAIL_NULL(xrSessionBeginDebugUtilsLabelRegionEXT_ptr); + + const XrDebugUtilsLabelEXT session_active_region_label = { + XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type + NULL, // next + p_label_name, // labelName + }; + + XrResult result = xrSessionBeginDebugUtilsLabelRegionEXT_ptr(OpenXRAPI::get_singleton()->get_session(), &session_active_region_label); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to begin label region [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } +} + +void OpenXRDebugUtilsExtension::end_debug_label_region() { + ERR_FAIL_COND(!debug_utils_ext); + ERR_FAIL_NULL(xrSessionEndDebugUtilsLabelRegionEXT_ptr); + + XrResult result = xrSessionEndDebugUtilsLabelRegionEXT_ptr(OpenXRAPI::get_singleton()->get_session()); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to end label region [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } +} + +void OpenXRDebugUtilsExtension::insert_debug_label(const char *p_label_name) { + ERR_FAIL_COND(!debug_utils_ext); + ERR_FAIL_NULL(xrSessionInsertDebugUtilsLabelEXT_ptr); + + const XrDebugUtilsLabelEXT session_active_region_label = { + XR_TYPE_DEBUG_UTILS_LABEL_EXT, // type + NULL, // next + p_label_name, // labelName + }; + + XrResult result = xrSessionInsertDebugUtilsLabelEXT_ptr(OpenXRAPI::get_singleton()->get_session(), &session_active_region_label); + if (XR_FAILED(result)) { + ERR_PRINT("OpenXR: Failed to insert label [" + OpenXRAPI::get_singleton()->get_error_string(result) + "]"); + } +} + +XrBool32 XRAPI_PTR OpenXRDebugUtilsExtension::_debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data) { + OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton(); + + if (debug_utils) { + return debug_utils->debug_callback(p_message_severity, p_message_types, p_callback_data, p_user_data); + } + + return XR_FALSE; +} + +XrBool32 OpenXRDebugUtilsExtension::debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data) { + String msg; + + ERR_FAIL_NULL_V(p_callback_data, XR_FALSE); + + if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) { + msg = ", type: General"; + } else if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { + msg = ", type: Validation"; + } else if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) { + msg = ", type: Performance"; + } else if (p_message_types == XR_DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT) { + msg = ", type: Conformance"; + } else { + msg = ", type: Unknown (" + String::num_uint64(p_message_types) + ")"; + } + + if (p_callback_data->functionName) { + msg += ", function Name: " + String(p_callback_data->functionName); + } + if (p_callback_data->messageId) { + msg += "\nMessage ID: " + String(p_callback_data->messageId); + } + if (p_callback_data->message) { + msg += "\nMessage: " + String(p_callback_data->message); + } + + if (p_callback_data->objectCount > 0) { + String objects; + + for (uint32_t i = 0; i < p_callback_data->objectCount; i++) { + if (!objects.is_empty()) { + objects += ", "; + } + objects += p_callback_data->objects[i].objectName; + } + + msg += "\nObjects: " + objects; + } + + if (p_callback_data->sessionLabelCount > 0) { + String labels; + + for (uint32_t i = 0; i < p_callback_data->sessionLabelCount; i++) { + if (!labels.is_empty()) { + labels += ", "; + } + labels += p_callback_data->sessionLabels[i].labelName; + } + + msg += "\nLabels: " + labels; + } + + if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + ERR_PRINT("OpenXR: Severity: Error" + msg); + } else if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + WARN_PRINT("OpenXR: Severity: Warning" + msg); + } else if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { + print_line("OpenXR: Severity: Info" + msg); + } else if (p_message_severity == XR_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) { + // This is a bit double because we won't output this unless verbose messaging in Godot is on. + print_verbose("OpenXR: Severity: Verbose" + msg); + } + + return XR_FALSE; +} diff --git a/modules/openxr/extensions/openxr_debug_utils_extension.h b/modules/openxr/extensions/openxr_debug_utils_extension.h new file mode 100644 index 0000000000..1ee4c2a101 --- /dev/null +++ b/modules/openxr/extensions/openxr_debug_utils_extension.h @@ -0,0 +1,76 @@ +/**************************************************************************/ +/* openxr_debug_utils_extension.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 OPENXR_DEBUG_UTILS_EXTENSION_H +#define OPENXR_DEBUG_UTILS_EXTENSION_H + +#include "../util.h" +#include "openxr_extension_wrapper.h" + +class OpenXRDebugUtilsExtension : public OpenXRExtensionWrapper { +public: + static OpenXRDebugUtilsExtension *get_singleton(); + + OpenXRDebugUtilsExtension(); + virtual ~OpenXRDebugUtilsExtension() override; + + virtual HashMap<String, bool *> get_requested_extensions() override; + virtual void on_instance_created(const XrInstance p_instance) override; + virtual void on_instance_destroyed() override; + + bool get_active(); + + void set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const char *p_object_name); + void begin_debug_label_region(const char *p_label_name); + void end_debug_label_region(); + void insert_debug_label(const char *p_label_name); + +private: + static OpenXRDebugUtilsExtension *singleton; + + // related extensions + bool debug_utils_ext = false; + + // debug handlers + XrDebugUtilsMessengerEXT default_messenger = XR_NULL_HANDLE; + + static XrBool32 XRAPI_PTR _debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data); + XrBool32 debug_callback(XrDebugUtilsMessageSeverityFlagsEXT p_message_severity, XrDebugUtilsMessageTypeFlagsEXT p_message_types, const XrDebugUtilsMessengerCallbackDataEXT *p_callback_data, void *p_user_data); + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC3(xrCreateDebugUtilsMessengerEXT, (XrInstance), p_instance, (const XrDebugUtilsMessengerCreateInfoEXT *), p_create_info, (XrDebugUtilsMessengerEXT *), p_messenger) + EXT_PROTO_XRRESULT_FUNC1(xrDestroyDebugUtilsMessengerEXT, (XrDebugUtilsMessengerEXT), p_messenger) + EXT_PROTO_XRRESULT_FUNC2(xrSetDebugUtilsObjectNameEXT, (XrInstance), p_instance, (const XrDebugUtilsObjectNameInfoEXT *), p_name_info) + EXT_PROTO_XRRESULT_FUNC2(xrSessionBeginDebugUtilsLabelRegionEXT, (XrSession), p_session, (const XrDebugUtilsLabelEXT *), p_label_info) + EXT_PROTO_XRRESULT_FUNC1(xrSessionEndDebugUtilsLabelRegionEXT, (XrSession), p_session) + EXT_PROTO_XRRESULT_FUNC2(xrSessionInsertDebugUtilsLabelEXT, (XrSession), p_session, (const XrDebugUtilsLabelEXT *), p_label_info) +}; + +#endif // OPENXR_DEBUG_UTILS_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_extension_wrapper.h b/modules/openxr/extensions/openxr_extension_wrapper.h index 8d05657afc..09a9556dfa 100644 --- a/modules/openxr/extensions/openxr_extension_wrapper.h +++ b/modules/openxr/extensions/openxr_extension_wrapper.h @@ -76,7 +76,7 @@ public: virtual void on_before_instance_created() {} // `on_before_instance_created` is called before we create our OpenXR instance. virtual void on_instance_created(const XrInstance p_instance) {} // `on_instance_created` is called right after we've successfully created our OpenXR instance. virtual void on_instance_destroyed() {} // `on_instance_destroyed` is called right before we destroy our OpenXR instance. - virtual void on_session_created(const XrSession p_instance) {} // `on_session_created` is called right after we've successfully created our OpenXR session. + virtual void on_session_created(const XrSession p_session) {} // `on_session_created` is called right after we've successfully created our OpenXR session. virtual void on_session_destroyed() {} // `on_session_destroyed` is called right before we destroy our OpenXR session. // `on_process` is called as part of our OpenXR process handling, diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp index d9a66aa827..ea64f077c5 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp @@ -58,9 +58,14 @@ OpenXRHandTrackingExtension::~OpenXRHandTrackingExtension() { HashMap<String, bool *> OpenXRHandTrackingExtension::get_requested_extensions() { HashMap<String, bool *> request_extensions; + unobstructed_data_source = GLOBAL_GET("xr/openxr/extensions/hand_tracking_unobstructed_data_source"); + controller_data_source = GLOBAL_GET("xr/openxr/extensions/hand_tracking_controller_data_source"); + request_extensions[XR_EXT_HAND_TRACKING_EXTENSION_NAME] = &hand_tracking_ext; request_extensions[XR_EXT_HAND_JOINTS_MOTION_RANGE_EXTENSION_NAME] = &hand_motion_range_ext; - request_extensions[XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME] = &hand_tracking_source_ext; + if (unobstructed_data_source || controller_data_source) { + request_extensions[XR_EXT_HAND_TRACKING_DATA_SOURCE_EXTENSION_NAME] = &hand_tracking_source_ext; + } return request_extensions; } @@ -141,10 +146,18 @@ void OpenXRHandTrackingExtension::on_process() { void *next_pointer = nullptr; // Originally not all XR runtimes supported hand tracking data sourced both from controllers and normal hand tracking. - // With this extension we can indicate we accept input from both sources so hand tracking data is consistently provided - // on runtimes that support this. - XrHandTrackingDataSourceEXT data_sources[2] = { XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT, XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT }; - XrHandTrackingDataSourceInfoEXT data_source_info = { XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT, next_pointer, 2, data_sources }; + // With this extension we can indicate we wish to accept input from either or both sources. + // This functionality is subject to the abilities of the XR runtime and requires the data source extension. + // Note: If the data source extension is not available, no guarantees can be made on what the XR runtime supports. + uint32_t data_source_count = 0; + XrHandTrackingDataSourceEXT data_sources[2]; + if (unobstructed_data_source) { + data_sources[data_source_count++] = XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT; + } + if (controller_data_source) { + data_sources[data_source_count++] = XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT; + } + XrHandTrackingDataSourceInfoEXT data_source_info = { XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT, next_pointer, data_source_count, data_sources }; if (hand_tracking_source_ext) { // If supported include this info next_pointer = &data_source_info; @@ -224,7 +237,9 @@ void OpenXRHandTrackingExtension::on_process() { if (XR_FAILED(result)) { // not successful? then we do nothing. print_line("OpenXR: Failed to get tracking for hand", i, "[", OpenXRAPI::get_singleton()->get_error_string(result), "]"); + godot_tracker->set_hand_tracking_source(XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN); godot_tracker->set_has_tracking_data(false); + godot_tracker->invalidate_pose("default"); continue; } @@ -235,8 +250,6 @@ void OpenXRHandTrackingExtension::on_process() { } if (hand_trackers[i].locations.isActive) { - godot_tracker->set_has_tracking_data(true); - // SKELETON_RIG_HUMANOID bone adjustment. This rotation performs: // OpenXR Z+ -> Godot Humanoid Y- (Back along the bone) // OpenXR Y+ -> Godot Humanoid Z- (Out the back of the hand) @@ -245,7 +258,6 @@ void OpenXRHandTrackingExtension::on_process() { for (int joint = 0; joint < XR_HAND_JOINT_COUNT_EXT; joint++) { const XrHandJointLocationEXT &location = hand_trackers[i].joint_locations[joint]; const XrHandJointVelocityEXT &velocity = hand_trackers[i].joint_velocities[joint]; - const XrHandTrackingDataSourceStateEXT &data_source = hand_trackers[i].data_source; const XrPosef &pose = location.pose; Transform3D transform; @@ -285,23 +297,35 @@ void OpenXRHandTrackingExtension::on_process() { godot_tracker->set_hand_joint_radius((XRHandTracker::HandJoint)joint, location.radius); if (joint == XR_HAND_JOINT_PALM_EXT) { - XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN; - if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) { - source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED; - } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) { - source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER; - } - - godot_tracker->set_hand_tracking_source(source); - if (location.locationFlags & XR_SPACE_LOCATION_POSITION_TRACKED_BIT) { + if (location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) { + XrHandTrackingDataSourceStateEXT &data_source = hand_trackers[i].data_source; + + XRHandTracker::HandTrackingSource source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN; + if (hand_tracking_source_ext) { + if (!data_source.isActive) { + source = XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED; + } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) { + source = XRHandTracker::HAND_TRACKING_SOURCE_UNOBSTRUCTED; + } else if (data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) { + source = XRHandTracker::HAND_TRACKING_SOURCE_CONTROLLER; + } else { + // Data source shouldn't be active, if new data sources are added to OpenXR we need to enable them. + WARN_PRINT_ONCE("Unknown active data source found!"); + source = XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN; + } + } + godot_tracker->set_hand_tracking_source(source); + godot_tracker->set_has_tracking_data(true); godot_tracker->set_pose("default", transform, linear_velocity, angular_velocity); } else { + godot_tracker->set_hand_tracking_source(hand_tracking_source_ext ? XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED : XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN); godot_tracker->set_has_tracking_data(false); godot_tracker->invalidate_pose("default"); } } } } else { + godot_tracker->set_hand_tracking_source(hand_tracking_source_ext ? XRHandTracker::HAND_TRACKING_SOURCE_NOT_TRACKED : XRHandTracker::HAND_TRACKING_SOURCE_UNKNOWN); godot_tracker->set_has_tracking_data(false); godot_tracker->invalidate_pose("default"); } @@ -349,16 +373,17 @@ XrHandJointsMotionRangeEXT OpenXRHandTrackingExtension::get_motion_range(HandTra OpenXRHandTrackingExtension::HandTrackedSource OpenXRHandTrackingExtension::get_hand_tracking_source(HandTrackedHands p_hand) const { ERR_FAIL_UNSIGNED_INDEX_V(p_hand, OPENXR_MAX_TRACKED_HANDS, OPENXR_SOURCE_UNKNOWN); - if (hand_tracking_source_ext && hand_trackers[p_hand].data_source.isActive) { - switch (hand_trackers[p_hand].data_source.dataSource) { - case XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT: - return OPENXR_SOURCE_UNOBSTRUCTED; - - case XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT: - return OPENXR_SOURCE_CONTROLLER; - - default: - return OPENXR_SOURCE_UNKNOWN; + if (hand_tracking_source_ext) { + if (!hand_trackers[p_hand].data_source.isActive) { + return OPENXR_SOURCE_NOT_TRACKED; + } else if (hand_trackers[p_hand].data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_UNOBSTRUCTED_EXT) { + return OPENXR_SOURCE_UNOBSTRUCTED; + } else if (hand_trackers[p_hand].data_source.dataSource == XR_HAND_TRACKING_DATA_SOURCE_CONTROLLER_EXT) { + return OPENXR_SOURCE_CONTROLLER; + } else { + // Data source shouldn't be active, if new data sources are added to OpenXR we need to enable them. + WARN_PRINT_ONCE("Unknown active data source found!"); + return OPENXR_SOURCE_UNKNOWN; } } diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h index f709bc05c2..2c34ff7f21 100644 --- a/modules/openxr/extensions/openxr_hand_tracking_extension.h +++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h @@ -48,6 +48,7 @@ public: OPENXR_SOURCE_UNKNOWN, OPENXR_SOURCE_UNOBSTRUCTED, OPENXR_SOURCE_CONTROLLER, + OPENXR_SOURCE_NOT_TRACKED, OPENXR_SOURCE_MAX }; @@ -110,6 +111,8 @@ private: bool hand_tracking_ext = false; bool hand_motion_range_ext = false; bool hand_tracking_source_ext = false; + bool unobstructed_data_source = false; + bool controller_data_source = false; // functions void cleanup_hand_tracking(); diff --git a/modules/openxr/extensions/openxr_mxink_extension.cpp b/modules/openxr/extensions/openxr_mxink_extension.cpp new file mode 100644 index 0000000000..fe48583c27 --- /dev/null +++ b/modules/openxr/extensions/openxr_mxink_extension.cpp @@ -0,0 +1,83 @@ +/**************************************************************************/ +/* openxr_mxink_extension.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "openxr_mxink_extension.h" + +#include "../action_map/openxr_interaction_profile_metadata.h" + +// Not in base XR libs needs def +#define XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME "XR_LOGITECH_mx_ink_stylus_interaction" + +HashMap<String, bool *> OpenXRMxInkExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME] = &available; + + return request_extensions; +} + +bool OpenXRMxInkExtension::is_available() { + return available; +} + +void OpenXRMxInkExtension::on_register_metadata() { + OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton(); + ERR_FAIL_NULL(metadata); + + // Logitech MX Ink Stylus + metadata->register_interaction_profile("Logitech MX Ink Stylus", "/interaction_profiles/logitech/mx_ink_stylus_logitech", XR_LOGITECH_MX_INK_STYLUS_INTERACTION_EXTENSION_NAME); + + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip Force", "/user/hand/left", "/user/hand/left/input/tip_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Middle force", "/user/hand/left", "/user/hand/left/input/cluster_middle_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front click", "/user/hand/left", "/user/hand/left/input/cluster_front_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front double", "/user/hand/left", "/user/hand/left/input/cluster_front_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back click", "/user/hand/left", "/user/hand/left/input/cluster_back_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back double", "/user/hand/left", "/user/hand/left/input/cluster_back_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "System click", "/user/hand/left", "/user/hand/left/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Docked", "/user/hand/left", "/user/hand/left/input/dock_logitech/docked_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Grip pose", "/user/hand/left", "/user/hand/left/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Aim pose", "/user/hand/left", "/user/hand/left/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip pose", "/user/hand/left", "/user/hand/left/input/tip_logitech/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip Force", "/user/hand/right", "/user/hand/right/input/tip_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Middle force", "/user/hand/right", "/user/hand/right/input/cluster_middle_logitech/force", "", OpenXRAction::OPENXR_ACTION_FLOAT); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front click", "/user/hand/right", "/user/hand/right/input/cluster_front_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Front double", "/user/hand/right", "/user/hand/right/input/cluster_front_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back click", "/user/hand/right", "/user/hand/right/input/cluster_back_logitech/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Back double", "/user/hand/right", "/user/hand/right/input/cluster_back_logitech/double_tap_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "System click", "/user/hand/right", "/user/hand/right/input/system/click", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Docked", "/user/hand/right", "/user/hand/right/input/dock_logitech/docked_logitech", "", OpenXRAction::OPENXR_ACTION_BOOL); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Grip pose", "/user/hand/right", "/user/hand/right/input/grip/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Aim pose", "/user/hand/right", "/user/hand/right/input/aim/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Tip pose", "/user/hand/right", "/user/hand/right/input/tip_logitech/pose", "", OpenXRAction::OPENXR_ACTION_POSE); + + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Haptic output", "/user/hand/left", "/user/hand/left/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC); + metadata->register_io_path("/interaction_profiles/logitech/mx_ink_stylus_logitech", "Haptic output", "/user/hand/right", "/user/hand/right/output/haptic", "", OpenXRAction::OPENXR_ACTION_HAPTIC); +} diff --git a/modules/openxr/extensions/openxr_mxink_extension.h b/modules/openxr/extensions/openxr_mxink_extension.h new file mode 100644 index 0000000000..fe0cf866aa --- /dev/null +++ b/modules/openxr/extensions/openxr_mxink_extension.h @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* openxr_mxink_extension.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 OPENXR_MXINK_EXTENSION_H +#define OPENXR_MXINK_EXTENSION_H + +#include "openxr_extension_wrapper.h" + +class OpenXRMxInkExtension : public OpenXRExtensionWrapper { +public: + virtual HashMap<String, bool *> get_requested_extensions() override; + + bool is_available(); + + virtual void on_register_metadata() override; + +private: + bool available = false; +}; + +#endif // OPENXR_MXINK_EXTENSION_H diff --git a/modules/openxr/extensions/openxr_visibility_mask_extension.cpp b/modules/openxr/extensions/openxr_visibility_mask_extension.cpp new file mode 100644 index 0000000000..3e193d7d50 --- /dev/null +++ b/modules/openxr/extensions/openxr_visibility_mask_extension.cpp @@ -0,0 +1,279 @@ +/**************************************************************************/ +/* openxr_visibility_mask_extension.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "openxr_visibility_mask_extension.h" + +#include "../openxr_api.h" +#include "core/string/print_string.h" +#include "core/variant/array.h" +#include "core/variant/variant.h" +#include "servers/rendering_server.h" + +static const char *VISIBILITY_MASK_SHADER_CODE = + "shader_type spatial;\n" + "render_mode unshaded, shadows_disabled, cull_disabled;\n" + "void vertex() {\n" + "\tif (int(VERTEX.z) == VIEW_INDEX) {\n" + "\t\tVERTEX.z = -1.0;\n" + "\t\tVERTEX += EYE_OFFSET;\n" + "\t\tPOSITION = PROJECTION_MATRIX * vec4(VERTEX, 1.0);\n" + "\t\tPOSITION.xy /= POSITION.w;\n" + "\t\tPOSITION.z = 1.0;\n" + "\t\tPOSITION.w = 1.0;\n" + "\t} else {\n" + "\t\tPOSITION = vec4(2.0, 2.0, 2.0, 1.0);\n" + "\t}\n" + "}\n" + "void fragment() {\n" + "\tALBEDO = vec3(0.0, 0.0, 0.0);\n" + "}\n"; + +OpenXRVisibilityMaskExtension *OpenXRVisibilityMaskExtension::singleton = nullptr; + +OpenXRVisibilityMaskExtension *OpenXRVisibilityMaskExtension::get_singleton() { + return singleton; +} + +OpenXRVisibilityMaskExtension::OpenXRVisibilityMaskExtension() { + singleton = this; +} + +OpenXRVisibilityMaskExtension::~OpenXRVisibilityMaskExtension() { + singleton = nullptr; +} + +HashMap<String, bool *> OpenXRVisibilityMaskExtension::get_requested_extensions() { + HashMap<String, bool *> request_extensions; + + request_extensions[XR_KHR_VISIBILITY_MASK_EXTENSION_NAME] = &available; + + return request_extensions; +} + +void OpenXRVisibilityMaskExtension::on_instance_created(const XrInstance p_instance) { + if (available) { + EXT_INIT_XR_FUNC(xrGetVisibilityMaskKHR); + } +} + +void OpenXRVisibilityMaskExtension::on_session_created(const XrSession p_instance) { + if (available) { + RS *rendering_server = RS::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + OpenXRAPI *openxr_api = (OpenXRAPI *)OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + + // Create our shader. + shader = rendering_server->shader_create(); + rendering_server->shader_set_code(shader, VISIBILITY_MASK_SHADER_CODE); + + // Create our material. + material = rendering_server->material_create(); + rendering_server->material_set_shader(material, shader); + rendering_server->material_set_render_priority(material, 99); + + // Create our mesh. + mesh = rendering_server->mesh_create(); + + // Get our initial mesh data. + mesh_count = openxr_api->get_view_count(); // We need a mesh for each view. + for (uint32_t i = 0; i < mesh_count; i++) { + _update_mesh_data(i); + } + + // And update our mesh + _update_mesh(); + } +} + +void OpenXRVisibilityMaskExtension::on_session_destroyed() { + RS *rendering_server = RS::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + // Free our mesh. + if (mesh.is_valid()) { + rendering_server->free(mesh); + mesh = RID(); + } + + // Free our material. + if (material.is_valid()) { + rendering_server->free(material); + material = RID(); + } + + // Free our shader. + if (shader.is_valid()) { + rendering_server->free(shader); + shader = RID(); + } + + mesh_count = 0; +} + +void OpenXRVisibilityMaskExtension::on_pre_render() { + // Update mesh data if its dirty. + // Here we call this from the rendering thread however as we're going through the rendering server this is safe. + _update_mesh(); +} + +bool OpenXRVisibilityMaskExtension::on_event_polled(const XrEventDataBuffer &event) { + if (event.type == XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR) { + XrEventDataVisibilityMaskChangedKHR *vismask_event = (XrEventDataVisibilityMaskChangedKHR *)&event; + + print_verbose("OpenXR EVENT: Visibility mask changed for view " + String::num_uint64(vismask_event->viewIndex)); + + if (available) { // This event won't be called if this extension is not available but better safe than sorry. + _update_mesh_data(vismask_event->viewIndex); + } + + return true; + } + + return false; +} + +bool OpenXRVisibilityMaskExtension::is_available() { + return available; +} + +RID OpenXRVisibilityMaskExtension::get_mesh() { + return mesh; +} + +void OpenXRVisibilityMaskExtension::_update_mesh_data(uint32_t p_view) { + if (available) { + ERR_FAIL_UNSIGNED_INDEX(p_view, 4); + + OpenXRAPI *openxr_api = OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + + XrSession session = openxr_api->get_session(); + XrViewConfigurationType view_configuration_type = openxr_api->get_view_configuration(); + + // Figure out how much data we're getting. + XrVisibilityMaskKHR visibility_mask_data = { + XR_TYPE_VISIBILITY_MASK_KHR, + nullptr, + 0, + 0, + nullptr, + 0, + 0, + nullptr, + }; + + XrResult result = xrGetVisibilityMaskKHR(session, view_configuration_type, p_view, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &visibility_mask_data); + if (XR_FAILED(result)) { + print_line("OpenXR: Unable to obtain visibility mask metrics [", openxr_api->get_error_string(result), "]"); + return; + } + + // Resize buffers + mesh_data[p_view].vertices.resize(visibility_mask_data.vertexCountOutput); + mesh_data[p_view].indices.resize(visibility_mask_data.indexCountOutput); + + visibility_mask_data.vertexCapacityInput = visibility_mask_data.vertexCountOutput; + visibility_mask_data.vertices = mesh_data[p_view].vertices.ptrw(); + visibility_mask_data.indexCapacityInput = visibility_mask_data.indexCountOutput; + visibility_mask_data.indices = mesh_data[p_view].indices.ptrw(); + + result = xrGetVisibilityMaskKHR(session, view_configuration_type, p_view, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &visibility_mask_data); + if (XR_FAILED(result)) { + print_line("OpenXR: Unable to obtain visibility mask data [", openxr_api->get_error_string(result), "]"); + return; + } + + // Mark as dirty, we have updated mesh data. + is_dirty = true; + } +} + +void OpenXRVisibilityMaskExtension::_update_mesh() { + if (available && is_dirty && mesh_count > 0) { + RS *rendering_server = RS::get_singleton(); + ERR_FAIL_NULL(rendering_server); + + OpenXRAPI *openxr_api = (OpenXRAPI *)OpenXRAPI::get_singleton(); + ERR_FAIL_NULL(openxr_api); + + // Combine all vertex and index buffers into one. + PackedVector3Array vertices; + PackedInt32Array indices; + + uint64_t vertice_count = 0; + uint64_t index_count = 0; + + for (uint32_t i = 0; i < mesh_count; i++) { + vertice_count += mesh_data[i].vertices.size(); + index_count += mesh_data[i].indices.size(); + } + + vertices.resize(vertice_count); + indices.resize(index_count); + uint64_t offset = 0; + + Vector3 *v_out = vertices.ptrw(); + int32_t *i_out = indices.ptrw(); + + for (uint32_t i = 0; i < mesh_count; i++) { + const XrVector2f *v_in = mesh_data[i].vertices.ptr(); + for (uint32_t j = 0; j < mesh_data[i].vertices.size(); j++) { + v_out->x = v_in->x; + v_out->y = v_in->y; + v_out->z = float(i); // We store our view in our Z component, our shader will filter the right faces out. + v_out++; + v_in++; + } + const uint32_t *i_in = mesh_data[i].indices.ptr(); + for (uint32_t j = 0; j < mesh_data[i].indices.size(); j++) { + *i_out = offset + *i_in; + i_out++; + i_in++; + } + + offset += mesh_data[i].vertices.size(); + } + + // Update our mesh. + Array arr; + arr.resize(RS::ARRAY_MAX); + arr[RS::ARRAY_VERTEX] = vertices; + arr[RS::ARRAY_INDEX] = indices; + + rendering_server->mesh_clear(mesh); + rendering_server->mesh_add_surface_from_arrays(mesh, RS::PRIMITIVE_TRIANGLES, arr); + rendering_server->mesh_surface_set_material(mesh, 0, material); + + // Set no longer dirty. + is_dirty = false; + } +} diff --git a/modules/openxr/extensions/openxr_visibility_mask_extension.h b/modules/openxr/extensions/openxr_visibility_mask_extension.h new file mode 100644 index 0000000000..ee80dd02ff --- /dev/null +++ b/modules/openxr/extensions/openxr_visibility_mask_extension.h @@ -0,0 +1,95 @@ +/**************************************************************************/ +/* openxr_visibility_mask_extension.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 OPENXR_VISIBILITY_MASK_EXTENSION_H +#define OPENXR_VISIBILITY_MASK_EXTENSION_H + +#include "../util.h" + +#include "core/templates/vector.h" +#include "openxr_extension_wrapper.h" +#include "scene/resources/mesh.h" + +// The OpenXR visibility mask extension provides a mesh for each eye that +// can be used as a mask to determine which part of our rendered result +// is actually visible to the user. Due to lens distortion the edges of +// the rendered image are never used in the final result output on the HMD. +// +// Blacking out this are of the render result can remove a fair amount of +// overhead in rendering part of the screen that is unused. +// +// https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_KHR_visibility_mask + +class OpenXRVisibilityMaskExtension : public OpenXRExtensionWrapper { +public: + static OpenXRVisibilityMaskExtension *get_singleton(); + + OpenXRVisibilityMaskExtension(); + virtual ~OpenXRVisibilityMaskExtension() 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_instance) override; + virtual void on_session_destroyed() override; + + virtual void on_pre_render() override; + virtual bool on_event_polled(const XrEventDataBuffer &event) override; + + bool is_available(); + RID get_mesh(); + +private: + static OpenXRVisibilityMaskExtension *singleton; + + bool available = false; + bool is_dirty = false; + + RID shader; + RID material; + RID mesh; + + struct MeshData { + Vector<XrVector2f> vertices; + Vector<uint32_t> indices; + }; + + uint32_t mesh_count = 0; + MeshData mesh_data[4]; + + void _update_mesh_data(uint32_t p_view); + void _update_mesh(); + + // OpenXR API call wrappers + EXT_PROTO_XRRESULT_FUNC5(xrGetVisibilityMaskKHR, (XrSession), session, (XrViewConfigurationType), viewConfigurationType, (uint32_t), viewIndex, (XrVisibilityMaskTypeKHR), visibilityMaskType, (XrVisibilityMaskKHR *), visibilityMask); +}; + +#endif // OPENXR_VISIBILITY_MASK_EXTENSION_H diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp index d92084a220..de4a9e4b8e 100644 --- a/modules/openxr/extensions/platform/openxr_opengl_extension.cpp +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.cpp @@ -56,11 +56,6 @@ // feature off. // See: https://registry.khronos.org/OpenGL/extensions/EXT/EXT_sRGB_write_control.txt -// On OpenGLES this is not defined in our standard headers.. -#ifndef GL_FRAMEBUFFER_SRGB -#define GL_FRAMEBUFFER_SRGB 0x8DB9 -#endif - HashMap<String, bool *> OpenXROpenGLExtension::get_requested_extensions() { HashMap<String, bool *> request_extensions; @@ -196,23 +191,6 @@ void OpenXROpenGLExtension::get_usable_depth_formats(Vector<int64_t> &p_usable_d p_usable_depth_formats.push_back(GL_DEPTH_COMPONENT24); } -void OpenXROpenGLExtension::on_pre_draw_viewport(RID p_render_target) { - if (srgb_ext_is_available) { - hw_linear_to_srgb_is_enabled = glIsEnabled(GL_FRAMEBUFFER_SRGB); - if (hw_linear_to_srgb_is_enabled) { - // Disable this. - glDisable(GL_FRAMEBUFFER_SRGB); - } - } -} - -void OpenXROpenGLExtension::on_post_draw_viewport(RID p_render_target) { - if (srgb_ext_is_available && hw_linear_to_srgb_is_enabled) { - // Re-enable this. - glEnable(GL_FRAMEBUFFER_SRGB); - } -} - bool OpenXROpenGLExtension::get_swapchain_image_data(XrSwapchain p_swapchain, int64_t p_swapchain_format, uint32_t p_width, uint32_t p_height, uint32_t p_sample_count, uint32_t p_array_size, void **r_swapchain_graphics_data) { GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); ERR_FAIL_NULL_V(texture_storage, false); diff --git a/modules/openxr/extensions/platform/openxr_opengl_extension.h b/modules/openxr/extensions/platform/openxr_opengl_extension.h index a3052d3f53..8da3ca48f4 100644 --- a/modules/openxr/extensions/platform/openxr_opengl_extension.h +++ b/modules/openxr/extensions/platform/openxr_opengl_extension.h @@ -49,9 +49,6 @@ public: virtual void on_instance_created(const XrInstance p_instance) override; virtual void *set_session_create_and_get_next_pointer(void *p_next_pointer) override; - virtual void on_pre_draw_viewport(RID p_render_target) override; - virtual void on_post_draw_viewport(RID p_render_target) override; - virtual void get_usable_swapchain_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual void get_usable_depth_formats(Vector<int64_t> &p_usable_swap_chains) override; virtual String get_swapchain_format_name(int64_t p_swapchain_format) const override; @@ -76,9 +73,6 @@ private: Vector<RID> texture_rids; }; - bool srgb_ext_is_available = true; - bool hw_linear_to_srgb_is_enabled = false; - bool check_graphics_api_support(XrVersion p_desired_version); #ifdef ANDROID_ENABLED diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 04edde8300..ecf7c05789 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -54,6 +54,7 @@ #endif #include "extensions/openxr_composition_layer_depth_extension.h" +#include "extensions/openxr_debug_utils_extension.h" #include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" #include "extensions/openxr_fb_foveation_extension.h" @@ -316,6 +317,46 @@ String OpenXRAPI::get_swapchain_format_name(int64_t p_swapchain_format) const { return String("Swapchain format ") + String::num_int64(int64_t(p_swapchain_format)); } +void OpenXRAPI::set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const String &p_object_name) { + OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton(); + if (!debug_utils || !debug_utils->get_active()) { + // Not enabled/active? Ignore. + return; + } + + debug_utils->set_object_name(p_object_type, p_object_handle, p_object_name.utf8().get_data()); +} + +void OpenXRAPI::begin_debug_label_region(const String &p_label_name) { + OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton(); + if (!debug_utils || !debug_utils->get_active()) { + // Not enabled/active? Ignore. + return; + } + + debug_utils->begin_debug_label_region(p_label_name.utf8().get_data()); +} + +void OpenXRAPI::end_debug_label_region() { + OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton(); + if (!debug_utils || !debug_utils->get_active()) { + // Not enabled/active? Ignore. + return; + } + + debug_utils->end_debug_label_region(); +} + +void OpenXRAPI::insert_debug_label(const String &p_label_name) { + OpenXRDebugUtilsExtension *debug_utils = OpenXRDebugUtilsExtension::get_singleton(); + if (!debug_utils || !debug_utils->get_active()) { + // Not enabled/active? Ignore. + return; + } + + debug_utils->insert_debug_label(p_label_name.utf8().get_data()); +} + bool OpenXRAPI::load_layer_properties() { // This queries additional layers that are available and can be initialized when we create our OpenXR instance if (layer_properties != nullptr) { @@ -525,7 +566,7 @@ bool OpenXRAPI::create_instance() { 1, // applicationVersion, we don't currently have this "Godot Game Engine", // engineName VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH, // engineVersion 4.0 -> 40000, 4.0.1 -> 40001, 4.1 -> 40100, etc. - XR_CURRENT_API_VERSION // apiVersion + XR_API_VERSION_1_0 // apiVersion }; void *next_pointer = nullptr; @@ -707,13 +748,6 @@ bool OpenXRAPI::load_supported_environmental_blend_modes() { print_verbose(String("OpenXR: Found environmental blend mode ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[i])); } - // Check value we loaded at startup... - if (!is_environment_blend_mode_supported(environment_blend_mode)) { - print_verbose(String("OpenXR: ") + OpenXRUtil::get_environment_blend_mode_name(environment_blend_mode) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[0])); - - environment_blend_mode = supported_environment_blend_modes[0]; - } - return true; } @@ -833,10 +867,21 @@ bool OpenXRAPI::create_session() { return false; } + set_object_name(XR_OBJECT_TYPE_SESSION, uint64_t(session), "Main Godot OpenXR Session"); + + begin_debug_label_region("Godot session active"); + for (OpenXRExtensionWrapper *wrapper : registered_extension_wrappers) { wrapper->on_session_created(session); } + // Check our environment blend mode. This needs to happen after we call `on_session_created()` + // on the extension wrappers, so they can emulate alpha blend mode. + if (!set_environment_blend_mode(environment_blend_mode)) { + print_verbose(String("OpenXR: ") + OpenXRUtil::get_environment_blend_mode_name(environment_blend_mode) + String(" isn't supported, defaulting to ") + OpenXRUtil::get_environment_blend_mode_name(supported_environment_blend_modes[0])); + set_environment_blend_mode(supported_environment_blend_modes[0]); + } + return true; } @@ -916,6 +961,8 @@ bool OpenXRAPI::setup_play_space() { print_line("OpenXR: Failed to create LOCAL space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); will_emulate_local_floor = false; } + + set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(local_floor_emulation.local_space), "Emulation local space"); } if (local_floor_emulation.stage_space == XR_NULL_HANDLE) { @@ -931,6 +978,8 @@ bool OpenXRAPI::setup_play_space() { print_line("OpenXR: Failed to create STAGE space in order to emulate LOCAL_FLOOR [", get_error_string(result), "]"); will_emulate_local_floor = false; } + + set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(local_floor_emulation.stage_space), "Emulation stage space"); } if (!will_emulate_local_floor) { @@ -972,6 +1021,8 @@ bool OpenXRAPI::setup_play_space() { play_space = new_play_space; reference_space = new_reference_space; + set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(play_space), "Play space"); + local_floor_emulation.enabled = will_emulate_local_floor; local_floor_emulation.should_reset_floor_height = will_emulate_local_floor; @@ -1007,6 +1058,8 @@ bool OpenXRAPI::setup_view_space() { return false; } + set_object_name(XR_OBJECT_TYPE_SPACE, uint64_t(view_space), "View space"); + return true; } @@ -1181,6 +1234,8 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, color_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) { return false; } + + set_object_name(XR_OBJECT_TYPE_SWAPCHAIN, uint64_t(render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain()), "Main color swapchain"); } // We create our depth swapchain if: @@ -1191,6 +1246,8 @@ bool OpenXRAPI::create_main_swapchains(Size2i p_size) { if (!render_state.main_swapchains[OPENXR_SWAPCHAIN_DEPTH].create(0, XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, depth_swapchain_format, render_state.main_swapchain_size.width, render_state.main_swapchain_size.height, sample_count, view_count)) { return false; } + + set_object_name(XR_OBJECT_TYPE_SWAPCHAIN, uint64_t(render_state.main_swapchains[OPENXR_SWAPCHAIN_COLOR].get_swapchain()), "Main depth swapchain"); } // We create our velocity swapchain if: @@ -1309,6 +1366,8 @@ void OpenXRAPI::destroy_session() { wrapper->on_session_destroyed(); } + end_debug_label_region(); + xrDestroySession(session); session = XR_NULL_HANDLE; } @@ -1459,6 +1518,10 @@ void OpenXRAPI::set_form_factor(XrFormFactor p_form_factor) { form_factor = p_form_factor; } +uint32_t OpenXRAPI::get_view_count() { + return view_count; +} + void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configuration) { ERR_FAIL_COND(is_initialized()); @@ -1733,8 +1796,12 @@ void OpenXRAPI::cleanup_extension_wrappers() { XrHandTrackerEXT OpenXRAPI::get_hand_tracker(int p_hand_index) { ERR_FAIL_INDEX_V(p_hand_index, OpenXRHandTrackingExtension::HandTrackedHands::OPENXR_MAX_TRACKED_HANDS, XR_NULL_HANDLE); + + OpenXRHandTrackingExtension *hand_tracking = OpenXRHandTrackingExtension::get_singleton(); + ERR_FAIL_NULL_V(hand_tracking, XR_NULL_HANDLE); + OpenXRHandTrackingExtension::HandTrackedHands hand = static_cast<OpenXRHandTrackingExtension::HandTrackedHands>(p_hand_index); - return OpenXRHandTrackingExtension::get_singleton()->get_hand_tracker(hand)->hand_tracker; + return hand_tracking->get_hand_tracker(hand)->hand_tracker; } Size2 OpenXRAPI::get_recommended_target_size() { @@ -1914,15 +1981,6 @@ bool OpenXRAPI::poll_events() { // We probably didn't poll fast enough, just output warning WARN_PRINT("OpenXR EVENT: " + itos(event->lostEventCount) + " event data lost!"); } break; - case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: { - // XrEventDataVisibilityMaskChangedKHR *event = (XrEventDataVisibilityMaskChangedKHR *)&runtimeEvent; - - // TODO implement this in the future, we should call xrGetVisibilityMaskKHR to obtain a mask, - // this will allow us to prevent rendering the part of our view which is never displayed giving us - // a decent performance improvement. - - print_verbose("OpenXR EVENT: STUB: visibility mask changed"); - } break; case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { XrEventDataInstanceLossPending *event = (XrEventDataInstanceLossPending *)&runtimeEvent; @@ -2216,6 +2274,9 @@ void OpenXRAPI::pre_render() { } } + // We should get our frame no from the rendering server, but this will do. + begin_debug_label_region(String("Session Frame ") + String::num_uint64(++render_state.frame)); + // let's start our frame.. XrFrameBeginInfo frame_begin_info = { XR_TYPE_FRAME_BEGIN_INFO, // type @@ -2334,6 +2395,8 @@ void OpenXRAPI::end_frame() { return; } + end_debug_label_region(); // Session frame # + // neither eye is rendered return; } @@ -2408,6 +2471,8 @@ void OpenXRAPI::end_frame() { print_line("OpenXR: failed to end frame! [", get_error_string(result), "]"); return; } + + end_debug_label_region(); // Session frame # } float OpenXRAPI::get_display_refresh_rate() const { @@ -2823,6 +2888,8 @@ RID OpenXRAPI::action_set_create(const String p_name, const String p_localized_n return RID(); } + set_object_name(XR_OBJECT_TYPE_ACTION_SET, uint64_t(action_set.handle), p_name); + return action_set_owner.make_rid(action_set); } @@ -2998,6 +3065,8 @@ RID OpenXRAPI::action_create(RID p_action_set, const String p_name, const String return RID(); } + set_object_name(XR_OBJECT_TYPE_ACTION, uint64_t(action.handle), p_name); + return action_owner.make_rid(action); } diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 748ef3af94..0d1e4eb414 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -336,6 +336,7 @@ private: XrTime predicted_display_time = 0; XrSpace play_space = XR_NULL_HANDLE; double render_target_size_multiplier = 1.0; + uint64_t frame = 0; uint32_t view_count = 0; XrView *views = nullptr; @@ -422,6 +423,10 @@ public: XrResult get_instance_proc_addr(const char *p_name, PFN_xrVoidFunction *p_addr); String get_error_string(XrResult result) const; String get_swapchain_format_name(int64_t p_swapchain_format) const; + void set_object_name(XrObjectType p_object_type, uint64_t p_object_handle, const String &p_object_name); + void begin_debug_label_region(const String &p_label_name); + void end_debug_label_region(); + void insert_debug_label(const String &p_label_name); OpenXRInterface *get_xr_interface() const { return xr_interface; } void set_xr_interface(OpenXRInterface *p_xr_interface); @@ -434,6 +439,7 @@ public: void set_form_factor(XrFormFactor p_form_factor); XrFormFactor get_form_factor() const { return form_factor; } + uint32_t get_view_count(); void set_view_configuration(XrViewConfigurationType p_view_configuration); XrViewConfigurationType get_view_configuration() const { return view_configuration; } @@ -454,7 +460,6 @@ public: _FORCE_INLINE_ XrTime get_predicted_display_time() { return frame_state.predictedDisplayTime; } _FORCE_INLINE_ XrTime get_next_frame_time() { return frame_state.predictedDisplayTime + frame_state.predictedDisplayPeriod; } _FORCE_INLINE_ bool can_render() { - ERR_ON_RENDER_THREAD_V(false); return instance != XR_NULL_HANDLE && session != XR_NULL_HANDLE && running && frame_state.shouldRender; } diff --git a/modules/openxr/openxr_api_extension.cpp b/modules/openxr/openxr_api_extension.cpp index a1744fa1db..f3bc178d3a 100644 --- a/modules/openxr/openxr_api_extension.cpp +++ b/modules/openxr/openxr_api_extension.cpp @@ -43,6 +43,10 @@ void OpenXRAPIExtension::_bind_methods() { ClassDB::bind_method(D_METHOD("get_instance_proc_addr", "name"), &OpenXRAPIExtension::get_instance_proc_addr); ClassDB::bind_method(D_METHOD("get_error_string", "result"), &OpenXRAPIExtension::get_error_string); ClassDB::bind_method(D_METHOD("get_swapchain_format_name", "swapchain_format"), &OpenXRAPIExtension::get_swapchain_format_name); + ClassDB::bind_method(D_METHOD("set_object_name", "object_type", "object_handle", "object_name"), &OpenXRAPIExtension::set_object_name); + ClassDB::bind_method(D_METHOD("begin_debug_label_region", "label_name"), &OpenXRAPIExtension::begin_debug_label_region); + ClassDB::bind_method(D_METHOD("end_debug_label_region"), &OpenXRAPIExtension::end_debug_label_region); + ClassDB::bind_method(D_METHOD("insert_debug_label", "label_name"), &OpenXRAPIExtension::insert_debug_label); ClassDB::bind_method(D_METHOD("is_initialized"), &OpenXRAPIExtension::is_initialized); ClassDB::bind_method(D_METHOD("is_running"), &OpenXRAPIExtension::is_running); @@ -116,6 +120,30 @@ String OpenXRAPIExtension::get_swapchain_format_name(int64_t p_swapchain_format) return OpenXRAPI::get_singleton()->get_swapchain_format_name(p_swapchain_format); } +void OpenXRAPIExtension::set_object_name(int64_t p_object_type, uint64_t p_object_handle, const String &p_object_name) { + ERR_FAIL_NULL(OpenXRAPI::get_singleton()); + + OpenXRAPI::get_singleton()->set_object_name(XrObjectType(p_object_type), p_object_handle, p_object_name); +} + +void OpenXRAPIExtension::begin_debug_label_region(const String &p_label_name) { + ERR_FAIL_NULL(OpenXRAPI::get_singleton()); + + OpenXRAPI::get_singleton()->begin_debug_label_region(p_label_name); +} + +void OpenXRAPIExtension::end_debug_label_region() { + ERR_FAIL_NULL(OpenXRAPI::get_singleton()); + + OpenXRAPI::get_singleton()->end_debug_label_region(); +} + +void OpenXRAPIExtension::insert_debug_label(const String &p_label_name) { + ERR_FAIL_NULL(OpenXRAPI::get_singleton()); + + OpenXRAPI::get_singleton()->insert_debug_label(p_label_name); +} + bool OpenXRAPIExtension::is_initialized() { ERR_FAIL_NULL_V(OpenXRAPI::get_singleton(), false); return OpenXRAPI::get_singleton()->is_initialized(); diff --git a/modules/openxr/openxr_api_extension.h b/modules/openxr/openxr_api_extension.h index cff2c4738e..1b88b418f6 100644 --- a/modules/openxr/openxr_api_extension.h +++ b/modules/openxr/openxr_api_extension.h @@ -64,6 +64,10 @@ public: uint64_t get_instance_proc_addr(String p_name); String get_error_string(uint64_t result); String get_swapchain_format_name(int64_t p_swapchain_format); + void set_object_name(int64_t p_object_type, uint64_t p_object_handle, const String &p_object_name); + void begin_debug_label_region(const String &p_label_name); + void end_debug_label_region(); + void insert_debug_label(const String &p_label_name); bool is_initialized(); bool is_running(); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index fbbc61a91c..73ac529537 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -36,7 +36,8 @@ #include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_hand_interaction_extension.h" -#include "thirdparty/openxr/include/openxr/openxr.h" + +#include <openxr/openxr.h> void OpenXRInterface::_bind_methods() { // lifecycle signals @@ -651,6 +652,10 @@ bool OpenXRInterface::initialize() { // make this our primary interface xr_server->set_primary_interface(this); + // Register an additional output with the display server, so rendering won't + // be skipped if no windows are visible. + DisplayServer::get_singleton()->register_additional_output(this); + initialized = true; return initialized; @@ -674,6 +679,8 @@ void OpenXRInterface::uninitialize() { } } + DisplayServer::get_singleton()->unregister_additional_output(this); + initialized = false; } diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp index 85514737f2..f3fda2517c 100644 --- a/modules/openxr/register_types.cpp +++ b/modules/openxr/register_types.cpp @@ -44,9 +44,11 @@ #include "scene/openxr_composition_layer_equirect.h" #include "scene/openxr_composition_layer_quad.h" #include "scene/openxr_hand.h" +#include "scene/openxr_visibility_mask.h" #include "extensions/openxr_composition_layer_depth_extension.h" #include "extensions/openxr_composition_layer_extension.h" +#include "extensions/openxr_debug_utils_extension.h" #include "extensions/openxr_eye_gaze_interaction.h" #include "extensions/openxr_fb_display_refresh_rate_extension.h" #include "extensions/openxr_hand_interaction_extension.h" @@ -57,8 +59,10 @@ #include "extensions/openxr_local_floor_extension.h" #include "extensions/openxr_meta_controller_extension.h" #include "extensions/openxr_ml2_controller_extension.h" +#include "extensions/openxr_mxink_extension.h" #include "extensions/openxr_palm_pose_extension.h" #include "extensions/openxr_pico_controller_extension.h" +#include "extensions/openxr_visibility_mask_extension.h" #include "extensions/openxr_wmr_controller_extension.h" #ifdef TOOLS_ENABLED @@ -126,8 +130,13 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRMetaControllerExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension)); OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandInteractionExtension)); + OpenXRAPI::register_extension_wrapper(memnew(OpenXRMxInkExtension)); + OpenXRAPI::register_extension_wrapper(memnew(OpenXRVisibilityMaskExtension)); // register gated extensions + if (int(GLOBAL_GET("xr/openxr/extensions/debug_utils")) > 0) { + OpenXRAPI::register_extension_wrapper(memnew(OpenXRDebugUtilsExtension)); + } if (GLOBAL_GET("xr/openxr/extensions/hand_tracking")) { OpenXRAPI::register_extension_wrapper(memnew(OpenXRHandTrackingExtension)); } @@ -179,6 +188,8 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) { GDREGISTER_CLASS(OpenXRHand); + GDREGISTER_CLASS(OpenXRVisibilityMask); + XRServer *xr_server = XRServer::get_singleton(); if (xr_server) { openxr_interface.instantiate(); diff --git a/modules/openxr/scene/openxr_visibility_mask.cpp b/modules/openxr/scene/openxr_visibility_mask.cpp new file mode 100644 index 0000000000..7214fac327 --- /dev/null +++ b/modules/openxr/scene/openxr_visibility_mask.cpp @@ -0,0 +1,106 @@ +/**************************************************************************/ +/* openxr_visibility_mask.cpp */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +#include "openxr_visibility_mask.h" + +#include "../extensions/openxr_visibility_mask_extension.h" +#include "../openxr_interface.h" +#include "scene/3d/xr_nodes.h" + +void OpenXRVisibilityMask::_bind_methods() { +} + +void OpenXRVisibilityMask::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + OpenXRVisibilityMaskExtension *vis_mask_ext = OpenXRVisibilityMaskExtension::get_singleton(); + if (vis_mask_ext && vis_mask_ext->is_available()) { + set_base(vis_mask_ext->get_mesh()); + } + } break; + case NOTIFICATION_EXIT_TREE: { + set_base(RID()); + } break; + } +} + +void OpenXRVisibilityMask::_on_openxr_session_begun() { + if (is_inside_tree()) { + OpenXRVisibilityMaskExtension *vis_mask_ext = OpenXRVisibilityMaskExtension::get_singleton(); + if (vis_mask_ext && vis_mask_ext->is_available()) { + set_base(vis_mask_ext->get_mesh()); + } + } +} + +void OpenXRVisibilityMask::_on_openxr_session_stopping() { + set_base(RID()); +} + +PackedStringArray OpenXRVisibilityMask::get_configuration_warnings() const { + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); + + if (is_visible() && is_inside_tree()) { + XRCamera3D *camera = Object::cast_to<XRCamera3D>(get_parent()); + if (camera == nullptr) { + warnings.push_back(RTR("OpenXR visibility mask must have an XRCamera3D node as their parent.")); + } + } + + return warnings; +} + +AABB OpenXRVisibilityMask::get_aabb() const { + AABB ret; + + // Make sure it's always visible, this is positioned through its shader. + ret.position = Vector3(-1000.0, -1000.0, -1000.0); + ret.size = Vector3(2000.0, 2000.0, 2000.0); + + return ret; +} + +OpenXRVisibilityMask::OpenXRVisibilityMask() { + Ref<OpenXRInterface> openxr_interface = XRServer::get_singleton()->find_interface("OpenXR"); + if (openxr_interface.is_valid()) { + openxr_interface->connect("session_begun", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_begun)); + openxr_interface->connect("session_stopping", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_stopping)); + } + + RS::get_singleton()->instance_geometry_set_cast_shadows_setting(get_instance(), RS::SHADOW_CASTING_SETTING_OFF); +} + +OpenXRVisibilityMask::~OpenXRVisibilityMask() { + Ref<OpenXRInterface> openxr_interface = XRServer::get_singleton()->find_interface("OpenXR"); + if (openxr_interface.is_valid()) { + openxr_interface->disconnect("session_begun", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_begun)); + openxr_interface->disconnect("session_stopping", callable_mp(this, &OpenXRVisibilityMask::_on_openxr_session_stopping)); + } +} diff --git a/modules/openxr/scene/openxr_visibility_mask.h b/modules/openxr/scene/openxr_visibility_mask.h new file mode 100644 index 0000000000..0acb30fc0a --- /dev/null +++ b/modules/openxr/scene/openxr_visibility_mask.h @@ -0,0 +1,56 @@ +/**************************************************************************/ +/* openxr_visibility_mask.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 OPENXR_VISIBILITY_MASK_H +#define OPENXR_VISIBILITY_MASK_H + +#include "scene/3d/visual_instance_3d.h" + +class OpenXRVisibilityMask : public VisualInstance3D { + GDCLASS(OpenXRVisibilityMask, VisualInstance3D); + +protected: + static void _bind_methods(); + + void _notification(int p_what); + + void _on_openxr_session_begun(); + void _on_openxr_session_stopping(); + +public: + virtual PackedStringArray get_configuration_warnings() const override; + + virtual AABB get_aabb() const override; + + OpenXRVisibilityMask(); + ~OpenXRVisibilityMask(); +}; + +#endif // OPENXR_VISIBILITY_MASK_H diff --git a/modules/squish/image_decompress_squish.cpp b/modules/squish/image_decompress_squish.cpp index fba76621d6..3841ba8db1 100644 --- a/modules/squish/image_decompress_squish.cpp +++ b/modules/squish/image_decompress_squish.cpp @@ -40,7 +40,7 @@ void image_decompress_squish(Image *p_image) { Image::Format target_format = Image::FORMAT_RGBA8; Vector<uint8_t> data; - int target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); + int64_t target_size = Image::get_image_data_size(w, h, target_format, p_image->has_mipmaps()); int mm_count = p_image->get_mipmap_count(); data.resize(target_size); @@ -77,10 +77,11 @@ void image_decompress_squish(Image *p_image) { } for (int i = 0; i <= mm_count; i++) { - int src_ofs = 0, mipmap_size = 0, mipmap_w = 0, mipmap_h = 0; + int64_t src_ofs = 0, mipmap_size = 0; + int mipmap_w = 0, mipmap_h = 0; p_image->get_mipmap_offset_size_and_dimensions(i, src_ofs, mipmap_size, mipmap_w, mipmap_h); - int dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i); + int64_t dst_ofs = Image::get_image_mipmap_offset(p_image->get_width(), p_image->get_height(), target_format, i); squish::DecompressImage(&wb[dst_ofs], w, h, &rb[src_ofs], squish_flags); w >>= 1; diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub index 68a5d499d4..4112b81622 100644 --- a/modules/text_server_adv/SCsub +++ b/modules/text_server_adv/SCsub @@ -142,7 +142,14 @@ if env["builtin_harfbuzz"]: env_harfbuzz.Append(CCFLAGS=["-DHAVE_ICU"]) if env["builtin_icu4c"]: env_harfbuzz.Prepend(CPPPATH=["#thirdparty/icu4c/common/", "#thirdparty/icu4c/i18n/"]) - env_harfbuzz.Append(CCFLAGS=["-DU_HAVE_LIB_SUFFIX=1", "-DU_LIB_SUFFIX_C_NAME=_godot", "-DHAVE_ICU_BUILTIN"]) + env_harfbuzz.Append( + CCFLAGS=[ + "-DU_STATIC_IMPLEMENTATION", + "-DU_HAVE_LIB_SUFFIX=1", + "-DU_LIB_SUFFIX_C_NAME=_godot", + "-DHAVE_ICU_BUILTIN", + ] + ) if freetype_enabled: env_harfbuzz.Append( @@ -499,6 +506,7 @@ if env["builtin_icu4c"]: ) env_text_server_adv.Append( CXXFLAGS=[ + "-DU_STATIC_IMPLEMENTATION", "-DU_HAVE_LIB_SUFFIX=1", "-DU_LIB_SUFFIX_C_NAME=_godot", "-DICU_DATA_NAME=" + icu_data_name, diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct index d0d13fec3f..effed1e772 100644 --- a/modules/text_server_adv/gdextension_build/SConstruct +++ b/modules/text_server_adv/gdextension_build/SConstruct @@ -415,6 +415,7 @@ if env["platform"] == "android" or env["platform"] == "linuxbsd": env_harfbuzz.Append( CCFLAGS=[ + "-DU_STATIC_IMPLEMENTATION", "-DU_HAVE_LIB_SUFFIX=1", "-DU_LIB_SUFFIX_C_NAME=_godot", "-DHAVE_ICU_BUILTIN", @@ -746,6 +747,7 @@ env_icu.Append( ) env.Append( CXXFLAGS=[ + "-DU_STATIC_IMPLEMENTATION", "-DU_HAVE_LIB_SUFFIX=1", "-DU_LIB_SUFFIX_C_NAME=_godot", "-DICU_DATA_NAME=" + icu_data_name, diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 0c87199635..d0c22e9e4d 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -51,7 +51,7 @@ using namespace godot; #include "core/error/error_macros.h" #include "core/object/worker_thread_pool.h" #include "core/string/print_string.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "scene/resources/image_texture.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. @@ -3528,6 +3528,37 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons return chars; } +PackedInt32Array TextServerAdvanced::_font_get_supported_glyphs(const RID &p_font_rid) const { + FontAdvanced *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); + + MutexLock lock(fd->mutex); + if (fd->cache.is_empty()) { + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array()); + } + FontForSizeAdvanced *at_size = fd->cache.begin()->value; + + PackedInt32Array glyphs; +#ifdef MODULE_FREETYPE_ENABLED + if (at_size && at_size->face) { + FT_UInt gindex; + FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex); + while (gindex != 0) { + glyphs.push_back(gindex); + charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex); + } + return glyphs; + } +#endif + if (at_size) { + const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map; + for (const KeyValue<int32_t, FontGlyph> &E : gl) { + glyphs.push_back(E.key); + } + } + return glyphs; +} + void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) { FontAdvanced *fd = _get_font_data(p_font_rid); ERR_FAIL_NULL(fd); @@ -5248,7 +5279,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) { if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) { - // Insert an additional space when cutting word bound for esthetics. + // Insert an additional space when cutting word bound for aesthetics. if (cut_per_word && (ellipsis_pos > 0)) { Glyph gl; gl.count = 1; diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index 92bdb93bcf..fdebb8e4cd 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -871,6 +871,7 @@ public: MODBIND2RC(bool, font_has_char, const RID &, int64_t); MODBIND1RC(String, font_get_supported_chars, const RID &); + MODBIND1RC(PackedInt32Array, font_get_supported_glyphs, const RID &); MODBIND4(font_render_range, const RID &, const Vector2i &, int64_t, int64_t); MODBIND3(font_render_glyph, const RID &, const Vector2i &, int64_t); diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 6cf6b236ed..a7ddfc719e 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -52,7 +52,7 @@ using namespace godot; #include "core/config/project_settings.h" #include "core/error/error_macros.h" #include "core/string/print_string.h" -#include "core/string/translation.h" +#include "core/string/translation_server.h" #include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg. @@ -2477,6 +2477,37 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons return chars; } +PackedInt32Array TextServerFallback::_font_get_supported_glyphs(const RID &p_font_rid) const { + FontFallback *fd = _get_font_data(p_font_rid); + ERR_FAIL_NULL_V(fd, PackedInt32Array()); + + MutexLock lock(fd->mutex); + if (fd->cache.is_empty()) { + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array()); + } + FontForSizeFallback *at_size = fd->cache.begin()->value; + + PackedInt32Array glyphs; +#ifdef MODULE_FREETYPE_ENABLED + if (at_size && at_size->face) { + FT_UInt gindex; + FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex); + while (gindex != 0) { + glyphs.push_back(gindex); + charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex); + } + return glyphs; + } +#endif + if (at_size) { + const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map; + for (const KeyValue<int32_t, FontGlyph> &E : gl) { + glyphs.push_back(E.key); + } + } + return glyphs; +} + void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) { FontFallback *fd = _get_font_data(p_font_rid); ERR_FAIL_NULL(fd); @@ -4061,7 +4092,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ if ((trim_pos >= 0 && sd->width > p_width) || enforce_ellipsis) { if (add_ellipsis && (ellipsis_pos > 0 || enforce_ellipsis)) { - // Insert an additional space when cutting word bound for esthetics. + // Insert an additional space when cutting word bound for aesthetics. if (cut_per_word && (ellipsis_pos > 0)) { Glyph gl; gl.count = 1; diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 2235247b31..1b76c6fa0f 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -739,6 +739,7 @@ public: MODBIND2RC(bool, font_has_char, const RID &, int64_t); MODBIND1RC(String, font_get_supported_chars, const RID &); + MODBIND1RC(PackedInt32Array, font_get_supported_glyphs, const RID &); MODBIND4(font_render_range, const RID &, const Vector2i &, int64_t, int64_t); MODBIND3(font_render_glyph, const RID &, const Vector2i &, int64_t); diff --git a/modules/upnp/doc_classes/UPNP.xml b/modules/upnp/doc_classes/UPNP.xml index 7eba3ad8ec..4b5ad07688 100644 --- a/modules/upnp/doc_classes/UPNP.xml +++ b/modules/upnp/doc_classes/UPNP.xml @@ -31,13 +31,13 @@ if err != OK: push_error(str(err)) - emit_signal("upnp_completed", err) + upnp_completed.emit(err) return if upnp.get_gateway() and upnp.get_gateway().is_valid_gateway(): upnp.add_port_mapping(server_port, server_port, ProjectSettings.get_setting("application/config/name"), "UDP") upnp.add_port_mapping(server_port, server_port, ProjectSettings.get_setting("application/config/name"), "TCP") - emit_signal("upnp_completed", OK) + upnp_completed.emit(OK) func _ready(): thread = Thread.new() diff --git a/modules/webrtc/webrtc_peer_connection.cpp b/modules/webrtc/webrtc_peer_connection.cpp index 0a50b677c4..69be873fcf 100644 --- a/modules/webrtc/webrtc_peer_connection.cpp +++ b/modules/webrtc/webrtc_peer_connection.cpp @@ -43,15 +43,20 @@ void WebRTCPeerConnection::set_default_extension(const StringName &p_extension) default_extension = StringName(p_extension, true); } -WebRTCPeerConnection *WebRTCPeerConnection::create() { +WebRTCPeerConnection *WebRTCPeerConnection::create(bool p_notify_postinitialize) { #ifdef WEB_ENABLED - return memnew(WebRTCPeerConnectionJS); + return static_cast<WebRTCPeerConnection *>(ClassDB::creator<WebRTCPeerConnectionJS>(p_notify_postinitialize)); #else if (default_extension == StringName()) { WARN_PRINT_ONCE("No default WebRTC extension configured."); - return memnew(WebRTCPeerConnectionExtension); + return static_cast<WebRTCPeerConnection *>(ClassDB::creator<WebRTCPeerConnectionExtension>(p_notify_postinitialize)); + } + Object *obj = nullptr; + if (p_notify_postinitialize) { + obj = ClassDB::instantiate(default_extension); + } else { + obj = ClassDB::instantiate_without_postinitialization(default_extension); } - Object *obj = ClassDB::instantiate(default_extension); return Object::cast_to<WebRTCPeerConnectionExtension>(obj); #endif } diff --git a/modules/webrtc/webrtc_peer_connection.h b/modules/webrtc/webrtc_peer_connection.h index 0f79c17519..33c95ccd0f 100644 --- a/modules/webrtc/webrtc_peer_connection.h +++ b/modules/webrtc/webrtc_peer_connection.h @@ -85,7 +85,7 @@ public: virtual Error poll() = 0; virtual void close() = 0; - static WebRTCPeerConnection *create(); + static WebRTCPeerConnection *create(bool p_notify_postinitialize = true); WebRTCPeerConnection(); ~WebRTCPeerConnection(); diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml index 4c2d0159a6..238dd30536 100644 --- a/modules/websocket/doc_classes/WebSocketPeer.xml +++ b/modules/websocket/doc_classes/WebSocketPeer.xml @@ -139,7 +139,7 @@ <return type="void" /> <param index="0" name="enabled" type="bool" /> <description> - Disable Nagle's algorithm on the underling TCP socket (default). See [method StreamPeerTCP.set_no_delay] for more information. + Disable Nagle's algorithm on the underlying TCP socket (default). See [method StreamPeerTCP.set_no_delay] for more information. [b]Note:[/b] Not available in the Web export. </description> </method> diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp index 329e5bd532..03a530909b 100644 --- a/modules/websocket/emws_peer.cpp +++ b/modules/websocket/emws_peer.cpp @@ -59,8 +59,10 @@ void EMWSPeer::_esws_on_close(void *p_obj, int p_code, const char *p_reason, int } Error EMWSPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_options) { + ERR_FAIL_COND_V(p_url.is_empty(), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_tls_options.is_valid() && p_tls_options->is_server(), ERR_INVALID_PARAMETER); - ERR_FAIL_COND_V(ready_state != STATE_CLOSED, ERR_ALREADY_IN_USE); + ERR_FAIL_COND_V(ready_state != STATE_CLOSED && ready_state != STATE_CLOSING, ERR_ALREADY_IN_USE); + _clear(); String host; diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h index 38f15c82e5..fe0bc594e6 100644 --- a/modules/websocket/emws_peer.h +++ b/modules/websocket/emws_peer.h @@ -68,7 +68,7 @@ private: String selected_protocol; String requested_url; - static WebSocketPeer *_create() { return memnew(EMWSPeer); } + static WebSocketPeer *_create(bool p_notify_postinitialize) { return static_cast<WebSocketPeer *>(ClassDB::creator<EMWSPeer>(p_notify_postinitialize)); } static void _esws_on_connect(void *obj, char *proto); static void _esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string); static void _esws_on_error(void *obj); diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp index 3c0d316bc9..95a1a238e9 100644 --- a/modules/websocket/websocket_peer.cpp +++ b/modules/websocket/websocket_peer.cpp @@ -30,7 +30,7 @@ #include "websocket_peer.h" -WebSocketPeer *(*WebSocketPeer::_create)() = nullptr; +WebSocketPeer *(*WebSocketPeer::_create)(bool p_notify_postinitialize) = nullptr; WebSocketPeer::WebSocketPeer() { } diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h index 3110e87071..ef0197cf6c 100644 --- a/modules/websocket/websocket_peer.h +++ b/modules/websocket/websocket_peer.h @@ -59,7 +59,7 @@ private: virtual Error _send_bind(const PackedByteArray &p_data, WriteMode p_mode = WRITE_MODE_BINARY); protected: - static WebSocketPeer *(*_create)(); + static WebSocketPeer *(*_create)(bool p_notify_postinitialize); static void _bind_methods(); @@ -74,11 +74,11 @@ protected: int max_queued_packets = 2048; public: - static WebSocketPeer *create() { + static WebSocketPeer *create(bool p_notify_postinitialize = true) { if (!_create) { return nullptr; } - return _create(); + return _create(p_notify_postinitialize); } virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_options = Ref<TLSOptions>()) = 0; diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp index 38cb614847..0a9a4053e3 100644 --- a/modules/websocket/wsl_peer.cpp +++ b/modules/websocket/wsl_peer.cpp @@ -99,6 +99,7 @@ void WSLPeer::Resolver::try_next_candidate(Ref<StreamPeerTCP> &p_tcp) { p_tcp->poll(); StreamPeerTCP::Status status = p_tcp->get_status(); if (status == StreamPeerTCP::STATUS_CONNECTED) { + // On Windows, setting TCP_NODELAY may fail if the socket is still connecting. p_tcp->set_no_delay(true); ip_candidates.clear(); return; @@ -124,8 +125,8 @@ void WSLPeer::Resolver::try_next_candidate(Ref<StreamPeerTCP> &p_tcp) { /// Server functions /// Error WSLPeer::accept_stream(Ref<StreamPeer> p_stream) { - ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(p_stream.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(ready_state != STATE_CLOSED && ready_state != STATE_CLOSING, ERR_ALREADY_IN_USE); _clear(); @@ -142,6 +143,7 @@ Error WSLPeer::accept_stream(Ref<StreamPeer> p_stream) { } ERR_FAIL_COND_V(connection.is_null() || tcp.is_null(), ERR_INVALID_PARAMETER); is_server = true; + tcp->set_no_delay(true); ready_state = STATE_CONNECTING; handshake_buffer->resize(WSL_MAX_HEADER_SIZE); handshake_buffer->seek(0); @@ -310,7 +312,7 @@ void WSLPeer::_do_client_handshake() { ERR_FAIL_COND(tcp.is_null()); // Try to connect to candidates. - if (resolver.has_more_candidates()) { + if (resolver.has_more_candidates() || tcp->get_status() == StreamPeerTCP::STATUS_CONNECTING) { resolver.try_next_candidate(tcp); if (resolver.has_more_candidates()) { return; // Still pending. @@ -471,9 +473,9 @@ bool WSLPeer::_verify_server_response() { } Error WSLPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_options) { - ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE); ERR_FAIL_COND_V(p_url.is_empty(), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_options.is_valid() && p_options->is_server(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(ready_state != STATE_CLOSED && ready_state != STATE_CLOSING, ERR_ALREADY_IN_USE); _clear(); diff --git a/modules/websocket/wsl_peer.h b/modules/websocket/wsl_peer.h index bf9f5c8527..fb01da7ce2 100644 --- a/modules/websocket/wsl_peer.h +++ b/modules/websocket/wsl_peer.h @@ -49,7 +49,7 @@ class WSLPeer : public WebSocketPeer { private: static CryptoCore::RandomGenerator *_static_rng; - static WebSocketPeer *_create() { return memnew(WSLPeer); } + static WebSocketPeer *_create(bool p_notify_postinitialize) { return static_cast<WebSocketPeer *>(ClassDB::creator<WSLPeer>(p_notify_postinitialize)); } // Callbacks. static ssize_t _wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data); |