Skip to content

Commit 201034a

Browse files
committed
[OIDN] Switch to using external library.
1 parent 56a2b14 commit 201034a

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
@@ -778,127 +778,137 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade
778778
return BAKE_OK;
779779
}
780780

781-
Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name) {
782-
Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index);
783-
Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, data);
784-
img->convert(Image::FORMAT_RGBF);
785-
Vector<uint8_t> data_float = img->get_data();
786-
787-
Error err = OK;
788-
Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::WRITE, &err);
789-
ERR_FAIL_COND_V_MSG(err, err, vformat("Can't save PFN at path: '%s'.", p_name));
790-
file->store_line("PF");
791-
file->store_line(vformat("%d %d", img->get_width(), img->get_height()));
792-
#ifdef BIG_ENDIAN_ENABLED
793-
file->store_line("1.0");
794-
#else
795-
file->store_line("-1.0");
796-
#endif
797-
file->store_buffer(data_float);
798-
file->close();
781+
bool LightmapperRD::_load_oidn(const String &p_library_path) {
782+
if (oidn_lib_path == p_library_path) {
783+
return oidn_lib_handle != nullptr;
784+
}
785+
oidn_lib_path = p_library_path;
799786

800-
return OK;
787+
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
788+
String lib_name;
789+
if (OS::get_singleton()->get_name() == "macOS") {
790+
lib_name = "libOpenImageDenoise.dylib";
791+
} else if (OS::get_singleton()->get_name() == "Windows") {
792+
lib_name = "OpenImageDenoise.dll";
793+
} else {
794+
lib_name = "libOpenImageDenoise.so";
795+
}
796+
String lib_path = oidn_lib_path.path_join(lib_name);
797+
if (!da->file_exists(lib_path)) {
798+
lib_path = oidn_lib_path.path_join("lib").path_join(lib_name);
799+
}
800+
if (!da->file_exists(lib_path)) {
801+
lib_path = oidn_lib_path.path_join("..").path_join("lib").path_join(lib_name);
802+
}
803+
804+
_unload_oidn();
805+
806+
if (OS::get_singleton()->open_dynamic_library(lib_path, oidn_lib_handle, true) != OK) {
807+
oidn_lib_handle = nullptr;
808+
ERR_PRINT(vformat("Failed to load %s.", lib_path));
809+
return false;
810+
}
811+
bool symbols_ok = true;
812+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnNewDevice", (void *&)oidnNewDevice) == OK);
813+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnCommitDevice", (void *&)oidnCommitDevice) == OK);
814+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnNewFilter", (void *&)oidnNewFilter) == OK);
815+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnSetSharedFilterImage", (void *&)oidnSetSharedFilterImage) == OK);
816+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnSetFilterBool", (void *&)oidnSetFilterBool) == OK);
817+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnCommitFilter", (void *&)oidnCommitFilter) == OK);
818+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnExecuteFilter", (void *&)oidnExecuteFilter) == OK);
819+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnGetDeviceError", (void *&)oidnGetDeviceError) == OK);
820+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnReleaseFilter", (void *&)oidnReleaseFilter) == OK);
821+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnReleaseDevice", (void *&)oidnReleaseDevice) == OK);
822+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnNewBuffer", (void *&)oidnNewBuffer) == OK);
823+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnGetBufferData", (void *&)oidnGetBufferData) == OK);
824+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnReadBuffer", (void *&)oidnReadBuffer) == OK);
825+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnWriteBuffer", (void *&)oidnWriteBuffer) == OK);
826+
symbols_ok = symbols_ok && (OS::get_singleton()->get_dynamic_library_symbol_handle(oidn_lib_handle, "oidnReleaseBuffer", (void *&)oidnReleaseBuffer) == OK);
827+
if (!symbols_ok) {
828+
ERR_PRINT("Failed to load OIDN symbols.");
829+
_unload_oidn();
830+
return false;
831+
}
832+
833+
oidn_device = oidnNewDevice(OIDN_DEVICE_TYPE_DEFAULT);
834+
oidnCommitDevice(oidn_device);
835+
const char *msg = nullptr;
836+
if (oidnGetDeviceError(oidn_device, &msg) != OIDN_ERROR_NONE) {
837+
ERR_PRINT(vformat("Failed to load OIDN device: %s", msg));
838+
_unload_oidn();
839+
return false;
840+
}
841+
842+
return true;
801843
}
802844

803-
Ref<Image> LightmapperRD::_read_pfm(const String &p_name) {
804-
Error err = OK;
805-
Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::READ, &err);
806-
ERR_FAIL_COND_V_MSG(err, Ref<Image>(), vformat("Can't load PFM at path: '%s'.", p_name));
807-
ERR_FAIL_COND_V(file->get_line() != "PF", Ref<Image>());
808-
809-
Vector<String> new_size = file->get_line().split(" ");
810-
ERR_FAIL_COND_V(new_size.size() != 2, Ref<Image>());
811-
int new_width = new_size[0].to_int();
812-
int new_height = new_size[1].to_int();
813-
814-
float endian = file->get_line().to_float();
815-
Vector<uint8_t> new_data = file->get_buffer(file->get_length() - file->get_position());
816-
file->close();
817-
818-
#ifdef BIG_ENDIAN_ENABLED
819-
if (unlikely(endian < 0.0)) {
820-
uint32_t count = new_data.size() / 4;
821-
uint16_t *dst = (uint16_t *)new_data.ptrw();
822-
for (uint32_t j = 0; j < count; j++) {
823-
dst[j * 4] = BSWAP32(dst[j * 4]);
824-
}
825-
}
826-
#else
827-
if (unlikely(endian > 0.0)) {
828-
uint32_t count = new_data.size() / 4;
829-
uint16_t *dst = (uint16_t *)new_data.ptrw();
830-
for (uint32_t j = 0; j < count; j++) {
831-
dst[j * 4] = BSWAP32(dst[j * 4]);
845+
void LightmapperRD::_unload_oidn() {
846+
if (oidn_lib_handle) {
847+
if (oidn_device) {
848+
oidnReleaseDevice(oidn_device);
849+
oidn_device = nullptr;
832850
}
851+
852+
OS::get_singleton()->close_dynamic_library(oidn_lib_handle);
853+
oidn_lib_handle = nullptr;
833854
}
834-
#endif
835-
Ref<Image> img = Image::create_from_data(new_width, new_height, false, Image::FORMAT_RGBF, new_data);
836-
img->convert(Image::FORMAT_RGBAH);
837-
return img;
838855
}
839856

840-
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) {
841-
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
842-
857+
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) {
843858
for (int i = 0; i < p_atlas_slices; i++) {
844-
String fname_norm_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_norm_%d.pfm", i));
845-
_store_pfm(p_rd, p_source_normal_tex, i, p_atlas_size, fname_norm_in);
859+
Vector<uint8_t> sn = p_rd->texture_get_data(p_source_normal_tex, i);
860+
861+
Ref<Image> imgn = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, sn);
862+
imgn->convert(Image::FORMAT_RGBF);
863+
Vector<uint8_t> datan = imgn->get_data();
864+
void *bufn = oidnNewBuffer(oidn_device, datan.size());
865+
oidnWriteBuffer(bufn, 0, datan.size(), (void *)datan.ptrw());
846866

847867
for (int j = 0; j < (p_bake_sh ? 4 : 1); j++) {
848868
int index = i * (p_bake_sh ? 4 : 1) + j;
849-
String fname_light_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_light_%d.pfm", index));
850-
String fname_out = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_denoised_%d.pfm", index));
851-
852-
_store_pfm(p_rd, p_source_light_tex, index, p_atlas_size, fname_light_in);
853-
854-
List<String> args;
855-
args.push_back("--device");
856-
args.push_back("default");
857-
858-
args.push_back("--filter");
859-
args.push_back("RTLightmap");
860869

861-
args.push_back("--hdr");
862-
args.push_back(fname_light_in);
870+
Vector<uint8_t> sl = p_rd->texture_get_data(p_source_light_tex, index);
863871

864-
args.push_back("--nrm");
865-
args.push_back(fname_norm_in);
872+
Ref<Image> imgl = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, sl);
873+
imgl->convert(Image::FORMAT_RGBF);
874+
Vector<uint8_t> datal = imgl->get_data();
866875

867-
args.push_back("--output");
868-
args.push_back(fname_out);
876+
void *bufl = oidnNewBuffer(oidn_device, datal.size());
877+
oidnWriteBuffer(bufl, 0, datal.size(), (void *)datal.ptrw());
869878

870-
String str;
871-
int exitcode = 0;
879+
void *filter = oidnNewFilter(oidn_device, "RTLightmap");
880+
oidnSetSharedFilterImage(filter, "color", oidnGetBufferData(bufl), OIDN_FORMAT_FLOAT3, imgl->get_width(), imgl->get_height(), 0, 0, 0);
881+
oidnSetSharedFilterImage(filter, "normal", oidnGetBufferData(bufn), OIDN_FORMAT_FLOAT3, imgn->get_width(), imgn->get_height(), 0, 0, 0);
882+
oidnSetSharedFilterImage(filter, "output", oidnGetBufferData(bufl), OIDN_FORMAT_FLOAT3, imgl->get_width(), imgl->get_height(), 0, 0, 0);
883+
oidnSetFilterBool(filter, "hdr", true);
884+
oidnCommitFilter(filter);
885+
oidnExecuteFilter(filter);
872886

873-
Error err = OS::get_singleton()->execute(p_exe, args, &str, &exitcode, true);
874-
875-
da->remove(fname_light_in);
876-
877-
if (err != OK || exitcode != 0) {
878-
da->remove(fname_out);
879-
print_verbose(str);
880-
ERR_FAIL_V_MSG(BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, vformat("OIDN denoiser failed, return code: %d", exitcode));
887+
const char *msg = nullptr;
888+
if (oidnGetDeviceError(oidn_device, &msg) != OIDN_ERROR_NONE) {
889+
oidnReleaseFilter(filter);
890+
ERR_FAIL_V_MSG(BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, String(msg));
881891
}
882-
883-
Ref<Image> img = _read_pfm(fname_out);
884-
da->remove(fname_out);
885-
886-
ERR_FAIL_COND_V(img.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
887-
888-
Vector<uint8_t> old_data = p_rd->texture_get_data(p_source_light_tex, index);
889-
Vector<uint8_t> new_data = img->get_data();
890-
img.unref(); // Avoid copy on write.
891-
892-
uint32_t count = old_data.size() / 2;
893-
const uint16_t *src = (const uint16_t *)old_data.ptr();
894-
uint16_t *dst = (uint16_t *)new_data.ptrw();
895-
for (uint32_t k = 0; k < count; k += 4) {
896-
dst[k + 3] = src[k + 3];
892+
oidnReleaseFilter(filter);
893+
894+
oidnReadBuffer(bufl, 0, datal.size(), (void *)datal.ptrw());
895+
oidnReleaseBuffer(bufl);
896+
897+
imgl->set_data(imgl->get_width(), imgl->get_height(), false, imgl->get_format(), datal);
898+
imgl->convert(Image::FORMAT_RGBAH);
899+
Vector<uint8_t> ds = imgl->get_data();
900+
imgl.unref(); // Avoid copy on write.
901+
{ // Restore alpha.
902+
uint32_t count = sl.size() / 2;
903+
const uint16_t *src = (const uint16_t *)sl.ptr();
904+
uint16_t *dst = (uint16_t *)ds.ptrw();
905+
for (uint32_t k = 0; k < count; k += 4) {
906+
dst[k + 3] = src[k + 3];
907+
}
897908
}
898-
899-
p_rd->texture_update(p_dest_light_tex, index, new_data);
909+
p_rd->texture_update(p_dest_light_tex, index, ds);
900910
}
901-
da->remove(fname_norm_in);
911+
oidnReleaseBuffer(bufn);
902912
}
903913
return BAKE_OK;
904914
}
@@ -980,15 +990,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
980990
if (p_use_denoiser && denoiser == 1) {
981991
// OIDN (external).
982992
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
983-
984-
if (da->dir_exists(oidn_path)) {
985-
if (OS::get_singleton()->get_name() == "Windows") {
986-
oidn_path = oidn_path.path_join("oidnDenoise.exe");
987-
} else {
988-
oidn_path = oidn_path.path_join("oidnDenoise");
989-
}
990-
}
991-
ERR_FAIL_COND_V_MSG(oidn_path.is_empty() || !da->file_exists(oidn_path), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, "OIDN denoiser is selected in the project settings, but no or invalid OIDN executable path is configured in the editor settings.");
993+
ERR_FAIL_COND_V_MSG(oidn_path.is_empty() || !_load_oidn(oidn_path), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, "OIDN denoiser is selected in the project settings, but no or invalid OIDN library path is configured in the editor settings.");
992994
}
993995

994996
if (p_step_function) {
@@ -1749,7 +1751,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
17491751
BakeError error;
17501752
if (denoiser == 1) {
17511753
// OIDN (external).
1752-
error = _denoise_oidn(rd, light_accum_tex, normal_tex, light_accum_tex, atlas_size, atlas_slices, p_bake_sh, oidn_path);
1754+
error = _denoise_oidn(rd, light_accum_tex, normal_tex, light_accum_tex, atlas_size, atlas_slices, p_bake_sh);
17531755
} else {
17541756
// JNLM (built-in).
17551757
SWAP(light_accum_tex, light_accum_tex2);
@@ -2018,3 +2020,7 @@ Vector<Color> LightmapperRD::get_bake_probe_sh(int p_probe) const {
20182020

20192021
LightmapperRD::LightmapperRD() {
20202022
}
2023+
2024+
LightmapperRD::~LightmapperRD() {
2025+
_unload_oidn();
2026+
}

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;
@@ -273,9 +343,10 @@ class LightmapperRD : public Lightmapper {
273343
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);
274344
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);
275345

276-
Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name);
277-
Ref<Image> _read_pfm(const String &p_name);
278-
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);
346+
bool _load_oidn(const String &p_library_path);
347+
void _unload_oidn();
348+
349+
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);
279350

280351
public:
281352
virtual void add_mesh(const MeshData &p_mesh) override;
@@ -296,6 +367,7 @@ class LightmapperRD : public Lightmapper {
296367
Vector<Color> get_bake_probe_sh(int p_probe) const override;
297368

298369
LightmapperRD();
370+
~LightmapperRD();
299371
};
300372

301373
#endif // LIGHTMAPPER_RD_H

0 commit comments

Comments
 (0)