Compare commits

..

19 Commits

Author SHA1 Message Date
Benjamin Doherty
979d6742e0 Merge branch 'rc/1.31.7' into release 2023-03-09 10:39:41 -08:00
Mathias Agopian
4a7a033d04 Fix timer query breakage on webgl 2023-03-08 12:33:11 -08:00
Ben Doherty
5c2bbcb4a1 Remove compute shader assertion in GLProgram 2023-03-08 10:56:24 -08:00
Benjamin Doherty
dec903a0ee Bump version to 1.31.7 2023-03-01 11:26:50 -08:00
Benjamin Doherty
78785a5bca Release Filament 1.31.6 2023-03-01 11:23:37 -08:00
Mathias Agopian
662b4b5ca3 fix typo that could cause a OOB
fortunately this would only happen when using SSBOs (which are not
used currently).
2023-02-27 13:06:25 -08:00
Mathias Agopian
bee96d1cf1 abstract OpenGL's GLSync
This small abstraction is (will be) needed because GLES 2.0 doesn't
have sync object, however synchronization is sometimes available
externally, in particular with EGL.

This PR doesn't provide other implementations.
2023-02-27 12:40:21 -08:00
Mathias Agopian
122ebe1ccb More improvements to how we handle extensions (#6591)
- make sure to initialize all extension booleans, we treat them
  as feature flags.
- be more explicit about #define'ing gl tokens, so we can more easily
  catch errors later.
- don't blindly use extension tokens that might not be available
  (e.g.: GL_TEXTURE_EXTERNAL_OES or GL_TEXTURE_CUBE_MAP_ARRAY). It
  would probably cause a spurious gl error.
2023-02-24 16:52:43 -08:00
Mathias Agopian
e741e165ae remove unused / no longer used code 2023-02-24 16:50:15 -08:00
Mathias Agopian
d38dc24915 Cleanup how we get GL extensions entry-points
- iOS is treated the same way than Android now. The only difference
  is that iOS only provides prototypes and no typedef, whereas 
  Android only provides typedefs and no prototypes.

- Timer queries is core in GL and an extension on all versions of
  GLES. On iOS it's not available at the header level. An additional
  subtlety is that glGetObjectuiv is core in GLES 3.0, so it conflicts
  with the extension.
  So, now we do things correctly:
  - on desktop we use the core methods
  - on ios we ifdef out everything related to timer queries
  - on gles 2.0 and up we use only the extension entry-points
2023-02-24 11:55:22 -08:00
Powei Feng
d12612f091 Add UV-based tangent space algorithms (#6572)
Added mikktspace as a third party lib
2023-02-24 09:32:02 -08:00
Mathias Agopian
e6d73135c1 fix DEPTH_STENCIL case for fbo attachments
We were not testing for that case properly. This case is taken when 
either:
- depth & stencil textures are the same and not null
- or, only depth is specified but both attachments are requested


Also cleanup the dimension checks in debug builds.
2023-02-23 14:47:17 -08:00
Mathias Agopian
816612cf0a Fix readPixels() for "auto-resolved" MS color buffers
glReadPixel doesn't resolve automatically, but it does with the 
auto-resolve extension, which we're always emulating.
2023-02-23 10:56:08 -08:00
Mathias Agopian
7ca8ba272c don't rely on GL PACK/UNPACK convenience
- some of the convenience are not available in ES2
- it's less efficient
- we can save some PBO space when reading back
  a partial framebuffer.

We also avoid using GL_PACK_ROW_LENGTH which technically is not
a convenience, but in our case we are doing an extra copy anyways, so
we can account for the row-length at that point.
2023-02-23 10:55:46 -08:00
Mathias Agopian
d337a6d019 Scene::prepare is now multithreaded 2023-02-22 16:28:40 -08:00
Mathias Agopian
f79c4c6080 Improve directional shadow performance with large scenes
- We now call visitScene only once for the directional shadowmap instead of
1 + cascade_count times.

- Don't use visitScene for spot shadows

it was only used to compute the near/far planes, but instead we can
use the radius of the light. This could degrade the quality of the
spot shadows, but this can be corrected by setting a correct radius.

caveat: currently the near is hardcoded to 0.01 units. this should be
user-settable.

- Improve performance of visitScene calls

instead of transforming 8 points of an AABB and finding the min/max,
we transform the AABB and use its minz and maxz. This works for affine
transforms.

This change also cleans-up AABB and Box transform APIs, which also
are now inline.
2023-02-22 16:28:40 -08:00
Mathias Agopian
2e7fc6229b Improve StructureOfArray
- StructureOfArray: don't initialize trivial ctors

We mimic the behavior of std::vector<> here, where a resize() won't
initialize the array if the type is trivially_default_constructible.
This can reveal existing bugs, where we depended on the initialization
to 0.

- StructureOfArray: add push_back(std::tuple<>)

This basically allows us to push_back() a struct of the SoA.

- Make PerRenderableData trivially constructible

this improves performance when we have tons of objects in the scene
because PerRenderableData is used in arrays.
2023-02-22 16:28:40 -08:00
Mathias Agopian
fc724575fa Make SYSTRACE_CALL work with SYSTRACE_CONTEXT
SYSTRACE_VALUE now requires a SYSTRACE_CONTEXT.
2023-02-22 16:28:40 -08:00
Mathias Agopian
84716610b8 cleanup: make const everything that can be 2023-02-22 16:28:40 -08:00
47 changed files with 3577 additions and 874 deletions

View File

@@ -687,6 +687,7 @@ add_subdirectory(${EXTERNAL}/robin-map/tnt)
add_subdirectory(${EXTERNAL}/smol-v/tnt)
add_subdirectory(${EXTERNAL}/benchmark/tnt)
add_subdirectory(${EXTERNAL}/meshoptimizer/tnt)
add_subdirectory(${EXTERNAL}/mikktspace)
add_subdirectory(${EXTERNAL}/cgltf/tnt)
add_subdirectory(${EXTERNAL}/draco/tnt)
add_subdirectory(${EXTERNAL}/jsmn/tnt)

View File

@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.31.6'
implementation 'com.google.android.filament:filament-android:1.31.7'
}
```
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```
pod 'Filament', '~> 1.31.6'
pod 'Filament', '~> 1.31.7'
```
### Snapshots

View File

@@ -7,6 +7,8 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.31.7
## v1.31.6
- engine: the default render channel is now 2 instead of 0

View File

@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.31.6
VERSION_NAME=1.31.7
POM_DESCRIPTION=Real-time physically based rendering engine for Android.

View File

@@ -50,7 +50,6 @@ set(PUBLIC_HDRS
set(SRCS
src/AtlasAllocator.cpp
src/Box.cpp
src/BufferObject.cpp
src/Camera.cpp
src/Color.cpp

View File

@@ -279,8 +279,8 @@ public:
break;
}
size_t bpr = bpp * stride;
size_t bprAligned = (bpr + (alignment - 1)) & (~alignment + 1);
size_t const bpr = bpp * stride;
size_t const bprAligned = (bpr + (alignment - 1)) & (~alignment + 1);
return bprAligned * height;
}

View File

@@ -75,6 +75,7 @@ CommandStream::CommandStream(Driver& driver, CircularBuffer& buffer) noexcept
void CommandStream::execute(void* buffer) {
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
Profiler profiler;

View File

@@ -120,7 +120,12 @@ constexpr inline GLenum getBufferBindingType(BufferObjectBinding bindingType) no
case BufferObjectBinding::UNIFORM:
return GL_UNIFORM_BUFFER;
case BufferObjectBinding::SHADER_STORAGE:
#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
return GL_SHADER_STORAGE_BUFFER;
#else
utils::panic(__func__, __FILE__, __LINE__, "SHADER_STORAGE not supported");
return 0x90D2; // just to return something
#endif
}
}

View File

@@ -16,6 +16,10 @@
#include "OpenGLContext.h"
#include <backend/platforms/OpenGLPlatform.h>
#include <utility>
// change to true to display all GL extensions in the console on start-up
#define DEBUG_PRINT_EXTENSIONS false
@@ -70,7 +74,9 @@ OpenGLContext::OpenGLContext() noexcept {
constexpr GLint MAX_FRAGMENT_SAMPLER_COUNT = caps3.MAX_FRAGMENT_SAMPLER_COUNT;
if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GLES) {
#if defined(GL_ES_VERSION_2_0)
initExtensionsGLES();
#endif
if (state.major == 3) {
assert_invariant(gets.max_texture_image_units >= 16);
assert_invariant(gets.max_combined_texture_image_units >= 32);
@@ -88,8 +94,9 @@ OpenGLContext::OpenGLContext() noexcept {
}
}
} else if constexpr (BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
// OpenGL version
#if defined(GL_VERSION_4_1)
initExtensionsGL();
#endif
if (state.major == 4) {
assert_invariant(state.minor >= 1);
mShaderModel = ShaderModel::DESKTOP;
@@ -351,6 +358,8 @@ void OpenGLContext::setDefaultState() noexcept {
#endif
}
#if defined(GL_ES_VERSION_2_0)
void OpenGLContext::initExtensionsGLES() noexcept {
const char * const extensions = (const char*)glGetString(GL_EXTENSIONS);
GLUtils::unordered_string_set const exts = GLUtils::split(extensions);
@@ -395,6 +404,10 @@ void OpenGLContext::initExtensionsGLES() noexcept {
}
}
#endif // defined(GL_ES_VERSION_2_0)
#if defined(GL_VERSION_4_1)
void OpenGLContext::initExtensionsGL() noexcept {
GLUtils::unordered_string_set exts;
GLint n = 0;
@@ -418,21 +431,31 @@ void OpenGLContext::initExtensionsGL() noexcept {
ext.EXT_color_buffer_float = true; // Assumes core profile.
ext.EXT_color_buffer_half_float = true; // Assumes core profile.
ext.EXT_debug_marker = exts.has("GL_EXT_debug_marker"sv);
ext.EXT_disjoint_timer_query = true;
ext.EXT_multisampled_render_to_texture = false;
ext.EXT_multisampled_render_to_texture2 = false;
ext.EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
ext.EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
ext.EXT_texture_compression_etc2 = exts.has("GL_ARB_ES3_compatibility"sv);
ext.EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
ext.EXT_texture_compression_s3tc = exts.has("GL_EXT_texture_compression_s3tc"sv);
ext.EXT_texture_compression_s3tc_srgb = exts.has("GL_EXT_texture_compression_s3tc_srgb"sv);
ext.EXT_texture_compression_rgtc = exts.has("GL_EXT_texture_compression_rgtc"sv);
ext.EXT_texture_compression_bptc = exts.has("GL_EXT_texture_compression_bptc"sv);
ext.EXT_texture_cube_map_array = true;
ext.EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"sv);
ext.EXT_texture_sRGB = exts.has("GL_EXT_texture_sRGB"sv);
ext.GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
ext.KHR_debug = major >= 4 && minor >= 3;
ext.KHR_texture_compression_astc_hdr = exts.has("GL_KHR_texture_compression_astc_hdr"sv);
ext.KHR_texture_compression_astc_ldr = exts.has("GL_KHR_texture_compression_astc_ldr"sv);
ext.OES_EGL_image_external_essl3 = exts.has("GL_OES_EGL_image_external_essl3"sv);
ext.OES_EGL_image_external_essl3 = false;
ext.QCOM_tiled_rendering = false;
ext.WEBGL_compressed_texture_etc = false;
ext.WEBGL_compressed_texture_s3tc = false;
ext.WEBGL_compressed_texture_s3tc_srgb = false;
}
#endif // defined(GL_VERSION_4_1)
void OpenGLContext::bindBuffer(GLenum target, GLuint buffer) noexcept {
if (target == GL_ELEMENT_ARRAY_BUFFER) {
constexpr size_t targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
@@ -465,13 +488,8 @@ void OpenGLContext::pixelStore(GLenum pname, GLint param) noexcept {
switch (pname) {
case GL_PACK_ALIGNMENT: pcur = &state.pack.alignment; break;
case GL_PACK_ROW_LENGTH: pcur = &state.pack.row_length; break;
case GL_PACK_SKIP_PIXELS: pcur = &state.pack.skip_pixels; break; // convenience
case GL_PACK_SKIP_ROWS: pcur = &state.pack.skip_row; break; // convenience
case GL_UNPACK_ALIGNMENT: pcur = &state.unpack.alignment; break;
case GL_UNPACK_ROW_LENGTH: pcur = &state.unpack.row_length; break;
case GL_UNPACK_SKIP_PIXELS: pcur = &state.unpack.skip_pixels; break; // convenience
case GL_UNPACK_SKIP_ROWS: pcur = &state.unpack.skip_row; break; // convenience
default:
goto default_case;
}
@@ -618,12 +636,10 @@ void OpenGLContext::resetState() noexcept {
GLenum const bufferTargets[] = {
GL_UNIFORM_BUFFER,
GL_TRANSFORM_FEEDBACK_BUFFER,
#if !defined(__EMSCRIPTEN__)
#if !defined(__EMSCRIPTEN__) && (defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1))
GL_SHADER_STORAGE_BUFFER,
#endif
GL_ARRAY_BUFFER,
GL_COPY_READ_BUFFER,
GL_COPY_WRITE_BUFFER,
GL_ELEMENT_ARRAY_BUFFER,
GL_PIXEL_PACK_BUFFER,
GL_PIXEL_UNPACK_BUFFER,
@@ -646,23 +662,30 @@ void OpenGLContext::resetState() noexcept {
// Reset state.textures to its default state to avoid the complexity and error-prone
// nature of resetting the GL state to its existing state
state.textures = {};
const GLuint textureTargets[] = {
GL_TEXTURE_2D,
GL_TEXTURE_2D_ARRAY,
GL_TEXTURE_CUBE_MAP,
GL_TEXTURE_3D,
const std::pair<GLuint, bool> textureTargets[] = {
{ GL_TEXTURE_2D, true },
{ GL_TEXTURE_2D_ARRAY, true },
{ GL_TEXTURE_CUBE_MAP, true },
{ GL_TEXTURE_3D, true },
#if !defined(__EMSCRIPTEN__)
GL_TEXTURE_2D_MULTISAMPLE,
GL_TEXTURE_EXTERNAL_OES,
GL_TEXTURE_CUBE_MAP_ARRAY,
#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
{ GL_TEXTURE_2D_MULTISAMPLE, true },
#endif
#if defined(GL_OES_EGL_image_external)
{ GL_TEXTURE_EXTERNAL_OES, ext.OES_EGL_image_external_essl3 },
#endif
#if defined(GL_VERSION_4_1) || defined(GL_EXT_texture_cube_map_array)
{ GL_TEXTURE_CUBE_MAP_ARRAY, ext.EXT_texture_cube_map_array },
#endif
#endif
};
for (GLint unit = 0; unit < gets.max_combined_texture_image_units; ++unit) {
glActiveTexture(GL_TEXTURE0 + unit);
glBindSampler(unit, 0);
for (auto const target : textureTargets) {
glBindTexture(target, 0);
for (auto [target, available] : textureTargets) {
if (available) {
glBindTexture(target, 0);
}
}
}
glActiveTexture(GL_TEXTURE0 + state.textures.active);
@@ -670,14 +693,10 @@ void OpenGLContext::resetState() noexcept {
// state.unpack
glPixelStorei(GL_UNPACK_ALIGNMENT, state.unpack.alignment);
glPixelStorei(GL_UNPACK_ROW_LENGTH, state.unpack.row_length);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, state.unpack.skip_pixels);
glPixelStorei(GL_UNPACK_SKIP_ROWS, state.unpack.skip_row);
// state.pack
glPixelStorei(GL_PACK_ALIGNMENT, state.pack.alignment);
glPixelStorei(GL_PACK_ROW_LENGTH, state.pack.row_length);
glPixelStorei(GL_PACK_SKIP_PIXELS, state.pack.skip_pixels);
glPixelStorei(GL_PACK_SKIP_ROWS, state.pack.skip_row);
glPixelStorei(GL_PACK_ROW_LENGTH, 0); // we rely on GL_PACK_ROW_LENGTH being zero
// state.window
glScissor(
@@ -696,4 +715,30 @@ void OpenGLContext::resetState() noexcept {
}
OpenGLContext::FenceSync OpenGLContext::createFenceSync(
OpenGLPlatform&) noexcept {
auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
CHECK_GL_ERROR(utils::slog.e)
return { .sync = sync };
}
void OpenGLContext::destroyFenceSync(
OpenGLPlatform&, FenceSync sync) noexcept {
glDeleteSync(sync.sync);
CHECK_GL_ERROR(utils::slog.e)
}
OpenGLContext::FenceSync::Status OpenGLContext::clientWaitSync(
OpenGLPlatform&, FenceSync sync) const noexcept {
GLenum const status = glClientWaitSync(sync.sync, 0, 0u);
CHECK_GL_ERROR(utils::slog.e)
using Status = OpenGLContext::FenceSync::Status;
switch (status) {
case GL_ALREADY_SIGNALED: return Status::ALREADY_SIGNALED;
case GL_TIMEOUT_EXPIRED: return Status::TIMEOUT_EXPIRED;
case GL_CONDITION_SATISFIED: return Status::CONDITION_SATISFIED;
default: return Status::FAILURE;
}
}
} // namesapce filament

View File

@@ -33,6 +33,8 @@
namespace filament::backend {
class OpenGLPlatform;
class OpenGLContext {
public:
static constexpr const size_t MAX_TEXTURE_UNIT_COUNT = MAX_SAMPLER_COUNT;
@@ -110,9 +112,6 @@ public:
GLenum sfailBack, GLenum dpfailBack, GLenum dppassBack) noexcept;
inline void stencilMaskSeparate(GLuint maskFront, GLuint maskBack) noexcept;
inline void polygonOffset(GLfloat factor, GLfloat units) noexcept;
inline void beginQuery(GLenum target, GLuint query) noexcept;
inline void endQuery(GLenum target) noexcept;
inline GLuint getQuery(GLenum target) const noexcept;
inline void setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept;
inline void viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept;
@@ -121,6 +120,26 @@ public:
void deleteBuffers(GLsizei n, const GLuint* buffers, GLenum target) noexcept;
void deleteVertexArrays(GLsizei n, const GLuint* arrays) noexcept;
// we abstract GL's sync because it's not available in ES2, but we can use EGL's sync
// instead, if available.
struct FenceSync {
enum class Status {
ALREADY_SIGNALED,
TIMEOUT_EXPIRED,
CONDITION_SATISFIED,
FAILURE
};
union {
void* fence;
GLsync sync;
};
};
FenceSync createFenceSync(OpenGLPlatform& platform) noexcept;
void destroyFenceSync(OpenGLPlatform& platform, FenceSync sync) noexcept;
FenceSync::Status clientWaitSync(OpenGLPlatform& platform, FenceSync sync) const noexcept;
// glGet*() values
struct {
GLfloat max_anisotropy;
@@ -149,21 +168,21 @@ public:
bool EXT_color_buffer_half_float;
bool EXT_debug_marker;
bool EXT_disjoint_timer_query;
bool EXT_multisampled_render_to_texture;
bool EXT_multisampled_render_to_texture2;
bool EXT_multisampled_render_to_texture;
bool EXT_shader_framebuffer_fetch;
bool KHR_texture_compression_astc_hdr;
bool KHR_texture_compression_astc_ldr;
bool EXT_texture_compression_bptc;
bool EXT_texture_compression_etc2;
bool EXT_texture_compression_rgtc;
bool EXT_texture_compression_s3tc;
bool EXT_texture_compression_s3tc_srgb;
bool EXT_texture_compression_rgtc;
bool EXT_texture_compression_bptc;
bool EXT_texture_cube_map_array;
bool EXT_texture_filter_anisotropic;
bool EXT_texture_sRGB;
bool GOOGLE_cpp_style_line_directive;
bool KHR_debug;
bool KHR_texture_compression_astc_hdr;
bool KHR_texture_compression_astc_ldr;
bool OES_EGL_image_external_essl3;
bool QCOM_tiled_rendering;
bool WEBGL_compressed_texture_etc;
@@ -315,8 +334,8 @@ public:
GLintptr offset = 0;
GLsizeiptr size = 0;
} buffers[MAX_BUFFER_BINDINGS];
} targets[2]; // there are only 2 indexed buffer target (uniform and transform feedback)
GLuint genericBinding[9] = { 0 };
} targets[3]; // there are only 3 indexed buffer targets
GLuint genericBinding[7] = {};
} buffers;
struct {
@@ -332,15 +351,10 @@ public:
struct {
GLint row_length = 0;
GLint alignment = 4;
GLint skip_pixels = 0;
GLint skip_row = 0;
} unpack;
struct {
GLint row_length = 0;
GLint alignment = 4;
GLint skip_pixels = 0;
GLint skip_row = 0;
} pack;
struct {
@@ -348,10 +362,6 @@ public:
vec4gli viewport { 0 };
vec2glf depthRange { 0.0f, 1.0f };
} window;
struct {
GLuint timer = -1u;
} queries;
} state;
private:
@@ -403,8 +413,12 @@ private:
RenderPrimitive mDefaultVAO;
// this is chosen to minimize code size
#if defined(GL_ES_VERSION_2_0)
void initExtensionsGLES() noexcept;
#endif
#if defined(GL_VERSION_4_1)
void initExtensionsGL() noexcept;
#endif
template <typename T, typename F>
static inline void update_state(T& state, T const& expected, F functor, bool force = false) noexcept {
@@ -429,7 +443,9 @@ constexpr size_t OpenGLContext::getIndexForTextureTarget(GLuint target) noexcept
case GL_TEXTURE_2D: return 0;
case GL_TEXTURE_2D_ARRAY: return 1;
case GL_TEXTURE_CUBE_MAP: return 2;
#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
case GL_TEXTURE_2D_MULTISAMPLE: return 3;
#endif
case GL_TEXTURE_EXTERNAL_OES: return 4;
case GL_TEXTURE_3D: return 5;
case GL_TEXTURE_CUBE_MAP_ARRAY: return 6;
@@ -450,17 +466,15 @@ constexpr size_t OpenGLContext::getIndexForCap(GLenum cap) noexcept { //NOLINT
case GL_SAMPLE_ALPHA_TO_COVERAGE: index = 6; break;
case GL_SAMPLE_COVERAGE: index = 7; break;
case GL_POLYGON_OFFSET_FILL: index = 8; break;
case GL_PRIMITIVE_RESTART_FIXED_INDEX: index = 9; break;
case GL_RASTERIZER_DISCARD: index = 10; break;
#ifdef GL_ARB_seamless_cube_map
case GL_TEXTURE_CUBE_MAP_SEAMLESS: index = 11; break;
case GL_TEXTURE_CUBE_MAP_SEAMLESS: index = 9; break;
#endif
#if BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL
case GL_PROGRAM_POINT_SIZE: index = 12; break;
case GL_PROGRAM_POINT_SIZE: index = 10; break;
#endif
default: index = 13; break; // should never happen
default: break;
}
assert_invariant(index < 13 && index < state.enables.caps.size());
assert_invariant(index < state.enables.caps.size());
return index;
}
@@ -470,15 +484,14 @@ constexpr size_t OpenGLContext::getIndexForBufferTarget(GLenum target) noexcept
// The indexed buffers MUST be first in this list (those usable with bindBufferRange)
case GL_UNIFORM_BUFFER: index = 0; break;
case GL_TRANSFORM_FEEDBACK_BUFFER: index = 1; break;
#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
case GL_SHADER_STORAGE_BUFFER: index = 2; break;
#endif
case GL_ARRAY_BUFFER: index = 3; break;
case GL_COPY_READ_BUFFER: index = 4; break;
case GL_COPY_WRITE_BUFFER: index = 5; break;
case GL_ELEMENT_ARRAY_BUFFER: index = 6; break;
case GL_PIXEL_PACK_BUFFER: index = 7; break;
case GL_PIXEL_UNPACK_BUFFER: index = 8; break;
default: index = 9; break; // should never happen
case GL_ELEMENT_ARRAY_BUFFER: index = 4; break;
case GL_PIXEL_PACK_BUFFER: index = 5; break;
case GL_PIXEL_UNPACK_BUFFER: index = 6; break;
default: break;
}
assert_invariant(index < sizeof(state.buffers.genericBinding)/sizeof(state.buffers.genericBinding[0])); // NOLINT(misc-redundant-expression)
return index;
@@ -501,21 +514,21 @@ void OpenGLContext::bindSampler(GLuint unit, GLuint sampler) noexcept {
}
void OpenGLContext::setScissor(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept {
vec4gli scissor(left, bottom, width, height);
vec4gli const scissor(left, bottom, width, height);
update_state(state.window.scissor, scissor, [&]() {
glScissor(left, bottom, width, height);
});
}
void OpenGLContext::viewport(GLint left, GLint bottom, GLsizei width, GLsizei height) noexcept {
vec4gli viewport(left, bottom, width, height);
vec4gli const viewport(left, bottom, width, height);
update_state(state.window.viewport, viewport, [&]() {
glViewport(left, bottom, width, height);
});
}
void OpenGLContext::depthRange(GLclampf near, GLclampf far) noexcept {
vec2glf depthRange(near, far);
vec2glf const depthRange(near, far);
update_state(state.window.depthRange, depthRange, [&]() {
glDepthRangef(near, far);
});
@@ -526,7 +539,7 @@ void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
update_state(state.vao.p, vao, [&]() {
glBindVertexArray(vao->vao);
// update GL_ELEMENT_ARRAY_BUFFER, which is updated by glBindVertexArray
size_t targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
size_t const targetIndex = getIndexForBufferTarget(GL_ELEMENT_ARRAY_BUFFER);
state.buffers.genericBinding[targetIndex] = vao->elementArray;
if (UTILS_UNLIKELY(bugs.vao_doesnt_store_element_array_buffer_binding)) {
// This shouldn't be needed, but it looks like some drivers don't do the implicit
@@ -538,8 +551,10 @@ void OpenGLContext::bindVertexArray(RenderPrimitive const* p) noexcept {
void OpenGLContext::bindBufferRange(GLenum target, GLuint index, GLuint buffer,
GLintptr offset, GLsizeiptr size) noexcept {
size_t targetIndex = getIndexForBufferTarget(target);
assert_invariant(targetIndex <= 2); // validity check
size_t const targetIndex = getIndexForBufferTarget(target);
// validity check
assert_invariant(targetIndex < sizeof(state.buffers.targets) / sizeof(*state.buffers.targets));
// this ALSO sets the generic binding
if ( state.buffers.targets[targetIndex].buffers[index].name != buffer
@@ -616,7 +631,7 @@ void OpenGLContext::disableVertexAttribArray(GLuint index) noexcept {
}
void OpenGLContext::enable(GLenum cap) noexcept {
size_t index = getIndexForCap(cap);
size_t const index = getIndexForCap(cap);
if (UTILS_UNLIKELY(!state.enables.caps[index])) {
state.enables.caps.set(index);
glEnable(cap);
@@ -624,7 +639,7 @@ void OpenGLContext::enable(GLenum cap) noexcept {
}
void OpenGLContext::disable(GLenum cap) noexcept {
size_t index = getIndexForCap(cap);
size_t const index = getIndexForCap(cap);
if (UTILS_UNLIKELY(state.enables.caps[index])) {
state.enables.caps.unset(index);
glDisable(cap);
@@ -723,41 +738,6 @@ void OpenGLContext::polygonOffset(GLfloat factor, GLfloat units) noexcept {
});
}
void OpenGLContext::beginQuery(GLenum target, GLuint query) noexcept {
switch (target) {
case GL_TIME_ELAPSED:
if (state.queries.timer != -1u) {
// this is an error
break;
}
state.queries.timer = query;
break;
default:
return;
}
glBeginQuery(target, query);
}
void OpenGLContext::endQuery(GLenum target) noexcept {
switch (target) {
case GL_TIME_ELAPSED:
state.queries.timer = -1u;
break;
default:
return;
}
glEndQuery(target);
}
GLuint OpenGLContext::getQuery(GLenum target) const noexcept {
switch (target) {
case GL_TIME_ELAPSED:
return state.queries.timer;
default:
return 0;
}
}
} // namespace filament
#endif //TNT_FILAMENT_BACKEND_OPENGLCONTEXT_H

View File

@@ -69,7 +69,9 @@ using namespace utils;
namespace filament::backend {
Driver* OpenGLDriverFactory::create(
OpenGLPlatform* const platform, void* const sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept {
OpenGLPlatform* const platform,
void* const sharedGLContext,
const Platform::DriverConfig& driverConfig) noexcept {
return OpenGLDriver::create(platform, sharedGLContext, driverConfig);
}
@@ -177,6 +179,10 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
slog.i << "OS version: " << mPlatform.getOSVersion() << io::endl;
#endif
// Timer queries are core in GL 3.3, otherwise we need EXT_disjoint_timer_query
// iOS headers don't define GL_EXT_disjoint_timer_query, so make absolutely sure
// we won't use it.
#if defined(GL_VERSION_3_3) || defined(GL_EXT_disjoint_timer_query)
if (mContext.ext.EXT_disjoint_timer_query ||
BACKEND_OPENGL_VERSION == BACKEND_OPENGL_VERSION_GL) {
// timer queries are available
@@ -187,7 +193,9 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
mTimerQueryImpl = new TimerQueryNative(mContext);
}
mFrameTimeSupported = true;
} else if (mPlatform.canCreateFence()) {
} else
#endif
if (mPlatform.canCreateFence()) {
// no timer queries, but we can use fences
mTimerQueryImpl = new OpenGLTimerQueryFence(mPlatform);
mFrameTimeSupported = true;
@@ -537,6 +545,7 @@ void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t,
GLsizei(width), GLsizei(height), GLsizei(depth) * 6);
break;
}
#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
case GL_TEXTURE_2D_MULTISAMPLE:
if constexpr (TEXTURE_2D_MULTISAMPLE_SUPPORTED) {
// NOTE: if there is a mix of texture and renderbuffers, "fixed_sample_locations" must be true
@@ -554,6 +563,7 @@ void OpenGLDriver::textureStorage(OpenGLDriver::GLTexture* t,
PANIC_LOG("GL_TEXTURE_2D_MULTISAMPLE is not supported");
}
break;
#endif
default: // cannot happen
break;
}
@@ -621,6 +631,7 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
if (t->samples > 1) {
// Note: we can't be here in practice because filament's user API doesn't
// allow the creation of multi-sampled textures.
#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
@@ -629,6 +640,7 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
} else {
// Turn off multi-sampling for that texture. It's just not supported.
}
#endif
}
textureStorage(t, w, h, depth);
}
@@ -721,6 +733,7 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
if (t->samples > 1) {
// Note: we can't be here in practice because filament's user API doesn't
// allow the creation of multi-sampled textures.
#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
@@ -728,6 +741,7 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
} else {
// Turn off multi-sampling for that texture. It's just not supported.
}
#endif
}
CHECK_GL_ERROR(utils::slog.e)
@@ -800,6 +814,7 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
GLTexture* t = handle_cast<GLTexture*>(binfo.handle);
assert_invariant(t);
assert_invariant(t->target != SamplerType::SAMPLER_EXTERNAL);
assert_invariant(rt->width <= valueForLevel(binfo.level, t->width) &&
rt->height <= valueForLevel(binfo.level, t->height));
@@ -907,7 +922,9 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
case GL_TEXTURE_2D:
#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
case GL_TEXTURE_2D_MULTISAMPLE:
#endif
if (any(t->usage & TextureUsage::SAMPLEABLE)) {
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment,
target, t->gl.id, binfo.level);
@@ -1135,21 +1152,26 @@ void OpenGLDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
rt->gl.samples = samples;
rt->targets = targets;
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmin = {std::numeric_limits<uint32_t>::max()};
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmax = {0};
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmin = { std::numeric_limits<uint32_t>::max() };
UTILS_UNUSED_IN_RELEASE math::vec2<uint32_t> tmax = { 0 };
auto checkDimensions = [&tmin, &tmax](GLTexture* t, uint8_t level) {
const auto twidth = std::max(1u, t->width >> level);
const auto theight = std::max(1u, t->height >> level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
};
if (any(targets & TargetBufferFlags::COLOR_ALL)) {
GLenum bufs[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = { GL_NONE };
const size_t maxDrawBuffers = getMaxDrawBuffers();
for (size_t i = 0; i < maxDrawBuffers; i++) {
if (any(targets & getTargetBufferFlagsAt(i))) {
auto t = rt->gl.color[i] = handle_cast<GLTexture*>(color[i].handle);
const auto twidth = std::max(1u, t->width >> color[i].level);
const auto theight = std::max(1u, t->height >> color[i].level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
assert_invariant(color[i].handle);
rt->gl.color[i] = handle_cast<GLTexture*>(color[i].handle);
framebufferTexture(color[i], rt, GL_COLOR_ATTACHMENT0 + i);
bufs[i] = GL_COLOR_ATTACHMENT0 + i;
checkDimensions(rt->gl.color[i], color[i].level);
}
}
glDrawBuffers((GLsizei)maxDrawBuffers, bufs);
@@ -1159,37 +1181,28 @@ void OpenGLDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
// handle special cases first (where depth/stencil are packed)
bool specialCased = false;
if ((targets & TargetBufferFlags::DEPTH_AND_STENCIL) == TargetBufferFlags::DEPTH_AND_STENCIL) {
assert_invariant(!stencil.handle || stencil.handle == depth.handle);
auto t = rt->gl.depth = handle_cast<GLTexture*>(depth.handle);
const auto twidth = std::max(1u, t->width >> depth.level);
const auto theight = std::max(1u, t->height >> depth.level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
if (any(rt->gl.depth->usage & TextureUsage::SAMPLEABLE) ||
(!depth.handle && !stencil.handle)) {
// special case: depth & stencil requested, and both provided as the same texture
// special case: depth & stencil requested, but both not provided
specialCased = true;
assert_invariant(depth.handle);
// either we supplied only the depth handle or both depth/stencil are identical and not null
if (depth.handle && (stencil.handle == depth.handle || !stencil.handle)) {
rt->gl.depth = handle_cast<GLTexture*>(depth.handle);
framebufferTexture(depth, rt, GL_DEPTH_STENCIL_ATTACHMENT);
specialCased = true;
checkDimensions(rt->gl.depth, depth.level);
}
}
if (!specialCased) {
if (any(targets & TargetBufferFlags::DEPTH)) {
auto t = rt->gl.depth = handle_cast<GLTexture*>(depth.handle);
const auto twidth = std::max(1u, t->width >> depth.level);
const auto theight = std::max(1u, t->height >> depth.level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
assert_invariant(depth.handle);
rt->gl.depth = handle_cast<GLTexture*>(depth.handle);
framebufferTexture(depth, rt, GL_DEPTH_ATTACHMENT);
checkDimensions(rt->gl.depth, depth.level);
}
if (any(targets & TargetBufferFlags::STENCIL)) {
auto t = rt->gl.stencil = handle_cast<GLTexture*>(stencil.handle);
const auto twidth = std::max(1u, t->width >> stencil.level);
const auto theight = std::max(1u, t->height >> stencil.level);
tmin = { std::min(tmin.x, twidth), std::min(tmin.y, theight) };
tmax = { std::max(tmax.x, twidth), std::max(tmax.y, theight) };
assert_invariant(stencil.handle);
rt->gl.stencil = handle_cast<GLTexture*>(stencil.handle);
framebufferTexture(stencil, rt, GL_STENCIL_ATTACHMENT);
checkDimensions(rt->gl.stencil, stencil.level);
}
}
@@ -1211,19 +1224,19 @@ void OpenGLDriver::createSyncR(Handle<HwSync> fh, int) {
DEBUG_MARKER()
GLSync* f = handle_cast<GLSync *>(fh);
f->gl.sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
CHECK_GL_ERROR(utils::slog.e)
f->handle = mContext.createFenceSync(mPlatform);
// check the status of the sync once a frame, since we must do this from our thread
std::weak_ptr<GLSync::State> const weak = f->result;
runEveryNowAndThen([sync = f->gl.sync, weak]() -> bool {
runEveryNowAndThen(
[&platform = mPlatform, context = mContext, handle = f->handle, weak]() -> bool {
auto result = weak.lock();
if (result) {
GLenum const status = glClientWaitSync(sync, 0, 0u);
auto const status = context.clientWaitSync(platform, handle);
result->status.store(status, std::memory_order_relaxed);
return (status != GL_TIMEOUT_EXPIRED);
return (status != OpenGLContext::FenceSync::Status::TIMEOUT_EXPIRED);
}
return true; // we're done
return true;
});
}
@@ -1332,9 +1345,6 @@ void OpenGLDriver::destroyTexture(Handle<HwTexture> th) {
assert_invariant(t->gl.target == GL_RENDERBUFFER);
glDeleteRenderbuffers(1, &t->gl.id);
}
if (t->gl.fence) {
glDeleteSync(t->gl.fence);
}
if (t->gl.sidecarRenderBufferMS) {
glDeleteRenderbuffers(1, &t->gl.sidecarRenderBufferMS);
}
@@ -1413,10 +1423,9 @@ void OpenGLDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
void OpenGLDriver::destroySync(Handle<HwSync> sh) {
DEBUG_MARKER()
if (sh) {
GLSync* s = handle_cast<GLSync*>(sh);
glDeleteSync(s->gl.sync);
mContext.destroyFenceSync(mPlatform, s->handle);
destruct(sh, s);
}
}
@@ -1970,7 +1979,9 @@ void OpenGLDriver::generateMipmaps(Handle<HwTexture> th) {
auto& gl = mContext;
GLTexture* t = handle_cast<GLTexture *>(th);
#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
assert_invariant(t->gl.target != GL_TEXTURE_2D_MULTISAMPLE);
#endif
// Note: glGenerateMimap can also fail if the internal format is not both
// color-renderable and filterable (i.e.: doesn't work for depth)
bindTexture(OpenGLContext::DUMMY_TEXTURE_BINDING, t);
@@ -2011,8 +2022,13 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
gl.pixelStore(GL_UNPACK_ROW_LENGTH, GLint(p.stride));
gl.pixelStore(GL_UNPACK_ALIGNMENT, GLint(p.alignment));
gl.pixelStore(GL_UNPACK_SKIP_PIXELS, GLint(p.left));
gl.pixelStore(GL_UNPACK_SKIP_ROWS, GLint(p.top));
// This is equivalent to using GL_UNPACK_SKIP_PIXELS and GL_UNPACK_SKIP_ROWS
using PBD = PixelBufferDescriptor;
size_t const stride = p.stride ? p.stride : width;
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const bpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
void const* const buffer = static_cast<char const*>(p.buffer) + p.left * bpp + bpr * p.top;
switch (t->target) {
case SamplerType::SAMPLER_EXTERNAL:
@@ -2026,7 +2042,7 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
assert_invariant(t->gl.target == GL_TEXTURE_2D);
glTexSubImage2D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset),
GLsizei(width), GLsizei(height), glFormat, glType, p.buffer);
GLsizei(width), GLsizei(height), glFormat, glType, buffer);
break;
case SamplerType::SAMPLER_3D:
assert_invariant(zoffset + depth <= std::max(1u, t->depth >> level));
@@ -2035,7 +2051,7 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
assert_invariant(t->gl.target == GL_TEXTURE_3D);
glTexSubImage3D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset), GLint(zoffset),
GLsizei(width), GLsizei(height), GLsizei(depth), glFormat, glType, p.buffer);
GLsizei(width), GLsizei(height), GLsizei(depth), glFormat, glType, buffer);
break;
case SamplerType::SAMPLER_2D_ARRAY:
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
@@ -2047,7 +2063,7 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
t->gl.target == GL_TEXTURE_CUBE_MAP_ARRAY);
glTexSubImage3D(t->gl.target, GLint(level),
GLint(xoffset), GLint(yoffset), GLint(zoffset),
GLsizei(width), GLsizei(height), GLsizei(depth), glFormat, glType, p.buffer);
GLsizei(width), GLsizei(height), GLsizei(depth), glFormat, glType, buffer);
break;
case SamplerType::SAMPLER_CUBEMAP: {
assert_invariant(t->gl.target == GL_TEXTURE_CUBE_MAP);
@@ -2063,7 +2079,7 @@ void OpenGLDriver::setTextureData(GLTexture* t, uint32_t level,
GLenum const target = getCubemapTarget(zoffset + face);
glTexSubImage2D(target, GLint(level), GLint(xoffset), GLint(yoffset),
GLsizei(width), GLsizei(height), glFormat, glType,
static_cast<uint8_t const*>(p.buffer) + faceSize * face);
static_cast<uint8_t const*>(buffer) + faceSize * face);
}
break;
}
@@ -2337,13 +2353,14 @@ SyncStatus OpenGLDriver::getSyncStatus(Handle<HwSync> sh) {
return SyncStatus::NOT_SIGNALED;
}
auto status = s->result->status.load(std::memory_order_relaxed);
using Status = OpenGLContext::FenceSync::Status;
switch (status) {
case GL_CONDITION_SATISFIED:
case GL_ALREADY_SIGNALED:
case Status::CONDITION_SATISFIED:
case Status::ALREADY_SIGNALED:
return SyncStatus::SIGNALED;
case GL_TIMEOUT_EXPIRED:
case Status::TIMEOUT_EXPIRED:
return SyncStatus::NOT_SIGNALED;
case GL_WAIT_FAILED:
case Status::FAILURE:
default:
return SyncStatus::ERROR;
}
@@ -2714,10 +2731,7 @@ void OpenGLDriver::readPixels(Handle<HwRenderTarget> src,
GLenum const glFormat = getFormat(p.format);
GLenum const glType = getType(p.type);
gl.pixelStore(GL_PACK_ROW_LENGTH, (GLint)p.stride);
gl.pixelStore(GL_PACK_ALIGNMENT, (GLint)p.alignment);
gl.pixelStore(GL_PACK_SKIP_PIXELS, (GLint)p.left);
gl.pixelStore(GL_PACK_SKIP_ROWS, (GLint)p.top);
gl.pixelStore(GL_PACK_ALIGNMENT, (GLint)p.alignment);
/*
* glReadPixel() operation...
@@ -2745,44 +2759,54 @@ void OpenGLDriver::readPixels(Handle<HwRenderTarget> src,
*/
GLRenderTarget const* s = handle_cast<GLRenderTarget const*>(src);
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo);
// glReadPixel doesn't resolve automatically, but it does with the auto-resolve extension,
// which we're always emulating. So if we have a resolved fbo (fbo_read), use that instead.
gl.bindFramebuffer(GL_READ_FRAMEBUFFER, s->gl.fbo_read ? s->gl.fbo_read : s->gl.fbo);
using PBD = PixelBufferDescriptor;
// The PBO only needs to accommodate the area we're reading, with alignment.
auto const pboSize = (GLsizeiptr)PBD::computeDataSize(
p.format, p.type, width, height, p.alignment);
GLuint pbo;
glGenBuffers(1, &pbo);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, (GLsizeiptr)p.size, nullptr, GL_STATIC_DRAW);
glBufferData(GL_PIXEL_PACK_BUFFER, pboSize, nullptr, GL_STATIC_DRAW);
glReadPixels(GLint(x), GLint(y), GLint(width), GLint(height), glFormat, glType, nullptr);
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, 0);
CHECK_GL_ERROR(utils::slog.e)
// we're forced to make a copy on the heap because otherwise it deletes std::function<> copy
// constructor.
auto* pUserBuffer = new PixelBufferDescriptor(std::move(p));
whenGpuCommandsComplete([this, width, height, pbo, pUserBuffer]() mutable {
auto* const pUserBuffer = new PixelBufferDescriptor(std::move(p));
whenGpuCommandsComplete([this, width, height, pbo, pboSize, pUserBuffer]() mutable {
PixelBufferDescriptor& p = *pUserBuffer;
auto& gl = mContext;
gl.bindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
void* vaddr = nullptr;
#if defined(__EMSCRIPTEN__)
std::unique_ptr<uint8_t> clientBuffer = std::make_unique<uint8_t>(p.size);
glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, p.size, clientBuffer.get());
std::unique_ptr<uint8_t> clientBuffer = std::make_unique<uint8_t>(pboSize);
glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, pboSize, clientBuffer.get());
vaddr = clientBuffer.get();
#else
vaddr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, (GLsizeiptr)p.size, GL_MAP_READ_BIT);
vaddr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, pboSize, GL_MAP_READ_BIT);
#endif
if (vaddr) {
// now we need to flip the buffer vertically to match our API
size_t const stride = p.stride ? p.stride : width;
size_t const bpp = PixelBufferDescriptor::computeDataSize(
p.format, p.type, 1, 1, 1);
size_t const bpr = PixelBufferDescriptor::computeDataSize(
p.format, p.type, stride, 1, p.alignment);
char const* head = (char const*)vaddr + p.left * bpp + bpr * p.top;
char* tail = (char*)p.buffer + p.left * bpp + bpr * (p.top + height - 1);
size_t const bpp = PBD::computeDataSize(p.format, p.type, 1, 1, 1);
size_t const dstBpr = PBD::computeDataSize(p.format, p.type, stride, 1, p.alignment);
char* pDst = (char*)p.buffer + p.left * bpp + dstBpr * (p.top + height - 1);
size_t const srcBpr = PBD::computeDataSize(p.format, p.type, width, 1, p.alignment);
char const* pSrc = (char const*)vaddr;
for (size_t i = 0; i < height; ++i) {
memcpy(tail, head, bpp * width);
head += bpr;
tail -= bpr;
memcpy(pDst, pSrc, bpp * width);
pSrc += srcBpr;
pDst -= dstBpr;
}
#if !defined(__EMSCRIPTEN__)
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
@@ -2849,7 +2873,7 @@ void OpenGLDriver::readBufferSubData(backend::BufferObjectHandle boh,
}
void OpenGLDriver::whenGpuCommandsComplete(std::function<void()> fn) noexcept {
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
OpenGLContext::FenceSync sync = mContext.createFenceSync(mPlatform);
mGpuCommandCompleteOps.emplace_back(sync, std::move(fn));
CHECK_GL_ERROR(utils::slog.e)
}
@@ -2862,15 +2886,16 @@ void OpenGLDriver::executeGpuCommandsCompleteOps() noexcept {
auto& v = mGpuCommandCompleteOps;
auto it = v.begin();
while (it != v.end()) {
GLenum const status = glClientWaitSync(it->first, 0, 0);
if (status == GL_ALREADY_SIGNALED || status == GL_CONDITION_SATISFIED) {
using Status = OpenGLContext::FenceSync::Status;
auto const status = mContext.clientWaitSync(mPlatform, it->first);
if (status == Status::ALREADY_SIGNALED || status == Status::CONDITION_SATISFIED) {
it->second();
glDeleteSync(it->first);
mContext.destroyFenceSync(mPlatform, it->first);
it = v.erase(it);
} else if (UTILS_UNLIKELY(status == GL_WAIT_FAILED)) {
} else if (UTILS_UNLIKELY(status == Status::FAILURE)) {
// This should never happen, but is very problematic if it does, as we might leak
// some data depending on what the callback does. However, we clean up our own state.
glDeleteSync(it->first);
mContext.destroyFenceSync(mPlatform, it->first);
it = v.erase(it);
} else {
++it;

View File

@@ -122,7 +122,6 @@ public:
GLenum target = 0;
GLenum internalFormat = 0;
GLuint sidecarRenderBufferMS = 0; // multi-sample sidecar renderbuffer
mutable GLsync fence = {};
// texture parameters go here too
GLfloat anisotropy = 1.0;
@@ -186,11 +185,10 @@ public:
struct GLSync : public HwSync {
using HwSync::HwSync;
struct State {
std::atomic<GLenum> status{ GL_TIMEOUT_EXPIRED };
std::atomic<OpenGLContext::FenceSync::Status> status{
OpenGLContext::FenceSync::Status::TIMEOUT_EXPIRED };
};
struct {
GLsync sync;
} gl;
OpenGLContext::FenceSync handle{};
std::shared_ptr<State> result{ std::make_shared<GLSync::State>() };
};
@@ -364,7 +362,7 @@ private:
// tasks executed on the main thread after the fence signaled
void whenGpuCommandsComplete(std::function<void()> fn) noexcept;
void executeGpuCommandsCompleteOps() noexcept;
std::vector<std::pair<GLsync, std::function<void()>>> mGpuCommandCompleteOps;
std::vector<std::pair<OpenGLContext::FenceSync, std::function<void()>>> mGpuCommandCompleteOps;
// tasks regularly executed on the main thread at until they return true
void runEveryNowAndThen(std::function<bool()> fn) noexcept;

View File

@@ -121,11 +121,21 @@ void OpenGLProgram::compileShaders(OpenGLContext& context,
UTILS_NOUNROLL
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
const ShaderStage stage = static_cast<ShaderStage>(i);
GLenum glShaderType;
GLenum glShaderType{};
switch (stage) {
case ShaderStage::VERTEX: glShaderType = GL_VERTEX_SHADER; break;
case ShaderStage::FRAGMENT: glShaderType = GL_FRAGMENT_SHADER; break;
case ShaderStage::COMPUTE: glShaderType = GL_COMPUTE_SHADER; break;
case ShaderStage::VERTEX:
glShaderType = GL_VERTEX_SHADER;
break;
case ShaderStage::FRAGMENT:
glShaderType = GL_FRAGMENT_SHADER;
break;
case ShaderStage::COMPUTE:
#if defined(GL_VERSION_4_1) || defined(GL_ES_VERSION_3_1)
glShaderType = GL_COMPUTE_SHADER;
#else
continue;
#endif
break;
}
if (UTILS_LIKELY(!shadersSource[i].empty())) {
@@ -406,11 +416,6 @@ void OpenGLProgram::updateSamplers(OpenGLDriver* gld) const noexcept {
const GLTexture* const t = sb->textureUnitEntries[j].texture;
GLuint const s = sb->textureUnitEntries[j].sampler;
if (t) { // program may not use all samplers of sampler group
if (UTILS_UNLIKELY(t->gl.fence)) {
glWaitSync(t->gl.fence, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(t->gl.fence);
t->gl.fence = nullptr;
}
gld->bindTexture(tmu, t);
gld->bindSampler(tmu, s);
}

View File

@@ -34,8 +34,9 @@ OpenGLTimerQueryInterface::~OpenGLTimerQueryInterface() = default;
// ------------------------------------------------------------------------------------------------
TimerQueryNative::TimerQueryNative(OpenGLContext& context)
: gl(context) {
#if defined(GL_VERSION_3_3) || defined(GL_EXT_disjoint_timer_query)
TimerQueryNative::TimerQueryNative(OpenGLContext&) {
}
TimerQueryNative::~TimerQueryNative() = default;
@@ -44,12 +45,12 @@ void TimerQueryNative::flush() {
}
void TimerQueryNative::beginTimeElapsedQuery(GLTimerQuery* query) {
gl.beginQuery(GL_TIME_ELAPSED, query->gl.query);
glBeginQuery(GL_TIME_ELAPSED, query->gl.query);
CHECK_GL_ERROR(utils::slog.e)
}
void TimerQueryNative::endTimeElapsedQuery(GLTimerQuery*) {
gl.endQuery(GL_TIME_ELAPSED);
glEndQuery(GL_TIME_ELAPSED);
CHECK_GL_ERROR(utils::slog.e)
}
@@ -62,14 +63,14 @@ bool TimerQueryNative::queryResultAvailable(GLTimerQuery* query) {
uint64_t TimerQueryNative::queryResult(GLTimerQuery* query) {
GLuint64 elapsedTime = 0;
// IOS doesn't have glGetQueryObjectui64v, we'll never end-up here on ios anyways
#ifndef IOS
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
glGetQueryObjectui64v(query->gl.query, GL_QUERY_RESULT, &elapsedTime);
#endif
CHECK_GL_ERROR(utils::slog.e)
return elapsedTime;
}
#endif
// ------------------------------------------------------------------------------------------------
OpenGLTimerQueryFence::OpenGLTimerQueryFence(OpenGLPlatform& platform)
@@ -85,7 +86,7 @@ OpenGLTimerQueryFence::OpenGLTimerQueryFence(OpenGLPlatform& platform)
});
exitRequested = mExitRequested;
if (!queue.empty()) {
Job job(queue.front());
Job const job(queue.front());
queue.erase(queue.begin());
lock.unlock();
job();
@@ -105,7 +106,7 @@ OpenGLTimerQueryFence::~OpenGLTimerQueryFence() {
}
void OpenGLTimerQueryFence::enqueue(OpenGLTimerQueryFence::Job&& job) {
std::unique_lock<utils::Mutex> lock(mLock);
std::unique_lock<utils::Mutex> const lock(mLock);
mQueue.push_back(std::forward<Job>(job));
mCondition.notify_one();
}
@@ -114,9 +115,9 @@ void OpenGLTimerQueryFence::flush() {
// Use calls to flush() as a proxy for when the GPU work started.
GLTimerQuery* query = mActiveQuery;
if (query) {
uint64_t elapsed = query->gl.emulation->elapsed.load(std::memory_order_relaxed);
uint64_t const elapsed = query->gl.emulation->elapsed.load(std::memory_order_relaxed);
if (!elapsed) {
uint64_t now = clock::now().time_since_epoch().count();
uint64_t const now = clock::now().time_since_epoch().count();
query->gl.emulation->elapsed.store(now, std::memory_order_relaxed);
//SYSTRACE_CONTEXT();
//SYSTRACE_ASYNC_BEGIN("gpu", query->gl.query);
@@ -139,7 +140,7 @@ void OpenGLTimerQueryFence::beginTimeElapsedQuery(GLTimerQuery* query) {
void OpenGLTimerQueryFence::endTimeElapsedQuery(GLTimerQuery* query) {
assert_invariant(mActiveQuery);
Platform::Fence* fence = mPlatform.createFence();
std::weak_ptr<GLTimerQuery::State> weak = query->gl.emulation;
std::weak_ptr<GLTimerQuery::State> const weak = query->gl.emulation;
mActiveQuery = nullptr;
//uint32_t cookie = cookie = query->gl.query;
push([&platform = mPlatform, fence, weak]() {

View File

@@ -48,6 +48,8 @@ public:
virtual uint64_t queryResult(GLTimerQuery* query) = 0;
};
#if defined(GL_VERSION_3_3) || defined(GL_EXT_disjoint_timer_query)
class TimerQueryNative : public OpenGLTimerQueryInterface {
public:
explicit TimerQueryNative(OpenGLContext& context);
@@ -58,9 +60,10 @@ private:
void endTimeElapsedQuery(GLTimerQuery* query) override;
bool queryResultAvailable(GLTimerQuery* query) override;
uint64_t queryResult(GLTimerQuery* query) override;
OpenGLContext& gl;
};
#endif
class OpenGLTimerQueryFence : public OpenGLTimerQueryInterface {
public:
explicit OpenGLTimerQueryFence(OpenGLPlatform& platform);

View File

@@ -16,11 +16,17 @@
#include "gl_headers.h"
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__)
#if defined(FILAMENT_IMPORT_ENTRY_POINTS)
#include <EGL/egl.h>
#include <mutex>
// for non EGL platforms, we'd need to implement this differently. Currently, it's not a problem.
template<typename T>
static void getProcAddress(T& pfn, const char* name) noexcept {
pfn = (T)eglGetProcAddress(name);
}
namespace glext {
#ifdef GL_QCOM_tiled_rendering
PFNGLSTARTTILINGQCOMPROC glStartTilingQCOM;
@@ -58,71 +64,39 @@ PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
static std::once_flag sGlExtInitialized;
void importGLESExtensionsEntryPoints() {
std::call_once(sGlExtInitialized, []() {
std::call_once(sGlExtInitialized, +[]() {
#ifdef GL_QCOM_tiled_rendering
glStartTilingQCOM =
(PFNGLSTARTTILINGQCOMPROC)eglGetProcAddress(
"glStartTilingQCOM");
glEndTilingQCOM =
(PFNGLENDTILINGQCOMPROC)eglGetProcAddress(
"glEndTilingQCOM");
getProcAddress(glStartTilingQCOM, "glStartTilingQCOM");
getProcAddress(glEndTilingQCOM, "glEndTilingQCOM");
#endif
#ifdef GL_OES_EGL_image
glEGLImageTargetTexture2DOES =
(PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress(
"glEGLImageTargetTexture2DOES");
getProcAddress(glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES");
#endif
#if GL_EXT_debug_marker
glInsertEventMarkerEXT =
(PFNGLINSERTEVENTMARKEREXTPROC)eglGetProcAddress(
"glInsertEventMarkerEXT");
glPushGroupMarkerEXT =
(PFNGLPUSHGROUPMARKEREXTPROC)eglGetProcAddress(
"glPushGroupMarkerEXT");
glPopGroupMarkerEXT =
(PFNGLPOPGROUPMARKEREXTPROC)eglGetProcAddress(
"glPopGroupMarkerEXT");
getProcAddress(glInsertEventMarkerEXT, "glInsertEventMarkerEXT");
getProcAddress(glPushGroupMarkerEXT, "glPushGroupMarkerEXT");
getProcAddress(glPopGroupMarkerEXT, "glPopGroupMarkerEXT");
#endif
#if GL_EXT_multisampled_render_to_texture
glFramebufferTexture2DMultisampleEXT =
(PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)eglGetProcAddress(
"glFramebufferTexture2DMultisampleEXT");
glRenderbufferStorageMultisampleEXT =
(PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)eglGetProcAddress(
"glRenderbufferStorageMultisampleEXT");
getProcAddress(glFramebufferTexture2DMultisampleEXT, "glFramebufferTexture2DMultisampleEXT");
getProcAddress(glRenderbufferStorageMultisampleEXT, "glRenderbufferStorageMultisampleEXT");
#endif
#ifdef GL_KHR_debug
glDebugMessageCallbackKHR =
(PFNGLDEBUGMESSAGECALLBACKKHRPROC)eglGetProcAddress(
"glDebugMessageCallbackKHR");
glGetDebugMessageLogKHR =
(PFNGLGETDEBUGMESSAGELOGKHRPROC)eglGetProcAddress(
"glGetDebugMessageLogKHR");
getProcAddress(glDebugMessageCallbackKHR, "glDebugMessageCallbackKHR");
getProcAddress(glGetDebugMessageLogKHR, "glGetDebugMessageLogKHR");
#endif
#ifdef GL_EXT_disjoint_timer_query
glGetQueryObjectui64v =
(PFNGLGETQUERYOBJECTUI64VEXTPROC)eglGetProcAddress(
"glGetQueryObjectui64vEXT");
getProcAddress(glGetQueryObjectui64v, "glGetQueryObjectui64vEXT");
#endif
#ifdef GL_EXT_clip_control
getProcAddress(glClipControl, "glClipControlEXT");
#endif
#if defined(__ANDROID__)
getProcAddress(glDispatchCompute, "glDispatchCompute");
#endif
});
#ifdef GL_EXT_clip_control
glClipControl =
(PFNGLCLIPCONTROLEXTPROC)eglGetProcAddress(
"glClipControlEXT");
#endif
#if defined(__ANDROID__)
glDispatchCompute =
(PFNGLDISPATCHCOMPUTEPROC)eglGetProcAddress(
"glDispatchCompute");
#endif
}
} // namespace glext
#endif
#endif // defined(FILAMENT_IMPORT_ENTRY_POINTS)

View File

@@ -26,45 +26,6 @@
#endif
#include <GLES2/gl2ext.h>
/* The Android NDK doesn't expose extensions, fake it with eglGetProcAddress */
namespace glext {
// importGLESExtensionsEntryPoints is thread-safe and can be called multiple times.
// it is currently called from PlatformEGL.
void importGLESExtensionsEntryPoints();
#ifdef GL_QCOM_tiled_rendering
extern PFNGLSTARTTILINGQCOMPROC glStartTilingQCOM;
extern PFNGLENDTILINGQCOMPROC glEndTilingQCOM;
#endif
#ifdef GL_OES_EGL_image
extern PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
#endif
#ifdef GL_EXT_debug_marker
extern PFNGLINSERTEVENTMARKEREXTPROC glInsertEventMarkerEXT;
extern PFNGLPUSHGROUPMARKEREXTPROC glPushGroupMarkerEXT;
extern PFNGLPOPGROUPMARKEREXTPROC glPopGroupMarkerEXT;
#endif
#ifdef GL_EXT_multisampled_render_to_texture
extern PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT;
extern PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glFramebufferTexture2DMultisampleEXT;
#endif
#ifdef GL_KHR_debug
extern PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR;
extern PFNGLGETDEBUGMESSAGELOGKHRPROC glGetDebugMessageLogKHR;
#endif
#ifdef GL_EXT_disjoint_timer_query
extern PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v;
#endif
#ifdef GL_EXT_clip_control
extern PFNGLCLIPCONTROLEXTPROC glClipControl;
#endif
#if defined(__ANDROID__)
extern PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
#endif
}
using namespace glext;
#elif defined(IOS)
#define GLES_SILENCE_DEPRECATION
@@ -85,111 +46,110 @@
#endif
#if (!defined(GL_ES_VERSION_2_0) && !defined(GL_VERSION_4_1))
#error "Minimum header version must be OpenGL ES 2.0 or OpenGL 4.1"
#endif
/*
* Since we need ES3.1 headers and iOS only has ES3.0, we also define the constants we
* need to avoid many #ifdef in the actual code.
* GLES extensions
*/
#if defined(GL_ES_VERSION_2_0)
#if defined(GL_ES_VERSION_2_0) // this basically means all versions of GLES
#ifdef GL_EXT_disjoint_timer_query
# ifndef GL_TIME_ELAPSED
# define GL_TIME_ELAPSED GL_TIME_ELAPSED_EXT
# endif
#if defined(IOS)
// iOS headers only provide prototypes, nothing to do.
#else
#define FILAMENT_IMPORT_ENTRY_POINTS
/* The Android NDK doesn't expose extensions, fake it with eglGetProcAddress */
namespace glext {
// importGLESExtensionsEntryPoints is thread-safe and can be called multiple times.
// it is currently called from PlatformEGL.
void importGLESExtensionsEntryPoints();
#ifdef GL_QCOM_tiled_rendering
extern PFNGLSTARTTILINGQCOMPROC glStartTilingQCOM;
extern PFNGLENDTILINGQCOMPROC glEndTilingQCOM;
#endif
#ifdef GL_OES_EGL_image
extern PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES;
#endif
#ifdef GL_EXT_debug_marker
extern PFNGLINSERTEVENTMARKEREXTPROC glInsertEventMarkerEXT;
extern PFNGLPUSHGROUPMARKEREXTPROC glPushGroupMarkerEXT;
extern PFNGLPOPGROUPMARKEREXTPROC glPopGroupMarkerEXT;
#endif
#ifdef GL_EXT_multisampled_render_to_texture
extern PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glRenderbufferStorageMultisampleEXT;
extern PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC glFramebufferTexture2DMultisampleEXT;
#endif
#ifdef GL_KHR_debug
extern PFNGLDEBUGMESSAGECALLBACKKHRPROC glDebugMessageCallbackKHR;
extern PFNGLGETDEBUGMESSAGELOGKHRPROC glGetDebugMessageLogKHR;
#endif
#ifdef GL_EXT_clip_control
# ifndef GL_LOWER_LEFT
# define GL_LOWER_LEFT GL_LOWER_LEFT_EXT
# endif
# ifndef GL_ZERO_TO_ONE
# define GL_ZERO_TO_ONE GL_ZERO_TO_ONE_EXT
# endif
extern PFNGLCLIPCONTROLEXTPROC glClipControl;
#endif
#ifdef GL_EXT_disjoint_timer_query
extern PFNGLGETQUERYOBJECTUI64VEXTPROC glGetQueryObjectui64v;
#endif
#if defined(__ANDROID__)
extern PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
#endif
} // namespace glext
using namespace glext;
#ifndef GL_TEXTURE_CUBE_MAP_ARRAY
# define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009
#endif
// Prevent lots of #ifdef's between desktop and mobile
#ifdef GL_EXT_disjoint_timer_query
# define GL_TIME_ELAPSED GL_TIME_ELAPSED_EXT
#endif
#ifdef GL_EXT_clip_control
# define GL_LOWER_LEFT GL_LOWER_LEFT_EXT
# define GL_ZERO_TO_ONE GL_ZERO_TO_ONE_EXT
#endif
// we need GL_TEXTURE_CUBE_MAP_ARRAY defined, but we won't use it if the extension/feature
// is not available.
#if defined(GL_EXT_texture_cube_map_array)
# define GL_TEXTURE_CUBE_MAP_ARRAY GL_TEXTURE_CUBE_MAP_ARRAY_EXT
#else
# define GL_TEXTURE_CUBE_MAP_ARRAY 0x9009
#endif
#if defined(GL_KHR_debug)
# ifndef GL_DEBUG_OUTPUT
# define GL_DEBUG_OUTPUT GL_DEBUG_OUTPUT_KHR
# endif
# ifndef GL_DEBUG_OUTPUT_SYNCHRONOUS
# define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
# endif
# ifndef GL_DEBUG_SEVERITY_HIGH
# define GL_DEBUG_SEVERITY_HIGH GL_DEBUG_SEVERITY_HIGH_KHR
# endif
# ifndef GL_DEBUG_SEVERITY_MEDIUM
# define GL_DEBUG_SEVERITY_MEDIUM GL_DEBUG_SEVERITY_MEDIUM_KHR
# endif
# ifndef GL_DEBUG_SEVERITY_LOW
# define GL_DEBUG_SEVERITY_LOW GL_DEBUG_SEVERITY_LOW_KHR
# endif
# ifndef GL_DEBUG_SEVERITY_NOTIFICATION
# define GL_DEBUG_SEVERITY_NOTIFICATION GL_DEBUG_SEVERITY_NOTIFICATION_KHR
# endif
# ifndef GL_DEBUG_TYPE_MARKER
# define GL_DEBUG_TYPE_MARKER GL_DEBUG_TYPE_MARKER_KHR
# endif
# ifndef GL_DEBUG_TYPE_ERROR
# define GL_DEBUG_TYPE_ERROR GL_DEBUG_TYPE_ERROR_KHR
# endif
# ifndef GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR
# define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR
# endif
# ifndef GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR
# define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR
# endif
# ifndef GL_DEBUG_TYPE_PORTABILITY
# define GL_DEBUG_TYPE_PORTABILITY GL_DEBUG_TYPE_PORTABILITY_KHR
# endif
# ifndef GL_DEBUG_TYPE_PERFORMANCE
# define GL_DEBUG_TYPE_PERFORMANCE GL_DEBUG_TYPE_PERFORMANCE_KHR
# endif
# ifndef GL_DEBUG_TYPE_OTHER
# define GL_DEBUG_TYPE_OTHER GL_DEBUG_TYPE_OTHER_KHR
# endif
# define glDebugMessageCallback glDebugMessageCallbackKHR
# define GL_DEBUG_OUTPUT GL_DEBUG_OUTPUT_KHR
# define GL_DEBUG_OUTPUT_SYNCHRONOUS GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR
# define GL_DEBUG_SEVERITY_HIGH GL_DEBUG_SEVERITY_HIGH_KHR
# define GL_DEBUG_SEVERITY_MEDIUM GL_DEBUG_SEVERITY_MEDIUM_KHR
# define GL_DEBUG_SEVERITY_LOW GL_DEBUG_SEVERITY_LOW_KHR
# define GL_DEBUG_SEVERITY_NOTIFICATION GL_DEBUG_SEVERITY_NOTIFICATION_KHR
# define GL_DEBUG_TYPE_MARKER GL_DEBUG_TYPE_MARKER_KHR
# define GL_DEBUG_TYPE_ERROR GL_DEBUG_TYPE_ERROR_KHR
# define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_KHR
# define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_KHR
# define GL_DEBUG_TYPE_PORTABILITY GL_DEBUG_TYPE_PORTABILITY_KHR
# define GL_DEBUG_TYPE_PERFORMANCE GL_DEBUG_TYPE_PERFORMANCE_KHR
# define GL_DEBUG_TYPE_OTHER GL_DEBUG_TYPE_OTHER_KHR
# define glDebugMessageCallback glDebugMessageCallbackKHR
#endif
/* The iOS SDK only provides OpenGL ES headers up to 3.0. Filament works with OpenGL 3.0, but
* requires ES3.1 headers */
#if !defined(GL_ES_VERSION_3_1)
#define GL_SHADER_STORAGE_BUFFER 0x90D2
#define GL_COMPUTE_SHADER 0x91B9
#define GL_TEXTURE_2D_MULTISAMPLE 0x9100
// FIXME: The GL_TIME_ELAPSED define is used unconditionally in Filament, but
// requires extension support.
#ifndef GL_TIME_ELAPSED
#define GL_TIME_ELAPSED 0x88BF
#endif
#define GL_TEXTURE_BINDING_CUBE_MAP_ARRAY 0x900A
#define GL_SAMPLER_CUBE_MAP_ARRAY 0x900C
#define GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW 0x900D
#define GL_INT_SAMPLER_CUBE_MAP_ARRAY 0x900E
#define GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY 0x900F
#define GL_IMAGE_CUBE_MAP_ARRAY 0x9054
#define GL_INT_IMAGE_CUBE_MAP_ARRAY 0x905F
#define GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY 0x906A
#endif
#endif // GL_ES_VERSION_2_0
// This is just to simplify the implementation (i.e. so we don't have to have #ifdefs everywhere)
#ifndef GL_OES_EGL_image_external
#define GL_TEXTURE_EXTERNAL_OES 0x8D65
#endif
// This is an odd duck function that exists in WebGL 2.0 but not in OpenGL ES.
#if defined(__EMSCRIPTEN__)
extern "C" {
@@ -222,11 +182,6 @@ void glGetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void *d
# define BACKEND_OPENGL_LEVEL BACKEND_OPENGL_LEVEL_GLES20
#endif
// This is just to simplify the implementation (i.e. so we don't have to have #ifdefs everywhere)
#ifndef GL_OES_EGL_image_external
#define GL_TEXTURE_EXTERNAL_OES 0x8D65
#endif
#include "NullGLES.h"
#endif // TNT_FILAMENT_BACKEND_OPENGL_GL_HEADERS_H

View File

@@ -94,9 +94,7 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
return nullptr;
}
#if defined(__ANDROID__) || defined(FILAMENT_USE_EXTERNAL_GLES3) || defined(__EMSCRIPTEN__)
// PlatofrmEGL is used with and without GLES, but this function is only
// meaningful when GLES is used.
#if defined(FILAMENT_IMPORT_ENTRY_POINTS)
importGLESExtensionsEntryPoints();
#endif

View File

@@ -262,7 +262,7 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
api.stopCapture(0);
api.endFrame(0);
readPixelsAndAssertHash("StencilBufferAutoResolve", 512, 512, renderTarget1, 0xC7E34C43, true);
readPixelsAndAssertHash("StencilBufferAutoResolve", 512, 512, renderTarget1, 0x6CEFAC8F, true);
flushAndWait();
getDriver().purge();

View File

@@ -104,22 +104,24 @@ public:
}
/**
* Computes the bounding box of a box transformed by a rigid transform
* Transform a Box by a linear transform and a translation.
*
* @param m a 3x3 matrix, the linear transform
* @param t a float3, the translation
* @param box the box to transform
* @param m a 4x4 matrix that must be a rigid transform
* @return the bounding box of the transformed box.
* Result is undefined if \p m is not a rigid transform
* @return the bounding box of the transformed box
*/
friend Box rigidTransform(Box const& box, const math::mat4f& m) noexcept;
static Box transform(const math::mat3f& m, math::float3 const& t, const Box& box) noexcept {
return { m * box.center + t, abs(m) * box.halfExtent };
}
/**
* Computes the bounding box of a box transformed by a rigid transform
* @param box the box to transform
* @param m a 3x3 matrix that must be a rigid transform
* @return the bounding box of the transformed box.
* Result is undefined if \p m is not a rigid transform
* @deprecated Use transform() instead
* @see transform()
*/
friend Box rigidTransform(Box const& box, const math::mat3f& m) noexcept;
friend Box rigidTransform(Box const& box, const math::mat4f& m) noexcept {
return transform(m.upperLeft(), m[3].xyz, box);
}
};
/**
@@ -174,7 +176,18 @@ struct UTILS_PUBLIC Aabb {
/**
* Returns the 8 corner vertices of the AABB.
*/
Corners getCorners() const;
Corners getCorners() const {
return Aabb::Corners{ .vertices = {
{ min.x, min.y, min.z },
{ max.x, min.y, min.z },
{ min.x, max.y, min.z },
{ max.x, max.y, min.z },
{ min.x, min.y, max.z },
{ max.x, min.y, max.z },
{ min.x, max.y, max.z },
{ max.x, max.y, max.z },
}};
}
/**
* Returns whether the box contains a given point.
@@ -182,15 +195,44 @@ struct UTILS_PUBLIC Aabb {
* @param p the point to test
* @return the maximum signed distance to the box. Negative if p is in the box
*/
float contains(math::float3 p) const noexcept;
float contains(math::float3 p) const noexcept {
float d = min.x - p.x;
d = std::max(d, min.y - p.y);
d = std::max(d, min.z - p.z);
d = std::max(d, p.x - max.x);
d = std::max(d, p.y - max.y);
d = std::max(d, p.z - max.z);
return d;
}
/**
* Applies an affine transformation to the AABB.
*
* @param m the 4x4 transformation to apply
* @param m the 3x3 transformation to apply
* @param t the translation
* @return the transformed box
*/
Aabb transform(const math::mat4f& m) const noexcept;
static Aabb transform(const math::mat3f& m, math::float3 const& t, const Aabb& box) noexcept {
// Fast AABB transformation per Jim Arvo in Graphics Gems (1990).
Aabb result{ t, t };
for (size_t col = 0; col < 3; ++col) {
for (size_t row = 0; row < 3; ++row) {
const float a = m[col][row] * box.min[col];
const float b = m[col][row] * box.max[col];
result.min[row] += a < b ? a : b;
result.max[row] += a < b ? b : a;
}
}
return result;
}
/**
* @deprecated Use transform() instead
* @see transform()
*/
Aabb transform(const math::mat4f& m) const noexcept {
return transform(m.upperLeft(), m[3].xyz, *this);
}
};
} // namespace filament

View File

@@ -1,71 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <filament/Box.h>
using namespace filament::math;
namespace filament {
Box rigidTransform(Box const& UTILS_RESTRICT box, const mat4f& UTILS_RESTRICT m) noexcept {
const mat3f u(m.upperLeft());
return { u * box.center + m[3].xyz, abs(u) * box.halfExtent };
}
Box rigidTransform(Box const& UTILS_RESTRICT box, const mat3f& UTILS_RESTRICT u) noexcept {
return { u * box.center, abs(u) * box.halfExtent };
}
Aabb::Corners Aabb::getCorners() const {
return Aabb::Corners{ .vertices = {
{ min.x, min.y, min.z },
{ max.x, min.y, min.z },
{ min.x, max.y, min.z },
{ max.x, max.y, min.z },
{ min.x, min.y, max.z },
{ max.x, min.y, max.z },
{ min.x, max.y, max.z },
{ max.x, max.y, max.z },
}};
}
float Aabb::contains(float3 p) const noexcept {
float d = min.x - p.x;
d = std::max(d, min.y - p.y);
d = std::max(d, min.z - p.z);
d = std::max(d, p.x - max.x);
d = std::max(d, p.y - max.y);
d = std::max(d, p.z - max.z);
return d;
}
// Fast AABB transformation per Jim Arvo in Graphics Gems (1990).
Aabb Aabb::transform(const mat4f& mat) const noexcept {
const float3 translation = mat[3].xyz;
const mat3f upperLeft = mat.upperLeft();
Aabb result = { translation, translation };
for (size_t col = 0; col < upperLeft.getColumnCount(); ++col) {
for (size_t row = 0; row < upperLeft.getRowCount(); ++row) {
const float a = upperLeft[col][row] * min[col];
const float b = upperLeft[col][row] * max[col];
result.min[row] += a < b ? a : b;
result.max[row] += a < b ? b : a;
}
}
return result;
}
} // namespace filament

View File

@@ -86,11 +86,12 @@ void RenderPass::setScissorViewport(backend::Viewport viewport) noexcept {
}
void RenderPass::appendCommands(FEngine& engine, CommandTypeFlags const commandTypeFlags) noexcept {
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
assert_invariant(mRenderableSoa);
utils::Range<uint32_t> vr = mVisibleRenderables;
utils::Range<uint32_t> const vr = mVisibleRenderables;
// trace the number of visible renderables
SYSTRACE_VALUE32("visibleRenderables", vr.size());
if (UTILS_UNLIKELY(vr.empty())) {
@@ -129,7 +130,7 @@ void RenderPass::appendCommands(FEngine& engine, CommandTypeFlags const commandT
work(vr.first, vr.size());
} else {
auto* jobCommandsParallel = jobs::parallel_for(js, nullptr, vr.first, (uint32_t)vr.size(),
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT, 4>());
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT, 5>());
js.runAndWait(jobCommandsParallel);
}
@@ -375,6 +376,8 @@ void RenderPass::generateCommands(uint32_t commandTypeFlags, Command* const comm
FScene::VisibleMaskType visibilityMask,
float3 cameraPosition, float3 cameraForward) noexcept {
SYSTRACE_CALL();
// generateCommands() writes both the draw and depth commands simultaneously such that
// we go throw the list of renderables just once.
// (in principle, we could have split this method into two, at the cost of going through
@@ -723,6 +726,7 @@ UTILS_NOINLINE // no need to be inlined
void RenderPass::Executor::execute(backend::DriverApi& driver,
const Command* first, const Command* last) const noexcept {
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
if (first != last) {
SYSTRACE_VALUE32("commandCount", last - first);

View File

@@ -389,9 +389,9 @@ private:
void resize(size_t count) noexcept;
void instanceify(FEngine& engine) noexcept;
// on 64-bits systems, we process batches of 256 (64 bytes) cache-lines, or 512 (32 bytes) commands
// on 32-bits systems, we process batches of 512 (32 bytes) cache-lines, or 512 (32 bytes) commands
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 512;
// we choose the command count per job to minimize JobSystem overhead.
// on a Pixel 4, 2048 commands is about half a millisecond of processing.
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 2048;
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_SIZE =
sizeof(Command) * JOBS_PARALLEL_FOR_COMMANDS_COUNT;

View File

@@ -58,7 +58,7 @@ ShadowMap::ShadowMap(FEngine& engine) noexcept
void ShadowMap::terminate(FEngine& engine) {
Entity entities[] = { mCamera->getEntity(), mDebugCamera->getEntity() };
for (Entity e : entities) {
for (Entity const e : entities) {
engine.destroyCameraComponent(e);
}
engine.getEntityManager().destroy(sizeof(entities) / sizeof(Entity), entities);
@@ -112,12 +112,12 @@ math::mat4f ShadowMap::getPointLightViewMatrix(backend::TextureCubemapFace face,
ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera,
ShadowMapInfo const& shadowMapInfo, FScene const& scene,
SceneInfo& sceneInfo) noexcept {
ShadowMapInfo const& shadowMapInfo,
SceneInfo const& sceneInfo) noexcept {
// Note: we keep the polygon offset even with VSM as it seems to help.
auto& lcm = engine.getLightManager();
FLightManager::Instance li = lightData.elementAt<FScene::LIGHT_INSTANCE>(index);
FLightManager::Instance const li = lightData.elementAt<FScene::LIGHT_INSTANCE>(index);
FLightManager::ShadowParams params = lcm.getShadowParams(li);
#ifndef NDEBUG
@@ -134,8 +134,8 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
// Adjust the camera's projection for the light's shadowFar
const mat4f cullingProjection{ [&](auto p) {
if (params.options.shadowFar > 0.0f) {
float n = camera.zn;
float f = params.options.shadowFar;
float const n = camera.zn;
float const f = params.options.shadowFar;
// orthographic projection
assert_invariant(std::abs(p[2].w) <= std::numeric_limits<float>::epsilon());
p[2].z = 2.0f / (n - f);
@@ -153,10 +153,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
// We compute the directional light's model matrix using the origin's as the light position.
// The choice of the light's origin initially doesn't matter for a directional light.
// This will be adjusted later because of how we compute the depth metric for VSM.
const mat4f MvAtOrigin = getDirectionalLightViewMatrix(direction);
// Compute scene-dependent values shared across all cascades
ShadowMap::updateSceneInfoDirectional(MvAtOrigin, scene, sceneInfo);
const mat4f MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction);
const Aabb wsShadowCastersVolume = sceneInfo.wsShadowCastersVolume;
const Aabb wsShadowReceiversVolume = sceneInfo.wsShadowReceiversVolume;
@@ -177,7 +174,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
// compute the intersection of the shadow receivers' volume with the view volume
// in world space. This returns a set of points on the convex-hull of the intersection.
size_t vertexCount = intersectFrustumWithBox(wsClippedShadowReceiverVolume,
size_t const vertexCount = intersectFrustumWithBox(wsClippedShadowReceiverVolume,
wsFrustum, wsViewFrustumVertices, wsShadowReceiversVolume);
if (UTILS_UNLIKELY(vertexCount < 4)) {
@@ -206,7 +203,7 @@ ShadowMap::ShaderParameters ShadowMap::updateDirectional(FEngine& engine,
}
for (size_t i = 0; i < vertexCount; ++i) {
// far: figure out the farthest shadow receivers
float3 v = mat4f::project(MvAtOrigin, wsClippedShadowReceiverVolume[i]);
float3 const v = mat4f::project(MvAtOrigin, wsClippedShadowReceiverVolume[i]);
lsLightFrustumBounds.min.z = std::min(lsLightFrustumBounds.min.z, v.z);
if constexpr (USE_DEPTH_CLAMP) {
// tighten the shadow receiver volume further
@@ -487,7 +484,7 @@ ShadowMap::ShaderParameters ShadowMap::updatePunctual(
ShadowMap::ShaderParameters ShadowMap::updateSpot(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera,
filament::CameraInfo const&,
const ShadowMapInfo& shadowMapInfo,
FScene const& scene, SceneInfo sceneInfo) noexcept {
@@ -499,8 +496,17 @@ ShadowMap::ShaderParameters ShadowMap::updateSpot(FEngine& engine,
const FLightManager::ShadowParams& params = lcm.getShadowParams(li);
const mat4f Mv = getDirectionalLightViewMatrix(direction, position);
// find decent near/far
ShadowMap::updateSceneInfoSpot(Mv, scene, sceneInfo);
// We only keep this for reference. updateSceneInfoSpot() is quite expensive on large scenes
// currently, and only needed to find a near/far. Instead, we just use a small near and the
// radius as far.
// TODO: Another potential solution would be to visit only the part of the scene that's visible
// by the light -- which should be much smaller.
if constexpr (false) {
// find decent near/far
ShadowMap::updateSceneInfoSpot(Mv, scene, sceneInfo);
} else {
sceneInfo.lsNearFar = { -0.01f, -radius };
}
// if the scene was empty, near > far
mHasVisibleShadows = -sceneInfo.lsNearFar[0] < -sceneInfo.lsNearFar[1];
@@ -509,22 +515,22 @@ ShadowMap::ShaderParameters ShadowMap::updateSpot(FEngine& engine,
}
// FIXME: we need a configuration for minimum near plane (for now hardcoded to 1cm)
float nearPlane = std::max(0.01f, -sceneInfo.lsNearFar[0]);
float farPlane = std::min(radius, -sceneInfo.lsNearFar[1]);
float const nearPlane = std::max(0.01f, -sceneInfo.lsNearFar[0]);
float const farPlane = std::min(radius, -sceneInfo.lsNearFar[1]);
auto outerConeAngle = lcm.getSpotLightOuterCone(li);
return updatePunctual(Mv, outerConeAngle, nearPlane, farPlane, shadowMapInfo, params);
}
ShadowMap::ShaderParameters ShadowMap::updatePoint(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera, const ShadowMapInfo& shadowMapInfo, FScene const& scene,
SceneInfo, uint8_t face) noexcept {
const FScene::LightSoa& lightData, size_t index, filament::CameraInfo const&,
const ShadowMapInfo& shadowMapInfo, FScene const& scene, uint8_t face) noexcept {
// check if this shadow map has anything to render
mHasVisibleShadows = false;
FScene::RenderableSoa const& UTILS_RESTRICT soa = scene.getRenderableData();
auto const* const UTILS_RESTRICT visibleMasks = soa.data<FScene::VISIBLE_MASK>();
size_t c = soa.size();
size_t const c = soa.size();
for (size_t i = 0; i < c; i++) {
if (visibleMasks[i] & VISIBLE_DYN_SHADOW_RENDERABLE) {
mHasVisibleShadows = true;
@@ -702,18 +708,12 @@ mat4f ShadowMap::directionalLightFrustum(float near, float far) noexcept {
return m;
}
float2 ShadowMap::computeNearFar(const mat4f& view,
Aabb const& wsShadowCastersVolume) noexcept {
const Aabb::Corners wsSceneCastersCorners = wsShadowCastersVolume.getCorners();
return computeNearFar(view, wsSceneCastersCorners.data(), wsSceneCastersCorners.size());
}
float2 ShadowMap::computeNearFar(const mat4f& view,
float3 const* wsVertices, size_t count) noexcept {
float2 nearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
for (size_t i = 0; i < count; i++) {
// we're on the z axis in light space (looking down to -z)
float c = mat4f::project(view, wsVertices[i]).z;
float const c = mat4f::project(view, wsVertices[i]).z;
nearFar.x = std::max(nearFar.x, c); // near
nearFar.y = std::min(nearFar.y, c); // far
}
@@ -726,7 +726,7 @@ float2 ShadowMap::computeNearFarOfWarpSpace(mat4f const& lightView,
#pragma nounroll
for (size_t i = 0; i < count; i++) {
// we're on the y-axis in light space (looking down to +y)
float c = mat4f::project(lightView, wsVertices[i]).y;
float const c = mat4f::project(lightView, wsVertices[i]).y;
nearFar.x = std::min(nearFar.x, c);
nearFar.y = std::max(nearFar.y, c);
}
@@ -791,11 +791,11 @@ void ShadowMap::intersectWithShadowCasters(Aabb& UTILS_RESTRICT lightFrustum,
// first intersect wsShadowCastersVolume with the light's frustum, otherwise we end-up
// transforming vertices that are "outside" the frustum, and that's forbidden.
FrustumBoxIntersection wsClippedShadowCasterVolumeVertices;
size_t vertexCount = intersectFrustumWithBox(wsClippedShadowCasterVolumeVertices,
size_t const vertexCount = intersectFrustumWithBox(wsClippedShadowCasterVolumeVertices,
wsLightFrustum, wsLightFrustumCorners, wsShadowCastersVolume);
// compute shadow-caster bounds in light space
Aabb box = compute2DBounds(lightView, wsClippedShadowCasterVolumeVertices.data(), vertexCount);
Aabb const box = compute2DBounds(lightView, wsClippedShadowCasterVolumeVertices.data(), vertexCount);
// intersect shadow-caster and current light frustum bounds
lightFrustum.min.xy = max(box.min.xy, lightFrustum.min.xy);
@@ -809,7 +809,7 @@ void ShadowMap::computeFrustumCorners(float3* UTILS_RESTRICT out,
// matrix to convert: ndc -> camera -> world
float near = csNearFar.x;
float far = csNearFar.y;
float3 csViewFrustumCorners[8] = {
float3 const csViewFrustumCorners[8] = {
{ -1, -1, far },
{ 1, -1, far },
{ -1, 1, far },
@@ -819,7 +819,7 @@ void ShadowMap::computeFrustumCorners(float3* UTILS_RESTRICT out,
{ -1, 1, near },
{ 1, 1, near },
};
for (float3 c : csViewFrustumCorners) {
for (float3 const c : csViewFrustumCorners) {
*out++ = mat4f::project(projectionViewInverse, c);
}
}
@@ -869,7 +869,7 @@ size_t ShadowMap::intersectFrustumWithBox(
// a) Keep the frustum's vertices that are known to be inside the scene's box
UTILS_NOUNROLL
for (size_t i = 0; i < 8; i++) {
float3 p = wsFrustumCorners[i];
float3 const p = wsFrustumCorners[i];
outVertices[vertexCount] = p;
if ((p.x >= wsBox.min.x && p.x <= wsBox.max.x) &&
(p.y >= wsBox.min.y && p.y <= wsBox.max.y) &&
@@ -889,14 +889,14 @@ size_t ShadowMap::intersectFrustumWithBox(
// the frustum. This actually often happens due to fitting light-space
// We fudge the distance to the plane by a small amount.
#pragma nounroll
for (float3 p : wsSceneReceiversCorners) {
for (float3 const p : wsSceneReceiversCorners) {
outVertices[vertexCount] = p;
float l = dot(wsFrustumPlanes[0].xyz, p) + wsFrustumPlanes[0].w;
float b = dot(wsFrustumPlanes[1].xyz, p) + wsFrustumPlanes[1].w;
float r = dot(wsFrustumPlanes[2].xyz, p) + wsFrustumPlanes[2].w;
float t = dot(wsFrustumPlanes[3].xyz, p) + wsFrustumPlanes[3].w;
float f = dot(wsFrustumPlanes[4].xyz, p) + wsFrustumPlanes[4].w;
float n = dot(wsFrustumPlanes[5].xyz, p) + wsFrustumPlanes[5].w;
float const l = dot(wsFrustumPlanes[0].xyz, p) + wsFrustumPlanes[0].w;
float const b = dot(wsFrustumPlanes[1].xyz, p) + wsFrustumPlanes[1].w;
float const r = dot(wsFrustumPlanes[2].xyz, p) + wsFrustumPlanes[2].w;
float const t = dot(wsFrustumPlanes[3].xyz, p) + wsFrustumPlanes[3].w;
float const f = dot(wsFrustumPlanes[4].xyz, p) + wsFrustumPlanes[4].w;
float const n = dot(wsFrustumPlanes[5].xyz, p) + wsFrustumPlanes[5].w;
if ((l <= EPSILON) && (b <= EPSILON) &&
(r <= EPSILON) && (t <= EPSILON) &&
(f <= EPSILON) && (n <= EPSILON)) {
@@ -981,7 +981,7 @@ size_t ShadowMap::intersectFrustum(
const float3 s0{ segmentsVertices[segment.v0] };
const float3 s1{ segmentsVertices[segment.v1] };
// each segment should only intersect with 2 quads at most
size_t maxVertexCount = vertexCount + 2;
size_t const maxVertexCount = vertexCount + 2;
for (size_t j = 0; j < 6 && vertexCount < maxVertexCount; ++j) {
const Quad quad = sBoxQuads[j];
const float3 t0{ quadsVertices[quad.v0] };
@@ -1036,8 +1036,8 @@ inline bool ShadowMap::intersectSegmentWithTriangle(float3& UTILS_RESTRICT p,
bool ShadowMap::intersectSegmentWithPlanarQuad(float3& UTILS_RESTRICT p,
float3 s0, float3 s1, float3 t0, float3 t1, float3 t2, float3 t3) noexcept {
bool hit = intersectSegmentWithTriangle(p, s0, s1, t0, t1, t2) ||
intersectSegmentWithTriangle(p, s0, s1, t0, t2, t3);
bool const hit = intersectSegmentWithTriangle(p, s0, s1, t0, t1, t2) ||
intersectSegmentWithTriangle(p, s0, s1, t0, t2, t3);
return hit;
}
@@ -1070,7 +1070,7 @@ float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF,
// The Jacobian is not constant, so we evaluate it in the center of the shadow-map texture.
// It might be better to do this computation in the vertex shader.
float3 p = {0.5, 0.5, 0.0};
float3 const p = { 0.5f, 0.5f, 0.0f };
const float ures = 1.0f / float(shadowDimension);
const float vres = 1.0f / float(shadowDimension);
@@ -1115,9 +1115,9 @@ float ShadowMap::texelSizeWorldSpace(const mat4f& Wp, const mat4f& MbMtF,
0.0f, j * Z * sx, j * dz * sx
});
float3 Jx = J[0] * ures;
float3 Jy = J[1] * vres;
UTILS_UNUSED float3 Jz = J[2] * dres;
float3 const Jx = J[0] * ures;
float3 const Jy = J[1] * vres;
UTILS_UNUSED float3 const Jz = J[2] * dres;
const float s = std::max(length(Jx), length(Jy));
return s;
}
@@ -1128,13 +1128,13 @@ void ShadowMap::visitScene(const FScene& scene, uint32_t visibleLayers,
SYSTRACE_CALL();
using State = FRenderableManager::Visibility;
FScene::RenderableSoa const& UTILS_RESTRICT soa = scene.getRenderableData();
float3 const* const UTILS_RESTRICT worldAABBCenter = soa.data<FScene::WORLD_AABB_CENTER>();
float3 const* const UTILS_RESTRICT worldAABBExtent = soa.data<FScene::WORLD_AABB_EXTENT>();
uint8_t const* const UTILS_RESTRICT layers = soa.data<FScene::LAYERS>();
State const* const UTILS_RESTRICT visibility = soa.data<FScene::VISIBILITY_STATE>();
auto const* const UTILS_RESTRICT visibleMasks = soa.data<FScene::VISIBLE_MASK>();
size_t c = soa.size();
FScene::RenderableSoa const& soa = scene.getRenderableData();
float3 const* const worldAABBCenter = soa.data<FScene::WORLD_AABB_CENTER>();
float3 const* const worldAABBExtent = soa.data<FScene::WORLD_AABB_EXTENT>();
uint8_t const* const layers = soa.data<FScene::LAYERS>();
State const* const visibility = soa.data<FScene::VISIBILITY_STATE>();
auto const* const visibleMasks = soa.data<FScene::VISIBLE_MASK>();
size_t const c = soa.size();
for (size_t i = 0; i < c; i++) {
if (layers[i] & visibleLayers) {
const Aabb aabb{ worldAABBCenter[i] - worldAABBExtent[i],
@@ -1149,46 +1149,47 @@ void ShadowMap::visitScene(const FScene& scene, uint32_t visibleLayers,
}
}
void ShadowMap::initSceneInfo(ShadowMap::SceneInfo& sceneInfo,
uint8_t visibleLayers, FScene const& scene, mat4f const& viewMatrix) {
sceneInfo.csNearFar = { -1.0f, 1.0f };
sceneInfo.lsNearFar = {};
sceneInfo.visibleLayers = visibleLayers;
sceneInfo.vsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
ShadowMap::SceneInfo::SceneInfo(
FScene const& scene, uint8_t visibleLayers, mat4f const& viewMatrix) noexcept
: vsNearFar(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max()),
visibleLayers(visibleLayers) {
// the code below only works with affine transforms
assert_invariant(transpose(viewMatrix)[3] == float4(0, 0, 0, 1));
// We assume the light is at the origin to compute the SceneInfo. This is consumed later by
// computeShadowCameraDirectional() which takes this into account.
// Compute scene bounds in world space, as well as the light-space and view-space near/far planes
sceneInfo.wsShadowCastersVolume = {};
sceneInfo.wsShadowReceiversVolume = {};
visitScene(scene, sceneInfo.visibleLayers,
wsShadowCastersVolume = {};
wsShadowReceiversVolume = {};
ShadowMap::visitScene(scene, visibleLayers,
[&](Aabb caster, Culler::result_type) {
sceneInfo.wsShadowCastersVolume.min =
min(sceneInfo.wsShadowCastersVolume.min, caster.min);
sceneInfo.wsShadowCastersVolume.max =
max(sceneInfo.wsShadowCastersVolume.max, caster.max);
wsShadowCastersVolume.min = min(wsShadowCastersVolume.min, caster.min);
wsShadowCastersVolume.max = max(wsShadowCastersVolume.max, caster.max);
},
[&](Aabb receiver, Culler::result_type) {
sceneInfo.wsShadowReceiversVolume.min =
min(sceneInfo.wsShadowReceiversVolume.min, receiver.min);
sceneInfo.wsShadowReceiversVolume.max =
max(sceneInfo.wsShadowReceiversVolume.max, receiver.max);
float2 nf = ShadowMap::computeNearFar(viewMatrix, receiver);
sceneInfo.vsNearFar.x = std::max(sceneInfo.vsNearFar.x, nf.x);
sceneInfo.vsNearFar.y = std::min(sceneInfo.vsNearFar.y, nf.y);
wsShadowReceiversVolume.min = min(wsShadowReceiversVolume.min, receiver.min);
wsShadowReceiversVolume.max = max(wsShadowReceiversVolume.max, receiver.max);
auto r = Aabb::transform(viewMatrix.upperLeft(), viewMatrix[3].xyz, receiver);
vsNearFar.x = std::max(vsNearFar.x, r.max.z);
vsNearFar.y = std::min(vsNearFar.y, r.min.z);
}
);
}
void ShadowMap::updateSceneInfoDirectional(mat4f const& Mv, FScene const& scene,
SceneInfo& sceneInfo) {
// the code below only works with affine transforms
assert_invariant(transpose(Mv)[3] == float4(0, 0, 0, 1));
sceneInfo.lsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
visitScene(scene, sceneInfo.visibleLayers,
[&](Aabb caster, Culler::result_type) {
float2 nf = ShadowMap::computeNearFar(Mv, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, nf.x); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, nf.y); // far
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, r.max.z); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, r.min.z); // far
},
[&](Aabb receiver, Culler::result_type) {
}
@@ -1197,14 +1198,17 @@ void ShadowMap::updateSceneInfoDirectional(mat4f const& Mv, FScene const& scene,
void ShadowMap::updateSceneInfoSpot(mat4f const& Mv, FScene const& scene,
SceneInfo& sceneInfo) {
// the code below only works with affine transforms
assert_invariant(transpose(Mv)[3] == float4(0, 0, 0, 1));
sceneInfo.lsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
sceneInfo.vsNearFar = { std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max() };
visitScene(scene, sceneInfo.visibleLayers,
[&](Aabb caster, Culler::result_type mask) {
if (mask & VISIBLE_DYN_SHADOW_RENDERABLE) {
float2 nf = ShadowMap::computeNearFar(Mv, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, nf.x); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, nf.y); // far
auto r = Aabb::transform(Mv.upperLeft(), Mv[3].xyz, caster);
sceneInfo.lsNearFar.x = std::max(sceneInfo.lsNearFar.x, r.max.z); // near
sceneInfo.lsNearFar.y = std::min(sceneInfo.lsNearFar.y, r.min.z); // far
}
},
[&](Aabb receiver, Culler::result_type) {

View File

@@ -95,16 +95,20 @@ public:
};
struct SceneInfo {
SceneInfo() noexcept = default;
SceneInfo(FScene const& scene, uint8_t visibleLayers, math::mat4f const& viewMatrix) noexcept;
// scratch data: The near and far planes, in clip space, to use for this shadow map
math::float2 csNearFar = { -1.0f, 1.0f };
// scratch data: light's near/far expressed in light-space, calculated from the scene's
// content assuming the light is at the origin.
math::float2 lsNearFar{};
math::float2 lsNearFar;
// scratch data: Viewing camera's near/far expressed in view-space, calculated from the
// Viewing camera's near/far expressed in view-space, calculated from the
// scene's content.
math::float2 vsNearFar{};
math::float2 vsNearFar;
// World-space shadow-casters volume
Aabb wsShadowCastersVolume;
@@ -136,8 +140,8 @@ public:
ShaderParameters updateDirectional(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera,
ShadowMapInfo const& shadowMapInfo, FScene const& scene,
SceneInfo& sceneInfo) noexcept;
ShadowMapInfo const& shadowMapInfo,
SceneInfo const& sceneInfo) noexcept;
ShaderParameters updateSpot(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
@@ -145,11 +149,9 @@ public:
const ShadowMapInfo& shadowMapInfo, FScene const& scene,
SceneInfo sceneInfo) noexcept;
ShaderParameters updatePoint(FEngine& engine,
const FScene::LightSoa& lightData, size_t index,
filament::CameraInfo const& camera, const ShadowMapInfo& shadowMapInfo,
FScene const& scene,
SceneInfo sceneInfo, uint8_t face) noexcept;
ShadowMap::ShaderParameters updatePoint(FEngine& engine,
const FScene::LightSoa& lightData, size_t index, filament::CameraInfo const& camera,
const ShadowMapInfo& shadowMapInfo, FScene const& scene, uint8_t face) noexcept;
// Do we have visible shadows. Valid after calling update().
bool hasVisibleShadows() const noexcept { return mHasVisibleShadows; }
@@ -160,11 +162,6 @@ public:
// use only for debugging
FCamera const& getDebugCamera() const noexcept { return *mDebugCamera; }
// Call once per frame to populate the SceneInfo struct, then pass to update().
// This computes values constant across all shadow maps.
static void initSceneInfo(ShadowMap::SceneInfo& sceneInfo, uint8_t visibleLayers,
FScene const& scene, math::mat4f const& viewMatrix);
// Update SceneInfo struct for a given light
static void updateSceneInfoDirectional(const math::mat4f& Mv, FScene const& scene,
SceneInfo& sceneInfo);
@@ -230,9 +227,6 @@ private:
static inline void computeFrustumCorners(math::float3* out,
const math::mat4f& projectionViewInverse, math::float2 csNearFar = { -1.0f, 1.0f }) noexcept;
static inline math::float2 computeNearFar(math::mat4f const& view,
Aabb const& wsShadowCastersVolume) noexcept;
static inline math::float2 computeNearFar(math::mat4f const& view,
math::float3 const* wsVertices, size_t count) noexcept;

View File

@@ -76,15 +76,16 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(FEngine& engine, FVie
calculateTextureRequirements(engine, view, lightData);
// Compute scene-dependent values shared across all shadow maps
ShadowMap::initSceneInfo(mSceneInfo,
view.getVisibleLayers(), *view.getScene(), cameraInfo.view);
ShadowMap::SceneInfo const info{ *view.getScene(), view.getVisibleLayers(), cameraInfo.view };
shadowTechnique |= updateCascadeShadowMaps(
engine, view, cameraInfo, renderableData, lightData, mSceneInfo);
engine, view, cameraInfo, renderableData, lightData, info);
shadowTechnique |= updateSpotShadowMaps(
engine, lightData);
mSceneInfo = info;
return shadowTechnique;
}
@@ -207,7 +208,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
},
[this, &engine, &view, vsmShadowOptions,
scene, mainCameraInfo, userTime, passTemplate = pass](
FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
FrameGraphResources const&, auto const& data, DriverApi& driver) {
// Note: we could almost parallel_for the loop below, the problem currently is
// that updatePrimitivesLod() updates temporary global state.
@@ -435,11 +436,11 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEngine& engine,
FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData,
FScene::LightSoa& lightData, ShadowMap::SceneInfo& sceneInfo) noexcept {
FScene::LightSoa& lightData, ShadowMap::SceneInfo sceneInfo) noexcept {
FScene* scene = view.getScene();
auto& lcm = engine.getLightManager();
FLightManager::Instance directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
FLightManager::Instance const directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
FLightManager::ShadowOptions const& options = lcm.getShadowOptions(directionalLight);
FLightManager::ShadowParams const& params = lcm.getShadowParams(directionalLight);
@@ -458,8 +459,18 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
// entire camera frustum, as if we only had a single cascade.
ShadowMap& shadowMap = *mCascadeShadowMaps[0];
const auto direction = lightData.elementAt<FScene::DIRECTION>(0);
// We compute the directional light's model matrix using the origin's as the light position.
// The choice of the light's origin initially doesn't matter for a directional light.
// This will be adjusted later because of how we compute the depth metric for VSM.
const mat4f MvAtOrigin = ShadowMap::getDirectionalLightViewMatrix(direction);
// Compute scene-dependent values shared across all cascades
ShadowMap::updateSceneInfoDirectional(MvAtOrigin, *scene, sceneInfo);
shadowMap.updateDirectional(mEngine,
lightData, 0, cameraInfo, shadowMapInfo, *scene, sceneInfo);
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
hasVisibleShadows = shadowMap.hasVisibleShadows();
@@ -533,7 +544,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
sceneInfo.csNearFar = { csSplitPosition[i], csSplitPosition[i + 1] };
auto shaderParameters = shadowMap.updateDirectional(mEngine,
lightData, 0, cameraInfo, shadowMapInfo, *scene, sceneInfo);
lightData, 0, cameraInfo, shadowMapInfo, sceneInfo);
if (shadowMap.hasVisibleShadows()) {
const size_t shadowIndex = shadowMap.getShadowIndex();
@@ -560,7 +571,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
}
// screen-space contact shadows for the directional light
float screenSpaceShadowDistance = options.maxShadowDistance;
float const screenSpaceShadowDistance = options.maxShadowDistance;
if (options.screenSpaceContactShadows) {
shadowTechnique |= ShadowTechnique::SCREEN_SPACE;
}
@@ -749,7 +760,7 @@ void ShadowMapManager::preparePointShadowMap(ShadowMap& shadowMap,
};
auto shaderParameters = shadowMap.updatePoint(mEngine, lightData, lightIndex,
mainCameraInfo, shadowMapInfo, *view.getScene(), sceneInfo, face);
mainCameraInfo, shadowMapInfo, *view.getScene(), face);
// and if we need to generate it, update all the UBO data
@@ -812,8 +823,8 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine
return shadowTechnique;
}
void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view,
FScene::LightSoa& lightData) noexcept {
void ShadowMapManager::calculateTextureRequirements(FEngine&, FView& view,
FScene::LightSoa&) noexcept {
// Lay out the shadow maps. For now, we take the largest requested dimension and allocate a
// texture of that size. Each cascade / shadow map gets its own layer in the array texture.
@@ -867,7 +878,7 @@ void ShadowMapManager::calculateTextureRequirements(FEngine& engine, FView& view
if (useMipmapping) {
// Limit the lowest mipmap level to 256x256.
// This avoids artifacts on high derivative tangent surfaces.
int lowMipmapLevel = 7; // log2(256) - 1
int const lowMipmapLevel = 7; // log2(256) - 1
mipLevels = std::max(1, FTexture::maxLevelCount(maxDimension) - lowMipmapLevel);
}

View File

@@ -110,7 +110,7 @@ public:
private:
ShadowMapManager::ShadowTechnique updateCascadeShadowMaps(FEngine& engine,
FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData,
FScene::LightSoa& lightData, ShadowMap::SceneInfo& sceneInfo) noexcept;
FScene::LightSoa& lightData, ShadowMap::SceneInfo sceneInfo) noexcept;
ShadowMapManager::ShadowTechnique updateSpotShadowMaps(FEngine& engine,
FScene::LightSoa& lightData) noexcept;

View File

@@ -49,76 +49,136 @@ FScene::FScene(FEngine& engine) :
FScene::~FScene() noexcept = default;
void FScene::prepare(const mat4& worldOriginTransform, bool shadowReceiversAreCasters) noexcept {
void FScene::prepare(utils::JobSystem& js,
LinearAllocatorArena& allocator,
const mat4& worldOriginTransform,
bool shadowReceiversAreCasters) noexcept {
// TODO: can we skip this in most cases? Since we rely on indices staying the same,
// we could only skip, if nothing changed in the RCM.
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
// This will reset the allocator upon exiting
ArenaScope const arena(allocator);
FEngine& engine = mEngine;
EntityManager& em = engine.getEntityManager();
FRenderableManager& rcm = engine.getRenderableManager();
FTransformManager& tcm = engine.getTransformManager();
FLightManager& lcm = engine.getLightManager();
EntityManager const& em = engine.getEntityManager();
FRenderableManager const& rcm = engine.getRenderableManager();
FTransformManager const& tcm = engine.getTransformManager();
FLightManager const& lcm = engine.getLightManager();
// go through the list of entities, and gather the data of those that are renderables
auto& sceneData = mRenderableData;
auto& lightData = mLightData;
auto const& entities = mEntities;
using RenderableContainerData = std::pair<RenderableManager::Instance, TransformManager::Instance>;
using RenderableInstanceContainer = FixedCapacityVector<RenderableContainerData,
utils::STLAllocator< RenderableContainerData, LinearAllocatorArena >, false>;
// NOTE: we can't know in advance how many entities are renderable or lights because the corresponding
// component can be added after the entity is added to the scene.
using LightContainerData = std::pair<LightManager::Instance, TransformManager::Instance>;
using LightInstanceContainer = FixedCapacityVector<LightContainerData,
utils::STLAllocator< LightContainerData, LinearAllocatorArena >, false>;
size_t renderableDataCapacity = entities.size();
// we need the capacity to be multiple of 16 for SIMD loops
renderableDataCapacity = (renderableDataCapacity + 0xFu) & ~0xFu;
// we need 1 extra entry at the end for the summed primitive count
renderableDataCapacity = renderableDataCapacity + 1;
RenderableInstanceContainer renderableInstances{
RenderableInstanceContainer::with_capacity(entities.size(), allocator) };
sceneData.clear();
if (sceneData.capacity() < renderableDataCapacity) {
sceneData.setCapacity(renderableDataCapacity);
}
// The light data list will always contain at least one entry for the
// dominating directional light, even if there are no entities.
size_t lightDataCapacity = std::max<size_t>(1, entities.size());
// we need the capacity to be multiple of 16 for SIMD loops
lightDataCapacity = (lightDataCapacity + 0xFu) & ~0xFu;
lightData.clear();
if (lightData.capacity() < lightDataCapacity) {
lightData.setCapacity(lightDataCapacity);
}
// the first entries are reserved for the directional lights (currently only one)
lightData.resize(DIRECTIONAL_LIGHTS_COUNT);
LightInstanceContainer lightInstances{
LightInstanceContainer::with_capacity(entities.size(), allocator) };
SYSTRACE_NAME_BEGIN("InstanceLoop");
// find the max intensity directional light index in our local array
float maxIntensity = 0.0f;
std::pair<LightManager::Instance, TransformManager::Instance> directionalLightInstances{};
for (Entity e : entities) {
if (!em.isAlive(e)) {
continue;
/*
* First compute the exact number of renderables and lights in the scene.
* Also find the main directional light.
*/
for (Entity const e: entities) {
if (UTILS_LIKELY(em.isAlive(e))) {
auto ti = tcm.getInstance(e);
auto li = lcm.getInstance(e);
auto ri = rcm.getInstance(e);
if (li) {
// we handle the directional light here because it'd prevent multithreading below
if (UTILS_UNLIKELY(lcm.isDirectionalLight(li))) {
// we don't store the directional lights, because we only have a single one
if (lcm.getIntensity(li) >= maxIntensity) {
maxIntensity = lcm.getIntensity(li);
directionalLightInstances = { li, ti };
}
} else {
lightInstances.emplace_back(li, ti);
}
}
if (ri) {
renderableInstances.emplace_back(ri, ti);
}
}
}
// getInstance() always returns null if the entity is the Null entity,
// so we don't need to check for that, but we need to check it's alive
auto ri = rcm.getInstance(e);
auto li = lcm.getInstance(e);
if (!ri && !li) {
continue;
SYSTRACE_NAME_END();
/*
* Evaluate the capacity needed for the renderable and light SoAs
*/
// we need the capacity to be multiple of 16 for SIMD loops
// we need 1 extra entry at the end for the summed primitive count
size_t renderableDataCapacity = entities.size();
renderableDataCapacity = (renderableDataCapacity + 0xFu) & ~0xFu;
renderableDataCapacity = renderableDataCapacity + 1;
// The light data list will always contain at least one entry for the
// dominating directional light, even if there are no entities.
// we need the capacity to be multiple of 16 for SIMD loops
size_t lightDataCapacity = std::max<size_t>(DIRECTIONAL_LIGHTS_COUNT, entities.size());
lightDataCapacity = (lightDataCapacity + 0xFu) & ~0xFu;
/*
* Now resize the SoAs if needed
*/
// TODO: the resize below could happen in a job
if (sceneData.size() != renderableInstances.size()) {
sceneData.clear();
if (sceneData.capacity() < renderableDataCapacity) {
sceneData.setCapacity(renderableDataCapacity);
}
assert_invariant(renderableInstances.size() <= sceneData.capacity());
sceneData.resize(renderableInstances.size());
}
// get the world transform
auto ti = tcm.getInstance(e);
// this is where we go from double to float for our transforms
const mat4f worldTransform{ worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
const bool reversedWindingOrder = det(worldTransform.upperLeft()) < 0;
if (lightData.size() != lightInstances.size() + DIRECTIONAL_LIGHTS_COUNT) {
lightData.clear();
if (lightData.capacity() < lightDataCapacity) {
lightData.setCapacity(lightDataCapacity);
}
assert_invariant(lightInstances.size() + DIRECTIONAL_LIGHTS_COUNT <= lightData.capacity());
lightData.resize(lightInstances.size() + DIRECTIONAL_LIGHTS_COUNT);
}
/*
* Fill the SoA with the JobSystem
*/
auto renderableWork = [first = renderableInstances.data(), &rcm, &tcm, &worldOriginTransform,
&sceneData, shadowReceiversAreCasters](auto* p, auto c) {
SYSTRACE_NAME("renderableWork");
for (size_t i = 0; i < c; i++) {
auto [ri, ti] = p[i];
// this is where we go from double to float for our transforms
const mat4f worldTransform{
worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
const bool reversedWindingOrder = det(worldTransform.upperLeft()) < 0;
// don't even draw this object if it doesn't have a transform (which shouldn't happen
// because one is always created when creating a Renderable component).
if (ri && ti) {
// compute the world AABB so we can perform culling
const Box worldAABB = rigidTransform(rcm.getAABB(ri), worldTransform);
@@ -131,81 +191,117 @@ void FScene::prepare(const mat4& worldOriginTransform, bool shadowReceiversAreCa
// FIXME: We compute and store the local scale because it's needed for glTF but
// we need a better way to handle this
const mat4f& transform = tcm.getTransform(ti);
float scale = (length(transform[0].xyz) + length(transform[1].xyz) +
length(transform[2].xyz)) / 3.0f;
float const scale = (length(transform[0].xyz) + length(transform[1].xyz) +
length(transform[2].xyz)) / 3.0f;
// we know there is enough space in the array
sceneData.push_back_unsafe(
ri, // RENDERABLE_INSTANCE
worldTransform, // WORLD_TRANSFORM
visibility, // VISIBILITY_STATE
rcm.getSkinningBufferInfo(ri), // SKINNING_BUFFER
rcm.getMorphingBufferInfo(ri), // MORPHING_BUFFER
worldAABB.center, // WORLD_AABB_CENTER
0, // VISIBLE_MASK
rcm.getChannels(ri), // CHANNELS
rcm.getInstanceCount(ri), // INSTANCE_COUNT
rcm.getLayerMask(ri), // LAYERS
worldAABB.halfExtent, // WORLD_AABB_EXTENT
{}, // PRIMITIVES
0, // SUMMED_PRIMITIVE_COUNT
{}, // UBO
scale // USER_DATA
);
size_t const index = std::distance(first, p) + i;
assert_invariant(index < sceneData.size());
sceneData.elementAt<RENDERABLE_INSTANCE>(index) = ri;
sceneData.elementAt<WORLD_TRANSFORM>(index) = worldTransform;
sceneData.elementAt<VISIBILITY_STATE>(index) = visibility;
sceneData.elementAt<SKINNING_BUFFER>(index) = rcm.getSkinningBufferInfo(ri);
sceneData.elementAt<MORPHING_BUFFER>(index) = rcm.getMorphingBufferInfo(ri);
sceneData.elementAt<WORLD_AABB_CENTER>(index) = worldAABB.center;
sceneData.elementAt<VISIBLE_MASK>(index) = 0;
sceneData.elementAt<CHANNELS>(index) = rcm.getChannels(ri);
sceneData.elementAt<INSTANCE_COUNT>(index) = rcm.getInstanceCount(ri);
sceneData.elementAt<LAYERS>(index) = rcm.getLayerMask(ri);
sceneData.elementAt<WORLD_AABB_EXTENT>(index) = worldAABB.halfExtent;
//sceneData.elementAt<PRIMITIVES>(index) = {}; // already initialized, Slice<>
sceneData.elementAt<SUMMED_PRIMITIVE_COUNT>(index) = 0;
//sceneData.elementAt<UBO>(index) = {}; // not needed here
sceneData.elementAt<USER_DATA>(index) = scale;
}
};
if (li) {
// find the dominant directional light
if (UTILS_UNLIKELY(lcm.isDirectionalLight(li))) {
// we don't store the directional lights, because we only have a single one
if (lcm.getIntensity(li) >= maxIntensity) {
maxIntensity = lcm.getIntensity(li);
float3 d = lcm.getLocalDirection(li);
// using mat3f::getTransformForNormals handles non-uniform scaling
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
lightData.elementAt<FScene::POSITION_RADIUS>(0) =
float4{ 0, 0, 0, std::numeric_limits<float>::infinity() };
lightData.elementAt<FScene::DIRECTION>(0) = d;
lightData.elementAt<FScene::LIGHT_INSTANCE>(0) = li;
}
} else {
const float4 p = worldTransform * float4{ lcm.getLocalPosition(li), 1 };
float3 d = 0;
if (!lcm.isPointLight(li) || lcm.isIESLight(li)) {
d = lcm.getLocalDirection(li);
// using mat3f::getTransformForNormals handles non-uniform scaling
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
}
lightData.push_back_unsafe(
float4{ p.xyz, lcm.getRadius(li) }, d, li, {}, {}, {});
auto lightWork = [first = lightInstances.data(), &lcm, &tcm, &worldOriginTransform,
&lightData](auto* p, auto c) {
SYSTRACE_NAME("lightWork");
for (size_t i = 0; i < c; i++) {
auto [li, ti] = p[i];
// this is where we go from double to float for our transforms
const mat4f worldTransform{ worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
const float4 position = worldTransform * float4{ lcm.getLocalPosition(li), 1 };
float3 d = 0;
if (!lcm.isPointLight(li) || lcm.isIESLight(li)) {
d = lcm.getLocalDirection(li);
// using mat3f::getTransformForNormals handles non-uniform scaling
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
}
size_t const index = DIRECTIONAL_LIGHTS_COUNT + std::distance(first, p) + i;
assert_invariant(index < lightData.size());
lightData.elementAt<POSITION_RADIUS>(index) = float4{ position.xyz, lcm.getRadius(li) };
lightData.elementAt<DIRECTION>(index) = d;
lightData.elementAt<LIGHT_INSTANCE>(index) = li;
}
};
SYSTRACE_NAME_BEGIN("Renderable and Light jobs");
JobSystem::Job* rootJob = js.createJob();
auto* renderableJob = jobs::parallel_for(js, rootJob,
renderableInstances.data(), renderableInstances.size(),
std::cref(renderableWork), jobs::CountSplitter<128, 5>());
auto* lightJob = jobs::parallel_for(js, rootJob,
lightInstances.data(), lightInstances.size(),
std::cref(lightWork), jobs::CountSplitter<32, 5>());
js.run(renderableJob);
js.run(lightJob);
// Everything below can be done in parallel.
/*
* Handle the directional light separately
*/
if (auto [li, ti] = directionalLightInstances ; li) {
const mat4f worldTransform{
worldOriginTransform * tcm.getWorldTransformAccurate(ti) };
// using mat3f::getTransformForNormals handles non-uniform scaling
float3 d = lcm.getLocalDirection(li);
d = normalize(mat3f::getTransformForNormals(worldTransform.upperLeft()) * d);
constexpr float inf = std::numeric_limits<float>::infinity();
lightData.elementAt<POSITION_RADIUS>(0) = float4{ 0, 0, 0, inf };
lightData.elementAt<DIRECTION>(0) = d;
lightData.elementAt<LIGHT_INSTANCE>(0) = li;
} else {
lightData.elementAt<LIGHT_INSTANCE>(0) = 0;
}
// some elements past the end of the array will be accessed by SIMD code, we need to make
// sure the data is valid enough as not to produce errors such as divide-by-zero
// (e.g. in computeLightRanges())
for (size_t i = lightData.size(), e = lightDataCapacity; i < e; i++) {
for (size_t i = lightData.size(), e = lightData.capacity(); i < e; i++) {
new(lightData.data<POSITION_RADIUS>() + i) float4{ 0, 0, 0, 1 };
}
// Purely for the benefit of MSAN, we can avoid uninitialized reads by zeroing out the
// unused scene elements between the end of the array and the rounded-up count.
if (UTILS_HAS_SANITIZE_MEMORY) {
for (size_t i = sceneData.size(), e = renderableDataCapacity; i < e; i++) {
for (size_t i = sceneData.size(), e = sceneData.capacity(); i < e; i++) {
sceneData.data<LAYERS>()[i] = 0;
sceneData.data<VISIBLE_MASK>()[i] = 0;
sceneData.data<VISIBILITY_STATE>()[i] = {};
}
}
js.runAndWait(rootJob);
SYSTRACE_NAME_END();
}
void FScene::prepareVisibleRenderables(Range<uint32_t> visibleRenderables) noexcept {
SYSTRACE_CALL();
RenderableSoa& sceneData = mRenderableData;
FRenderableManager& rcm = mEngine.getRenderableManager();
FRenderableManager const& rcm = mEngine.getRenderableManager();
mHasContactShadows = false;
for (uint32_t i : visibleRenderables) {
for (uint32_t const i : visibleRenderables) {
PerRenderableData& uboData = sceneData.elementAt<UBO>(i);
auto const visibility = sceneData.elementAt<VISIBILITY_STATE>(i);
@@ -257,6 +353,7 @@ void FScene::prepareVisibleRenderables(Range<uint32_t> visibleRenderables) noexc
void FScene::updateUBOs(
Range<uint32_t> visibleRenderables,
Handle<HwBufferObject> renderableUbh) noexcept {
SYSTRACE_CALL();
FEngine::DriverApi& driver = mEngine.getDriverApi();
// store the UBO handle
@@ -278,7 +375,7 @@ void FScene::updateUBOs(
// copy our data into the UBO for each visible renderable
PerRenderableData const* const uboData = mRenderableData.data<UBO>();
for (uint32_t i : visibleRenderables) {
for (uint32_t const i : visibleRenderables) {
buffer[i] = uboData[i];
}
@@ -308,15 +405,15 @@ void FScene::updateUBOs(
}
}
void FScene::terminate(FEngine& engine) {
void FScene::terminate(FEngine&) {
// DO NOT destroy this UBO, it's owned by the View
mRenderableViewUbh.clear();
}
void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootArena,
void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope&,
Handle<HwBufferObject> lightUbh) noexcept {
FEngine::DriverApi& driver = mEngine.getDriverApi();
FLightManager& lcm = mEngine.getLightManager();
FLightManager const& lcm = mEngine.getLightManager();
FScene::LightSoa& lightData = getLightData();
/*
@@ -325,7 +422,7 @@ void FScene::prepareDynamicLights(const CameraInfo& camera, ArenaScope& rootAren
size_t const size = lightData.size();
// number of point-light/spotlights
size_t positionalLightCount = size - DIRECTIONAL_LIGHTS_COUNT;
size_t const positionalLightCount = size - DIRECTIONAL_LIGHTS_COUNT;
assert_invariant(positionalLightCount);
float4 const* const UTILS_RESTRICT spheres = lightData.data<FScene::POSITION_RADIUS>();
@@ -418,11 +515,11 @@ void FScene::removeEntities(const Entity* entities, size_t count) {
UTILS_NOINLINE
size_t FScene::getRenderableCount() const noexcept {
FEngine& engine = mEngine;
EntityManager& em = engine.getEntityManager();
FRenderableManager& rcm = engine.getRenderableManager();
EntityManager const& em = engine.getEntityManager();
FRenderableManager const& rcm = engine.getRenderableManager();
size_t count = 0;
auto const& entities = mEntities;
for (Entity e : entities) {
for (Entity const e : entities) {
count += em.isAlive(e) && rcm.getInstance(e) ? 1 : 0;
}
return count;
@@ -431,11 +528,11 @@ size_t FScene::getRenderableCount() const noexcept {
UTILS_NOINLINE
size_t FScene::getLightCount() const noexcept {
FEngine& engine = mEngine;
EntityManager& em = engine.getEntityManager();
FLightManager& lcm = engine.getLightManager();
EntityManager const& em = engine.getEntityManager();
FLightManager const& lcm = engine.getLightManager();
size_t count = 0;
auto const& entities = mEntities;
for (Entity e : entities) {
for (Entity const e : entities) {
count += em.isAlive(e) && lcm.getInstance(e) ? 1 : 0;
}
return count;

View File

@@ -70,7 +70,8 @@ public:
~FScene() noexcept;
void terminate(FEngine& engine);
void prepare(const math::mat4& worldOriginTransform, bool shadowReceiversAreCasters) noexcept;
void prepare(utils::JobSystem& js, LinearAllocatorArena& allocator,
math::mat4 const& worldOriginTransform, bool shadowReceiversAreCasters) noexcept;
void prepareVisibleRenderables(utils::Range<uint32_t> visibleRenderables) noexcept;

View File

@@ -268,9 +268,8 @@ bool FView::isSkyboxVisible() const noexcept {
return skybox != nullptr && (skybox->getLayerMask() & mVisibleLayers);
}
void FView::prepareShadowing(FEngine& engine, DriverApi& driver,
FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData,
CameraInfo const& cameraInfo) noexcept {
void FView::prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableData,
FScene::LightSoa& lightData, CameraInfo const& cameraInfo) noexcept {
SYSTRACE_CALL();
mHasShadowing = false;
@@ -284,7 +283,7 @@ void FView::prepareShadowing(FEngine& engine, DriverApi& driver,
auto& lcm = engine.getLightManager();
// dominant directional light is always as index 0
FLightManager::Instance directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
FLightManager::Instance const directionalLight = lightData.elementAt<FScene::LIGHT_INSTANCE>(0);
const bool hasDirectionalShadows = directionalLight && lcm.isShadowCaster(directionalLight);
if (UTILS_UNLIKELY(hasDirectionalShadows)) {
const auto& shadowOptions = lcm.getShadowOptions(directionalLight);
@@ -338,6 +337,7 @@ void FView::prepareShadowing(FEngine& engine, DriverApi& driver,
void FView::prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaScope& arena,
filament::Viewport const& viewport, CameraInfo const& cameraInfo) noexcept {
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
FScene* const scene = mScene;
auto const& lightData = scene->getLightData();
@@ -433,6 +433,9 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
filament::Viewport const& viewport, CameraInfo const& cameraInfo,
float4 const& userTime, bool needsAlphaChannel) noexcept {
SYSTRACE_CALL();
SYSTRACE_CONTEXT();
JobSystem& js = engine.getJobSystem();
/*
@@ -464,7 +467,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
* Gather all information needed to render this scene. Apply the world origin to all
* objects in the scene.
*/
scene->prepare(cameraInfo.worldOrigin, hasVSM());
scene->prepare(js, arena.getAllocator(), cameraInfo.worldOrigin, hasVSM());
/*
* Light culling: runs in parallel with Renderable culling (below)
@@ -504,7 +507,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
if (prepareVisibleLightsJob) {
js.waitAndRelease(prepareVisibleLightsJob);
}
prepareShadowing(engine, driver, renderableData, scene->getLightData(), cameraInfo);
prepareShadowing(engine, renderableData, scene->getLightData(), cameraInfo);
/*
* Partition the SoA so that renderables are partitioned w.r.t their visibility into the
@@ -528,6 +531,8 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
// TODO: we need to compare performance of doing this partitioning vs not doing it.
// and rely on checking visibility in the loops
SYSTRACE_NAME_BEGIN("Partitioning");
// calculate the sorting key for all elements, based on their visibility
uint8_t const* layers = renderableData.data<FScene::LAYERS>();
auto const* visibility = renderableData.data<FScene::VISIBILITY_STATE>();
@@ -568,6 +573,8 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
mSpotLightShadowCasters = merged;
SYSTRACE_NAME_END();
// TODO: when any spotlight is used, `merged` ends-up being the whole list. However,
// some of the items will end-up not being visible by any light. Can we do better?
// e.g. could we deffer some of the prepareVisibleRenderables() to later?

View File

@@ -140,9 +140,8 @@ public:
const Viewport& physicalViewport,
const filament::Viewport& logicalViewport) const noexcept;
void prepareShadowing(FEngine& engine, backend::DriverApi& driver,
FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData,
CameraInfo const& cameraInfo) noexcept;
void prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableData,
FScene::LightSoa& lightData, CameraInfo const& cameraInfo) noexcept;
void prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaScope& arena,
filament::Viewport const& viewport, CameraInfo const &cameraInfo) noexcept;

View File

@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.31.6"
spec.version = "1.31.7"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.31.6/filament-v1.31.6-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.31.7/filament-v1.31.7-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {

View File

@@ -175,6 +175,7 @@ static_assert(sizeof(PerViewUib) == sizeof(math::float4) * 128,
struct PerRenderableData {
struct alignas(16) vec3_std140 : public std::array<float, 3> { };
struct alignas(16) vec4_std140 : public std::array<float, 4> { };
struct mat33_std140 : public std::array<vec3_std140, 3> {
mat33_std140& operator=(math::mat3f const& rhs) noexcept {
for (int i = 0; i < 3; i++) {
@@ -185,8 +186,19 @@ struct PerRenderableData {
return *this;
}
};
struct mat44_std140 : public std::array<vec4_std140, 4> {
mat44_std140& operator=(math::mat4f const& rhs) noexcept {
for (int i = 0; i < 4; i++) {
(*this)[i][0] = rhs[i][0];
(*this)[i][1] = rhs[i][1];
(*this)[i][2] = rhs[i][2];
(*this)[i][3] = rhs[i][3];
}
return *this;
}
};
math::mat4f worldFromModelMatrix;
mat44_std140 worldFromModelMatrix;
mat33_std140 worldFromModelNormalMatrix;
uint32_t morphTargetCount;
uint32_t flagsChannels; // see packFlags() below (0x00000fll)
@@ -204,6 +216,13 @@ struct PerRenderableData {
channels;
}
};
#ifndef _MSC_VER
// not sure why this static_assert fails on MSVC
static_assert(std::is_trivially_default_constructible_v<PerRenderableData>,
"make sure PerRenderableData stays trivially_default_constructible");
#endif
static_assert(sizeof(PerRenderableData) == 256,
"sizeof(PerRenderableData) must be 256 bytes");

View File

@@ -14,6 +14,7 @@ set(PUBLIC_HDRS
)
set(SRCS
src/MikktspaceImpl.cpp
src/SurfaceOrientation.cpp
src/TangentSpaceMesh.cpp
src/Transcoder.cpp
@@ -26,7 +27,13 @@ include_directories(${PUBLIC_HDR_DIR})
add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRCS})
set(GEOMETRY_DEPS
meshoptimizer
mikktspace
)
target_link_libraries(${TARGET} PUBLIC math utils)
target_link_libraries(${TARGET} PRIVATE ${GEOMETRY_DEPS})
target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
set_target_properties(${TARGET} PROPERTIES FOLDER Libs)
@@ -47,6 +54,19 @@ endif()
install(TARGETS ${TARGET} ARCHIVE DESTINATION lib/${DIST_DIR})
install(DIRECTORY ${PUBLIC_HDR_DIR}/geometry DESTINATION include)
set(COMBINED_DEPS
${TARGET}
${GEOMETRY_DEPS}
)
# Combine the deps into a single static lib so that client only have to link this lib and not have
# to link its dependencies.
set(GEOMETRY_COMBINED_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/libgeometry_combined.a")
combine_static_libs(${TARGET} "${GEOMETRY_COMBINED_OUTPUT}" "${COMBINED_DEPS}")
set(GEOMETRY_LIB_NAME ${CMAKE_STATIC_LIBRARY_PREFIX}geometry${CMAKE_STATIC_LIBRARY_SUFFIX})
install(FILES "${GEOMETRY_COMBINED_OUTPUT}" DESTINATION lib/${DIST_DIR} RENAME ${GEOMETRY_LIB_NAME})
# ==================================================================================================
# Tests
# ==================================================================================================

View File

@@ -20,7 +20,6 @@
#include <math/quat.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <utils/compiler.h>
namespace filament {
namespace geometry {

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MikktspaceImpl.h"
#include <math/mat3.h>
#include <math/norm.h>
#include <meshoptimizer.h>
#include <mikktspace/mikktspace.h>
#include <vector>
namespace filament::geometry {
using namespace filament::math;
int MikktspaceImpl::getNumFaces(SMikkTSpaceContext const* context) noexcept {
auto const wrapper = MikktspaceImpl::getThis(context);
return wrapper->mFaceCount;
}
int MikktspaceImpl::getNumVerticesOfFace(SMikkTSpaceContext const* context,
int const iFace) noexcept {
return 3;
}
void MikktspaceImpl::getPosition(SMikkTSpaceContext const* context, float fvPosOut[],
const int iFace, const int iVert) noexcept {
auto const wrapper = MikktspaceImpl::getThis(context);
float3 const pos = *pointerAdd(wrapper->mPositions, wrapper->getTriangle(iFace)[iVert],
wrapper->mPositionStride);
fvPosOut[0] = pos.x;
fvPosOut[1] = pos.y;
fvPosOut[2] = pos.z;
}
void MikktspaceImpl::getNormal(SMikkTSpaceContext const* context, float fvNormOut[],
int const iFace, int const iVert) noexcept {
auto const wrapper = MikktspaceImpl::getThis(context);
float3 const normal = *pointerAdd(wrapper->mNormals, wrapper->getTriangle(iFace)[iVert],
wrapper->mNormalStride);
fvNormOut[0] = normal.x;
fvNormOut[1] = normal.y;
fvNormOut[2] = normal.z;
}
void MikktspaceImpl::getTexCoord(SMikkTSpaceContext const* context, float fvTexcOut[],
const int iFace, const int iVert) noexcept {
auto const wrapper = MikktspaceImpl::getThis(context);
float2 const texc =
*pointerAdd(wrapper->mUVs, wrapper->getTriangle(iFace)[iVert], wrapper->mUVStride);
fvTexcOut[0] = texc.x;
fvTexcOut[1] = texc.y;
}
void MikktspaceImpl::setTSpaceBasic(SMikkTSpaceContext const* context, float const fvTangent[],
float const fSign, int const iFace, int const iVert) noexcept {
auto const wrapper = MikktspaceImpl::getThis(context);
uint32_t const vertInd = wrapper->getTriangle(iFace)[iVert];
float3 const pos = *pointerAdd(wrapper->mPositions, vertInd, wrapper->mPositionStride);
float3 const n = normalize(*pointerAdd(wrapper->mNormals, vertInd, wrapper->mNormalStride));
float2 const uv = *pointerAdd(wrapper->mUVs, vertInd, wrapper->mUVStride);
float3 const t{fvTangent[0], fvTangent[1], fvTangent[2]};
float3 const b = fSign * normalize(cross(n, t));
// TODO: packTangentFrame actually changes the orientation of b.
quatf const quat = mat3f::packTangentFrame({t, b, n}, sizeof(int32_t));
wrapper->mOutVertices.push_back({pos, uv, quat});
}
MikktspaceImpl::MikktspaceImpl(const TangentSpaceMeshInput* input) noexcept
: mFaceCount((int) input->triangleCount),
mPositions(input->positions),
mPositionStride(input->positionStride ? input->positionStride : sizeof(float3)),
mNormals(input->normals),
mNormalStride(input->normalStride ? input->normalStride : sizeof(float3)),
mUVs(input->uvs),
mUVStride(input->uvStride ? input->uvStride : sizeof(float2)),
mIsTriangle16(input->triangles16),
mTriangles(
input->triangles16 ? (uint8_t*) input->triangles16 : (uint8_t*) input->triangles32) {
mOutVertices.reserve(mFaceCount * 3);
}
MikktspaceImpl* MikktspaceImpl::getThis(SMikkTSpaceContext const* context) noexcept {
return (MikktspaceImpl*) context->m_pUserData;
}
inline const uint3 MikktspaceImpl::getTriangle(int triangleIndex) const noexcept {
const size_t tstride = mIsTriangle16 ? sizeof(ushort3) : sizeof(uint3);
return mIsTriangle16 ? uint3(*(ushort3*) (pointerAdd(mTriangles, triangleIndex, tstride)))
: *(uint3*) (pointerAdd(mTriangles, triangleIndex, tstride));
}
void MikktspaceImpl::run(TangentSpaceMeshOutput* output) noexcept {
SMikkTSpaceInterface interface {
.m_getNumFaces = MikktspaceImpl::getNumFaces,
.m_getNumVerticesOfFace = MikktspaceImpl::getNumVerticesOfFace,
.m_getPosition = MikktspaceImpl::getPosition,
.m_getNormal = MikktspaceImpl::getNormal,
.m_getTexCoord = MikktspaceImpl::getTexCoord,
.m_setTSpaceBasic = MikktspaceImpl::setTSpaceBasic
};
SMikkTSpaceContext context{.m_pInterface = &interface, .m_pUserData = this};
genTangSpaceDefault(&context);
std::vector<unsigned int> remap(mOutVertices.size());
size_t vertexCount = meshopt_generateVertexRemap(remap.data(), NULL, mOutVertices.size(),
mOutVertices.data(), mOutVertices.size(), sizeof(IOVertex));
std::vector<IOVertex> newVertices(vertexCount);
meshopt_remapVertexBuffer((void*) newVertices.data(), mOutVertices.data(), mOutVertices.size(),
sizeof(IOVertex), remap.data());
uint3* triangles32 = new uint3[mFaceCount];
meshopt_remapIndexBuffer((uint32_t*) triangles32, NULL, mOutVertices.size(), remap.data());
float3* outPositions = new float3[vertexCount];
float2* outUVs = new float2[vertexCount];
quatf* outQuats = new quatf[vertexCount];
for (size_t i = 0; i < vertexCount; ++i) {
outPositions[i] = newVertices[i].position;
outUVs[i] = newVertices[i].uv;
outQuats[i] = newVertices[i].tangentSpace;
}
output->vertexCount = vertexCount;
output->positions = outPositions;
output->uvs = outUVs;
output->tangentSpace = outQuats;
output->triangles32 = triangles32;
output->triangleCount = mFaceCount;
}
}// namespace filament::geometry

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_GEOMETRY_MIKKTSPACEIMPL_H
#define TNT_GEOMETRY_MIKKTSPACEIMPL_H
#include "TangentSpaceMeshInternal.h"
#include <math/quat.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <vector>
struct SMikkTSpaceContext;
namespace filament::geometry {
using namespace filament::math;
class MikktspaceImpl {
public:
struct IOVertex {
float3 position;
float2 uv;
quatf tangentSpace;
};
MikktspaceImpl(const TangentSpaceMeshInput* input) noexcept;
MikktspaceImpl(const MikktspaceImpl&) = delete;
MikktspaceImpl& operator=(const MikktspaceImpl&) = delete;
void run(TangentSpaceMeshOutput* output) noexcept;
private:
static int getNumFaces(SMikkTSpaceContext const* context) noexcept;
static int getNumVerticesOfFace(SMikkTSpaceContext const* context, int const iFace) noexcept;
static void getPosition(SMikkTSpaceContext const* context, float fvPosOut[], const int iFace,
const int iVert) noexcept;
static void getNormal(SMikkTSpaceContext const* context, float fvNormOut[], int const iFace,
int const iVert) noexcept;
static void getTexCoord(SMikkTSpaceContext const* context, float fvTexcOut[], const int iFace,
const int iVert) noexcept;
static void setTSpaceBasic(SMikkTSpaceContext const* context, float const fvTangent[],
float const fSign, int const iFace, int const iVert) noexcept;
static MikktspaceImpl* getThis(SMikkTSpaceContext const* context) noexcept;
inline const uint3 getTriangle(int triangleIndex) const noexcept;
int const mFaceCount;
float3 const* mPositions;
size_t const mPositionStride;
float3 const* mNormals;
size_t const mNormalStride;
float2 const* mUVs;
size_t const mUVStride;
uint8_t const* mTriangles;
bool mIsTriangle16;
std::vector<IOVertex> mOutVertices;
};
}// namespace filament::geometry
#endif//TNT_GEOMETRY_MIKKTSPACEIMPL_H

View File

@@ -16,61 +16,36 @@
#include <geometry/TangentSpaceMesh.h>
#include "MikktspaceImpl.h"
#include "TangentSpaceMeshInternal.h"
#include <math/mat3.h>
#include <math/norm.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <vector>
namespace filament {
namespace geometry {
using namespace filament::math;
using Builder = TangentSpaceMesh::Builder;
using Algorithm = TangentSpaceMesh::Algorithm;
using MethodPtr = void(*)(const TangentSpaceMeshInput*, TangentSpaceMeshOutput*);
using NormalsOnlyKernelPtr = void(*)(const float3& N, float3& T, float3& B);
struct TangentSpaceMeshInput {
size_t vertexCount = 0;
const float3* normals = nullptr;
const float2* uvs = nullptr;
const float3* positions = nullptr;
const ushort3* triangles16 = nullptr;
const uint3* triangles32 = nullptr;
size_t normalStride = 0;
size_t uvStride = 0;
size_t positionStride = 0;
size_t triangleCount = 0;
Algorithm algorithm;
};
struct TangentSpaceMeshOutput {
Algorithm algorithm;
size_t triangleCount = 0;
size_t vertexCount = 0;
quatf const* tangentSpace = nullptr;
float2 const* uvs = nullptr;
float3 const* positions = nullptr;
uint3 const* triangles32 = nullptr;
ushort3 const* triangles16 = nullptr;
};
using MethodPtr = void(*)(TangentSpaceMeshInput const*, TangentSpaceMeshOutput*);
namespace {
const uint8_t NORMALS_BIT = 0x01;
const uint8_t UVS_BIT = 0x02;
const uint8_t POSITIONS_BIT = 0x04;
const uint8_t INDICES_BIT = 0x08;
uint8_t const NORMALS_BIT = 0x01;
uint8_t const UVS_BIT = 0x02;
uint8_t const POSITIONS_BIT = 0x04;
uint8_t const INDICES_BIT = 0x08;
// Input types
const uint8_t NORMALS = NORMALS_BIT;
const uint8_t POSITIONS_INDICES = POSITIONS_BIT | INDICES_BIT;
const uint8_t NORMALS_UVS_POSITIONS_INDICES = NORMALS_BIT | UVS_BIT | POSITIONS_BIT | INDICES_BIT;
uint8_t const NORMALS = NORMALS_BIT;
uint8_t const POSITIONS_INDICES = POSITIONS_BIT | INDICES_BIT;
uint8_t const NORMALS_UVS_POSITIONS_INDICES = NORMALS_BIT | UVS_BIT | POSITIONS_BIT | INDICES_BIT;
std::string_view to_string(Algorithm algorithm) noexcept {
switch (algorithm) {
@@ -89,20 +64,10 @@ std::string_view to_string(Algorithm algorithm) noexcept {
}
}
inline bool isInputType(const uint8_t inputType, const uint8_t checkType) noexcept {
inline bool isInputType(uint8_t const inputType, uint8_t const checkType) noexcept {
return ((inputType & checkType) == checkType);
}
template<typename InputType>
inline const InputType* pointerAdd(const InputType* ptr, size_t index, size_t stride) noexcept {
return (InputType*) (((const uint8_t*) ptr) + (index * stride));
}
template<typename InputType>
inline InputType* pointerAdd(InputType* ptr, size_t index, size_t stride) noexcept {
return (InputType*) (((uint8_t*) ptr) + (index * stride));
}
template <typename InputType>
inline void takeStride(InputType*& out, size_t stride) noexcept {
out = pointerAdd(out, 1, stride);
@@ -179,7 +144,7 @@ Algorithm selectAlgorithm(TangentSpaceMeshInput *input) noexcept {
if (!foundAlgo) {
outAlgo = selectBestDefaultAlgorithm(inputType);
utils::slog.w << "Cannot satisfy algorithm=" << to_string(input->algorithm)
<< ". Selected algorithm=" << to_string(input->algorithm) << " instead"
<< ". Selected algorithm=" << to_string(outAlgo) << " instead"
<< utils::io::endl;
}
@@ -187,32 +152,32 @@ Algorithm selectAlgorithm(TangentSpaceMeshInput *input) noexcept {
}
// The paper uses a Z-up world basis, which has been converted to Y-up here
inline std::pair<float3, float3> frisvadKernel(const float3& n) {
inline std::pair<float3, float3> frisvadKernel(float3 const& n) {
float3 b, t;
if (n.y < -1.0f + std::numeric_limits<float>::epsilon()) {
// Handle the singularity
t = float3{-1.0f, 0.0f, 0.0f};
b = float3{0.0f, 0.0f, -1.0f};
} else {
const float va = 1.0f / (1.0f + n.y);
const float vb = -n.z * n.x * va;
float const va = 1.0f / (1.0f + n.y);
float const vb = -n.z * n.x * va;
t = float3{vb, -n.z, 1.0f - n.z * n.z * va};
b = float3{1.0f - n.x * n.x * va, -n.x, vb};
}
return {b, t};
}
void frisvadMethod(const TangentSpaceMeshInput* input, TangentSpaceMeshOutput* output)
void frisvadMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* output)
noexcept {
const size_t vertexCount = input->vertexCount;
size_t const vertexCount = input->vertexCount;
quatf* quats = new quatf[vertexCount];
const float3* UTILS_RESTRICT normals = input->normals;
float3 const* UTILS_RESTRICT normals = input->normals;
size_t nstride = input->normalStride ? input->normalStride : sizeof(float3);
for (size_t qindex = 0; qindex < vertexCount; ++qindex) {
const float3 n = *normals;
const auto [b, t] = frisvadKernel(n);
float3 const n = *normals;
auto const [b, t] = frisvadKernel(n);
quats[qindex] = mat3f::packTangentFrame({t, b, n}, sizeof(int32_t));
normals = pointerAdd(normals, 1, nstride);
}
@@ -225,17 +190,16 @@ void frisvadMethod(const TangentSpaceMeshInput* input, TangentSpaceMeshOutput* o
output->triangles16 = input->triangles16;
}
void hughesMollerMethod(const TangentSpaceMeshInput* input, TangentSpaceMeshOutput* output)
void hughesMollerMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* output)
noexcept {
const size_t vertexCount = input->vertexCount;
size_t const vertexCount = input->vertexCount;
quatf* quats = new quatf[vertexCount];
const float3* UTILS_RESTRICT normals = input->normals;
float3 const* UTILS_RESTRICT normals = input->normals;
size_t nstride = input->normalStride ? input->normalStride : sizeof(float3);
for (size_t qindex = 0; qindex < vertexCount; ++qindex) {
const float3 n = *normals;
float3 const n = *normals;
float3 b, t;
if (abs(n.x) > abs(n.z) + std::numeric_limits<float>::epsilon()) {
@@ -258,7 +222,7 @@ void hughesMollerMethod(const TangentSpaceMeshInput* input, TangentSpaceMeshOutp
output->triangles16 = input->triangles16;
}
void flatShadingMethod(const TangentSpaceMeshInput* input, TangentSpaceMeshOutput* output)
void flatShadingMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* output)
noexcept {
const float3* positions = input->positions;
const size_t pstride = input->positionStride ? input->positionStride : sizeof(float3);
@@ -321,6 +285,102 @@ void flatShadingMethod(const TangentSpaceMeshInput* input, TangentSpaceMeshOutpu
output->triangleCount = outTriangleCount;
}
void mikktspaceMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* output) {
MikktspaceImpl impl(input);
impl.run(output);
}
inline float3 randomPerp(const float3& n) {
float3 perp = cross(n, float3{1, 0, 0});
float sqrlen = dot(perp, perp);
if (sqrlen <= std::numeric_limits<float>::epsilon()) {
perp = cross(n, float3{0, 1, 0});
sqrlen = dot(perp, perp);
}
return perp / sqrlen;
}
void lengyelMethod(TangentSpaceMeshInput const* input, TangentSpaceMeshOutput* output) {
size_t const vertexCount = input->vertexCount;
size_t const triangleCount = input->triangleCount;
size_t const positionStride = input->positionStride ? input->positionStride : sizeof(float3);
size_t const normalStride = input->normalStride ? input->normalStride : sizeof(float3);
size_t const uvStride = input->uvStride ? input->uvStride : sizeof(float2);
auto const* triangles16 = input->triangles16;
auto const* triangles32 = input->triangles32;
auto const* positions = input->positions;
auto const* uvs = input->uvs;
auto const* normals = input->normals;
std::vector<float3> tan1(vertexCount, float3{0.0f});
std::vector<float3> tan2(vertexCount, float3{0.0f});
for (size_t a = 0; a < triangleCount; ++a) {
uint3 tri = triangles16 ? uint3(triangles16[a]) : triangles32[a];
assert_invariant(tri.x < vertexCount && tri.y < vertexCount && tri.z < vertexCount);
float3 const& v1 = *pointerAdd(positions, tri.x, positionStride);
float3 const& v2 = *pointerAdd(positions, tri.y, positionStride);
float3 const& v3 = *pointerAdd(positions, tri.z, positionStride);
float2 const& w1 = *pointerAdd(uvs, tri.x, uvStride);
float2 const& w2 = *pointerAdd(uvs, tri.y, uvStride);
float2 const& w3 = *pointerAdd(uvs, tri.z, uvStride);
float const x1 = v2.x - v1.x;
float const x2 = v3.x - v1.x;
float const y1 = v2.y - v1.y;
float const y2 = v3.y - v1.y;
float const z1 = v2.z - v1.z;
float const z2 = v3.z - v1.z;
float const s1 = w2.x - w1.x;
float const s2 = w3.x - w1.x;
float const t1 = w2.y - w1.y;
float const t2 = w3.y - w1.y;
float const d = s1 * t2 - s2 * t1;
float3 sdir, tdir;
// In general we can't guarantee smooth tangents when the UV's are non-smooth, but let's at
// least avoid divide-by-zero and fall back to normals-only method.
if (d == 0.0) {
float3 const& n1 = *pointerAdd(normals, tri.x, normalStride);
sdir = randomPerp(n1);
tdir = cross(n1, sdir);
} else {
sdir = {t2 * x1 - t1 * x2, t2 * y1 - t1 * y2, t2 * z1 - t1 * z2};
tdir = {s1 * x2 - s2 * x1, s1 * y2 - s2 * y1, s1 * z2 - s2 * z1};
float const r = 1.0f / d;
sdir *= r;
tdir *= r;
}
tan1[tri.x] += sdir;
tan1[tri.y] += sdir;
tan1[tri.z] += sdir;
tan2[tri.x] += tdir;
tan2[tri.y] += tdir;
tan2[tri.z] += tdir;
}
quatf* quats = new quatf[vertexCount];
for (size_t a = 0; a < vertexCount; a++) {
float3 const& n = normals[a];
float3 const& t1 = tan1[a];
float3 const& t2 = tan2[a];
// Gram-Schmidt orthogonalize
float3 t = normalize(t1 - n * dot(n, t1));
// Calculate handedness
float w = (dot(cross(n, t1), t2) < 0.0f) ? -1.0f : 1.0f;
float3 b = w < 0 ? cross(t, n) : cross(n, t);
quats[a] = mat3f::packTangentFrame({t, b, n}, sizeof(int32_t));
}
output->tangentSpace = quats;
output->vertexCount = vertexCount;
output->triangleCount = triangleCount;
output->uvs = uvs;
output->positions = positions;
output->triangles32 = triangles32;
output->triangles16 = triangles16;
}
template<typename DataType, typename InputType>
inline void cleanOutputPointer(DataType*& ptr, InputType inputPtr) noexcept {
if (ptr && ptr != (const DataType*) inputPtr) {
@@ -397,12 +457,18 @@ TangentSpaceMesh* Builder::build() {
mMesh->mOutput->algorithm = selectAlgorithm(mMesh->mInput);
MethodPtr method = nullptr;
switch (mMesh->mOutput->algorithm) {
case Algorithm::FRISVAD:
method = frisvadMethod;
case Algorithm::MIKKTSPACE:
method = mikktspaceMethod;
break;
case Algorithm::LENGYEL:
method = lengyelMethod;
break;
case Algorithm::HUGHES_MOLLER:
method = hughesMollerMethod;
break;
case Algorithm::FRISVAD:
method = frisvadMethod;
break;
case Algorithm::FLAT_SHADING:
method = flatShadingMethod;
break;

View File

@@ -0,0 +1,71 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_GEOMETRY_TANGENTSPACEMESHIMPL_H
#define TNT_GEOMETRY_TANGENTSPACEMESHIMPL_H
#include <geometry/TangentSpaceMesh.h>
#include <math/mat3.h>
#include <math/norm.h>
namespace filament::geometry {
using namespace filament::math;
using Algorithm = TangentSpaceMesh::Algorithm;
struct TangentSpaceMeshInput {
size_t vertexCount = 0;
float3 const* normals = nullptr;
float2 const* uvs = nullptr;
float3 const* positions = nullptr;
ushort3 const* triangles16 = nullptr;
uint3 const* triangles32 = nullptr;
size_t normalStride = 0;
size_t uvStride = 0;
size_t positionStride = 0;
size_t triangleCount = 0;
Algorithm algorithm;
};
struct TangentSpaceMeshOutput {
Algorithm algorithm;
size_t triangleCount = 0;
size_t vertexCount = 0;
quatf const* tangentSpace = nullptr;
float2 const* uvs = nullptr;
float3 const* positions = nullptr;
uint3 const* triangles32 = nullptr;
ushort3 const* triangles16 = nullptr;
};
template<typename InputType>
inline const InputType* pointerAdd(InputType const* ptr, size_t index, size_t stride) noexcept {
return (InputType*) (((uint8_t const*) ptr) + (index * stride));
}
template<typename InputType>
inline InputType* pointerAdd(InputType* ptr, size_t index, size_t stride) noexcept {
return (InputType*) (((uint8_t*) ptr) + (index * stride));
}
}// namespace filament::geometry
#endif//TNT_GEOMETRY_TANGENTSPACEMESHIMPL_H

View File

@@ -18,7 +18,6 @@
#include <math/quat.h>
#include <math/vec3.h>
#include <utils/Log.h>
#include <gtest/gtest.h>
@@ -39,15 +38,27 @@ const std::vector<float3> CUBE_VERTS {
float3{1, 1, 1}
};
const std::vector<float2> CUBE_UVS{
const std::vector<float2> CUBE_UVS {
float2{0, 0},
float2{0, 0},
float2{1, 0},
float2{1, 1},
float2{0, 1},
float2{0, 1},
float2{1, 1},
float2{.5, 0},
float2{0, .5},
float2{.5, .5},
float2{0, 0}
float2{0, 1}
};
const float3 CUBE_CENTER{.5, .5, .5};
const std::vector<float3> CUBE_NORMALS {
normalize(CUBE_VERTS[0] - CUBE_CENTER),
normalize(CUBE_VERTS[1] - CUBE_CENTER),
normalize(CUBE_VERTS[2] - CUBE_CENTER),
normalize(CUBE_VERTS[3] - CUBE_CENTER),
normalize(CUBE_VERTS[4] - CUBE_CENTER),
normalize(CUBE_VERTS[5] - CUBE_CENTER),
normalize(CUBE_VERTS[6] - CUBE_CENTER),
normalize(CUBE_VERTS[7] - CUBE_CENTER),
};
const std::vector<ushort3> CUBE_TRIANGLES {
@@ -121,6 +132,17 @@ TEST_F(TangentSpaceMeshTest, BuilderDefaultAlgorithms) {
.build();
EXPECT_EQ(mesh->getAlgorithm(), TangentSpaceMesh::Algorithm::FRISVAD);
TangentSpaceMesh::destroy(mesh);
mesh = TangentSpaceMesh::Builder()
.vertexCount(CUBE_VERTS.size())
.positions(CUBE_VERTS.data())
.uvs(CUBE_UVS.data())
.normals(CUBE_NORMALS.data())
.triangleCount(CUBE_TRIANGLES.size())
.triangles(CUBE_TRIANGLES.data())
.build();
EXPECT_EQ(mesh->getAlgorithm(), TangentSpaceMesh::Algorithm::MIKKTSPACE);
TangentSpaceMesh::destroy(mesh);
}
// Remeshed vertices/uvs should map to input vertices/uvs
@@ -189,7 +211,6 @@ TEST_F(TangentSpaceMeshTest, FlatShading) {
TangentSpaceMesh::destroy(mesh);
}
TEST_F(TangentSpaceMeshTest, Frisvad) {
TangentSpaceMesh* mesh = TangentSpaceMesh::Builder()
.vertexCount(TEST_NORMALS.size())
@@ -244,6 +265,103 @@ TEST_F(TangentSpaceMeshTest, HughesMoller) {
TangentSpaceMesh::destroy(mesh);
}
TEST_F(TangentSpaceMeshTest, MikktspaceRemesh) {
TangentSpaceMesh* mesh = TangentSpaceMesh::Builder()
.vertexCount(CUBE_VERTS.size())
.normals(CUBE_NORMALS.data())
.positions(CUBE_VERTS.data())
.uvs(CUBE_UVS.data())
.triangleCount(CUBE_TRIANGLES.size())
.triangles(CUBE_TRIANGLES.data())
.algorithm(TangentSpaceMesh::Algorithm::MIKKTSPACE)
.build();
size_t const vertexCount = mesh->getVertexCount();
std::vector<float3> outPositions(vertexCount);
mesh->getPositions(outPositions.data());
std::vector<float2> outUVs(vertexCount);
mesh->getUVs(outUVs.data());
for (size_t i = 0; i < outPositions.size(); ++i) {
auto const& outPos = outPositions[i];
auto const& outUV = outUVs[i];
bool found = false;
for (size_t j = 0; j < CUBE_VERTS.size(); ++j) {
auto const& inPos = CUBE_VERTS[j];
auto const& inUV = CUBE_UVS[j];
if (isAlmostEqual3(outPos, inPos)) {
found = true;
EXPECT_PRED2(isAlmostEqual2, outUV, inUV);
break;
}
}
EXPECT_TRUE(found);
}
TangentSpaceMesh::destroy(mesh);
}
TEST_F(TangentSpaceMeshTest, Mikktspace) {
// It's unclear why the dot product between n and b is greater epsilon, but since we don't
// control the implementation of mikktspace, we simply add a little slack to the test.
constexpr float MAGIC_SLACK = 1.00001;
TangentSpaceMesh* mesh = TangentSpaceMesh::Builder()
.vertexCount(CUBE_VERTS.size())
.normals(CUBE_NORMALS.data())
.positions(CUBE_VERTS.data())
.uvs(CUBE_UVS.data())
.triangleCount(CUBE_TRIANGLES.size())
.triangles(CUBE_TRIANGLES.data())
.algorithm(TangentSpaceMesh::Algorithm::MIKKTSPACE)
.build();
size_t const vertexCount = mesh->getVertexCount();
std::vector<quatf> quats(vertexCount);
mesh->getQuats(quats.data());
for (size_t i = 0; i < vertexCount; ++i) {
float3 const n = quats[i] * NORMAL_AXIS;
float3 const b = quats[i] * BITANGENT_AXIS;
float3 const t = quats[i] * TANGENT_AXIS;
EXPECT_LT(abs(dot(b, t)), std::numeric_limits<float>::epsilon());
EXPECT_LT(abs(dot(n, t)), std::numeric_limits<float>::epsilon());
EXPECT_LT(abs(dot(n, b)), std::numeric_limits<float>::epsilon() * MAGIC_SLACK);
EXPECT_PRED2(isAlmostEqual3, cross(n, t), b);
}
TangentSpaceMesh::destroy(mesh);
}
TEST_F(TangentSpaceMeshTest, Lengyel) {
TangentSpaceMesh* mesh = TangentSpaceMesh::Builder()
.vertexCount(CUBE_VERTS.size())
.normals(CUBE_NORMALS.data())
.positions(CUBE_VERTS.data())
.uvs(CUBE_UVS.data())
.triangleCount(CUBE_TRIANGLES.size())
.triangles(CUBE_TRIANGLES.data())
.algorithm(TangentSpaceMesh::Algorithm::LENGYEL)
.build();
size_t const vertexCount = mesh->getVertexCount();
std::vector<quatf> quats(vertexCount);
mesh->getQuats(quats.data());
for (size_t i = 0; i < vertexCount; ++i) {
float3 const n = quats[i] * NORMAL_AXIS;
EXPECT_PRED2(isAlmostEqual3, n, CUBE_NORMALS[i]);
float3 const b = quats[i] * BITANGENT_AXIS;
float3 const t = quats[i] * TANGENT_AXIS;
EXPECT_LT(abs(dot(b, t)), std::numeric_limits<float>::epsilon());
EXPECT_LT(abs(dot(n, t)), std::numeric_limits<float>::epsilon());
EXPECT_LT(abs(dot(n, b)), std::numeric_limits<float>::epsilon());
EXPECT_PRED2(isAlmostEqual3, cross(n, t), b);
}
TangentSpaceMesh::destroy(mesh);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();

View File

@@ -60,16 +60,19 @@ private:
protected:
static constexpr size_t ENTITY_INDEX = sizeof ... (Elements);
public:
using SoA = StructureOfArrays<Elements ..., Entity>;
using Structure = typename SoA::Structure;
using Instance = EntityInstanceBase::Type;
SingleInstanceComponentManager() noexcept {
// We always start with a dummy entry because index=0 is reserved. The component
// at index = 0, is guaranteed to be default-initialized.
// Sub-classes can use this to their advantage.
mData.push_back();
mData.push_back(Structure{});
}
SingleInstanceComponentManager(SingleInstanceComponentManager&&) noexcept {/* = default */}
@@ -269,7 +272,7 @@ SingleInstanceComponentManager<Elements ...>::addComponent(Entity e) {
if (!e.isNull()) {
if (!hasComponent(e)) {
// this is like a push_back(e);
mData.push_back().template back<ENTITY_INDEX>() = e;
mData.push_back(Structure{}).template back<ENTITY_INDEX>() = e;
// index 0 is used when the component doesn't exist
ci = Instance(mData.size() - 1);
mInstanceMap[e] = ci;

View File

@@ -41,11 +41,13 @@ class StructureOfArraysBase {
static constexpr const size_t kArrayCount = sizeof...(Elements);
public:
using SoA = StructureOfArraysBase<Allocator, Elements ...>;
using SoA = StructureOfArraysBase<Allocator, Elements...>;
using Structure = std::tuple<Elements...>;
// Type of the Nth array
template<size_t N>
using TypeAt = typename std::tuple_element_t<N, std::tuple<Elements...>>;
using TypeAt = typename std::tuple_element_t<N, Structure>;
// Number of arrays
static constexpr size_t getArrayCount() noexcept { return kArrayCount; }
@@ -57,7 +59,7 @@ public:
// --------------------------------------------------------------------------------------------
class Structure;
class IteratorValue;
template<typename T> class Iterator;
using iterator = Iterator<StructureOfArraysBase*>;
using const_iterator = Iterator<StructureOfArraysBase const*>;
@@ -69,45 +71,45 @@ public:
* In other words, it's the return type of iterator::operator*(), and since it
* cannot be a C++ reference (&), it's an object that acts like it.
*/
class StructureRef {
friend class Structure;
class IteratorValueRef {
friend class IteratorValue;
friend iterator;
friend const_iterator;
StructureOfArraysBase* const UTILS_RESTRICT soa;
size_t const index;
StructureRef(StructureOfArraysBase* soa, size_t index) : soa(soa), index(index) { }
IteratorValueRef(StructureOfArraysBase* soa, size_t index) : soa(soa), index(index) { }
// assigns a value_type to a reference (i.e. assigns to what's pointed to by the reference)
template<size_t ... Is>
StructureRef& assign(Structure const& rhs, std::index_sequence<Is...>);
IteratorValueRef& assign(IteratorValue const& rhs, std::index_sequence<Is...>);
// assigns a value_type to a reference (i.e. assigns to what's pointed to by the reference)
template<size_t ... Is>
StructureRef& assign(Structure&& rhs, std::index_sequence<Is...>) noexcept;
IteratorValueRef& assign(IteratorValue&& rhs, std::index_sequence<Is...>) noexcept;
// objects pointed to by reference can be swapped, so provide the special swap() function.
friend void swap(StructureRef lhs, StructureRef rhs) {
friend void swap(IteratorValueRef lhs, IteratorValueRef rhs) {
lhs.soa->swap(lhs.index, rhs.index);
}
public:
// references can be created by copy-assignment only
StructureRef(StructureRef const& rhs) noexcept : soa(rhs.soa), index(rhs.index) { }
IteratorValueRef(IteratorValueRef const& rhs) noexcept : soa(rhs.soa), index(rhs.index) { }
// copy the content of a reference to the content of this one
StructureRef& operator=(StructureRef const& rhs);
IteratorValueRef& operator=(IteratorValueRef const& rhs);
// move the content of a reference to the content of this one
StructureRef& operator=(StructureRef&& rhs) noexcept;
IteratorValueRef& operator=(IteratorValueRef&& rhs) noexcept;
// copy a value_type to the content of this reference
StructureRef& operator=(Structure const& rhs) {
IteratorValueRef& operator=(IteratorValue const& rhs) {
return assign(rhs, std::make_index_sequence<kArrayCount>());
}
// move a value_type to the content of this reference
StructureRef& operator=(Structure&& rhs) noexcept {
IteratorValueRef& operator=(IteratorValue&& rhs) noexcept {
return assign(rhs, std::make_index_sequence<kArrayCount>());
}
@@ -122,36 +124,36 @@ public:
* Internally we're using a tuple<> to store the data.
* This object is not trivial to construct, as it copies an entry of the SoA.
*/
class Structure {
friend class StructureRef;
class IteratorValue {
friend class IteratorValueRef;
friend iterator;
friend const_iterator;
using Type = std::tuple<typename std::decay<Elements>::type...>;
Type elements;
template<size_t ... Is>
static Type init(StructureRef const& rhs, std::index_sequence<Is...>) {
static Type init(IteratorValueRef const& rhs, std::index_sequence<Is...>) {
return Type{ rhs.soa->template elementAt<Is>(rhs.index)... };
}
template<size_t ... Is>
static Type init(StructureRef&& rhs, std::index_sequence<Is...>) noexcept {
static Type init(IteratorValueRef&& rhs, std::index_sequence<Is...>) noexcept {
return Type{ std::move(rhs.soa->template elementAt<Is>(rhs.index))... };
}
public:
Structure(Structure const& rhs) = default;
Structure(Structure&& rhs) noexcept = default;
Structure& operator=(Structure const& rhs) = default;
Structure& operator=(Structure&& rhs) noexcept = default;
IteratorValue(IteratorValue const& rhs) = default;
IteratorValue(IteratorValue&& rhs) noexcept = default;
IteratorValue& operator=(IteratorValue const& rhs) = default;
IteratorValue& operator=(IteratorValue&& rhs) noexcept = default;
// initialize and assign from a StructureRef
Structure(StructureRef const& rhs)
IteratorValue(IteratorValueRef const& rhs)
: elements(init(rhs, std::make_index_sequence<kArrayCount>())) {}
Structure(StructureRef&& rhs) noexcept
IteratorValue(IteratorValueRef&& rhs) noexcept
: elements(init(rhs, std::make_index_sequence<kArrayCount>())) {}
Structure& operator=(StructureRef const& rhs) { return operator=(Structure(rhs)); }
Structure& operator=(StructureRef&& rhs) noexcept { return operator=(Structure(rhs)); }
IteratorValue& operator=(IteratorValueRef const& rhs) { return operator=(IteratorValue(rhs)); }
IteratorValue& operator=(IteratorValueRef&& rhs) noexcept { return operator=(IteratorValue(rhs)); }
// access the elements of this value_Type (i.e. the "fields" of the structure)
template<size_t I> TypeAt<I> const& get() const { return std::get<I>(elements); }
@@ -174,9 +176,9 @@ public:
Iterator(CVQualifiedSOAPointer soa, size_t index) : soa(soa), index(index) {}
public:
using value_type = Structure;
using reference = StructureRef;
using pointer = StructureRef*; // FIXME: this should be a StructurePtr type
using value_type = IteratorValue;
using reference = IteratorValueRef;
using pointer = IteratorValueRef*; // FIXME: this should be a StructurePtr type
using difference_type = ptrdiff_t;
using iterator_category = std::random_access_iterator_tag;
@@ -335,6 +337,11 @@ public:
return *this;
}
StructureOfArraysBase& push_back(Structure&& args) noexcept {
ensureCapacity(mSize + 1);
return push_back_unsafe(std::forward<Structure>(args));
}
StructureOfArraysBase& push_back(Elements const& ... args) noexcept {
ensureCapacity(mSize + 1);
return push_back_unsafe(args...);
@@ -349,23 +356,29 @@ public:
struct PushBackUnsafeClosure {
size_t last;
std::tuple<Elements...> args;
inline explicit PushBackUnsafeClosure(size_t last, Elements&& ... args)
: last(last), args(std::forward<Elements>(args)...) {}
inline explicit PushBackUnsafeClosure(size_t last, Elements const& ... args)
: last(last), args(args...) {}
inline explicit PushBackUnsafeClosure(size_t last, Structure&& args)
: last(last), args(std::forward<Structure>(args)) {}
template<size_t I>
inline void operator()(TypeAt<I>* p) {
new(p + last) TypeAt<I>{ std::get<I>(args) };
}
};
StructureOfArraysBase& push_back_unsafe(Structure&& args) noexcept {
for_each_index(mArrays,
PushBackUnsafeClosure{ mSize++, std::forward<Structure>(args) });
return *this;
}
StructureOfArraysBase& push_back_unsafe(Elements const& ... args) noexcept {
for_each_index(mArrays, PushBackUnsafeClosure{ mSize++, args... });
for_each_index(mArrays,
PushBackUnsafeClosure{ mSize++, { args... } });
return *this;
}
StructureOfArraysBase& push_back_unsafe(Elements&& ... args) noexcept {
for_each_index(mArrays, PushBackUnsafeClosure{ mSize++, std::forward<Elements>(args)... });
for_each_index(mArrays,
PushBackUnsafeClosure{ mSize++, { std::forward<Elements>(args)... }});
return *this;
}
@@ -562,8 +575,10 @@ private:
forEach([from, to](auto p) {
using T = typename std::decay<decltype(*p)>::type;
// note: scalar types like int/float get initialized to zero
for (size_t i = from; i < to; i++) {
new(p + i) T();
if constexpr (!std::is_trivially_default_constructible_v<T>) {
for (size_t i = from; i < to; i++) {
new(p + i) T();
}
}
});
}
@@ -571,8 +586,10 @@ private:
void destroy_each(size_t from, size_t to) noexcept {
forEach([from, to](auto p) {
using T = typename std::decay<decltype(*p)>::type;
for (size_t i = from; i < to; i++) {
p[i].~T();
if constexpr (!std::is_trivially_destructible_v<T>) {
for (size_t i = from; i < to; i++) {
p[i].~T();
}
}
});
}
@@ -592,15 +609,17 @@ private:
reinterpret_cast<T*>(uintptr_t(b) + offsets[index]);
// for trivial cases, just call memcpy()
if (std::is_trivially_copyable<T>::value &&
std::is_trivially_destructible<T>::value) {
if constexpr (std::is_trivially_copyable_v<T> &&
std::is_trivially_destructible_v<T>) {
memcpy(arrayPointer, p, size * sizeof(T));
} else {
for (size_t i = 0; i < size; i++) {
// we move an element by using the in-place move-constructor
new(arrayPointer + i) T(std::move(p[i]));
// and delete them by calling the destructor directly
p[i].~T();
if constexpr (!std::is_trivially_destructible_v<T>) {
// and delete them by calling the destructor directly
p[i].~T();
}
}
}
index++;
@@ -626,27 +645,27 @@ private:
template<typename Allocator, typename... Elements>
inline
typename StructureOfArraysBase<Allocator, Elements...>::StructureRef&
StructureOfArraysBase<Allocator, Elements...>::StructureRef::operator=(
StructureOfArraysBase::StructureRef const& rhs) {
return operator=(Structure(rhs));
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::operator=(
StructureOfArraysBase::IteratorValueRef const& rhs) {
return operator=(IteratorValue(rhs));
}
template<typename Allocator, typename... Elements>
inline
typename StructureOfArraysBase<Allocator, Elements...>::StructureRef&
StructureOfArraysBase<Allocator, Elements...>::StructureRef::operator=(
StructureOfArraysBase::StructureRef&& rhs) noexcept {
return operator=(Structure(rhs));
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::operator=(
StructureOfArraysBase::IteratorValueRef&& rhs) noexcept {
return operator=(IteratorValue(rhs));
}
template<typename Allocator, typename... Elements>
template<size_t... Is>
inline
typename StructureOfArraysBase<Allocator, Elements...>::StructureRef&
StructureOfArraysBase<Allocator, Elements...>::StructureRef::assign(
StructureOfArraysBase::Structure const& rhs, std::index_sequence<Is...>) {
// implements StructureRef& StructureRef::operator=(Structure const& rhs)
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::assign(
StructureOfArraysBase::IteratorValue const& rhs, std::index_sequence<Is...>) {
// implements IteratorValueRef& IteratorValueRef::operator=(IteratorValue const& rhs)
auto UTILS_UNUSED l = { (soa->elementAt<Is>(index) = std::get<Is>(rhs.elements), 0)... };
return *this;
}
@@ -654,10 +673,10 @@ StructureOfArraysBase<Allocator, Elements...>::StructureRef::assign(
template<typename Allocator, typename... Elements>
template<size_t... Is>
inline
typename StructureOfArraysBase<Allocator, Elements...>::StructureRef&
StructureOfArraysBase<Allocator, Elements...>::StructureRef::assign(
StructureOfArraysBase::Structure&& rhs, std::index_sequence<Is...>) noexcept {
// implements StructureRef& StructureRef::operator=(Structure&& rhs) noexcept
typename StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef&
StructureOfArraysBase<Allocator, Elements...>::IteratorValueRef::assign(
StructureOfArraysBase::IteratorValue&& rhs, std::index_sequence<Is...>) noexcept {
// implements IteratorValueRef& IteratorValueRef::operator=(IteratorValue&& rhs) noexcept
auto UTILS_UNUSED l = {
(soa->elementAt<Is>(index) = std::move(std::get<Is>(rhs.elements)), 0)... };
return *this;

View File

@@ -36,7 +36,7 @@
* Creates a Systrace context in the current scope. needed for calling all other systrace
* commands below.
*/
#define SYSTRACE_CONTEXT() ::utils::details::Systrace ___tracer(SYSTRACE_TAG)
#define SYSTRACE_CONTEXT() ::utils::details::Systrace ___trctx(SYSTRACE_TAG)
// SYSTRACE_NAME traces the beginning and end of the current scope. To trace
@@ -57,10 +57,10 @@
#define SYSTRACE_CALL() SYSTRACE_NAME(__FUNCTION__)
#define SYSTRACE_NAME_BEGIN(name) \
___tracer.traceBegin(SYSTRACE_TAG, name)
___trctx.traceBegin(SYSTRACE_TAG, name)
#define SYSTRACE_NAME_END() \
___tracer.traceEnd(SYSTRACE_TAG)
___trctx.traceEnd(SYSTRACE_TAG)
/**
@@ -71,24 +71,24 @@
* used to end it.
*/
#define SYSTRACE_ASYNC_BEGIN(name, cookie) \
___tracer.asyncBegin(SYSTRACE_TAG, name, cookie)
___trctx.asyncBegin(SYSTRACE_TAG, name, cookie)
/**
* Trace the end of an asynchronous event.
* This should have a corresponding SYSTRACE_ASYNC_BEGIN.
*/
#define SYSTRACE_ASYNC_END(name, cookie) \
___tracer.asyncEnd(SYSTRACE_TAG, name, cookie)
___trctx.asyncEnd(SYSTRACE_TAG, name, cookie)
/**
* Traces an integer counter value. name is used to identify the counter.
* This can be used to track how a value changes over time.
*/
#define SYSTRACE_VALUE32(name, val) \
___tracer.value(SYSTRACE_TAG, name, int32_t(val))
___trctx.value(SYSTRACE_TAG, name, int32_t(val))
#define SYSTRACE_VALUE64(name, val) \
___tracer.value(SYSTRACE_TAG, name, int64_t(val))
___trctx.value(SYSTRACE_TAG, name, int64_t(val))
// ------------------------------------------------------------------------------------------------
// No user serviceable code below...
@@ -221,9 +221,9 @@ class Systrace {
// ------------------------------------------------------------------------------------------------
class ScopedTrace {
public:
public:
// we don't inline this because it's relatively heavy due to a global check
ScopedTrace(uint32_t tag, const char* name) noexcept : mTrace(tag), mTag(tag) {
ScopedTrace(uint32_t tag, const char* name) noexcept: mTrace(tag), mTag(tag) {
mTrace.traceBegin(tag, name);
}
@@ -231,15 +231,7 @@ class ScopedTrace {
mTrace.traceEnd(mTag);
}
inline void value(uint32_t tag, const char* name, int32_t v) noexcept {
mTrace.value(tag, name, v);
}
inline void value(uint32_t tag, const char* name, int64_t v) noexcept {
mTrace.value(tag, name, v);
}
private:
private:
Systrace mTrace;
const uint32_t mTag;
};

40
third_party/mikktspace/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.19)
project(mikktspace)
set(TARGET mikktspace)
set(PUBLIC_HDR_DIR include)
# ==================================================================================================
# Sources and headers
# ==================================================================================================
set(PUBLIC_HDRS
${PUBLIC_HDR_DIR}/mikktspace/mikktspace.h
)
set(SRCS
src/mikktspace.c
)
# ==================================================================================================
# Target definitions
# ==================================================================================================
include_directories(${PUBLIC_HDR_DIR})
add_library(${TARGET} ${PUBLIC_HDRS} ${PRIVATE_HDRS} ${SRCS})
target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR})
set_target_properties(${TARGET} PROPERTIES FOLDER Libs)
# ==================================================================================================
# Compile options and optimizations
# ==================================================================================================
if (MSVC)
target_compile_options(${TARGET} PRIVATE /fp:fast)
else()
target_compile_options(${TARGET} PRIVATE -ffast-math)
endif()
# ==================================================================================================
# Installation
# ==================================================================================================
install(TARGETS ${TARGET} ARCHIVE DESTINATION lib/${DIST_DIR})
install(DIRECTORY ${PUBLIC_HDR_DIR}/mikktspace DESTINATION include)

View File

@@ -0,0 +1,145 @@
/** \file mikktspace/mikktspace.h
* \ingroup mikktspace
*/
/**
* Copyright (C) 2011 by Morten S. Mikkelsen
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifndef __MIKKTSPACE_H__
#define __MIKKTSPACE_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Author: Morten S. Mikkelsen
* Version: 1.0
*
* The files mikktspace.h and mikktspace.c are designed to be
* stand-alone files and it is important that they are kept this way.
* Not having dependencies on structures/classes/libraries specific
* to the program, in which they are used, allows them to be copied
* and used as is into any tool, program or plugin.
* The code is designed to consistently generate the same
* tangent spaces, for a given mesh, in any tool in which it is used.
* This is done by performing an internal welding step and subsequently an order-independent evaluation
* of tangent space for meshes consisting of triangles and quads.
* This means faces can be received in any order and the same is true for
* the order of vertices of each face. The generated result will not be affected
* by such reordering. Additionally, whether degenerate (vertices or texture coordinates)
* primitives are present or not will not affect the generated results either.
* Once tangent space calculation is done the vertices of degenerate primitives will simply
* inherit tangent space from neighboring non degenerate primitives.
* The analysis behind this implementation can be found in my master's thesis
* which is available for download --> http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf
* Note that though the tangent spaces at the vertices are generated in an order-independent way,
* by this implementation, the interpolated tangent space is still affected by which diagonal is
* chosen to split each quad. A sensible solution is to have your tools pipeline always
* split quads by the shortest diagonal. This choice is order-independent and works with mirroring.
* If these have the same length then compare the diagonals defined by the texture coordinates.
* XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin
* and also quad triangulator plugin.
*/
typedef int tbool;
typedef struct SMikkTSpaceContext SMikkTSpaceContext;
typedef struct {
// Returns the number of faces (triangles/quads) on the mesh to be processed.
int (*m_getNumFaces)(const SMikkTSpaceContext * pContext);
// Returns the number of vertices on face number iFace
// iFace is a number in the range {0, 1, ..., getNumFaces()-1}
int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace);
// returns the position/normal/texcoord of the referenced face of vertex number iVert.
// iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads.
void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert);
void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert);
void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert);
// either (or both) of the two setTSpace callbacks can be set.
// The call-back m_setTSpaceBasic() is sufficient for basic normal mapping.
// This function is used to return the tangent and fSign to the application.
// fvTangent is a unit length vector.
// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.
// bitangent = fSign * cross(vN, tangent);
// Note that the results are returned unindexed. It is possible to generate a new index list
// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results.
// DO NOT! use an already existing index list.
void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert);
// This function is used to return tangent space results to the application.
// fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their
// true magnitudes which can be used for relief mapping effects.
// fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent.
// However, both are perpendicular to the vertex normal.
// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.
// fSign = bIsOrientationPreserving ? 1.0f : (-1.0f);
// bitangent = fSign * cross(vN, tangent);
// Note that the results are returned unindexed. It is possible to generate a new index list
// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results.
// DO NOT! use an already existing index list.
void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT,
const tbool bIsOrientationPreserving, const int iFace, const int iVert);
} SMikkTSpaceInterface;
struct SMikkTSpaceContext
{
SMikkTSpaceInterface * m_pInterface; // initialized with callback functions
void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call)
};
// these are both thread safe!
tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled)
tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold);
// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the
// normal map sampler must use the exact inverse of the pixel shader transformation.
// The most efficient transformation we can possibly do in the pixel shader is
// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN.
// pixel shader (fast transform out)
// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN );
// where vNt is the tangent space normal. The normal map sampler must likewise use the
// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader.
// sampler does (exact inverse of pixel shader):
// float3 row0 = cross(vB, vN);
// float3 row1 = cross(vN, vT);
// float3 row2 = cross(vT, vB);
// float fSign = dot(vT, row0)<0 ? -1 : 1;
// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) );
// where vNout is the sampled normal in some chosen 3D space.
//
// Should you choose to reconstruct the bitangent in the pixel shader instead
// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also.
// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of
// quads as your renderer then problems will occur since the interpolated tangent spaces will differ
// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before
// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier.
// However, this must be used both by the sampler and your tools/rendering pipeline.
#ifdef __cplusplus
}
#endif
#endif

1899
third_party/mikktspace/src/mikktspace.c vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "filament",
"version": "1.31.6",
"version": "1.31.7",
"description": "Real-time physically based rendering engine",
"main": "filament.js",
"module": "filament.js",