From 4d9d99bb827967e2bb931eeb8c3f0e079b39ae1a Mon Sep 17 00:00:00 2001 From: jfons Date: Tue, 20 Apr 2021 18:40:24 +0200 Subject: Implement occlusion culling Added an occlusion culling system with support for static occluder meshes. It can be enabled via `Project Settings > Rendering > Occlusion Culling > Use Occlusion Culling`. Occluders are defined via the new `Occluder3D` resource and instanced using the new `OccluderInstance3D` node. The occluders can also be automatically baked from a scene using the built-in editor plugin. --- modules/raycast/SCsub | 86 +++++ modules/raycast/config.py | 12 + modules/raycast/godot_update_embree.py | 260 +++++++++++++ modules/raycast/lightmap_raycaster.cpp | 202 ++++++++++ modules/raycast/lightmap_raycaster.h | 77 ++++ modules/raycast/raycast_occlusion_cull.cpp | 583 +++++++++++++++++++++++++++++ modules/raycast/raycast_occlusion_cull.h | 184 +++++++++ modules/raycast/register_types.cpp | 49 +++ modules/raycast/register_types.h | 32 ++ 9 files changed, 1485 insertions(+) create mode 100644 modules/raycast/SCsub create mode 100644 modules/raycast/config.py create mode 100644 modules/raycast/godot_update_embree.py create mode 100644 modules/raycast/lightmap_raycaster.cpp create mode 100644 modules/raycast/lightmap_raycaster.h create mode 100644 modules/raycast/raycast_occlusion_cull.cpp create mode 100644 modules/raycast/raycast_occlusion_cull.h create mode 100644 modules/raycast/register_types.cpp create mode 100644 modules/raycast/register_types.h (limited to 'modules/raycast') diff --git a/modules/raycast/SCsub b/modules/raycast/SCsub new file mode 100644 index 0000000000..68e9df5263 --- /dev/null +++ b/modules/raycast/SCsub @@ -0,0 +1,86 @@ +#!/usr/bin/env python + +Import("env") +Import("env_modules") + +embree_src = [ + "common/sys/sysinfo.cpp", + "common/sys/alloc.cpp", + "common/sys/filename.cpp", + "common/sys/library.cpp", + "common/sys/thread.cpp", + "common/sys/string.cpp", + "common/sys/regression.cpp", + "common/sys/mutex.cpp", + "common/sys/condition.cpp", + "common/sys/barrier.cpp", + "common/math/constants.cpp", + "common/simd/sse.cpp", + "common/lexers/stringstream.cpp", + "common/lexers/tokenstream.cpp", + "common/tasking/taskschedulerinternal.cpp", + "common/algorithms/parallel_for.cpp", + "common/algorithms/parallel_reduce.cpp", + "common/algorithms/parallel_prefix_sum.cpp", + "common/algorithms/parallel_for_for.cpp", + "common/algorithms/parallel_for_for_prefix_sum.cpp", + "common/algorithms/parallel_partition.cpp", + "common/algorithms/parallel_sort.cpp", + "common/algorithms/parallel_set.cpp", + "common/algorithms/parallel_map.cpp", + "common/algorithms/parallel_filter.cpp", + "kernels/common/device.cpp", + "kernels/common/stat.cpp", + "kernels/common/acceln.cpp", + "kernels/common/accelset.cpp", + "kernels/common/state.cpp", + "kernels/common/rtcore.cpp", + "kernels/common/rtcore_builder.cpp", + "kernels/common/scene.cpp", + "kernels/common/alloc.cpp", + "kernels/common/geometry.cpp", + "kernels/common/scene_triangle_mesh.cpp", + "kernels/geometry/primitive4.cpp", + "kernels/builders/primrefgen.cpp", + "kernels/bvh/bvh.cpp", + "kernels/bvh/bvh_statistics.cpp", + "kernels/bvh/bvh4_factory.cpp", + "kernels/bvh/bvh8_factory.cpp", + "kernels/bvh/bvh_collider.cpp", + "kernels/bvh/bvh_rotate.cpp", + "kernels/bvh/bvh_refit.cpp", + "kernels/bvh/bvh_builder.cpp", + "kernels/bvh/bvh_builder_morton.cpp", + "kernels/bvh/bvh_builder_sah.cpp", + "kernels/bvh/bvh_builder_sah_spatial.cpp", + "kernels/bvh/bvh_builder_sah_mb.cpp", + "kernels/bvh/bvh_builder_twolevel.cpp", + "kernels/bvh/bvh_intersector1_bvh4.cpp", +] + +embree_dir = "#thirdparty/embree-aarch64/" + +env_embree = env_modules.Clone() +embree_sources = [embree_dir + file for file in embree_src] +env_embree.Prepend(CPPPATH=[embree_dir, embree_dir + "include"]) +env_embree.Append(CPPFLAGS=["-DEMBREE_TARGET_SSE2", "-DEMBREE_LOWEST_ISA", "-DTASKING_INTERNAL", "-DNDEBUG"]) + +if not env_embree.msvc: + env_embree.Append(CPPFLAGS=["-msse2", "-mxsave"]) + if env["platform"] == "windows": + env_embree.Append(CPPFLAGS=["-mstackrealign"]) + +if env["platform"] == "windows": + if env.msvc: + env.Append(LINKFLAGS=["psapi.lib"]) + env_embree.Append(CPPFLAGS=["-D__SSE2__", "-D__SSE__"]) + else: + env.Append(LIBS=["psapi"]) + +env_embree.disable_warnings() +env_embree.add_source_files(env.modules_sources, embree_sources) + +env_raycast = env_modules.Clone() +env_raycast.Prepend(CPPPATH=[embree_dir, embree_dir + "include", embree_dir + "common"]) + +env_raycast.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/raycast/config.py b/modules/raycast/config.py new file mode 100644 index 0000000000..26493da41b --- /dev/null +++ b/modules/raycast/config.py @@ -0,0 +1,12 @@ +def can_build(env, platform): + if platform == "android": + return env["android_arch"] in ["arm64v8", "x86", "x86_64"] + + if platform == "javascript": + return False # No SIMD support yet + + return True + + +def configure(env): + pass diff --git a/modules/raycast/godot_update_embree.py b/modules/raycast/godot_update_embree.py new file mode 100644 index 0000000000..db4fa95c21 --- /dev/null +++ b/modules/raycast/godot_update_embree.py @@ -0,0 +1,260 @@ +import glob, os, shutil, subprocess, re + +include_dirs = [ + "common/tasking", + "kernels/bvh", + "kernels/builders", + "common/sys", + "kernels", + "kernels/common", + "common/math", + "common/algorithms", + "common/lexers", + "common/simd", + "include/embree3", + "kernels/subdiv", + "kernels/geometry", +] + +cpp_files = [ + "common/sys/sysinfo.cpp", + "common/sys/alloc.cpp", + "common/sys/filename.cpp", + "common/sys/library.cpp", + "common/sys/thread.cpp", + "common/sys/string.cpp", + "common/sys/regression.cpp", + "common/sys/mutex.cpp", + "common/sys/condition.cpp", + "common/sys/barrier.cpp", + "common/math/constants.cpp", + "common/simd/sse.cpp", + "common/lexers/stringstream.cpp", + "common/lexers/tokenstream.cpp", + "common/tasking/taskschedulerinternal.cpp", + "common/algorithms/parallel_for.cpp", + "common/algorithms/parallel_reduce.cpp", + "common/algorithms/parallel_prefix_sum.cpp", + "common/algorithms/parallel_for_for.cpp", + "common/algorithms/parallel_for_for_prefix_sum.cpp", + "common/algorithms/parallel_partition.cpp", + "common/algorithms/parallel_sort.cpp", + "common/algorithms/parallel_set.cpp", + "common/algorithms/parallel_map.cpp", + "common/algorithms/parallel_filter.cpp", + "kernels/common/device.cpp", + "kernels/common/stat.cpp", + "kernels/common/acceln.cpp", + "kernels/common/accelset.cpp", + "kernels/common/state.cpp", + "kernels/common/rtcore.cpp", + "kernels/common/rtcore_builder.cpp", + "kernels/common/scene.cpp", + "kernels/common/alloc.cpp", + "kernels/common/geometry.cpp", + "kernels/common/scene_triangle_mesh.cpp", + "kernels/geometry/primitive4.cpp", + "kernels/builders/primrefgen.cpp", + "kernels/bvh/bvh.cpp", + "kernels/bvh/bvh_statistics.cpp", + "kernels/bvh/bvh4_factory.cpp", + "kernels/bvh/bvh8_factory.cpp", + "kernels/bvh/bvh_collider.cpp", + "kernels/bvh/bvh_rotate.cpp", + "kernels/bvh/bvh_refit.cpp", + "kernels/bvh/bvh_builder.cpp", + "kernels/bvh/bvh_builder_morton.cpp", + "kernels/bvh/bvh_builder_sah.cpp", + "kernels/bvh/bvh_builder_sah_spatial.cpp", + "kernels/bvh/bvh_builder_sah_mb.cpp", + "kernels/bvh/bvh_builder_twolevel.cpp", + "kernels/bvh/bvh_intersector1.cpp", + "kernels/bvh/bvh_intersector1_bvh4.cpp", +] + +os.chdir("../../thirdparty") + +dir_name = "embree-aarch64" +if os.path.exists(dir_name): + shutil.rmtree(dir_name) + +subprocess.run(["git", "clone", "https://github.com/lighttransport/embree-aarch64.git", "embree-tmp"]) +os.chdir("embree-tmp") + +commit_hash = str(subprocess.check_output(["git", "rev-parse", "HEAD"], universal_newlines=True)).strip() + +all_files = set(cpp_files) + +dest_dir = os.path.join("..", dir_name) +for include_dir in include_dirs: + headers = glob.iglob(os.path.join(include_dir, "*.h")) + all_files.update(headers) + +for f in all_files: + d = os.path.join(dest_dir, os.path.dirname(f)) + if not os.path.exists(d): + os.makedirs(d) + shutil.copy2(f, d) + +with open(os.path.join(dest_dir, "kernels/hash.h"), "w") as hash_file: + hash_file.write( + f""" +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#define RTC_HASH "{commit_hash}" +""" + ) + +with open(os.path.join(dest_dir, "kernels/config.h"), "w") as config_file: + config_file.write( + """ +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +/* #undef EMBREE_RAY_MASK */ +/* #undef EMBREE_STAT_COUNTERS */ +/* #undef EMBREE_BACKFACE_CULLING */ +/* #undef EMBREE_BACKFACE_CULLING_CURVES */ +#define EMBREE_FILTER_FUNCTION +/* #undef EMBREE_IGNORE_INVALID_RAYS */ +#define EMBREE_GEOMETRY_TRIANGLE +/* #undef EMBREE_GEOMETRY_QUAD */ +/* #undef EMBREE_GEOMETRY_CURVE */ +/* #undef EMBREE_GEOMETRY_SUBDIVISION */ +/* #undef EMBREE_GEOMETRY_USER */ +/* #undef EMBREE_GEOMETRY_INSTANCE */ +/* #undef EMBREE_GEOMETRY_GRID */ +/* #undef EMBREE_GEOMETRY_POINT */ +/* #undef EMBREE_RAY_PACKETS */ +/* #undef EMBREE_COMPACT_POLYS */ + +#define EMBREE_CURVE_SELF_INTERSECTION_AVOIDANCE_FACTOR 2.0 + +#if defined(EMBREE_GEOMETRY_TRIANGLE) + #define IF_ENABLED_TRIS(x) x +#else + #define IF_ENABLED_TRIS(x) +#endif + +#if defined(EMBREE_GEOMETRY_QUAD) + #define IF_ENABLED_QUADS(x) x +#else + #define IF_ENABLED_QUADS(x) +#endif + +#if defined(EMBREE_GEOMETRY_CURVE) || defined(EMBREE_GEOMETRY_POINT) + #define IF_ENABLED_CURVES_OR_POINTS(x) x +#else + #define IF_ENABLED_CURVES_OR_POINTS(x) +#endif + +#if defined(EMBREE_GEOMETRY_CURVE) + #define IF_ENABLED_CURVES(x) x +#else + #define IF_ENABLED_CURVES(x) +#endif + +#if defined(EMBREE_GEOMETRY_POINT) + #define IF_ENABLED_POINTS(x) x +#else + #define IF_ENABLED_POINTS(x) +#endif + +#if defined(EMBREE_GEOMETRY_SUBDIVISION) + #define IF_ENABLED_SUBDIV(x) x +#else + #define IF_ENABLED_SUBDIV(x) +#endif + +#if defined(EMBREE_GEOMETRY_USER) + #define IF_ENABLED_USER(x) x +#else + #define IF_ENABLED_USER(x) +#endif + +#if defined(EMBREE_GEOMETRY_INSTANCE) + #define IF_ENABLED_INSTANCE(x) x +#else + #define IF_ENABLED_INSTANCE(x) +#endif + +#if defined(EMBREE_GEOMETRY_GRID) + #define IF_ENABLED_GRIDS(x) x +#else + #define IF_ENABLED_GRIDS(x) +#endif +""" + ) + + +with open("CMakeLists.txt", "r") as cmake_file: + cmake_content = cmake_file.read() + major_version = int(re.compile(r"EMBREE_VERSION_MAJOR\s(\d+)").findall(cmake_content)[0]) + minor_version = int(re.compile(r"EMBREE_VERSION_MINOR\s(\d+)").findall(cmake_content)[0]) + patch_version = int(re.compile(r"EMBREE_VERSION_PATCH\s(\d+)").findall(cmake_content)[0]) + +with open(os.path.join(dest_dir, "include/embree3/rtcore_config.h"), "w") as config_file: + config_file.write( + f""" +// Copyright 2009-2020 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#define RTC_VERSION_MAJOR {major_version} +#define RTC_VERSION_MINOR {minor_version} +#define RTC_VERSION_PATCH {patch_version} +#define RTC_VERSION {major_version}{minor_version:02d}{patch_version:02d} +#define RTC_VERSION_STRING "{major_version}.{minor_version}.{patch_version}" + +#define RTC_MAX_INSTANCE_LEVEL_COUNT 1 + +#define EMBREE_MIN_WIDTH 0 +#define RTC_MIN_WIDTH EMBREE_MIN_WIDTH + +#define EMBREE_STATIC_LIB +/* #undef EMBREE_API_NAMESPACE */ + +#if defined(EMBREE_API_NAMESPACE) +# define RTC_NAMESPACE +# define RTC_NAMESPACE_BEGIN namespace {{ +# define RTC_NAMESPACE_END }} +# define RTC_NAMESPACE_USE using namespace ; +# define RTC_API_EXTERN_C +# undef EMBREE_API_NAMESPACE +#else +# define RTC_NAMESPACE_BEGIN +# define RTC_NAMESPACE_END +# define RTC_NAMESPACE_USE +# if defined(__cplusplus) +# define RTC_API_EXTERN_C extern "C" +# else +# define RTC_API_EXTERN_C +# endif +#endif + +#if defined(ISPC) +# define RTC_API_IMPORT extern "C" unmasked +# define RTC_API_EXPORT extern "C" unmasked +#elif defined(EMBREE_STATIC_LIB) +# define RTC_API_IMPORT RTC_API_EXTERN_C +# define RTC_API_EXPORT RTC_API_EXTERN_C +#elif defined(_WIN32) +# define RTC_API_IMPORT RTC_API_EXTERN_C __declspec(dllimport) +# define RTC_API_EXPORT RTC_API_EXTERN_C __declspec(dllexport) +#else +# define RTC_API_IMPORT RTC_API_EXTERN_C +# define RTC_API_EXPORT RTC_API_EXTERN_C __attribute__ ((visibility ("default"))) +#endif + +#if defined(RTC_EXPORT_API) +# define RTC_API RTC_API_EXPORT +#else +# define RTC_API RTC_API_IMPORT +#endif +""" + ) + +os.chdir("..") +shutil.rmtree("embree-tmp") diff --git a/modules/raycast/lightmap_raycaster.cpp b/modules/raycast/lightmap_raycaster.cpp new file mode 100644 index 0000000000..9039622d3d --- /dev/null +++ b/modules/raycast/lightmap_raycaster.cpp @@ -0,0 +1,202 @@ +/*************************************************************************/ +/* lightmap_raycaster.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef TOOLS_ENABLED + +#include "lightmap_raycaster.h" + +// From Embree. +#include +#include + +#include + +using namespace embree; + +LightmapRaycaster *LightmapRaycasterEmbree::create_embree_raycaster() { + return memnew(LightmapRaycasterEmbree); +} + +void LightmapRaycasterEmbree::make_default_raycaster() { + create_function = create_embree_raycaster; +} + +void LightmapRaycasterEmbree::filter_function(const struct RTCFilterFunctionNArguments *p_args) { + RTCHit *hit = (RTCHit *)p_args->hit; + + unsigned int geomID = hit->geomID; + float u = hit->u; + float v = hit->v; + + LightmapRaycasterEmbree *scene = (LightmapRaycasterEmbree *)p_args->geometryUserPtr; + RTCGeometry geom = rtcGetGeometry(scene->embree_scene, geomID); + + rtcInterpolate0(geom, hit->primID, hit->u, hit->v, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, 0, &hit->u, 2); + + if (scene->alpha_textures.has(geomID)) { + const AlphaTextureData &alpha_texture = scene->alpha_textures[geomID]; + + if (alpha_texture.sample(hit->u, hit->v) < 128) { + p_args->valid[0] = 0; + return; + } + } + + rtcInterpolate0(geom, hit->primID, u, v, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, 1, &hit->Ng_x, 3); +} + +bool LightmapRaycasterEmbree::intersect(Ray &r_ray) { + RTCIntersectContext context; + + rtcInitIntersectContext(&context); + + rtcIntersect1(embree_scene, &context, (RTCRayHit *)&r_ray); + return r_ray.geomID != RTC_INVALID_GEOMETRY_ID; +} + +void LightmapRaycasterEmbree::intersect(Vector &r_rays) { + Ray *rays = r_rays.ptrw(); + for (int i = 0; i < r_rays.size(); ++i) { + intersect(rays[i]); + } +} + +void LightmapRaycasterEmbree::set_mesh_alpha_texture(Ref p_alpha_texture, unsigned int p_id) { + if (p_alpha_texture.is_valid() && p_alpha_texture->get_size() != Vector2i()) { + AlphaTextureData tex; + tex.size = p_alpha_texture->get_size(); + tex.data = p_alpha_texture->get_data(); + alpha_textures.insert(p_id, tex); + } +} + +float blerp(float c00, float c10, float c01, float c11, float tx, float ty) { + return Math::lerp(Math::lerp(c00, c10, tx), Math::lerp(c01, c11, tx), ty); +} + +uint8_t LightmapRaycasterEmbree::AlphaTextureData::sample(float u, float v) const { + float x = u * size.x; + float y = v * size.y; + int xi = (int)x; + int yi = (int)y; + + uint8_t texels[4]; + + for (int i = 0; i < 4; ++i) { + int sample_x = CLAMP(xi + i % 2, 0, size.x - 1); + int sample_y = CLAMP(yi + i / 2, 0, size.y - 1); + texels[i] = data[sample_y * size.x + sample_x]; + } + + return Math::round(blerp(texels[0], texels[1], texels[2], texels[3], x - xi, y - yi)); +} + +void LightmapRaycasterEmbree::add_mesh(const Vector &p_vertices, const Vector &p_normals, const Vector &p_uv2s, unsigned int p_id) { + RTCGeometry embree_mesh = rtcNewGeometry(embree_device, RTC_GEOMETRY_TYPE_TRIANGLE); + + rtcSetGeometryVertexAttributeCount(embree_mesh, 2); + + int vertex_count = p_vertices.size(); + + ERR_FAIL_COND(vertex_count % 3 != 0); + ERR_FAIL_COND(vertex_count != p_uv2s.size()); + + Vec3fa *embree_vertices = (Vec3fa *)rtcSetNewGeometryBuffer(embree_mesh, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, sizeof(Vec3fa), vertex_count); + Vec2fa *embree_light_uvs = (Vec2fa *)rtcSetNewGeometryBuffer(embree_mesh, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, 0, RTC_FORMAT_FLOAT2, sizeof(Vec2fa), vertex_count); + uint32_t *embree_triangles = (uint32_t *)rtcSetNewGeometryBuffer(embree_mesh, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, sizeof(uint32_t) * 3, vertex_count / 3); + + Vec3fa *embree_normals = nullptr; + if (!p_normals.is_empty()) { + embree_normals = (Vec3fa *)rtcSetNewGeometryBuffer(embree_mesh, RTC_BUFFER_TYPE_VERTEX_ATTRIBUTE, 1, RTC_FORMAT_FLOAT3, sizeof(Vec3fa), vertex_count); + } + + for (int i = 0; i < vertex_count; i++) { + embree_vertices[i] = Vec3fa(p_vertices[i].x, p_vertices[i].y, p_vertices[i].z); + embree_light_uvs[i] = Vec2fa(p_uv2s[i].x, p_uv2s[i].y); + if (embree_normals != nullptr) { + embree_normals[i] = Vec3fa(p_normals[i].x, p_normals[i].y, p_normals[i].z); + } + embree_triangles[i] = i; + } + + rtcCommitGeometry(embree_mesh); + rtcSetGeometryIntersectFilterFunction(embree_mesh, filter_function); + rtcSetGeometryUserData(embree_mesh, this); + rtcAttachGeometryByID(embree_scene, embree_mesh, p_id); + rtcReleaseGeometry(embree_mesh); +} + +void LightmapRaycasterEmbree::commit() { + rtcCommitScene(embree_scene); +} + +void LightmapRaycasterEmbree::set_mesh_filter(const Set &p_mesh_ids) { + for (Set::Element *E = p_mesh_ids.front(); E; E = E->next()) { + rtcDisableGeometry(rtcGetGeometry(embree_scene, E->get())); + } + rtcCommitScene(embree_scene); + filter_meshes = p_mesh_ids; +} + +void LightmapRaycasterEmbree::clear_mesh_filter() { + for (Set::Element *E = filter_meshes.front(); E; E = E->next()) { + rtcEnableGeometry(rtcGetGeometry(embree_scene, E->get())); + } + rtcCommitScene(embree_scene); + filter_meshes.clear(); +} + +void embree_error_handler(void *p_user_data, RTCError p_code, const char *p_str) { + print_error("Embree error: " + String(p_str)); +} + +LightmapRaycasterEmbree::LightmapRaycasterEmbree() { + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); + _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); + + embree_device = rtcNewDevice(nullptr); + rtcSetDeviceErrorFunction(embree_device, &embree_error_handler, nullptr); + embree_scene = rtcNewScene(embree_device); +} + +LightmapRaycasterEmbree::~LightmapRaycasterEmbree() { + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF); + _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF); + + if (embree_scene != nullptr) { + rtcReleaseScene(embree_scene); + } + + if (embree_device != nullptr) { + rtcReleaseDevice(embree_device); + } +} + +#endif diff --git a/modules/raycast/lightmap_raycaster.h b/modules/raycast/lightmap_raycaster.h new file mode 100644 index 0000000000..4c3de27837 --- /dev/null +++ b/modules/raycast/lightmap_raycaster.h @@ -0,0 +1,77 @@ +/*************************************************************************/ +/* lightmap_raycaster.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifdef TOOLS_ENABLED + +#include "core/object/object.h" +#include "scene/3d/lightmapper.h" +#include "scene/resources/mesh.h" + +#include + +class LightmapRaycasterEmbree : public LightmapRaycaster { + GDCLASS(LightmapRaycasterEmbree, LightmapRaycaster); + +private: + struct AlphaTextureData { + Vector data; + Vector2i size; + + uint8_t sample(float u, float v) const; + }; + + RTCDevice embree_device; + RTCScene embree_scene; + + static void filter_function(const struct RTCFilterFunctionNArguments *p_args); + + Map alpha_textures; + Set filter_meshes; + +public: + virtual bool intersect(Ray &p_ray) override; + + virtual void intersect(Vector &r_rays) override; + + virtual void add_mesh(const Vector &p_vertices, const Vector &p_normals, const Vector &p_uv2s, unsigned int p_id) override; + virtual void set_mesh_alpha_texture(Ref p_alpha_texture, unsigned int p_id) override; + virtual void commit() override; + + virtual void set_mesh_filter(const Set &p_mesh_ids) override; + virtual void clear_mesh_filter() override; + + static LightmapRaycaster *create_embree_raycaster(); + static void make_default_raycaster(); + + LightmapRaycasterEmbree(); + ~LightmapRaycasterEmbree(); +}; + +#endif diff --git a/modules/raycast/raycast_occlusion_cull.cpp b/modules/raycast/raycast_occlusion_cull.cpp new file mode 100644 index 0000000000..ea43255eef --- /dev/null +++ b/modules/raycast/raycast_occlusion_cull.cpp @@ -0,0 +1,583 @@ +/*************************************************************************/ +/* raycast_occlusion_cull.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "raycast_occlusion_cull.h" +#include "core/config/project_settings.h" +#include "core/templates/local_vector.h" + +#ifdef __SSE2__ +#include +#endif + +RaycastOcclusionCull *RaycastOcclusionCull::raycast_singleton = nullptr; + +void RaycastOcclusionCull::RaycastHZBuffer::clear() { + HZBuffer::clear(); + + camera_rays.clear(); + camera_ray_masks.clear(); + packs_size = Size2i(); +} + +void RaycastOcclusionCull::RaycastHZBuffer::resize(const Size2i &p_size) { + if (p_size == Size2i()) { + clear(); + return; + } + + if (!sizes.is_empty() && p_size == sizes[0]) { + return; // Size didn't change + } + + HZBuffer::resize(p_size); + + packs_size = Size2i(Math::ceil(p_size.x / (float)TILE_SIZE), Math::ceil(p_size.y / (float)TILE_SIZE)); + int ray_packets_count = packs_size.x * packs_size.y; + camera_rays.resize(ray_packets_count); + camera_ray_masks.resize(ray_packets_count * TILE_SIZE * TILE_SIZE); +} + +void RaycastOcclusionCull::RaycastHZBuffer::update_camera_rays(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, ThreadWorkPool &p_thread_work_pool) { + CameraRayThreadData td; + td.camera_matrix = p_cam_projection; + td.camera_transform = p_cam_transform; + td.camera_orthogonal = p_cam_orthogonal; + td.thread_count = p_thread_work_pool.get_thread_count(); + + p_thread_work_pool.do_work(td.thread_count, this, &RaycastHZBuffer::_camera_rays_threaded, &td); +} + +void RaycastOcclusionCull::RaycastHZBuffer::_camera_rays_threaded(uint32_t p_thread, RaycastOcclusionCull::RaycastHZBuffer::CameraRayThreadData *p_data) { + uint32_t packs_total = camera_rays.size(); + uint32_t total_threads = p_data->thread_count; + uint32_t from = p_thread * packs_total / total_threads; + uint32_t to = (p_thread + 1 == total_threads) ? packs_total : ((p_thread + 1) * packs_total / total_threads); + _generate_camera_rays(p_data->camera_transform, p_data->camera_matrix, p_data->camera_orthogonal, from, to); +} + +void RaycastOcclusionCull::RaycastHZBuffer::_generate_camera_rays(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, int p_from, int p_to) { + Size2i buffer_size = sizes[0]; + + CameraMatrix inv_camera_matrix = p_cam_projection.inverse(); + float z_far = p_cam_projection.get_z_far() * 1.05f; + debug_tex_range = z_far; + + RayPacket *ray_packets = camera_rays.ptr(); + uint32_t *ray_masks = camera_ray_masks.ptr(); + + for (int i = p_from; i < p_to; i++) { + RayPacket &packet = ray_packets[i]; + int tile_x = (i % packs_size.x) * TILE_SIZE; + int tile_y = (i / packs_size.x) * TILE_SIZE; + + for (int j = 0; j < TILE_RAYS; j++) { + float x = tile_x + j % TILE_SIZE; + float y = tile_y + j / TILE_SIZE; + + ray_masks[i * TILE_RAYS + j] = ~0U; + + if (x >= buffer_size.x || y >= buffer_size.y) { + ray_masks[i * TILE_RAYS + j] = 0U; + } else { + float u = x / (buffer_size.x - 1); + float v = y / (buffer_size.y - 1); + u = u * 2.0f - 1.0f; + v = v * 2.0f - 1.0f; + + Plane pixel_proj = Plane(u, v, -1.0, 1.0); + Plane pixel_view = inv_camera_matrix.xform4(pixel_proj); + Vector3 pixel_world = p_cam_transform.xform(pixel_view.normal); + + Vector3 dir; + if (p_cam_orthogonal) { + dir = -p_cam_transform.basis.get_axis(2); + } else { + dir = (pixel_world - p_cam_transform.origin).normalized(); + } + + packet.ray.org_x[j] = pixel_world.x; + packet.ray.org_y[j] = pixel_world.y; + packet.ray.org_z[j] = pixel_world.z; + + packet.ray.dir_x[j] = dir.x; + packet.ray.dir_y[j] = dir.y; + packet.ray.dir_z[j] = dir.z; + + packet.ray.tnear[j] = 0.0f; + + packet.ray.time[j] = 0.0f; + + packet.ray.flags[j] = 0; + packet.ray.mask[j] = -1; + packet.hit.geomID[j] = RTC_INVALID_GEOMETRY_ID; + } + + packet.ray.tfar[j] = z_far; + } + } +} + +void RaycastOcclusionCull::RaycastHZBuffer::sort_rays() { + if (is_empty()) { + return; + } + + Size2i buffer_size = sizes[0]; + for (int i = 0; i < packs_size.y; i++) { + for (int j = 0; j < packs_size.x; j++) { + for (int tile_i = 0; tile_i < TILE_SIZE; tile_i++) { + for (int tile_j = 0; tile_j < TILE_SIZE; tile_j++) { + int x = j * TILE_SIZE + tile_j; + int y = i * TILE_SIZE + tile_i; + if (x >= buffer_size.x || y >= buffer_size.y) { + continue; + } + int k = tile_i * TILE_SIZE + tile_j; + int packet_index = i * packs_size.x + j; + mips[0][y * buffer_size.x + x] = camera_rays[packet_index].ray.tfar[k]; + } + } + } + } +} + +//////////////////////////////////////////////////////// + +bool RaycastOcclusionCull::is_occluder(RID p_rid) { + return occluder_owner.owns(p_rid); +} + +RID RaycastOcclusionCull::occluder_allocate() { + return occluder_owner.allocate_rid(); +} + +void RaycastOcclusionCull::occluder_initialize(RID p_occluder) { + Occluder *occluder = memnew(Occluder); + occluder_owner.initialize_rid(p_occluder, occluder); +} + +void RaycastOcclusionCull::occluder_set_mesh(RID p_occluder, const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices) { + Occluder *occluder = occluder_owner.getornull(p_occluder); + ERR_FAIL_COND(!occluder); + + occluder->vertices = p_vertices; + occluder->indices = p_indices; + + for (Set::Element *E = occluder->users.front(); E; E = E->next()) { + RID scenario_rid = E->get().scenario; + RID instance_rid = E->get().instance; + ERR_CONTINUE(!scenarios.has(scenario_rid)); + Scenario &scenario = scenarios[scenario_rid]; + ERR_CONTINUE(!scenario.instances.has(instance_rid)); + + if (!scenario.dirty_instances.has(instance_rid)) { + scenario.dirty_instances.insert(instance_rid); + scenario.dirty_instances_array.push_back(instance_rid); + } + } +} + +void RaycastOcclusionCull::free_occluder(RID p_occluder) { + Occluder *occluder = occluder_owner.getornull(p_occluder); + ERR_FAIL_COND(!occluder); + memdelete(occluder); + occluder_owner.free(p_occluder); +} + +//////////////////////////////////////////////////////// + +void RaycastOcclusionCull::add_scenario(RID p_scenario) { + if (scenarios.has(p_scenario)) { + scenarios[p_scenario].removed = false; + } else { + scenarios[p_scenario] = Scenario(); + } +} + +void RaycastOcclusionCull::remove_scenario(RID p_scenario) { + ERR_FAIL_COND(!scenarios.has(p_scenario)); + Scenario &scenario = scenarios[p_scenario]; + scenario.removed = true; +} + +void RaycastOcclusionCull::scenario_set_instance(RID p_scenario, RID p_instance, RID p_occluder, const Transform &p_xform, bool p_enabled) { + ERR_FAIL_COND(!scenarios.has(p_scenario)); + Scenario &scenario = scenarios[p_scenario]; + + if (!scenario.instances.has(p_instance)) { + scenario.instances[p_instance] = OccluderInstance(); + } + + OccluderInstance &instance = scenario.instances[p_instance]; + + if (instance.removed) { + instance.removed = false; + scenario.removed_instances.erase(p_instance); + } + + bool changed = false; + + if (instance.occluder != p_occluder) { + Occluder *old_occluder = occluder_owner.getornull(instance.occluder); + if (old_occluder) { + old_occluder->users.erase(InstanceID(p_scenario, p_instance)); + } + + instance.occluder = p_occluder; + + if (p_occluder.is_valid()) { + Occluder *occluder = occluder_owner.getornull(p_occluder); + ERR_FAIL_COND(!occluder); + occluder->users.insert(InstanceID(p_scenario, p_instance)); + } + changed = true; + } + + if (instance.xform != p_xform) { + scenario.instances[p_instance].xform = p_xform; + changed = true; + } + + if (instance.enabled != p_enabled) { + instance.enabled = p_enabled; + scenario.dirty = true; // The scenario needs a scene re-build, but the instance doesn't need update + } + + if (changed && !scenario.dirty_instances.has(p_instance)) { + scenario.dirty_instances.insert(p_instance); + scenario.dirty_instances_array.push_back(p_instance); + scenario.dirty = true; + } +} + +void RaycastOcclusionCull::scenario_remove_instance(RID p_scenario, RID p_instance) { + ERR_FAIL_COND(!scenarios.has(p_scenario)); + Scenario &scenario = scenarios[p_scenario]; + + if (scenario.instances.has(p_instance)) { + OccluderInstance &instance = scenario.instances[p_instance]; + + if (!instance.removed) { + Occluder *occluder = occluder_owner.getornull(instance.occluder); + if (occluder) { + occluder->users.erase(InstanceID(p_scenario, p_instance)); + } + + scenario.removed_instances.push_back(p_instance); + instance.removed = true; + } + } +} + +void RaycastOcclusionCull::Scenario::_update_dirty_instance_thread(int p_idx, RID *p_instances) { + _update_dirty_instance(p_idx, p_instances, nullptr); +} + +void RaycastOcclusionCull::Scenario::_update_dirty_instance(int p_idx, RID *p_instances, ThreadWorkPool *p_thread_pool) { + OccluderInstance *occ_inst = instances.getptr(p_instances[p_idx]); + + if (!occ_inst) { + return; + } + + Occluder *occ = raycast_singleton->occluder_owner.getornull(occ_inst->occluder); + + if (!occ) { + return; + } + + int vertices_size = occ->vertices.size(); + + // Embree requires the last element to be readable by a 16-byte SSE load instruction, so we add padding to be safe. + occ_inst->xformed_vertices.resize(vertices_size + 1); + + const Vector3 *read_ptr = occ->vertices.ptr(); + Vector3 *write_ptr = occ_inst->xformed_vertices.ptr(); + + if (p_thread_pool && vertices_size > 1024) { + TransformThreadData td; + td.xform = occ_inst->xform; + td.read = read_ptr; + td.write = write_ptr; + td.vertex_count = vertices_size; + td.thread_count = p_thread_pool->get_thread_count(); + p_thread_pool->do_work(td.thread_count, this, &Scenario::_transform_vertices_thread, &td); + } else { + _transform_vertices_range(read_ptr, write_ptr, occ_inst->xform, 0, vertices_size); + } + + occ_inst->indices.resize(occ->indices.size()); + copymem(occ_inst->indices.ptr(), occ->indices.ptr(), occ->indices.size() * sizeof(int32_t)); +} + +void RaycastOcclusionCull::Scenario::_transform_vertices_thread(uint32_t p_thread, TransformThreadData *p_data) { + uint32_t vertex_total = p_data->vertex_count; + uint32_t total_threads = p_data->thread_count; + uint32_t from = p_thread * vertex_total / total_threads; + uint32_t to = (p_thread + 1 == total_threads) ? vertex_total : ((p_thread + 1) * vertex_total / total_threads); + _transform_vertices_range(p_data->read, p_data->write, p_data->xform, from, to); +} + +void RaycastOcclusionCull::Scenario::_transform_vertices_range(const Vector3 *p_read, Vector3 *p_write, const Transform &p_xform, int p_from, int p_to) { + for (int i = p_from; i < p_to; i++) { + p_write[i] = p_xform.xform(p_read[i]); + } +} + +void RaycastOcclusionCull::Scenario::_commit_scene(void *p_ud) { + Scenario *scenario = (Scenario *)p_ud; + int commit_idx = 1 - (scenario->current_scene_idx); + rtcCommitScene(scenario->ebr_scene[commit_idx]); + scenario->commit_done = true; +} + +bool RaycastOcclusionCull::Scenario::update(ThreadWorkPool &p_thread_pool) { + ERR_FAIL_COND_V(singleton == nullptr, false); + + if (commit_thread == nullptr) { + commit_thread = memnew(Thread); + } + + if (commit_thread->is_started()) { + if (commit_done) { + commit_thread->wait_to_finish(); + current_scene_idx = 1 - current_scene_idx; + } else { + return false; + } + } + + if (removed) { + if (ebr_scene[0]) { + rtcReleaseScene(ebr_scene[0]); + } + if (ebr_scene[1]) { + rtcReleaseScene(ebr_scene[1]); + } + return true; + } + + if (!dirty && removed_instances.is_empty() && dirty_instances_array.is_empty()) { + return false; + } + + for (unsigned int i = 0; i < removed_instances.size(); i++) { + instances.erase(removed_instances[i]); + } + + if (dirty_instances_array.size() / p_thread_pool.get_thread_count() > 128) { + // Lots of instances, use per-instance threading + p_thread_pool.do_work(dirty_instances_array.size(), this, &Scenario::_update_dirty_instance_thread, dirty_instances_array.ptr()); + } else { + // Few instances, use threading on the vertex transforms + for (unsigned int i = 0; i < dirty_instances_array.size(); i++) { + _update_dirty_instance(i, dirty_instances_array.ptr(), &p_thread_pool); + } + } + + dirty_instances.clear(); + dirty_instances_array.clear(); + removed_instances.clear(); + + if (raycast_singleton->ebr_device == nullptr) { + raycast_singleton->_init_embree(); + } + + int next_scene_idx = 1 - current_scene_idx; + RTCScene &next_scene = ebr_scene[next_scene_idx]; + + if (next_scene) { + rtcReleaseScene(next_scene); + } + + next_scene = rtcNewScene(raycast_singleton->ebr_device); + rtcSetSceneBuildQuality(next_scene, RTCBuildQuality(raycast_singleton->build_quality)); + + const RID *inst_rid = nullptr; + while ((inst_rid = instances.next(inst_rid))) { + OccluderInstance *occ_inst = instances.getptr(*inst_rid); + Occluder *occ = raycast_singleton->occluder_owner.getornull(occ_inst->occluder); + + if (!occ || !occ_inst->enabled) { + continue; + } + + RTCGeometry geom = rtcNewGeometry(raycast_singleton->ebr_device, RTC_GEOMETRY_TYPE_TRIANGLE); + rtcSetSharedGeometryBuffer(geom, RTC_BUFFER_TYPE_VERTEX, 0, RTC_FORMAT_FLOAT3, occ_inst->xformed_vertices.ptr(), 0, sizeof(Vector3), occ_inst->xformed_vertices.size()); + rtcSetSharedGeometryBuffer(geom, RTC_BUFFER_TYPE_INDEX, 0, RTC_FORMAT_UINT3, occ_inst->indices.ptr(), 0, sizeof(uint32_t) * 3, occ_inst->indices.size() / 3); + rtcCommitGeometry(geom); + rtcAttachGeometry(next_scene, geom); + rtcReleaseGeometry(geom); + } + + dirty = false; + commit_done = false; + commit_thread->start(&Scenario::_commit_scene, this); + return false; +} + +void RaycastOcclusionCull::Scenario::_raycast(uint32_t p_idx, const RaycastThreadData *p_raycast_data) const { + RTCIntersectContext ctx; + rtcInitIntersectContext(&ctx); + ctx.flags = RTC_INTERSECT_CONTEXT_FLAG_COHERENT; + + rtcIntersect16((const int *)&p_raycast_data->masks[p_idx * TILE_RAYS], ebr_scene[current_scene_idx], &ctx, &p_raycast_data->rays[p_idx]); +} + +void RaycastOcclusionCull::Scenario::raycast(LocalVector &r_rays, const LocalVector p_valid_masks, ThreadWorkPool &p_thread_pool) const { + ERR_FAIL_COND(singleton == nullptr); + if (raycast_singleton->ebr_device == nullptr) { + return; // Embree is initialized on demand when there is some scenario with occluders in it. + } + + if (ebr_scene[current_scene_idx] == nullptr) { + return; + } + + RaycastThreadData td; + td.rays = r_rays.ptr(); + td.masks = p_valid_masks.ptr(); + + p_thread_pool.do_work(r_rays.size(), this, &Scenario::_raycast, &td); +} + +//////////////////////////////////////////////////////// + +void RaycastOcclusionCull::add_buffer(RID p_buffer) { + ERR_FAIL_COND(buffers.has(p_buffer)); + buffers[p_buffer] = RaycastHZBuffer(); +} + +void RaycastOcclusionCull::remove_buffer(RID p_buffer) { + ERR_FAIL_COND(!buffers.has(p_buffer)); + buffers.erase(p_buffer); +} + +void RaycastOcclusionCull::buffer_set_scenario(RID p_buffer, RID p_scenario) { + ERR_FAIL_COND(!buffers.has(p_buffer)); + ERR_FAIL_COND(p_scenario.is_valid() && !scenarios.has(p_scenario)); + buffers[p_buffer].scenario_rid = p_scenario; +} + +void RaycastOcclusionCull::buffer_set_size(RID p_buffer, const Vector2i &p_size) { + ERR_FAIL_COND(!buffers.has(p_buffer)); + buffers[p_buffer].resize(p_size); +} + +void RaycastOcclusionCull::buffer_update(RID p_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, ThreadWorkPool &p_thread_pool) { + if (!buffers.has(p_buffer)) { + return; + } + + RaycastHZBuffer &buffer = buffers[p_buffer]; + + if (buffer.is_empty() || !scenarios.has(buffer.scenario_rid)) { + return; + } + + Scenario &scenario = scenarios[buffer.scenario_rid]; + + bool removed = scenario.update(p_thread_pool); + + if (removed) { + scenarios.erase(buffer.scenario_rid); + return; + } + + buffer.update_camera_rays(p_cam_transform, p_cam_projection, p_cam_orthogonal, p_thread_pool); + + scenario.raycast(buffer.camera_rays, buffer.camera_ray_masks, p_thread_pool); + buffer.sort_rays(); + buffer.update_mips(); +} + +RaycastOcclusionCull::HZBuffer *RaycastOcclusionCull::buffer_get_ptr(RID p_buffer) { + if (!buffers.has(p_buffer)) { + return nullptr; + } + return &buffers[p_buffer]; +} + +RID RaycastOcclusionCull::buffer_get_debug_texture(RID p_buffer) { + ERR_FAIL_COND_V(!buffers.has(p_buffer), RID()); + return buffers[p_buffer].get_debug_texture(); +} + +//////////////////////////////////////////////////////// + +void RaycastOcclusionCull::set_build_quality(RS::ViewportOcclusionCullingBuildQuality p_quality) { + if (build_quality == p_quality) { + return; + } + + build_quality = p_quality; + + const RID *scenario_rid = nullptr; + while ((scenario_rid = scenarios.next(scenario_rid))) { + scenarios[*scenario_rid].dirty = true; + } +} + +void RaycastOcclusionCull::_init_embree() { +#ifdef __SSE2__ + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); + _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); +#endif + + String settings = vformat("threads=%d", MAX(1, OS::get_singleton()->get_processor_count() - 2)); + ebr_device = rtcNewDevice(settings.utf8().ptr()); +} + +RaycastOcclusionCull::RaycastOcclusionCull() { + raycast_singleton = this; + int default_quality = GLOBAL_GET("rendering/occlusion_culling/bvh_build_quality"); + build_quality = RS::ViewportOcclusionCullingBuildQuality(default_quality); +} + +RaycastOcclusionCull::~RaycastOcclusionCull() { + const RID *scenario_rid = nullptr; + while ((scenario_rid = scenarios.next(scenario_rid))) { + Scenario &scenario = scenarios[*scenario_rid]; + if (scenario.commit_thread) { + scenario.commit_thread->wait_to_finish(); + memdelete(scenario.commit_thread); + } + } + + if (ebr_device != nullptr) { +#ifdef __SSE2__ + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF); + _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF); +#endif + rtcReleaseDevice(ebr_device); + } + + raycast_singleton = nullptr; +} diff --git a/modules/raycast/raycast_occlusion_cull.h b/modules/raycast/raycast_occlusion_cull.h new file mode 100644 index 0000000000..acaceb9459 --- /dev/null +++ b/modules/raycast/raycast_occlusion_cull.h @@ -0,0 +1,184 @@ +/*************************************************************************/ +/* raycast_occlusion_cull.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 OCCLUSION_CULL_RAYCASTER_H +#define OCCLUSION_CULL_RAYCASTER_H + +#include "core/io/image.h" +#include "core/math/camera_matrix.h" +#include "core/object/object.h" +#include "core/object/reference.h" +#include "core/templates/local_vector.h" +#include "core/templates/rid_owner.h" +#include "scene/resources/mesh.h" +#include "servers/rendering/renderer_scene_occlusion_cull.h" + +#include + +class RaycastOcclusionCull : public RendererSceneOcclusionCull { + typedef RTCRayHit16 RayPacket; + +public: + class RaycastHZBuffer : public HZBuffer { + private: + Size2i packs_size; + + struct CameraRayThreadData { + CameraMatrix camera_matrix; + Transform camera_transform; + bool camera_orthogonal; + int thread_count; + Size2i buffer_size; + }; + + void _camera_rays_threaded(uint32_t p_thread, CameraRayThreadData *p_data); + void _generate_camera_rays(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, int p_from, int p_to); + + public: + LocalVector camera_rays; + LocalVector camera_ray_masks; + RID scenario_rid; + + virtual void clear() override; + virtual void resize(const Size2i &p_size) override; + void sort_rays(); + void update_camera_rays(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, ThreadWorkPool &p_thread_work_pool); + }; + +private: + struct InstanceID { + RID scenario; + RID instance; + + bool operator<(const InstanceID &rhs) const { + if (instance == rhs.instance) { + return rhs.scenario < scenario; + } + return instance < rhs.instance; + } + + InstanceID() {} + InstanceID(RID s, RID i) : + scenario(s), instance(i) {} + }; + + struct Occluder { + PackedVector3Array vertices; + PackedInt32Array indices; + Set users; + }; + + struct OccluderInstance { + RID occluder; + LocalVector indices; + LocalVector xformed_vertices; + Transform xform; + bool enabled = true; + bool removed = false; + }; + + struct Scenario { + struct RaycastThreadData { + RayPacket *rays; + const uint32_t *masks; + }; + + struct TransformThreadData { + uint32_t thread_count; + uint32_t vertex_count; + Transform xform; + const Vector3 *read; + Vector3 *write; + }; + + Thread *commit_thread = nullptr; + bool commit_done = true; + bool dirty = false; + bool removed = false; + + RTCScene ebr_scene[2] = { nullptr, nullptr }; + int current_scene_idx = 0; + + HashMap instances; + Set dirty_instances; // To avoid duplicates + LocalVector dirty_instances_array; // To iterate and split into threads + LocalVector removed_instances; + + void _update_dirty_instance_thread(int p_idx, RID *p_instances); + void _update_dirty_instance(int p_idx, RID *p_instances, ThreadWorkPool *p_thread_pool); + void _transform_vertices_thread(uint32_t p_thread, TransformThreadData *p_data); + void _transform_vertices_range(const Vector3 *p_read, Vector3 *p_write, const Transform &p_xform, int p_from, int p_to); + static void _commit_scene(void *p_ud); + bool update(ThreadWorkPool &p_thread_pool); + + void _raycast(uint32_t p_thread, const RaycastThreadData *p_raycast_data) const; + void raycast(LocalVector &r_rays, const LocalVector p_valid_masks, ThreadWorkPool &p_thread_pool) const; + }; + + static RaycastOcclusionCull *raycast_singleton; + + static const int TILE_SIZE = 4; + static const int TILE_RAYS = TILE_SIZE * TILE_SIZE; + + RTCDevice ebr_device = nullptr; + RID_PtrOwner occluder_owner; + HashMap scenarios; + HashMap buffers; + RS::ViewportOcclusionCullingBuildQuality build_quality; + + void _init_embree(); + +public: + virtual bool is_occluder(RID p_rid) override; + virtual RID occluder_allocate() override; + virtual void occluder_initialize(RID p_occluder) override; + virtual void occluder_set_mesh(RID p_occluder, const PackedVector3Array &p_vertices, const PackedInt32Array &p_indices) override; + virtual void free_occluder(RID p_occluder) override; + + virtual void add_scenario(RID p_scenario) override; + virtual void remove_scenario(RID p_scenario) override; + virtual void scenario_set_instance(RID p_scenario, RID p_instance, RID p_occluder, const Transform &p_xform, bool p_enabled) override; + virtual void scenario_remove_instance(RID p_scenario, RID p_instance) override; + + virtual void add_buffer(RID p_buffer) override; + virtual void remove_buffer(RID p_buffer) override; + virtual HZBuffer *buffer_get_ptr(RID p_buffer) override; + virtual void buffer_set_scenario(RID p_buffer, RID p_scenario) override; + virtual void buffer_set_size(RID p_buffer, const Vector2i &p_size) override; + virtual void buffer_update(RID p_buffer, const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, ThreadWorkPool &p_thread_pool) override; + virtual RID buffer_get_debug_texture(RID p_buffer) override; + + virtual void set_build_quality(RS::ViewportOcclusionCullingBuildQuality p_quality) override; + + RaycastOcclusionCull(); + ~RaycastOcclusionCull(); +}; + +#endif // OCCLUSION_CULL_RAYCASTER_H diff --git a/modules/raycast/register_types.cpp b/modules/raycast/register_types.cpp new file mode 100644 index 0000000000..78ca91309f --- /dev/null +++ b/modules/raycast/register_types.cpp @@ -0,0 +1,49 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "lightmap_raycaster.h" +#include "raycast_occlusion_cull.h" + +RaycastOcclusionCull *raycast_occlusion_cull = nullptr; + +void register_raycast_types() { +#ifdef TOOLS_ENABLED + LightmapRaycasterEmbree::make_default_raycaster(); +#endif + raycast_occlusion_cull = memnew(RaycastOcclusionCull); +} + +void unregister_raycast_types() { + if (raycast_occlusion_cull) { + memdelete(raycast_occlusion_cull); + } +} diff --git a/modules/raycast/register_types.h b/modules/raycast/register_types.h new file mode 100644 index 0000000000..789604a491 --- /dev/null +++ b/modules/raycast/register_types.h @@ -0,0 +1,32 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +void register_raycast_types(); +void unregister_raycast_types(); -- cgit v1.2.3