Skip to content

Commit e03e467

Browse files
committed
[OIDN] Switch to using external library.
1 parent a574c02 commit e03e467

File tree

2 files changed

+193
-115
lines changed

2 files changed

+193
-115
lines changed

modules/lightmapper_rd/lightmapper_rd.cpp

Lines changed: 118 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -677,127 +677,137 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
677677
return BAKE_OK;
678678
}
679679

680-
Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name) {
681-
Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index);
682-
Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, data);
683-
img->convert(Image::FORMAT_RGBF);
684-
Vector<uint8_t> data_float = img->get_data();
685-
686-
Error err = OK;
687-
Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::WRITE, &err);
688-
ERR_FAIL_COND_V_MSG(err, err, vformat("Can't save PFN at path: '%s'.", p_name));
689-
file->store_line("PF");
690-
file->store_line(vformat("%d %d", img->get_width(), img->get_height()));
691-
#ifdef BIG_ENDIAN_ENABLED
692-
file->store_line("1.0");
693-
#else
694-
file->store_line("-1.0");
695-
#endif
696-
file->store_buffer(data_float);
697-
file->close();
680+
bool LightmapperRD::_load_oidn(const String &p_library_path) {
681+
if (oidn_lib_path == p_library_path) {
682+
return oidn_lib_handle != nullptr;
683+
}
684+
oidn_lib_path = p_library_path;
698685

699-
return OK;
686+
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
687+
String lib_name;
688+
if (OS::get_singleton()->get_name() == "macOS") {
689+
lib_name = "libOpenImageDenoise.dylib";
690+
} else if (OS::get_singleton()->get_name() == "Windows") {
691+
lib_name = "OpenImageDenoise.dll";
692+
} else {
693+
lib_name = "libOpenImageDenoise.so";
694+
}
695+
String lib_path = oidn_lib_path.path_join(lib_name);
696+
if (!da->file_exists(lib_path)) {
697+
lib_path = oidn_lib_path.path_join("lib").path_join(lib_name);
698+
}
699+
if (!da->file_exists(lib_path)) {
700+
lib_path = oidn_lib_path.path_join("..").path_join("lib").path_join(lib_name);
701+
}
702+
703+
_unload_oidn();
704+
705+
if (OS::get_singleton()->open_dynamic_library(lib_path, oidn_lib_handle, true) != OK) {
706+
oidn_lib_handle = nullptr;
707+
ERR_PRINT(vformat(TTR("Failed to load %s."), lib_path));
708+
return false;
709+
}
710+
bool symbols_ok = true;
711+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnNewDevice", (void *&)oidnNewDevice) == OK);
712+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnCommitDevice", (void *&)oidnCommitDevice) == OK);
713+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnNewFilter", (void *&)oidnNewFilter) == OK);
714+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnSetSharedFilterImage", (void *&)oidnSetSharedFilterImage) == OK);
715+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnSetFilterBool", (void *&)oidnSetFilterBool) == OK);
716+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnCommitFilter", (void *&)oidnCommitFilter) == OK);
717+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnExecuteFilter", (void *&)oidnExecuteFilter) == OK);
718+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnGetDeviceError", (void *&)oidnGetDeviceError) == OK);
719+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnReleaseFilter", (void *&)oidnReleaseFilter) == OK);
720+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnReleaseDevice", (void *&)oidnReleaseDevice) == OK);
721+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnNewBuffer", (void *&)oidnNewBuffer) == OK);
722+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnGetBufferData", (void *&)oidnGetBufferData) == OK);
723+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnReadBuffer", (void *&)oidnReadBuffer) == OK);
724+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnWriteBuffer", (void *&)oidnWriteBuffer) == OK);
725+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnReleaseBuffer", (void *&)oidnReleaseBuffer) == OK);
726+
if (!symbols_ok) {
727+
ERR_PRINT(TTR("Failed to load OIDN symbols."));
728+
_unload_oidn();
729+
return false;
730+
}
731+
732+
oidn_device = oidnNewDevice(OIDN_DEVICE_TYPE_DEFAULT);
733+
oidnCommitDevice(oidn_device);
734+
const char *msg = nullptr;
735+
if (oidnGetDeviceError(oidn_device, &msg) != OIDN_ERROR_NONE) {
736+
ERR_PRINT(vformat(TTR("Failed to load OIDN device: %s"), msg));
737+
_unload_oidn();
738+
return false;
739+
}
740+
741+
return true;
700742
}
701743

702-
Ref<Image> LightmapperRD::_read_pfm(const String &p_name) {
703-
Error err = OK;
704-
Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::READ, &err);
705-
ERR_FAIL_COND_V_MSG(err, Ref<Image>(), vformat("Can't load PFM at path: '%s'.", p_name));
706-
ERR_FAIL_COND_V(file->get_line() != "PF", Ref<Image>());
707-
708-
Vector<String> new_size = file->get_line().split(" ");
709-
ERR_FAIL_COND_V(new_size.size() != 2, Ref<Image>());
710-
int new_width = new_size[0].to_int();
711-
int new_height = new_size[1].to_int();
712-
713-
float endian = file->get_line().to_float();
714-
Vector<uint8_t> new_data = file->get_buffer(file->get_length() - file->get_position());
715-
file->close();
716-
717-
#ifdef BIG_ENDIAN_ENABLED
718-
if (unlikely(endian < 0.0)) {
719-
uint32_t count = new_data.size() / 4;
720-
uint16_t *dst = (uint16_t *)new_data.ptrw();
721-
for (uint32_t j = 0; j < count; j++) {
722-
dst[j * 4] = BSWAP32(dst[j * 4]);
723-
}
724-
}
725-
#else
726-
if (unlikely(endian > 0.0)) {
727-
uint32_t count = new_data.size() / 4;
728-
uint16_t *dst = (uint16_t *)new_data.ptrw();
729-
for (uint32_t j = 0; j < count; j++) {
730-
dst[j * 4] = BSWAP32(dst[j * 4]);
744+
void LightmapperRD::_unload_oidn() {
745+
if (oidn_lib_handle) {
746+
if (oidn_device) {
747+
oidnReleaseDevice(oidn_device);
748+
oidn_device = nullptr;
731749
}
750+
751+
OS::get_singleton()->close_dynamic_library(oidn_lib_handle);
752+
oidn_lib_handle = nullptr;
732753
}
733-
#endif
734-
Ref<Image> img = Image::create_from_data(new_width, new_height, false, Image::FORMAT_RGBF, new_data);
735-
img->convert(Image::FORMAT_RGBAH);
736-
return img;
737754
}
738755

739-
LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, const String &p_exe) {
740-
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
741-
756+
LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh) {
742757
for (int i = 0; i < p_atlas_slices; i++) {
743-
String fname_norm_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_norm_%d.pfm", i));
744-
_store_pfm(p_rd, p_source_normal_tex, i, p_atlas_size, fname_norm_in);
758+
Vector<uint8_t> sn = p_rd->texture_get_data(p_source_normal_tex, i);
759+
760+
Ref<Image> imgn = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, sn);
761+
imgn->convert(Image::FORMAT_RGBF);
762+
Vector<uint8_t> datan = imgn->get_data();
763+
void *bufn = oidnNewBuffer(oidn_device, datan.size());
764+
oidnWriteBuffer(bufn, 0, datan.size(), (void *)datan.ptrw());
745765

746766
for (int j = 0; j < (p_bake_sh ? 4 : 1); j++) {
747767
int index = i * (p_bake_sh ? 4 : 1) + j;
748-
String fname_light_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_light_%d.pfm", index));
749-
String fname_out = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_denoised_%d.pfm", index));
750-
751-
_store_pfm(p_rd, p_source_light_tex, index, p_atlas_size, fname_light_in);
752-
753-
List<String> args;
754-
args.push_back("--device");
755-
args.push_back("default");
756-
757-
args.push_back("--filter");
758-
args.push_back("RTLightmap");
759-
760-
args.push_back("--hdr");
761-
args.push_back(fname_light_in);
762768

763-
args.push_back("--nrm");
764-
args.push_back(fname_norm_in);
769+
Vector<uint8_t> sl = p_rd->texture_get_data(p_source_light_tex, index);
765770

766-
args.push_back("--output");
767-
args.push_back(fname_out);
771+
Ref<Image> imgl = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, sl);
772+
imgl->convert(Image::FORMAT_RGBF);
773+
Vector<uint8_t> datal = imgl->get_data();
768774

769-
String str;
770-
int exitcode = 0;
775+
void *bufl = oidnNewBuffer(oidn_device, datal.size());
776+
oidnWriteBuffer(bufl, 0, datal.size(), (void *)datal.ptrw());
771777

772-
Error err = OS::get_singleton()->execute(p_exe, args, &str, &exitcode, true);
778+
void *filter = oidnNewFilter(oidn_device, "RTLightmap");
779+
oidnSetSharedFilterImage(filter, "color", oidnGetBufferData(bufl), OIDN_FORMAT_FLOAT3, imgl->get_width(), imgl->get_height(), 0, 0, 0);
780+
oidnSetSharedFilterImage(filter, "normal", oidnGetBufferData(bufn), OIDN_FORMAT_FLOAT3, imgn->get_width(), imgn->get_height(), 0, 0, 0);
781+
oidnSetSharedFilterImage(filter, "output", oidnGetBufferData(bufl), OIDN_FORMAT_FLOAT3, imgl->get_width(), imgl->get_height(), 0, 0, 0);
782+
oidnSetFilterBool(filter, "hdr", true);
783+
oidnCommitFilter(filter);
784+
oidnExecuteFilter(filter);
773785

774-
da->remove(fname_light_in);
775-
776-
if (err != OK || exitcode != 0) {
777-
da->remove(fname_out);
778-
print_verbose(str);
779-
ERR_FAIL_V_MSG(BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, vformat(TTR("OIDN denoiser failed, return code: %d"), exitcode));
786+
const char *msg = nullptr;
787+
if (oidnGetDeviceError(oidn_device, &msg) != OIDN_ERROR_NONE) {
788+
oidnReleaseFilter(filter);
789+
ERR_FAIL_V_MSG(BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, String(msg));
780790
}
781-
782-
Ref<Image> img = _read_pfm(fname_out);
783-
da->remove(fname_out);
784-
785-
ERR_FAIL_COND_V(img.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
786-
787-
Vector<uint8_t> old_data = p_rd->texture_get_data(p_source_light_tex, index);
788-
Vector<uint8_t> new_data = img->get_data();
789-
img.unref(); // Avoid copy on write.
790-
791-
uint32_t count = old_data.size() / 2;
792-
const uint16_t *src = (const uint16_t *)old_data.ptr();
793-
uint16_t *dst = (uint16_t *)new_data.ptrw();
794-
for (uint32_t k = 0; k < count; k += 4) {
795-
dst[k + 3] = src[k + 3];
791+
oidnReleaseFilter(filter);
792+
793+
oidnReadBuffer(bufl, 0, datal.size(), (void *)datal.ptrw());
794+
oidnReleaseBuffer(bufl);
795+
796+
imgl->set_data(imgl->get_width(), imgl->get_height(), false, imgl->get_format(), datal);
797+
imgl->convert(Image::FORMAT_RGBAH);
798+
Vector<uint8_t> ds = imgl->get_data();
799+
imgl.unref(); // Avoid copy on write.
800+
{ // Restore alpha.
801+
uint32_t count = sl.size() / 2;
802+
const uint16_t *src = (const uint16_t *)sl.ptr();
803+
uint16_t *dst = (uint16_t *)ds.ptrw();
804+
for (uint32_t k = 0; k < count; k += 4) {
805+
dst[k + 3] = src[k + 3];
806+
}
796807
}
797-
798-
p_rd->texture_update(p_dest_light_tex, index, new_data);
808+
p_rd->texture_update(p_dest_light_tex, index, ds);
799809
}
800-
da->remove(fname_norm_in);
810+
oidnReleaseBuffer(bufn);
801811
}
802812
return BAKE_OK;
803813
}
@@ -879,15 +889,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
879889
if (p_use_denoiser && denoiser == 1) {
880890
// OIDN (external).
881891
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
882-
883-
if (da->dir_exists(oidn_path)) {
884-
if (OS::get_singleton()->get_name() == "Windows") {
885-
oidn_path = oidn_path.path_join("oidnDenoise.exe");
886-
} else {
887-
oidn_path = oidn_path.path_join("oidnDenoise");
888-
}
889-
}
890-
ERR_FAIL_COND_V_MSG(oidn_path.is_empty() || !da->file_exists(oidn_path), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, TTR("OIDN denoiser is selected in the project settings, but no or invalid OIDN executable path is configured in the editor settings."));
892+
ERR_FAIL_COND_V_MSG(oidn_path.is_empty() || !_load_oidn(oidn_path), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, TTR("OIDN denoiser is selected in the project settings, but no or invalid OIDN library path is configured in the editor settings."));
891893
}
892894

893895
if (p_step_function) {
@@ -1627,7 +1629,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
16271629
BakeError error;
16281630
if (denoiser == 1) {
16291631
// OIDN (external).
1630-
error = _denoise_oidn(rd, light_accum_tex, normal_tex, light_accum_tex, atlas_size, atlas_slices, p_bake_sh, oidn_path);
1632+
error = _denoise_oidn(rd, light_accum_tex, normal_tex, light_accum_tex, atlas_size, atlas_slices, p_bake_sh);
16311633
} else {
16321634
// JNLM (built-in).
16331635
SWAP(light_accum_tex, light_accum_tex2);
@@ -1896,3 +1898,7 @@ Vector<Color> LightmapperRD::get_bake_probe_sh(int p_probe) const {
18961898

18971899
LightmapperRD::LightmapperRD() {
18981900
}
1901+
1902+
LightmapperRD::~LightmapperRD() {
1903+
_unload_oidn();
1904+
}

modules/lightmapper_rd/lightmapper_rd.h

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,80 @@
3636
#include "scene/resources/mesh.h"
3737
#include "servers/rendering/rendering_device.h"
3838

39+
typedef enum {
40+
OIDN_DEVICE_TYPE_DEFAULT = 0, // select device automatically
41+
42+
OIDN_DEVICE_TYPE_CPU = 1, // CPU device
43+
OIDN_DEVICE_TYPE_SYCL = 2, // SYCL device
44+
OIDN_DEVICE_TYPE_CUDA = 3, // CUDA device
45+
OIDN_DEVICE_TYPE_HIP = 4, // HIP device
46+
} OIDNDeviceType;
47+
48+
typedef enum {
49+
OIDN_FORMAT_UNDEFINED = 0,
50+
51+
// 32-bit single-precision floating-point scalar and vector formats
52+
OIDN_FORMAT_FLOAT = 1,
53+
OIDN_FORMAT_FLOAT2,
54+
OIDN_FORMAT_FLOAT3,
55+
OIDN_FORMAT_FLOAT4,
56+
57+
// 16-bit half-precision floating-point scalar and vector formats
58+
OIDN_FORMAT_HALF = 257,
59+
OIDN_FORMAT_HALF2,
60+
OIDN_FORMAT_HALF3,
61+
OIDN_FORMAT_HALF4,
62+
} OIDNFormat;
63+
64+
typedef enum {
65+
OIDN_ERROR_NONE = 0, // no error occurred
66+
OIDN_ERROR_UNKNOWN = 1, // an unknown error occurred
67+
OIDN_ERROR_INVALID_ARGUMENT = 2, // an invalid argument was specified
68+
OIDN_ERROR_INVALID_OPERATION = 3, // the operation is not allowed
69+
OIDN_ERROR_OUT_OF_MEMORY = 4, // not enough memory to execute the operation
70+
OIDN_ERROR_UNSUPPORTED_HARDWARE = 5, // the hardware (e.g. CPU) is not supported
71+
OIDN_ERROR_CANCELLED = 6, // the operation was cancelled by the user
72+
} OIDNError;
73+
74+
typedef void *(*oidnNewDevicePtr)(OIDNDeviceType type);
75+
typedef void (*oidnCommitDevicePtr)(void *device);
76+
typedef void *(*oidnNewFilterPtr)(void *device, const char *type);
77+
typedef void (*oidnSetSharedFilterImagePtr)(void *filter, const char *name, void *devPtr, OIDNFormat format, size_t width, size_t height, size_t byteOffset, size_t pixelByteStride, size_t rowByteStride);
78+
typedef void (*oidnSetFilterBoolPtr)(void *filter, const char *name, bool value);
79+
typedef void (*oidnCommitFilterPtr)(void *filter);
80+
typedef void (*oidnExecuteFilterPtr)(void *filter);
81+
typedef OIDNError (*oidnGetDeviceErrorPtr)(void *device, const char **outMessage);
82+
typedef void (*oidnReleaseFilterPtr)(void *filter);
83+
typedef void (*oidnReleaseDevicePtr)(void *device);
84+
typedef void *(*oidnNewBufferPtr)(void *device, size_t byteSize);
85+
typedef void *(*oidnGetBufferDataPtr)(void *buffer);
86+
typedef void (*oidnReadBufferPtr)(void *buffer, size_t byteOffset, size_t byteSize, void *dstHostPtr);
87+
typedef void (*oidnWriteBufferPtr)(void *buffer, size_t byteOffset, size_t byteSize, const void *srcHostPtr);
88+
typedef void (*oidnReleaseBufferPtr)(void *buffer);
89+
3990
class RDShaderFile;
4091
class LightmapperRD : public Lightmapper {
4192
GDCLASS(LightmapperRD, Lightmapper)
4293

94+
String oidn_lib_path;
95+
void *oidn_lib_handle = nullptr;
96+
void *oidn_device = nullptr;
97+
oidnNewDevicePtr oidnNewDevice = nullptr;
98+
oidnCommitDevicePtr oidnCommitDevice = nullptr;
99+
oidnNewFilterPtr oidnNewFilter = nullptr;
100+
oidnSetSharedFilterImagePtr oidnSetSharedFilterImage = nullptr;
101+
oidnSetFilterBoolPtr oidnSetFilterBool = nullptr;
102+
oidnCommitFilterPtr oidnCommitFilter = nullptr;
103+
oidnExecuteFilterPtr oidnExecuteFilter = nullptr;
104+
oidnGetDeviceErrorPtr oidnGetDeviceError = nullptr;
105+
oidnReleaseFilterPtr oidnReleaseFilter = nullptr;
106+
oidnReleaseDevicePtr oidnReleaseDevice = nullptr;
107+
oidnNewBufferPtr oidnNewBuffer = nullptr;
108+
oidnGetBufferDataPtr oidnGetBufferData = nullptr;
109+
oidnReadBufferPtr oidnReadBuffer = nullptr;
110+
oidnWriteBufferPtr oidnWriteBuffer = nullptr;
111+
oidnReleaseBufferPtr oidnReleaseBuffer = nullptr;
112+
43113
struct BakeParameters {
44114
float world_size[3] = {};
45115
float bias = 0.0;
@@ -256,9 +326,10 @@ class LightmapperRD : public Lightmapper {
256326
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);
257327
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, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function);
258328

259-
Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name);
260-
Ref<Image> _read_pfm(const String &p_name);
261-
BakeError _denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, const String &p_exe);
329+
bool _load_oidn(const String &p_library_path);
330+
void _unload_oidn();
331+
332+
BakeError _denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh);
262333

263334
public:
264335
virtual void add_mesh(const MeshData &p_mesh) override;
@@ -279,6 +350,7 @@ class LightmapperRD : public Lightmapper {
279350
Vector<Color> get_bake_probe_sh(int p_probe) const override;
280351

281352
LightmapperRD();
353+
~LightmapperRD();
282354
};
283355

284356
#endif // LIGHTMAPPER_RD_H

0 commit comments

Comments
 (0)