Compare commits
41
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
791f51dcb4 | ||
|
|
4621f3ec5d | ||
|
|
ab9c09d0d9 | ||
|
|
dbf51c1c7d | ||
|
|
ebd5f150c1 | ||
|
|
156dcb54e8 | ||
|
|
88ef76989c | ||
|
|
fc284af684 | ||
|
|
3a8685cd15 | ||
|
|
f035655f5b | ||
|
|
f498e08d1e | ||
|
|
db3186a65f | ||
|
|
248815a3e0 | ||
|
|
ebb62b5d55 | ||
|
|
b9a5ad205a | ||
|
|
100986d8d9 | ||
|
|
b7c9a36d57 | ||
|
|
d76551ab28 | ||
|
|
92bc0752d1 | ||
|
|
657c251463 | ||
|
|
b6b8fee483 | ||
|
|
be5b60f895 | ||
|
|
681d31758f | ||
|
|
80dc2f5c9d | ||
|
|
ce7dd7a1fb | ||
|
|
85617c96a2 | ||
|
|
58760adb6a | ||
|
|
4ef0b72773 | ||
|
|
45631c3b81 | ||
|
|
6f6dde2579 | ||
|
|
e0acbe42cf | ||
|
|
4f137ccf89 | ||
|
|
04010e7dd9 | ||
|
|
f4f9f331c0 | ||
|
|
dada291f6b | ||
|
|
18f2f8b6d4 | ||
|
|
d4117ac968 | ||
|
|
eccbc8c242 | ||
|
|
3cdf24f515 | ||
|
|
d91faf9df8 | ||
|
|
d950efa6ea |
+1
-1
@@ -349,7 +349,7 @@ same version that our continuous builds use.
|
||||
|
||||
```
|
||||
cd <your chosen parent folder for the emscripten SDK>
|
||||
curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.5.zip > emsdk.zip
|
||||
curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.9.zip > emsdk.zip
|
||||
unzip emsdk.zip ; mv emsdk-* emsdk ; cd emsdk
|
||||
python ./emsdk.py install latest
|
||||
python ./emsdk.py activate latest
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.21.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.21.3'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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.21.2'
|
||||
pod 'Filament', '~> 1.21.3'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
@@ -177,6 +177,7 @@ steps:
|
||||
- [x] KHR_materials_variants
|
||||
- [x] KHR_materials_volume
|
||||
- [x] KHR_mesh_quantization
|
||||
- [x] KHR_texture_basisu
|
||||
- [x] KHR_texture_transform
|
||||
|
||||
|
||||
|
||||
+17
-8
@@ -3,16 +3,25 @@
|
||||
This file contains one line summaries of commits that are worthy of mentioning in release notes.
|
||||
A new header is inserted each time a *tag* is created.
|
||||
|
||||
## v1.21.3 (currently main branch)
|
||||
## v1.22.1 (currently main branch)
|
||||
|
||||
- Renamed the KTXLoader Kotlin class to KTX1Loader. [⚠️ **API Change**].
|
||||
- Added Ktx2Reader and BasisEncoder to support Basis-encoded KTX2 files.
|
||||
- Desktop and Web Suzanne samples now use KTX2 instead of KTX1.
|
||||
- Removed KTX1 compression support from mipgen. [⚠️ **API Change**].
|
||||
- Added support for KTX2 to mipgen.
|
||||
- gltfio: Added resetBoneMatrices() method.
|
||||
## v1.22.0
|
||||
|
||||
- engine: Changed UBOs layout [⚠️ **Material breakage**].
|
||||
- engine: Improve effects relying on mipmapping
|
||||
|
||||
## v1.21.3
|
||||
|
||||
- Java: Renamed the `KTXLoader` Kotlin class to `KTX1Loader`. [⚠️ **API Change**].
|
||||
- libs: Added `Ktx2Reader` and `BasisEncoder` to support Basis-encoded KTX2 files.
|
||||
- engine: Remove deprecated `Stream` APIs, see `Texture::import()` for an alternative [⚠️ **API Change**].
|
||||
- tools: Removed KTX1 compression support from mipgen. [⚠️ **API Change**].
|
||||
- tools: Added support for KTX2 to mipgen.
|
||||
- gltfio: Added `resetBoneMatrices()` method.
|
||||
- gltfio: Introduced `TextureProvider` interface. [⚠️ **API Change**].
|
||||
- engine: remove deprecated `Stream` APIs, see `Texture::import()` for an alternative [⚠️ **API Change**].
|
||||
- gltfio: Fix progress indicator when error occurs.
|
||||
- samples: Desktop and Web Suzanne samples now use KTX2 instead of KTX1.
|
||||
- samples: Fix regression with sample-gltf-viewer and zip files.
|
||||
|
||||
## v1.21.2
|
||||
|
||||
|
||||
@@ -11,10 +11,18 @@ add_library(dracodec STATIC IMPORTED)
|
||||
set_target_properties(dracodec PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libdracodec.a)
|
||||
|
||||
add_library(ktxreader STATIC IMPORTED)
|
||||
set_target_properties(ktxreader PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libktxreader.a)
|
||||
|
||||
add_library(stb STATIC IMPORTED)
|
||||
set_target_properties(stb PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libstb.a)
|
||||
|
||||
add_library(basis_transcoder STATIC IMPORTED)
|
||||
set_target_properties(basis_transcoder PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libbasis_transcoder.a)
|
||||
|
||||
add_library(utils STATIC IMPORTED)
|
||||
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
|
||||
@@ -50,6 +58,7 @@ set(GLTFIO_SRCS
|
||||
${GLTFIO_DIR}/src/FFilamentInstance.h
|
||||
${GLTFIO_DIR}/src/FilamentInstance.cpp
|
||||
${GLTFIO_DIR}/src/GltfEnums.h
|
||||
${GLTFIO_DIR}/src/Ktx2Provider.cpp
|
||||
${GLTFIO_DIR}/src/MaterialProvider.cpp
|
||||
${GLTFIO_DIR}/src/ResourceLoader.cpp
|
||||
${GLTFIO_DIR}/src/StbProvider.cpp
|
||||
@@ -86,6 +95,7 @@ set(GLTFIO_INCLUDE_DIRS
|
||||
../../third_party/hat-trie
|
||||
../../third_party/stb
|
||||
../../libs/utils/include
|
||||
../../libs/ktxreader/include
|
||||
)
|
||||
|
||||
add_library(gltfio-jni SHARED ${GLTFIO_SRCS})
|
||||
@@ -95,9 +105,9 @@ set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_
|
||||
|
||||
if(GLTFIO_LITE)
|
||||
target_compile_definitions(gltfio-jni PUBLIC GLTFIO_LITE=1)
|
||||
target_link_libraries(gltfio-jni filament-jni utils log stb gltfio_resources_lite)
|
||||
target_link_libraries(gltfio-jni filament-jni utils log stb ktxreader basis_transcoder gltfio_resources_lite)
|
||||
else()
|
||||
target_link_libraries(gltfio-jni filament-jni utils log stb gltfio_resources)
|
||||
target_link_libraries(gltfio-jni filament-jni utils log stb ktxreader basis_transcoder gltfio_resources)
|
||||
|
||||
# Enable Draco in the non-lite variant of gltfio.
|
||||
target_link_libraries(gltfio-jni dracodec)
|
||||
|
||||
@@ -117,12 +117,19 @@ Java_com_google_android_filament_gltfio_ResourceLoader_nAsyncCancelLoad(JNIEnv*,
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_gltfio_ResourceLoader_nCreateTextureProvider(JNIEnv*, jclass,
|
||||
Java_com_google_android_filament_gltfio_ResourceLoader_nCreateStbProvider(JNIEnv*, jclass,
|
||||
jlong nativeEngine) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
return (jlong) createStbProvider(engine);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jlong JNICALL
|
||||
Java_com_google_android_filament_gltfio_ResourceLoader_nCreateKtx2Provider(JNIEnv*, jclass,
|
||||
jlong nativeEngine) {
|
||||
Engine* engine = (Engine*) nativeEngine;
|
||||
return (jlong) createKtx2Provider(engine);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_gltfio_ResourceLoader_nDestroyTextureProvider(JNIEnv*, jclass,
|
||||
jlong nativeProvider) {
|
||||
|
||||
+16
-9
@@ -35,7 +35,8 @@ import java.nio.Buffer;
|
||||
*/
|
||||
public class ResourceLoader {
|
||||
private final long mNativeObject;
|
||||
private final long mNativeProvider;
|
||||
private final long mNativeStbProvider;
|
||||
private final long mNativeKtx2Provider;
|
||||
|
||||
/**
|
||||
* Constructs a resource loader tied to the given Filament engine.
|
||||
@@ -47,9 +48,11 @@ public class ResourceLoader {
|
||||
public ResourceLoader(@NonNull Engine engine) {
|
||||
long nativeEngine = engine.getNativeObject();
|
||||
mNativeObject = nCreateResourceLoader(nativeEngine, false, false, false);
|
||||
mNativeProvider = nCreateTextureProvider(nativeEngine);
|
||||
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeProvider);
|
||||
nAddTextureProvider(mNativeObject, "image/png", mNativeProvider);
|
||||
mNativeStbProvider = nCreateStbProvider(nativeEngine);
|
||||
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
|
||||
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
|
||||
nAddTextureProvider(mNativeObject, "image/png", mNativeStbProvider);
|
||||
nAddTextureProvider(mNativeObject, "image/ktx2", mNativeKtx2Provider);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,9 +70,11 @@ public class ResourceLoader {
|
||||
long nativeEngine = engine.getNativeObject();
|
||||
mNativeObject = nCreateResourceLoader(nativeEngine, normalizeSkinningWeights,
|
||||
recomputeBoundingBoxes, ignoreBindTransform);
|
||||
mNativeProvider = nCreateTextureProvider(nativeEngine);
|
||||
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeProvider);
|
||||
nAddTextureProvider(mNativeObject, "image/png", mNativeProvider);
|
||||
mNativeStbProvider = nCreateStbProvider(nativeEngine);
|
||||
mNativeKtx2Provider = nCreateKtx2Provider(nativeEngine);
|
||||
nAddTextureProvider(mNativeObject, "image/jpeg", mNativeStbProvider);
|
||||
nAddTextureProvider(mNativeObject, "image/png", mNativeStbProvider);
|
||||
nAddTextureProvider(mNativeObject, "image/ktx2", mNativeKtx2Provider);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +82,8 @@ public class ResourceLoader {
|
||||
*/
|
||||
public void destroy() {
|
||||
nDestroyResourceLoader(mNativeObject);
|
||||
nDestroyTextureProvider(mNativeProvider);
|
||||
nDestroyTextureProvider(mNativeStbProvider);
|
||||
nDestroyTextureProvider(mNativeKtx2Provider);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,7 +193,8 @@ public class ResourceLoader {
|
||||
private static native void nAsyncUpdateLoad(long nativeLoader);
|
||||
private static native void nAsyncCancelLoad(long nativeLoader);
|
||||
|
||||
private static native long nCreateTextureProvider(long nativeEngine);
|
||||
private static native long nCreateStbProvider(long nativeEngine);
|
||||
private static native long nCreateKtx2Provider(long nativeEngine);
|
||||
private static native void nAddTextureProvider(long nativeLoader, String url, long nativeProvider);
|
||||
private static native void nDestroyTextureProvider(long nativeProvider);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.21.2
|
||||
VERSION_NAME=1.21.3
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export PATH="$PWD:$PATH"
|
||||
# npm install -g typescript
|
||||
|
||||
# Install emscripten.
|
||||
curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.5.zip > emsdk.zip
|
||||
curl -L https://github.com/emscripten-core/emsdk/archive/refs/tags/3.1.9.zip > emsdk.zip
|
||||
unzip emsdk.zip ; mv emsdk-* emsdk ; cd emsdk
|
||||
./emsdk install latest
|
||||
./emsdk activate latest
|
||||
|
||||
@@ -2169,7 +2169,7 @@ type aliases:
|
||||
|
||||
Name | Type | Description
|
||||
:-----------------------------------|:--------:|:------------------------------------
|
||||
**getResolution()** | float4 | Resolution of the view in pixels: `width`, `height`, `1 / width`, `1 / height`
|
||||
**getResolution()** | float4 | Dimensions of the view's effective viewport in pixels: `width`, `height`, `1 / width`, `1 / height`. This might be different from `View::getViewport()` for instance because of added rendering guard-bands. This can be used in conjunction with `getNormalizedViewportCoord()` to generate pixel coordinates.
|
||||
**getWorldCameraPosition()** | float3 | Position of the camera/eye in world space
|
||||
**getWorldOffset()** | float3 | The shift required to obtain API-level world space
|
||||
**getTime()** | float | Current time as a remainder of 1 second. Yields a value between 0 and 1
|
||||
|
||||
@@ -77,6 +77,7 @@ set(SRCS
|
||||
src/RenderTarget.cpp
|
||||
src/RenderableManager.cpp
|
||||
src/Renderer.cpp
|
||||
src/RendererUtils.cpp
|
||||
src/ResourceAllocator.cpp
|
||||
src/ResourceList.cpp
|
||||
src/Scene.cpp
|
||||
@@ -143,6 +144,7 @@ set(PRIVATE_HDRS
|
||||
src/PerViewUniforms.h
|
||||
src/PIDController.h
|
||||
src/PostProcessManager.h
|
||||
src/RendererUtils.h
|
||||
src/RenderPass.h
|
||||
src/RenderPrimitive.h
|
||||
src/ResourceAllocator.h
|
||||
|
||||
@@ -42,22 +42,22 @@
|
||||
*/
|
||||
namespace filament::backend {
|
||||
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_TRANSPARENT = 0x1;
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_READABLE = 0x2;
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_ENABLE_XCB = 0x4;
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_TRANSPARENT = 0x1;
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_READABLE = 0x2;
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_ENABLE_XCB = 0x4;
|
||||
static constexpr uint64_t SWAP_CHAIN_CONFIG_APPLE_CVPIXELBUFFER = 0x8;
|
||||
|
||||
static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_VERTEX_SAMPLER_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_FRAGMENT_SAMPLER_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_SAMPLER_COUNT = 32; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects.
|
||||
static constexpr size_t MAX_VERTEX_ATTRIBUTE_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_VERTEX_SAMPLER_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_FRAGMENT_SAMPLER_COUNT = 16; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_SAMPLER_COUNT = 32; // This is guaranteed by OpenGL ES.
|
||||
static constexpr size_t MAX_VERTEX_BUFFER_COUNT = 16; // Max number of bound buffer objects.
|
||||
|
||||
static_assert(MAX_VERTEX_BUFFER_COUNT <= MAX_VERTEX_ATTRIBUTE_COUNT,
|
||||
"The number of buffer objects that can be attached to a VertexBuffer must be "
|
||||
"less than or equal to the maximum number of vertex attributes.");
|
||||
|
||||
static constexpr size_t CONFIG_BINDING_COUNT = 8;
|
||||
static constexpr size_t CONFIG_BINDING_COUNT = 12; // This is guaranteed by OpenGL ES.
|
||||
|
||||
/**
|
||||
* Selects which driver a particular Engine should use.
|
||||
@@ -912,7 +912,7 @@ struct ShaderStageFlags {
|
||||
(fragment && type == ShaderType::FRAGMENT);
|
||||
}
|
||||
};
|
||||
static constexpr ShaderStageFlags ALL_SHADER_STAGE_FLAGS = { .vertex = true, .fragment = true };
|
||||
static constexpr ShaderStageFlags ALL_SHADER_STAGE_FLAGS = { true, true };
|
||||
|
||||
/**
|
||||
* Selects which buffers to clear at the beginning of the render pass, as well as which buffers
|
||||
|
||||
@@ -42,9 +42,11 @@ void assertFramebufferStatus(utils::io::ostream& out, GLenum target, const char*
|
||||
|
||||
#ifdef NDEBUG
|
||||
# define CHECK_GL_ERROR(out)
|
||||
# define CHECK_GL_ERROR_NON_FATAL(out)
|
||||
# define CHECK_GL_FRAMEBUFFER_STATUS(out, target)
|
||||
#else
|
||||
# define CHECK_GL_ERROR(out) { GLUtils::assertGLError(out, __func__, __LINE__); }
|
||||
# define CHECK_GL_ERROR_NON_FATAL(out) { GLUtils::checkGLError(out, __func__, __LINE__); }
|
||||
# define CHECK_GL_FRAMEBUFFER_STATUS(out, target) { GLUtils::checkFramebufferStatus(out, target, __func__, __LINE__); }
|
||||
#endif
|
||||
|
||||
|
||||
@@ -306,7 +306,9 @@ void OpenGLContext::initExtensionsGLES() noexcept {
|
||||
ext.EXT_multisampled_render_to_texture = exts.has("GL_EXT_multisampled_render_to_texture"sv);
|
||||
ext.EXT_multisampled_render_to_texture2 = exts.has("GL_EXT_multisampled_render_to_texture2"sv);
|
||||
ext.EXT_shader_framebuffer_fetch = exts.has("GL_EXT_shader_framebuffer_fetch"sv);
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
ext.EXT_texture_compression_etc2 = true;
|
||||
#endif
|
||||
ext.EXT_texture_filter_anisotropic = exts.has("GL_EXT_texture_filter_anisotropic"sv);
|
||||
ext.GOOGLE_cpp_style_line_directive = exts.has("GL_GOOGLE_cpp_style_line_directive"sv);
|
||||
ext.KHR_debug = exts.has("GL_KHR_debug"sv);
|
||||
|
||||
@@ -2986,7 +2986,11 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
|
||||
instanceCount);
|
||||
}
|
||||
|
||||
#ifdef FILAMENT_ENABLE_MATDBG
|
||||
CHECK_GL_ERROR_NON_FATAL(utils::slog.e)
|
||||
#else
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
#endif
|
||||
}
|
||||
|
||||
// explicit instantiation of the Dispatcher
|
||||
|
||||
@@ -345,7 +345,7 @@ private:
|
||||
void setViewportScissor(Viewport const& viewportScissor) noexcept;
|
||||
|
||||
// sampler buffer binding points (nullptr if not used)
|
||||
std::array<HwSamplerGroup*, Program::BINDING_COUNT> mSamplerBindings = {}; // 8 pointers
|
||||
std::array<HwSamplerGroup*, Program::BINDING_COUNT> mSamplerBindings = {}; // 12 pointers
|
||||
|
||||
mutable tsl::robin_map<uint32_t, GLuint> mSamplerMap;
|
||||
mutable std::vector<GLTexture*> mExternalStreams;
|
||||
|
||||
@@ -264,7 +264,7 @@ void OpenGLProgram::initialize(OpenGLContext& context) {
|
||||
mValid = OpenGLProgram::checkProgramStatus(name.c_str_safe(),
|
||||
gl.program, gl.shaders, initializationData->shaderSourceCode);
|
||||
|
||||
if (mValid) {
|
||||
if (UTILS_LIKELY(mValid)) {
|
||||
initializeProgramState(context, gl.program,
|
||||
initializationData->uniformBlockInfo,
|
||||
initializationData->samplerGroupInfo);
|
||||
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
|
||||
// turns out the former might be relatively cheap to check, the later requires
|
||||
// a bit less. Compared to what updateSamplers() actually does, which is
|
||||
// pretty little, I'm not sure we'll get ahead.
|
||||
// pretty little, I'm not sure if we'll get ahead.
|
||||
|
||||
updateSamplers(gld);
|
||||
}
|
||||
@@ -113,7 +113,7 @@ private:
|
||||
union {
|
||||
// when mInitialized == true:
|
||||
// information about each USED sampler buffer per binding (no gaps)
|
||||
std::array<uint8_t, Program::BINDING_COUNT> mUsedBindingPoints; // 8 bytes
|
||||
std::array<uint8_t, Program::BINDING_COUNT> mUsedBindingPoints; // 12 bytes
|
||||
// when mInitialized == false:
|
||||
// lazy initialization data pointer
|
||||
LazyInitializationData* mLazyInitializationData;
|
||||
|
||||
@@ -289,18 +289,17 @@ private:
|
||||
|
||||
// Represents all the Vulkan state that comprises a bound descriptor set.
|
||||
struct DescriptorKey {
|
||||
VkBuffer uniformBuffers[UBUFFER_BINDING_COUNT]; // 0
|
||||
DescriptorImageInfo samplers[SAMPLER_BINDING_COUNT]; // 64
|
||||
DescriptorImageInfo inputAttachments[TARGET_BINDING_COUNT]; // 832
|
||||
uint32_t uniformBufferOffsets[UBUFFER_BINDING_COUNT]; // 1024
|
||||
uint32_t uniformBufferSizes[UBUFFER_BINDING_COUNT]; // 1056
|
||||
VkBuffer uniformBuffers[UBUFFER_BINDING_COUNT]; // 96 0
|
||||
DescriptorImageInfo samplers[SAMPLER_BINDING_COUNT]; // 768 96
|
||||
DescriptorImageInfo inputAttachments[TARGET_BINDING_COUNT]; // 192 864
|
||||
uint32_t uniformBufferOffsets[UBUFFER_BINDING_COUNT]; // 48 1056
|
||||
uint32_t uniformBufferSizes[UBUFFER_BINDING_COUNT]; // 48 1104
|
||||
};
|
||||
|
||||
static_assert(offsetof(DescriptorKey, samplers) == 64);
|
||||
static_assert(offsetof(DescriptorKey, inputAttachments) == 832);
|
||||
static_assert(offsetof(DescriptorKey, uniformBufferOffsets) == 1024);
|
||||
static_assert(offsetof(DescriptorKey, uniformBufferSizes) == 1056);
|
||||
static_assert(sizeof(DescriptorKey) == 1088, "DescriptorKey must not have implicit padding.");
|
||||
static_assert(offsetof(DescriptorKey, samplers) == 96);
|
||||
static_assert(offsetof(DescriptorKey, inputAttachments) == 864);
|
||||
static_assert(offsetof(DescriptorKey, uniformBufferOffsets) == 1056);
|
||||
static_assert(offsetof(DescriptorKey, uniformBufferSizes) == 1104);
|
||||
static_assert(sizeof(DescriptorKey) == 1152, "DescriptorKey must not have implicit padding.");
|
||||
|
||||
using DescHashFn = utils::hash::MurmurHashFn<DescriptorKey>;
|
||||
|
||||
|
||||
@@ -244,7 +244,6 @@ bool Froxelizer::update() noexcept {
|
||||
mClipToFroxelX = (0.5f * viewport.width) / froxelDimension.x;
|
||||
mClipToFroxelY = (0.5f * viewport.height) / froxelDimension.y;
|
||||
|
||||
mOneOverDimension = 1.0f / float2(froxelDimension);
|
||||
uniformsNeedUpdating = true;
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -125,10 +125,8 @@ public:
|
||||
|
||||
void updateUniforms(PerViewUib& s) {
|
||||
s.zParams = mParamsZ;
|
||||
s.fParams = mParamsF.yz;
|
||||
s.fParamsX = mParamsF.x;
|
||||
s.oneOverFroxelDimensionX = mOneOverDimension.x;
|
||||
s.oneOverFroxelDimensionY = mOneOverDimension.y;
|
||||
s.fParams = mParamsF;
|
||||
s.froxelCountXY = math::float2{ mViewport.width, mViewport.height } / mFroxelDimension;
|
||||
}
|
||||
|
||||
// send froxel data to GPU
|
||||
@@ -242,7 +240,6 @@ private:
|
||||
float mLinearizer = 0.0f;
|
||||
float mClipToFroxelX = 0.0f;
|
||||
float mClipToFroxelY = 0.0f;
|
||||
math::float2 mOneOverDimension = {};
|
||||
backend::BufferObjectHandle mRecordsBuffer;
|
||||
backend::Handle<backend::HwTexture> mFroxelTexture;
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ void PerViewUniforms::prepareCamera(const CameraInfo& camera) noexcept {
|
||||
s.clipFromWorldMatrix = clipFromWorld; // projection * view
|
||||
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
|
||||
s.cameraPosition = float3{ camera.getPosition() };
|
||||
s.worldOffset = camera.worldOffset;
|
||||
s.worldOffset = camera.getWorldOffset();
|
||||
s.cameraFar = camera.zf;
|
||||
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
|
||||
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
|
||||
@@ -105,12 +105,14 @@ void PerViewUniforms::prepareExposure(float ev100) noexcept {
|
||||
s.ev100 = ev100;
|
||||
}
|
||||
|
||||
void PerViewUniforms::prepareViewport(const filament::Viewport &viewport) noexcept {
|
||||
void PerViewUniforms::prepareViewport(const filament::Viewport& viewport,
|
||||
uint32_t xoffset, uint32_t yoffset) noexcept {
|
||||
const float w = float(viewport.width);
|
||||
const float h = float(viewport.height);
|
||||
auto& s = mUniforms.edit();
|
||||
s.resolution = float4{ w, h, 1.0f / w, 1.0f / h };
|
||||
s.origin = float2{ viewport.left, viewport.bottom };
|
||||
s.offset = float2{ xoffset, yoffset };
|
||||
}
|
||||
|
||||
void PerViewUniforms::prepareTime(math::float4 const& userTime) noexcept {
|
||||
@@ -283,23 +285,24 @@ void PerViewUniforms::prepareDynamicLights(Froxelizer& froxelizer) noexcept {
|
||||
s.lightFarAttenuationParams = 0.5f * float2{ 10.0f, 10.0f / (f * f) };
|
||||
}
|
||||
|
||||
void PerViewUniforms::prepareShadowMapping(ShadowMappingUniforms const& shadowMappingUniforms,
|
||||
VsmShadowOptions const& options) noexcept {
|
||||
void PerViewUniforms::prepareShadowMapping() noexcept {
|
||||
auto& s = mUniforms.edit();
|
||||
s.vsmExponent = 5.54f; // fp16: max 5.54f, fp32: max 42.0
|
||||
s.vsmDepthScale = options.minVarianceScale * 0.01f * s.vsmExponent;
|
||||
s.vsmLightBleedReduction = options.lightBleedReduction;
|
||||
}
|
||||
|
||||
s.lightFromWorldMatrix = shadowMappingUniforms.lightFromWorldMatrix;
|
||||
s.cascadeSplits = shadowMappingUniforms.cascadeSplits;
|
||||
s.shadowBulbRadiusLs = shadowMappingUniforms.shadowBulbRadiusLs;
|
||||
s.shadowBias = shadowMappingUniforms.shadowBias;
|
||||
s.ssContactShadowDistance = shadowMappingUniforms.ssContactShadowDistance;
|
||||
s.directionalShadows = shadowMappingUniforms.directionalShadows;
|
||||
s.cascades = shadowMappingUniforms.cascades;
|
||||
void PerViewUniforms::prepareShadowSampling(PerViewUib& uniforms,
|
||||
ShadowMappingUniforms const& shadowMappingUniforms) noexcept {
|
||||
uniforms.lightFromWorldMatrix = shadowMappingUniforms.lightFromWorldMatrix;
|
||||
uniforms.cascadeSplits = shadowMappingUniforms.cascadeSplits;
|
||||
uniforms.shadowBulbRadiusLs = shadowMappingUniforms.shadowBulbRadiusLs;
|
||||
uniforms.shadowBias = shadowMappingUniforms.shadowBias;
|
||||
uniforms.ssContactShadowDistance = shadowMappingUniforms.ssContactShadowDistance;
|
||||
uniforms.directionalShadows = shadowMappingUniforms.directionalShadows;
|
||||
uniforms.cascades = shadowMappingUniforms.cascades;
|
||||
}
|
||||
|
||||
void PerViewUniforms::prepareShadowVSM(Handle<HwTexture> texture,
|
||||
ShadowMappingUniforms const& shadowMappingUniforms,
|
||||
VsmShadowOptions const& options) noexcept {
|
||||
SamplerMinFilter filterMin = SamplerMinFilter::LINEAR;
|
||||
if (options.anisotropy > 0 || options.mipmapping) {
|
||||
@@ -313,9 +316,14 @@ void PerViewUniforms::prepareShadowVSM(Handle<HwTexture> texture,
|
||||
}});
|
||||
auto& s = mUniforms.edit();
|
||||
s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_VSM;
|
||||
s.vsmExponent = 5.54f; // fp16: max 5.54f, fp32: max 42.0
|
||||
s.vsmDepthScale = options.minVarianceScale * 0.01f * s.vsmExponent;
|
||||
s.vsmLightBleedReduction = options.lightBleedReduction;
|
||||
PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms);
|
||||
}
|
||||
|
||||
void PerViewUniforms::prepareShadowPCF(Handle<HwTexture> texture) noexcept {
|
||||
void PerViewUniforms::prepareShadowPCF(Handle<HwTexture> texture,
|
||||
ShadowMappingUniforms const& shadowMappingUniforms) noexcept {
|
||||
mSamplers.setSampler(PerViewSib::SHADOW_MAP, {
|
||||
texture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
@@ -325,22 +333,27 @@ void PerViewUniforms::prepareShadowPCF(Handle<HwTexture> texture) noexcept {
|
||||
}});
|
||||
auto& s = mUniforms.edit();
|
||||
s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_PCF;
|
||||
PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms);
|
||||
}
|
||||
|
||||
void PerViewUniforms::prepareShadowDPCF(Handle <HwTexture> texture,
|
||||
void PerViewUniforms::prepareShadowDPCF(Handle<HwTexture> texture,
|
||||
ShadowMappingUniforms const& shadowMappingUniforms,
|
||||
SoftShadowOptions const& options) noexcept {
|
||||
mSamplers.setSampler(PerViewSib::SHADOW_MAP, { texture, {}});
|
||||
auto& s = mUniforms.edit();
|
||||
s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_DPCF;
|
||||
s.shadowPenumbraRatioScale = options.penumbraRatioScale;
|
||||
PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms);
|
||||
}
|
||||
|
||||
void PerViewUniforms::prepareShadowPCSS(Handle <HwTexture> texture,
|
||||
void PerViewUniforms::prepareShadowPCSS(Handle<HwTexture> texture,
|
||||
ShadowMappingUniforms const& shadowMappingUniforms,
|
||||
SoftShadowOptions const& options) noexcept {
|
||||
mSamplers.setSampler(PerViewSib::SHADOW_MAP, { texture, {}});
|
||||
auto& s = mUniforms.edit();
|
||||
s.shadowSamplingType = SHADOW_SAMPLING_RUNTIME_PCSS;
|
||||
s.shadowPenumbraRatioScale = options.penumbraRatioScale;
|
||||
PerViewUniforms::prepareShadowSampling(s, shadowMappingUniforms);
|
||||
}
|
||||
|
||||
void PerViewUniforms::commit(backend::DriverApi& driver) noexcept {
|
||||
|
||||
@@ -65,7 +65,16 @@ public:
|
||||
|
||||
void prepareCamera(const CameraInfo& camera) noexcept;
|
||||
void prepareUpscaler(math::float2 scale, DynamicResolutionOptions const& options) noexcept;
|
||||
void prepareViewport(const filament::Viewport& viewport) noexcept;
|
||||
|
||||
/*
|
||||
* @param viewport viewport (should be same as RenderPassParams::viewport)
|
||||
* @param xoffset horizontal rendering offset *within* the viewport.
|
||||
* Non-zero when we have guard bands.
|
||||
* @param yoffset vertical rendering offset *within* the viewport.
|
||||
* Non-zero when we have guard bands.
|
||||
*/
|
||||
void prepareViewport(const filament::Viewport& viewport, uint32_t xoffset, uint32_t yoffset) noexcept;
|
||||
|
||||
void prepareTime(math::float4 const& userTime) noexcept;
|
||||
void prepareTemporalNoise(TemporalAntiAliasingOptions const& options) noexcept;
|
||||
void prepareExposure(float ev100) noexcept;
|
||||
@@ -84,8 +93,7 @@ public:
|
||||
math::mat4f const& uvFromViewMatrix,
|
||||
ScreenSpaceReflectionsOptions const& ssrOptions) noexcept;
|
||||
|
||||
void prepareShadowMapping(ShadowMappingUniforms const& shadowMappingUniforms,
|
||||
VsmShadowOptions const& options) noexcept;
|
||||
void prepareShadowMapping() noexcept;
|
||||
|
||||
void prepareDirectionalLight(float exposure,
|
||||
math::float3 const& sceneSpaceDirection, LightManagerInstance instance) noexcept;
|
||||
@@ -94,11 +102,20 @@ public:
|
||||
|
||||
void prepareDynamicLights(Froxelizer& froxelizer) noexcept;
|
||||
|
||||
// maybe these should have their own UBO, they're needed only when GENERATING the shadowmaps
|
||||
void prepareShadowVSM(TextureHandle texture, VsmShadowOptions const& options) noexcept;
|
||||
void prepareShadowPCF(TextureHandle texture) noexcept;
|
||||
void prepareShadowDPCF(TextureHandle texture, SoftShadowOptions const& options) noexcept;
|
||||
void prepareShadowPCSS(TextureHandle texture, SoftShadowOptions const& options) noexcept;
|
||||
void prepareShadowVSM(TextureHandle texture,
|
||||
ShadowMappingUniforms const& shadowMappingUniforms,
|
||||
VsmShadowOptions const& options) noexcept;
|
||||
|
||||
void prepareShadowPCF(TextureHandle texture,
|
||||
ShadowMappingUniforms const& shadowMappingUniforms) noexcept;
|
||||
|
||||
void prepareShadowDPCF(TextureHandle texture,
|
||||
ShadowMappingUniforms const& shadowMappingUniforms,
|
||||
SoftShadowOptions const& options) noexcept;
|
||||
|
||||
void prepareShadowPCSS(TextureHandle texture,
|
||||
ShadowMappingUniforms const& shadowMappingUniforms,
|
||||
SoftShadowOptions const& options) noexcept;
|
||||
|
||||
// update local data into GPU UBO
|
||||
void commit(backend::DriverApi& driver) noexcept;
|
||||
@@ -116,6 +133,8 @@ private:
|
||||
backend::Handle<backend::HwBufferObject> mUniformBufferHandle;
|
||||
backend::Handle<backend::HwSamplerGroup> mSamplerGroupHandle;
|
||||
std::uniform_real_distribution<float> mUniformDistribution{ 0.0f, 1.0f };
|
||||
static void prepareShadowSampling(PerViewUib& uniforms,
|
||||
ShadowMappingUniforms const& shadowMappingUniforms) noexcept;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -531,8 +531,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
|
||||
data.history = builder.sample(history);
|
||||
}
|
||||
},
|
||||
[this, projection = cameraInfo.projection, viewMatrix = cameraInfo.view,
|
||||
worldOrigin = cameraInfo.worldOrigin, uvFromClipMatrix, historyProjection,
|
||||
[this, projection = cameraInfo.projection,
|
||||
userViewMatrix = cameraInfo.getUserViewMatrix(), uvFromClipMatrix, historyProjection,
|
||||
options, &uniforms, renderPass = pass]
|
||||
(FrameGraphResources const& resources, auto const& data, DriverApi& driver) mutable {
|
||||
// set structure sampler
|
||||
@@ -542,7 +542,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
|
||||
// set screen-space reflections and screen-space refractions
|
||||
mat4f uvFromViewMatrix = uvFromClipMatrix * projection;
|
||||
mat4f reprojection = mat4f{ uvFromClipMatrix * historyProjection
|
||||
* inverse(viewMatrix * worldOrigin) };
|
||||
* inverse(userViewMatrix) };
|
||||
|
||||
// the history sampler is a regular texture2D
|
||||
TextureHandle history = data.history ?
|
||||
@@ -805,11 +805,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
|
||||
mi->setParameter("ssctConeAngleTangeant", std::tan(options.ssct.lightConeRad * 0.5f));
|
||||
mi->setParameter("ssctContactDistanceMaxInv", 1.0f / options.ssct.contactDistanceMax);
|
||||
// light direction in view space
|
||||
// (note: this is actually equivalent to using the camera view matrix -- before the
|
||||
// world matrix is accounted for)
|
||||
const mat4f m{ cameraInfo.view * cameraInfo.worldOrigin };
|
||||
const mat4f view{ cameraInfo.getUserViewMatrix() };
|
||||
const float3 l = normalize(
|
||||
mat3f::getTransformForNormals(m.upperLeft())
|
||||
mat3f::getTransformForNormals(view.upperLeft())
|
||||
* options.ssct.lightDirection);
|
||||
mi->setParameter("ssctIntensity",
|
||||
options.ssct.enabled ? options.ssct.intensity : 0.0f);
|
||||
@@ -973,8 +971,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::generateGaussianMipmap(Frame
|
||||
auto from = input;
|
||||
for (size_t i = 0; i < levels - 1; i++) {
|
||||
auto output = mipmapPass->out[i];
|
||||
gaussianBlurPass(fg, from, output, reinhard, kernelWidth, sigma);
|
||||
from = output;
|
||||
from = gaussianBlurPass(fg, from, output, reinhard, kernelWidth, sigma);
|
||||
reinhard = false; // only do the reinhard filtering on the first level
|
||||
}
|
||||
|
||||
@@ -1035,7 +1032,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
|
||||
// and because it's a separable filter, the effective 2D filter kernel size is 17*17
|
||||
// The total number of samples needed over the two passes is 18.
|
||||
|
||||
fg.addPass<BlurPassData>("Gaussian Blur Pass (separable)",
|
||||
auto& blurPass = fg.addPass<BlurPassData>("Gaussian Blur Pass (separable)",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
auto inDesc = builder.getDescriptor(input);
|
||||
|
||||
@@ -1143,7 +1140,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
|
||||
render(hwOutRT, separableGaussianBlur.getPipelineState(mEngine), driver);
|
||||
});
|
||||
|
||||
return output;
|
||||
return blurPass->out;
|
||||
}
|
||||
|
||||
PostProcessManager::ScreenSpaceRefConfig PostProcessManager::prepareMipmapSSR(FrameGraph& fg,
|
||||
@@ -1342,7 +1339,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::generateMipmapSSR(
|
||||
input = ppm.resolveBaseLevel(fg, "ssr", input);
|
||||
// then blit into an appropriate texture
|
||||
// this handles scaling, format conversion and mipmaping
|
||||
input = ppm.opaqueBlit(fg, input, outDesc);
|
||||
input = ppm.opaqueBlit(fg, input, { 0, 0, desc.width, desc.height }, outDesc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1471,13 +1468,16 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::dof(FrameGraph& fg,
|
||||
*/
|
||||
const uint32_t dofResolution = dofOptions.nativeResolution ? 1u : 2u;
|
||||
|
||||
constexpr const uint32_t maxMipLevels = 4u; // mip levels at full-resolution
|
||||
constexpr const uint32_t maxMipLevelsMask = (1u << maxMipLevels) - 1u;
|
||||
auto const& colorDesc = fg.getDescriptor(input);
|
||||
const uint32_t width = ((colorDesc.width + maxMipLevelsMask) & ~maxMipLevelsMask) / dofResolution;
|
||||
const uint32_t height = ((colorDesc.height + maxMipLevelsMask) & ~maxMipLevelsMask) / dofResolution;
|
||||
const uint8_t maxLevelCount = FTexture::maxLevelCount(width, height);
|
||||
uint8_t mipmapCount = min(maxLevelCount, uint8_t(maxMipLevels));
|
||||
const uint32_t width = colorDesc.width / dofResolution;
|
||||
const uint32_t height = colorDesc.height / dofResolution;
|
||||
|
||||
// at full resolution, 4 "safe" levels are guaranteed
|
||||
constexpr const uint32_t maxMipLevels = 4u;
|
||||
|
||||
// compute numbers of "safe" levels (should be 4, but can be 3 at half res)
|
||||
const uint8_t mipmapCount = std::min(maxMipLevels, ctz(width | height));
|
||||
assert_invariant(mipmapCount == maxMipLevels || mipmapCount == maxMipLevels-1);
|
||||
|
||||
/*
|
||||
* Setup:
|
||||
@@ -1928,7 +1928,7 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloomPass(FrameGraph& fg
|
||||
if (2 * width < desc.width || 2 * height < desc.height) {
|
||||
// if we're scaling down by more than 2x, prescale the image with a blit to improve
|
||||
// performance. This is important on mobile/tilers.
|
||||
input = opaqueBlit(fg, input, {
|
||||
input = opaqueBlit(fg, input, { 0, 0, desc.width, desc.height }, {
|
||||
.width = std::max(1u, desc.width / 2),
|
||||
.height = std::max(1u, desc.height / 2),
|
||||
.format = outFormat
|
||||
@@ -2265,14 +2265,13 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::customResolveUncompressPass(
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
FrameGraphId<FrameGraphTexture> bloom,
|
||||
FrameGraphId<FrameGraphTexture> flare,
|
||||
FColorGrading const* colorGrading,
|
||||
ColorGradingConfig const& colorGradingConfig,
|
||||
BloomOptions const& bloomOptions,
|
||||
VignetteOptions const& vignetteOptions,
|
||||
float2 scale) noexcept
|
||||
VignetteOptions const& vignetteOptions) noexcept
|
||||
{
|
||||
FrameGraphId<FrameGraphTexture> bloomDirt;
|
||||
FrameGraphId<FrameGraphTexture> starburst;
|
||||
@@ -2309,11 +2308,10 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
|
||||
auto& ppColorGrading = fg.addPass<PostProcessColorGrading>("colorGrading",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
auto const& inputDesc = fg.getDescriptor(input);
|
||||
data.input = builder.sample(input);
|
||||
data.output = builder.createTexture("colorGrading output", {
|
||||
.width = inputDesc.width,
|
||||
.height = inputDesc.height,
|
||||
.width = vp.width,
|
||||
.height = vp.height,
|
||||
.format = colorGradingConfig.ldrFormat
|
||||
});
|
||||
data.output = builder.declareRenderPass(data.output);
|
||||
@@ -2390,6 +2388,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
bloomParameters.y = 1.0f - bloomParameters.x;
|
||||
}
|
||||
|
||||
auto const& input = resources.getDescriptor(data.input);
|
||||
auto const& output = resources.getDescriptor(data.output);
|
||||
float4 vignetteParameters = getVignetteParameters(
|
||||
vignetteOptions, output.width, output.height);
|
||||
@@ -2402,6 +2401,12 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
mi->setParameter("vignetteColor", vignetteOptions.color);
|
||||
mi->setParameter("fxaa", colorGradingConfig.fxaa);
|
||||
mi->setParameter("temporalNoise", temporalNoise);
|
||||
mi->setParameter("viewport", float4{
|
||||
(float)vp.left / input.width,
|
||||
(float)vp.bottom / input.height,
|
||||
(float)vp.width / input.width,
|
||||
(float)vp.height / input.height
|
||||
});
|
||||
|
||||
const uint8_t variant = uint8_t(colorGradingConfig.translucent ?
|
||||
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE);
|
||||
@@ -2414,7 +2419,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::fxaa(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
TextureFormat outFormat, bool translucent) noexcept {
|
||||
|
||||
struct PostProcessFXAA {
|
||||
@@ -2424,17 +2429,16 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::fxaa(FrameGraph& fg,
|
||||
|
||||
auto& ppFXAA = fg.addPass<PostProcessFXAA>("fxaa",
|
||||
[&](FrameGraph::Builder& builder, auto& data) {
|
||||
auto const& inputDesc = fg.getDescriptor(input);
|
||||
data.input = builder.sample(input);
|
||||
data.output = builder.createTexture("fxaa output", {
|
||||
.width = inputDesc.width,
|
||||
.height = inputDesc.height,
|
||||
.width = vp.width,
|
||||
.height = vp.height,
|
||||
.format = outFormat
|
||||
});
|
||||
data.output = builder.declareRenderPass(data.output);
|
||||
},
|
||||
[=](FrameGraphResources const& resources,
|
||||
auto const& data, DriverApi& driver) {
|
||||
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
auto const& inDesc = resources.getDescriptor(data.input);
|
||||
auto const& texture = resources.getTexture(data.input);
|
||||
auto const& out = resources.getRenderPassInfo();
|
||||
|
||||
@@ -2444,6 +2448,13 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::fxaa(FrameGraph& fg,
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR
|
||||
});
|
||||
mi->setParameter("viewport", float4{
|
||||
(float)vp.left / inDesc.width,
|
||||
(float)vp.bottom / inDesc.height,
|
||||
(float)vp.width / inDesc.width,
|
||||
(float)vp.height / inDesc.height
|
||||
});
|
||||
mi->setParameter("texelSize", 1.0f / float2{ inDesc.width, inDesc.height });
|
||||
|
||||
const uint8_t variant = uint8_t(translucent ?
|
||||
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE);
|
||||
@@ -2463,7 +2474,7 @@ void PostProcessManager::prepareTaa(FrameGraph& fg, filament::Viewport const& sv
|
||||
auto& current = frameHistory.getCurrent().*pTaa;
|
||||
|
||||
// compute projection
|
||||
current.projection = mat4f{ inoutCameraInfo->projection * (inoutCameraInfo->view * inoutCameraInfo->worldOrigin) };
|
||||
current.projection = mat4f{ inoutCameraInfo->projection * inoutCameraInfo->getUserViewMatrix() };
|
||||
current.frameId = previous.frameId + 1;
|
||||
|
||||
// sample position within a pixel [-0.5, 0.5]
|
||||
@@ -2560,7 +2571,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
float2 d = sampleOffsets[i] - current.jitter;
|
||||
d *= 1.0f / taaOptions.filterWidth;
|
||||
// this is a gaussian fit of a 3.3 Blackman Harris window
|
||||
// see: "High Quality Temporal Supersampling" by Bruan Karis
|
||||
// see: "High Quality Temporal Supersampling" by Brian Karis
|
||||
weights[i] = std::exp2(-3.3f * (d.x * d.x + d.y * d.y));
|
||||
sum += weights[i];
|
||||
}
|
||||
@@ -2628,7 +2639,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::opaqueBlit(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, FrameGraphTexture::Descriptor const& outDesc,
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
SamplerMagFilter filter) noexcept {
|
||||
|
||||
struct PostProcessScaling {
|
||||
@@ -2654,7 +2666,9 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::opaqueBlit(FrameGraph& fg,
|
||||
// HwTexture handle. Using a RenderPass works because data.input will resolve
|
||||
// to the actual imported render target and will have the correct viewport.
|
||||
builder.declareRenderPass("opaque blit input", {
|
||||
.attachments = { .color = { data.input }}});
|
||||
.attachments = { .color = { data.input }},
|
||||
.viewport = vp
|
||||
});
|
||||
},
|
||||
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
auto out = resources.getRenderPassInfo(0);
|
||||
@@ -2671,7 +2685,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::opaqueBlit(FrameGraph& fg,
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::blendBlit(
|
||||
FrameGraph& fg, bool translucent, DynamicResolutionOptions dsrOptions,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
FrameGraphTexture::Descriptor const& outDesc) noexcept {
|
||||
|
||||
Handle<HwRenderPrimitive> fullScreenRenderPrimitive = mEngine.getFullScreenRenderPrimitive();
|
||||
@@ -2688,13 +2702,12 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blendBlit(
|
||||
|| dsrOptions.quality == QualityLevel::HIGH);
|
||||
|
||||
// helper to set the EASU uniforms
|
||||
auto setEasuUniforms = [](FMaterialInstance* mi,
|
||||
auto setEasuUniforms = [vp](FMaterialInstance* mi,
|
||||
FrameGraphTexture::Descriptor const& inputDesc,
|
||||
FrameGraphTexture::Descriptor const& outputDesc) {
|
||||
FSRUniforms uniforms{};
|
||||
FSR_ScalingSetup(&uniforms, {
|
||||
.viewportWidth = inputDesc.width,
|
||||
.viewportHeight = inputDesc.height,
|
||||
.input = vp,
|
||||
.inputWidth = inputDesc.width,
|
||||
.inputHeight = inputDesc.height,
|
||||
.outputWidth = outputDesc.width,
|
||||
@@ -2786,6 +2799,12 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::blendBlit(
|
||||
mi->setParameter("resolution",
|
||||
float4{ outputDesc.width, outputDesc.height,
|
||||
1.0f / outputDesc.width, 1.0f / outputDesc.height });
|
||||
mi->setParameter("viewport", float4{
|
||||
(float)vp.left / inputDesc.width,
|
||||
(float)vp.bottom / inputDesc.height,
|
||||
(float)vp.width / inputDesc.width,
|
||||
(float)vp.height / inputDesc.height
|
||||
});
|
||||
mi->commit(driver);
|
||||
mi->use(driver);
|
||||
}
|
||||
|
||||
@@ -162,14 +162,13 @@ public:
|
||||
|
||||
// Color grading, tone mapping, dithering and bloom
|
||||
FrameGraphId<FrameGraphTexture> colorGrading(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
FrameGraphId<FrameGraphTexture> bloom,
|
||||
FrameGraphId<FrameGraphTexture> flare,
|
||||
const FColorGrading* colorGrading,
|
||||
ColorGradingConfig const& colorGradingConfig,
|
||||
BloomOptions const& bloomOptions,
|
||||
VignetteOptions const& vignetteOptions,
|
||||
math::float2 scale) noexcept;
|
||||
VignetteOptions const& vignetteOptions) noexcept;
|
||||
|
||||
void colorGradingPrepareSubpass(backend::DriverApi& driver, const FColorGrading* colorGrading,
|
||||
ColorGradingConfig const& colorGradingConfig,
|
||||
@@ -188,8 +187,8 @@ public:
|
||||
|
||||
// Anti-aliasing
|
||||
FrameGraphId<FrameGraphTexture> fxaa(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, backend::TextureFormat outFormat,
|
||||
bool translucent) noexcept;
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
backend::TextureFormat outFormat, bool translucent) noexcept;
|
||||
|
||||
// Temporal Anti-aliasing
|
||||
void prepareTaa(FrameGraph& fg, filament::Viewport const& svp,
|
||||
@@ -208,12 +207,13 @@ public:
|
||||
|
||||
// Blit/rescaling/resolves
|
||||
FrameGraphId<FrameGraphTexture> opaqueBlit(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input, FrameGraphTexture::Descriptor const& outDesc,
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
FrameGraphTexture::Descriptor const& outDesc,
|
||||
backend::SamplerMagFilter filter = backend::SamplerMagFilter::LINEAR) noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> blendBlit(
|
||||
FrameGraph& fg, bool translucent, DynamicResolutionOptions dsrOptions,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> input, filament::Viewport const& vp,
|
||||
FrameGraphTexture::Descriptor const& outDesc) noexcept;
|
||||
|
||||
// resolve base level of input and outputs a 1-level texture
|
||||
|
||||
@@ -138,6 +138,15 @@ void RenderPass::appendCommands(CommandTypeFlags const commandTypeFlags) noexcep
|
||||
// "eof" command. these commands are guaranteed to be sorted last in the
|
||||
// command buffer.
|
||||
curr[commandCount - 1].key = uint64_t(Pass::SENTINEL);
|
||||
|
||||
// Go over all the commands and call prepareProgram().
|
||||
// This must be done from the main thread.
|
||||
for (Command const* first = curr, *last = curr + commandCount ; first != last ; ++first) {
|
||||
if (UTILS_LIKELY((first->key & CUSTOM_MASK) == uint64_t(CustomCommand::PASS))) {
|
||||
auto ma = first->primitive.mi->getMaterial();
|
||||
ma->prepareProgram(first->primitive.materialVariant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RenderPass::appendCustomCommand(Pass pass, CustomCommand custom, uint32_t order,
|
||||
@@ -402,7 +411,6 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags,
|
||||
if constexpr (isColorPass) {
|
||||
cmdColor.primitive.primitiveHandle = primitive.getHwHandle();
|
||||
RenderPass::setupColorCommand(cmdColor, variant, mi, inverseFrontFaces);
|
||||
ma->prepareProgram(cmdColor.primitive.materialVariant);
|
||||
|
||||
cmdColor.primitive.morphWeightBuffer = morphing.handle;
|
||||
cmdColor.primitive.morphTargetBuffer = morphTargets.buffer->getHwHandle();
|
||||
@@ -487,7 +495,6 @@ void RenderPass::generateCommandsImpl(uint32_t extraFlags,
|
||||
}
|
||||
|
||||
if constexpr (isDepthPass) {
|
||||
ma->prepareProgram(cmdDepth.primitive.materialVariant);
|
||||
const RasterState rs = ma->getRasterState();
|
||||
const TransparencyMode mode = mi->getTransparencyMode();
|
||||
const BlendingMode blendingMode = ma->getBlendingMode();
|
||||
@@ -535,7 +542,7 @@ void RenderPass::updateSummedPrimitiveCounts(
|
||||
|
||||
void RenderPass::Executor::execute(const char* name,
|
||||
backend::Handle<backend::HwRenderTarget> renderTarget,
|
||||
backend::RenderPassParams params) const noexcept {
|
||||
backend::RenderPassParams const& params) const noexcept {
|
||||
FEngine& engine = mEngine;
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
|
||||
@@ -333,7 +333,7 @@ public:
|
||||
~Executor() noexcept;
|
||||
void execute(const char* name,
|
||||
backend::Handle<backend::HwRenderTarget> renderTarget,
|
||||
backend::RenderPassParams params) const noexcept;
|
||||
backend::RenderPassParams const& params) const noexcept;
|
||||
};
|
||||
|
||||
// returns a new executor for this pass
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 "RendererUtils.h"
|
||||
|
||||
#include "details/Engine.h"
|
||||
#include "details/View.h"
|
||||
|
||||
#include "fg/FrameGraph.h"
|
||||
#include "fg/FrameGraphId.h"
|
||||
#include "fg/FrameGraphResources.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace backend;
|
||||
using namespace math;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
|
||||
FrameGraph& fg, const char* name, FEngine& engine, FView const& view,
|
||||
FrameGraphTexture::Descriptor const& colorBufferDesc,
|
||||
ColorPassConfig const& config, PostProcessManager::ColorGradingConfig colorGradingConfig,
|
||||
RenderPass::Executor const& passExecutor) noexcept {
|
||||
|
||||
struct ColorPassData {
|
||||
FrameGraphId<FrameGraphTexture> shadows;
|
||||
FrameGraphId<FrameGraphTexture> color;
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
FrameGraphId<FrameGraphTexture> depth;
|
||||
FrameGraphId<FrameGraphTexture> ssao;
|
||||
FrameGraphId<FrameGraphTexture> ssr; // either screen-space reflections or refractions
|
||||
FrameGraphId<FrameGraphTexture> structure;
|
||||
float4 clearColor{};
|
||||
TargetBufferFlags clearFlags{};
|
||||
};
|
||||
|
||||
Blackboard& blackboard = fg.getBlackboard();
|
||||
|
||||
auto& colorPass = fg.addPass<ColorPassData>(name,
|
||||
[&](FrameGraph::Builder& builder, ColorPassData& data) {
|
||||
|
||||
TargetBufferFlags clearDepthFlags = config.clearFlags & TargetBufferFlags::DEPTH;
|
||||
TargetBufferFlags clearColorFlags = config.clearFlags & TargetBufferFlags::COLOR;
|
||||
|
||||
data.shadows = blackboard.get<FrameGraphTexture>("shadows");
|
||||
data.ssao = blackboard.get<FrameGraphTexture>("ssao");
|
||||
data.color = blackboard.get<FrameGraphTexture>("color");
|
||||
data.depth = blackboard.get<FrameGraphTexture>("depth");
|
||||
|
||||
// Screen-space reflection or refractions
|
||||
data.ssr = blackboard.get<FrameGraphTexture>("ssr");
|
||||
if (data.ssr) {
|
||||
data.ssr = builder.sample(data.ssr);
|
||||
}
|
||||
|
||||
if (config.hasContactShadows) {
|
||||
data.structure = blackboard.get<FrameGraphTexture>("structure");
|
||||
assert_invariant(data.structure);
|
||||
data.structure = builder.sample(data.structure);
|
||||
}
|
||||
|
||||
if (data.shadows) {
|
||||
data.shadows = builder.sample(data.shadows);
|
||||
}
|
||||
|
||||
if (data.ssao) {
|
||||
data.ssao = builder.sample(data.ssao);
|
||||
}
|
||||
|
||||
if (!data.color) {
|
||||
// FIXME: this works only when the viewport is full
|
||||
// if (view.isSkyboxVisible()) {
|
||||
// // if the skybox is visible, then we don't need to clear at all
|
||||
// clearColorFlags &= ~TargetBufferFlags::COLOR;
|
||||
// }
|
||||
data.color = builder.createTexture("Color Buffer", colorBufferDesc);
|
||||
}
|
||||
|
||||
const bool canResolveDepth = engine.getDriverApi().isAutoDepthResolveSupported();
|
||||
|
||||
if (!data.depth) {
|
||||
// clear newly allocated depth buffers, regardless of given clear flags
|
||||
clearDepthFlags = TargetBufferFlags::DEPTH;
|
||||
data.depth = builder.createTexture("Depth Buffer", {
|
||||
.width = colorBufferDesc.width,
|
||||
.height = colorBufferDesc.height,
|
||||
// If the color attachment requested MS, we assume this means the MS buffer
|
||||
// must be kept, and for that reason we allocate the depth buffer with MS
|
||||
// as well. On the other hand, if the color attachment was allocated without
|
||||
// MS, no need to allocate the depth buffer with MS, if the RT is MS,
|
||||
// the tile depth buffer will be MS, but it'll be resolved to single
|
||||
// sample automatically -- which is what we want.
|
||||
.samples = canResolveDepth ? colorBufferDesc.samples : uint8_t(config.msaa),
|
||||
.format = TextureFormat::DEPTH32F,
|
||||
});
|
||||
}
|
||||
|
||||
if (colorGradingConfig.asSubpass) {
|
||||
data.output = builder.createTexture("Tonemapped Buffer", {
|
||||
.width = colorBufferDesc.width,
|
||||
.height = colorBufferDesc.height,
|
||||
.format = colorGradingConfig.ldrFormat
|
||||
});
|
||||
data.color = builder.read(data.color, FrameGraphTexture::Usage::SUBPASS_INPUT);
|
||||
data.output = builder.write(data.output, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
} else if (colorGradingConfig.customResolve) {
|
||||
data.color = builder.read(data.color, FrameGraphTexture::Usage::SUBPASS_INPUT);
|
||||
}
|
||||
|
||||
// We set a "read" constraint on these attachments here because we need to preserve them
|
||||
// when the color pass happens in several passes (e.g. with SSR)
|
||||
data.color = builder.read(data.color, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
data.depth = builder.read(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
|
||||
data.color = builder.write(data.color, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
data.depth = builder.write(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
|
||||
/*
|
||||
* There is a bit of magic happening here regarding the viewport used.
|
||||
* We do not specify the viewport in declareRenderPass() below, so it will be
|
||||
* deduced automatically to be { 0, 0, w, h }, with w,h the min width/height of
|
||||
* all the attachments. This has the side effect of moving the viewport to the
|
||||
* origin and ignore the left/bottom of 'svp'. The attachment sizes are set from
|
||||
* svp's width/height, however.
|
||||
* But that's not all! When we're rendering directly into the swap-chain (by way
|
||||
* of calling forwardResource() later), the effective viewport comes from the
|
||||
* imported resource (i.e. the swap-chain) and is set to 'vp' which has its
|
||||
* left/bottom honored -- the view is therefore rendered directly where it should
|
||||
* be (the imported resource viewport is set to 'vp', see how 'fgViewRenderTarget'
|
||||
* is initialized in this file).
|
||||
*/
|
||||
builder.declareRenderPass("Color Pass Target", {
|
||||
.attachments = { .color = { data.color, data.output }, .depth = data.depth },
|
||||
.samples = config.msaa,
|
||||
.clearFlags = clearColorFlags | clearDepthFlags });
|
||||
|
||||
data.clearColor = config.clearColor;
|
||||
data.clearFlags = clearColorFlags | clearDepthFlags;
|
||||
|
||||
blackboard["depth"] = data.depth;
|
||||
},
|
||||
[=, &view, &engine](FrameGraphResources const& resources,
|
||||
ColorPassData const& data, DriverApi& driver) {
|
||||
auto out = resources.getRenderPassInfo();
|
||||
|
||||
// set samplers and uniforms
|
||||
view.prepareSSAO(data.ssao ?
|
||||
resources.getTexture(data.ssao) : engine.getOneTextureArray());
|
||||
|
||||
view.prepareShadowMap();
|
||||
|
||||
// set shadow sampler
|
||||
view.prepareShadow(data.shadows ?
|
||||
resources.getTexture(data.shadows) : engine.getOneTextureArray());
|
||||
|
||||
// set structure sampler
|
||||
view.prepareStructure(data.structure ?
|
||||
resources.getTexture(data.structure) : engine.getOneTexture());
|
||||
|
||||
// set screen-space reflections and screen-space refractions
|
||||
TextureHandle ssr = data.ssr ?
|
||||
resources.getTexture(data.ssr) : engine.getOneTextureArray();
|
||||
|
||||
view.prepareSSR(ssr, config.ssrLodOffset,
|
||||
view.getScreenSpaceReflectionsOptions());
|
||||
|
||||
// Note: here we can't use data.color's descriptor for the viewport because
|
||||
// the actual viewport might be offset when the target is the swapchain.
|
||||
// However, the width/height should be the same.
|
||||
assert_invariant(
|
||||
out.params.viewport.width == resources.getDescriptor(data.color).width);
|
||||
assert_invariant(
|
||||
out.params.viewport.height == resources.getDescriptor(data.color).height);
|
||||
|
||||
view.prepareViewport(static_cast<filament::Viewport&>(out.params.viewport),
|
||||
config.xoffset, config.yoffset);
|
||||
view.commitUniforms(driver);
|
||||
|
||||
out.params.clearColor = data.clearColor;
|
||||
out.params.flags.clear = data.clearFlags;
|
||||
if (view.getBlendMode() == BlendMode::TRANSLUCENT) {
|
||||
if (any(out.params.flags.discardStart & TargetBufferFlags::COLOR0)) {
|
||||
// if the buffer is discarded (e.g. it's new) and we're blending,
|
||||
// then clear it to transparent
|
||||
out.params.flags.clear |= TargetBufferFlags::COLOR;
|
||||
out.params.clearColor = {};
|
||||
}
|
||||
}
|
||||
|
||||
if (colorGradingConfig.asSubpass || colorGradingConfig.customResolve) {
|
||||
out.params.subpassMask = 1;
|
||||
}
|
||||
passExecutor.execute(resources.getPassName(), out.target, out.params);
|
||||
|
||||
// color pass is typically heavy, and we don't have much CPU work left after
|
||||
// this point, so flushing now allows us to start the GPU earlier and reduce
|
||||
// latency, without creating bubbles.
|
||||
driver.flush();
|
||||
}
|
||||
);
|
||||
|
||||
// when color grading is done as a subpass, the output of the color-pass is the ldr buffer
|
||||
auto output = colorGradingConfig.asSubpass ? colorPass->output : colorPass->color;
|
||||
|
||||
blackboard["color"] = output;
|
||||
return output;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> RendererUtils::refractionPass(
|
||||
FrameGraph& fg, FEngine& engine, FView const& view,
|
||||
ColorPassConfig config,
|
||||
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
|
||||
PostProcessManager::ColorGradingConfig colorGradingConfig,
|
||||
RenderPass const& pass) noexcept {
|
||||
|
||||
auto& blackboard = fg.getBlackboard();
|
||||
auto input = blackboard.get<FrameGraphTexture>("color");
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
|
||||
// find the first refractive object
|
||||
RenderPass::Command const* const refraction = std::partition_point(pass.begin(), pass.end(),
|
||||
[](auto const& command) {
|
||||
return (command.key & RenderPass::PASS_MASK) < uint64_t(RenderPass::Pass::REFRACT);
|
||||
});
|
||||
|
||||
const bool hasScreenSpaceRefraction =
|
||||
(refraction->key & RenderPass::PASS_MASK) == uint64_t(RenderPass::Pass::REFRACT);
|
||||
|
||||
// if there wasn't any refractive object, just skip everything below.
|
||||
if (UTILS_UNLIKELY(hasScreenSpaceRefraction)) {
|
||||
PostProcessManager& ppm = engine.getPostProcessManager();
|
||||
|
||||
// clear the color/depth buffers, which will orphan (and cull) the color pass
|
||||
input.clear();
|
||||
blackboard.remove("color");
|
||||
blackboard.remove("depth");
|
||||
|
||||
input = RendererUtils::colorPass(fg, "Color Pass (opaque)", engine, view, {
|
||||
// When rendering the opaques, we need to conserve the sample buffer,
|
||||
// so create config that specifies the sample count.
|
||||
.width = config.width,
|
||||
.height = config.height,
|
||||
.samples = config.msaa,
|
||||
.format = config.hdrFormat
|
||||
}, config, { .asSubpass = false },
|
||||
pass.getExecutor(pass.begin(), refraction));
|
||||
|
||||
// generate the mipmap chain
|
||||
PostProcessManager::generateMipmapSSR(ppm, fg,
|
||||
input, ssrConfig.refraction, true, ssrConfig);
|
||||
|
||||
// Now we're doing the refraction pass proper.
|
||||
// This uses the same framebuffer (color and depth) used by the opaque pass. This happens
|
||||
// automatically because these are set in the Blackboard (they were set by the opaque
|
||||
// pass). For this reason, `desc` below is only used in colorPass() for the width and
|
||||
// height.
|
||||
config.clearFlags = TargetBufferFlags::NONE;
|
||||
output = RendererUtils::colorPass(fg, "Color Pass (transparent)", engine, view,
|
||||
{ .width = config.width, .height = config.height },
|
||||
config, colorGradingConfig, pass.getExecutor(refraction, pass.end()));
|
||||
|
||||
if (config.msaa > 1 && !colorGradingConfig.asSubpass) {
|
||||
// We need to do a resolve here because later passes (such as color grading or DoF) will need
|
||||
// to sample from 'output'. However, because we have MSAA, we know we're not sampleable.
|
||||
// And this is because in the SSR case, we had to use a renderbuffer to conserve the
|
||||
// multi-sample buffer.
|
||||
output = ppm.resolveBaseLevel(fg, "Resolved Color Buffer", output);
|
||||
}
|
||||
} else {
|
||||
output = input;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void RendererUtils::readPixels(backend::DriverApi& driver, Handle<HwRenderTarget> renderTargetHandle,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
|
||||
backend::PixelBufferDescriptor&& buffer) {
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(
|
||||
buffer.type != PixelDataType::COMPRESSED,
|
||||
"buffer.format cannot be COMPRESSED")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(
|
||||
buffer.alignment > 0 && buffer.alignment <= 8 &&
|
||||
!(buffer.alignment & (buffer.alignment - 1u)),
|
||||
"buffer.alignment must be 1, 2, 4 or 8")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It's not really possible to know here which formats will be supported because
|
||||
// it can vary depending on the RenderTarget, in GL the following are ALWAYS supported though:
|
||||
// format: RGBA, RGBA_INTEGER
|
||||
// type: UBYTE, UINT, INT, FLOAT
|
||||
|
||||
const size_t sizeNeeded = PixelBufferDescriptor::computeDataSize(
|
||||
buffer.format, buffer.type,
|
||||
buffer.stride ? buffer.stride : width,
|
||||
buffer.top + height,
|
||||
buffer.alignment);
|
||||
|
||||
if (!ASSERT_POSTCONDITION_NON_FATAL(buffer.size >= sizeNeeded,
|
||||
"Pixel buffer too small: has %u bytes, needs %u bytes", buffer.size, sizeNeeded)) {
|
||||
return;
|
||||
}
|
||||
|
||||
driver.readPixels(renderTargetHandle, xoffset, yoffset, width, height, std::move(buffer));
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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_FILAMENT_DETAILS_RENDERERUTILS_H
|
||||
#define TNT_FILAMENT_DETAILS_RENDERERUTILS_H
|
||||
|
||||
#include "PostProcessManager.h"
|
||||
#include "RenderPass.h"
|
||||
|
||||
#include "fg/FrameGraphId.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/PixelBufferDescriptor.h>
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
class FRenderTarget;
|
||||
class FrameGraph;
|
||||
class FrameGraph;
|
||||
class FView;
|
||||
|
||||
class RendererUtils {
|
||||
public:
|
||||
|
||||
struct ColorPassConfig {
|
||||
// Rendering viewport width (e.g. scaled down viewport from dynamic resolution)
|
||||
uint32_t width;
|
||||
// Rendering viewport height (e.g. scaled down viewport from dynamic resolution)
|
||||
uint32_t height;
|
||||
// Rendering offset within the viewport (e.g. non-zero when we have guard bands)
|
||||
uint32_t xoffset;
|
||||
// Rendering offset within the viewport (e.g. non-zero when we have guard bands)
|
||||
uint32_t yoffset;
|
||||
// dynamic resolution scale
|
||||
math::float2 scale;
|
||||
// HDR format
|
||||
backend::TextureFormat hdrFormat;
|
||||
// MSAA sample count
|
||||
uint8_t msaa;
|
||||
// Clear flags
|
||||
backend::TargetBufferFlags clearFlags;
|
||||
// Clear color
|
||||
math::float4 clearColor = {};
|
||||
// Lod offset for the SSR passes
|
||||
float ssrLodOffset;
|
||||
// Contact shadow enabled?
|
||||
bool hasContactShadows;
|
||||
};
|
||||
|
||||
static FrameGraphId<FrameGraphTexture> colorPass(
|
||||
FrameGraph& fg, const char* name, FEngine& engine, FView const& view,
|
||||
FrameGraphTexture::Descriptor const& colorBufferDesc,
|
||||
ColorPassConfig const& config,
|
||||
PostProcessManager::ColorGradingConfig colorGradingConfig,
|
||||
RenderPass::Executor const& passExecutor) noexcept;
|
||||
|
||||
static FrameGraphId<FrameGraphTexture> refractionPass(
|
||||
FrameGraph& fg, FEngine& engine, FView const& view,
|
||||
ColorPassConfig config,
|
||||
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
|
||||
PostProcessManager::ColorGradingConfig colorGradingConfig,
|
||||
RenderPass const& pass) noexcept;
|
||||
|
||||
static void readPixels(backend::DriverApi& driver,
|
||||
backend::Handle<backend::HwRenderTarget> renderTargetHandle,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
|
||||
backend::PixelBufferDescriptor&& buffer);
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_FILAMENT_DETAILS_RENDERERUTILS_H
|
||||
@@ -60,6 +60,9 @@ ShadowMap::ShadowMap(FEngine& engine) noexcept :
|
||||
}
|
||||
}
|
||||
|
||||
void ShadowMap::terminate(FEngine& engine) {
|
||||
}
|
||||
|
||||
ShadowMap::~ShadowMap() {
|
||||
FEngine& engine = mEngine;
|
||||
Entity entities[] = { mCamera->getEntity(), mDebugCamera->getEntity() };
|
||||
@@ -318,7 +321,7 @@ void ShadowMap::updateDirectional(const FScene::LightSoa& lightData, size_t inde
|
||||
|
||||
if (params.options.stable) {
|
||||
// Use the world origin as reference point, fixed w.r.t. the camera
|
||||
snapLightFrustum(s, o, Mv, camera.worldOrigin[3].xyz,
|
||||
snapLightFrustum(s, o, Mv, -camera.getWorldOffset(),
|
||||
1.0f / mShadowMapInfo.shadowDimension);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ public:
|
||||
|
||||
~ShadowMap();
|
||||
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
struct ShadowMapInfo {
|
||||
// the dimension of the encompassing texture atlas
|
||||
uint16_t atlasDimension = 0;
|
||||
|
||||
@@ -40,6 +40,10 @@ ShadowMapManager::ShadowMapManager(FEngine& engine) { // NOLINT(cppcoreguideline
|
||||
for (auto& entry : mShadowMapCache) {
|
||||
new (&entry) ShadowMap(engine);
|
||||
}
|
||||
|
||||
mShadowUbh = engine.getDriverApi().createBufferObject(mShadowUb.getSize(),
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC);
|
||||
|
||||
FDebugRegistry& debugRegistry = engine.getDebugRegistry();
|
||||
debugRegistry.registerProperty("d.shadowmap.visualize_cascades",
|
||||
&engine.debug.shadowmap.visualize_cascades);
|
||||
@@ -47,16 +51,25 @@ ShadowMapManager::ShadowMapManager(FEngine& engine) { // NOLINT(cppcoreguideline
|
||||
&engine.debug.shadowmap.tightly_bound_scene);
|
||||
}
|
||||
|
||||
void ShadowMapManager::terminate(FEngine& engine) {
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
driver.destroyBufferObject(mShadowUbh);
|
||||
UTILS_NOUNROLL
|
||||
for (auto& entry : mShadowMapCache) {
|
||||
std::launder(reinterpret_cast<ShadowMap*>(&entry))->terminate(engine);
|
||||
}
|
||||
}
|
||||
|
||||
ShadowMapManager::~ShadowMapManager() {
|
||||
// destroy the ShadowMap array in-place
|
||||
UTILS_NOUNROLL
|
||||
for (auto& entry : mShadowMapCache) {
|
||||
std::launder(reinterpret_cast<ShadowMap*>(&entry))->~ShadowMap();
|
||||
std::destroy_at(std::launder(reinterpret_cast<ShadowMap*>(&entry)));
|
||||
}
|
||||
}
|
||||
|
||||
ShadowMapManager::ShadowTechnique ShadowMapManager::update(FEngine& engine, FView& view,
|
||||
CameraInfo const& cameraInfo, TypedUniformBuffer<ShadowUib>& shadowUb,
|
||||
CameraInfo const& cameraInfo,
|
||||
FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData) noexcept {
|
||||
ShadowTechnique shadowTechnique = {};
|
||||
|
||||
@@ -71,7 +84,13 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::update(FEngine& engine, FVie
|
||||
engine, view, cameraInfo, renderableData, lightData, sceneInfo);
|
||||
|
||||
shadowTechnique |= updateSpotShadowMaps(
|
||||
engine, view, cameraInfo, renderableData, lightData, sceneInfo, shadowUb);
|
||||
engine, view, cameraInfo, renderableData, lightData, sceneInfo);
|
||||
|
||||
if (mShadowUb.isDirty()) {
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
driver.updateBufferObject(mShadowUbh,
|
||||
mShadowUb.toBufferDescriptor(driver), 0);
|
||||
}
|
||||
|
||||
return shadowTechnique;
|
||||
}
|
||||
@@ -166,8 +185,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FrameGraph& fg,
|
||||
.format = view.hasVSM() ? vsmTextureFormat : mTextureFormat
|
||||
});
|
||||
},
|
||||
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) { });
|
||||
|
||||
[&view](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
|
||||
// set uniforms needed to render this ShadowMap
|
||||
view.prepareShadowMap();
|
||||
});
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -177,12 +198,11 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FrameGraph& fg,
|
||||
|
||||
struct ShadowPassData {
|
||||
FrameGraphId<FrameGraphTexture> tempBlurSrc{}; // temporary shadowmap when blurring
|
||||
FrameGraphId<FrameGraphTexture> output;
|
||||
uint32_t blurRt{};
|
||||
uint32_t shadowRt{};
|
||||
};
|
||||
|
||||
auto shadows = prepareShadowPass.getData().shadows;
|
||||
|
||||
auto& ppm = engine.getPostProcessManager();
|
||||
|
||||
for (auto const& entry : passList) {
|
||||
@@ -195,7 +215,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FrameGraph& fg,
|
||||
|
||||
FrameGraphRenderPass::Descriptor renderTargetDesc{};
|
||||
|
||||
auto attachment = builder.createSubresource(prepareShadowPass->shadows,
|
||||
data.output = builder.createSubresource(prepareShadowPass->shadows,
|
||||
"Shadowmap Layer", { .layer = layer });
|
||||
|
||||
if (view.hasVSM()) {
|
||||
@@ -220,10 +240,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FrameGraph& fg,
|
||||
depth = builder.write(depth,
|
||||
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
|
||||
attachment = builder.write(attachment,
|
||||
data.output = builder.write(data.output,
|
||||
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
|
||||
renderTargetDesc.attachments.color[0] = attachment;
|
||||
renderTargetDesc.attachments.color[0] = data.output;
|
||||
renderTargetDesc.attachments.depth = depth;
|
||||
renderTargetDesc.clearFlags =
|
||||
TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH;
|
||||
@@ -247,9 +267,9 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FrameGraph& fg,
|
||||
}
|
||||
} else {
|
||||
// the shadowmap layer
|
||||
attachment = builder.write(attachment,
|
||||
data.output = builder.write(data.output,
|
||||
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
renderTargetDesc.attachments.depth = attachment;
|
||||
renderTargetDesc.attachments.depth = data.output;
|
||||
renderTargetDesc.clearFlags = TargetBufferFlags::DEPTH;
|
||||
}
|
||||
|
||||
@@ -286,12 +306,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FrameGraph& fg,
|
||||
// than clearing.
|
||||
const uint32_t dim = options->mapSize;
|
||||
filament::Viewport viewport{ 1, 1, dim - 2, dim - 2 };
|
||||
view.prepareViewport(viewport);
|
||||
|
||||
// set uniforms needed to render this ShadowMap
|
||||
// Currently these uniforms are owned by View and are global, but eventually
|
||||
// this will set a separate per shadowmap UBO
|
||||
view.prepareShadowMap();
|
||||
view.prepareViewport(viewport, 0, 0);
|
||||
|
||||
view.commitUniforms(driver);
|
||||
|
||||
@@ -314,7 +329,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FrameGraph& fg,
|
||||
const float ratio = float(kernelWidth + 1) / sigma;
|
||||
ppm.gaussianBlurPass(fg,
|
||||
shadowPass->tempBlurSrc,
|
||||
shadows,
|
||||
shadowPass->output,
|
||||
false, kernelWidth, ratio);
|
||||
}
|
||||
|
||||
@@ -324,14 +339,14 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FrameGraph& fg,
|
||||
if (textureRequirements.levels > 1) {
|
||||
for (size_t level = 0; level < textureRequirements.levels - 1; level++) {
|
||||
const bool finalize = level == textureRequirements.levels - 2;
|
||||
shadows = ppm.vsmMipmapPass(fg, shadows, layer, level,
|
||||
ppm.vsmMipmapPass(fg, prepareShadowPass->shadows, layer, level,
|
||||
vsmClearColor, finalize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shadows;
|
||||
return prepareShadowPass->shadows;
|
||||
}
|
||||
|
||||
ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEngine& engine,
|
||||
@@ -476,8 +491,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateCascadeShadowMaps(FEng
|
||||
|
||||
ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine& engine,
|
||||
FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FScene::LightSoa& lightData, ShadowMap::SceneInfo& sceneInfo,
|
||||
TypedUniformBuffer<ShadowUib>& shadowUb) noexcept {
|
||||
FScene::LightSoa& lightData, ShadowMap::SceneInfo& sceneInfo) noexcept {
|
||||
|
||||
auto& lcm = engine.getLightManager();
|
||||
|
||||
@@ -537,7 +551,7 @@ ShadowMapManager::ShadowTechnique ShadowMapManager::updateSpotShadowMaps(FEngine
|
||||
// note: normalBias is set to zero for VSM
|
||||
const float normalBias = shadowMapInfo.vsm ? 0.0f : options->normalBias;
|
||||
|
||||
auto& s = shadowUb.edit();
|
||||
auto& s = mShadowUb.edit();
|
||||
const float n = shadowMap.getCamera().getNear();
|
||||
const float f = shadowMap.getCamera().getCullingFar();
|
||||
s.shadows[i].lightFromWorldMatrix = shadowMap.getLightSpaceMatrix();
|
||||
|
||||
@@ -74,6 +74,8 @@ public:
|
||||
explicit ShadowMapManager(FEngine& engine);
|
||||
~ShadowMapManager();
|
||||
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
// Reset shadow map layout.
|
||||
void reset() noexcept;
|
||||
|
||||
@@ -83,7 +85,7 @@ public:
|
||||
// Updates all of the shadow maps and performs culling.
|
||||
// Returns true if any of the shadow maps have visible shadows.
|
||||
ShadowMapManager::ShadowTechnique update(FEngine& engine, FView& view,
|
||||
CameraInfo const& cameraInfo, TypedUniformBuffer<ShadowUib>& shadowUb,
|
||||
CameraInfo const& cameraInfo,
|
||||
FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData) noexcept;
|
||||
|
||||
// Renders all the shadow maps.
|
||||
@@ -114,6 +116,10 @@ public:
|
||||
return mShadowMappingUniforms;
|
||||
}
|
||||
|
||||
auto& getShadowUniforms() const { return mShadowUb; }
|
||||
|
||||
auto& getShadowUniformsHandle() const { return mShadowUbh; }
|
||||
|
||||
private:
|
||||
ShadowMapManager::ShadowTechnique updateCascadeShadowMaps(FEngine& engine,
|
||||
FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
@@ -121,8 +127,7 @@ private:
|
||||
|
||||
ShadowMapManager::ShadowTechnique updateSpotShadowMaps(FEngine& engine,
|
||||
FView& view, CameraInfo const& cameraInfo, FScene::RenderableSoa& renderableData,
|
||||
FScene::LightSoa& lightData, ShadowMap::SceneInfo& sceneInfo,
|
||||
TypedUniformBuffer<ShadowUib>& shadowUb) noexcept;
|
||||
FScene::LightSoa& lightData, ShadowMap::SceneInfo& sceneInfo) noexcept;
|
||||
|
||||
void calculateTextureRequirements(FEngine& engine, FView& view, FScene::LightSoa& lightData) noexcept;
|
||||
|
||||
@@ -207,6 +212,9 @@ private:
|
||||
// TODO: iOS does not support the DEPTH16 texture format.
|
||||
backend::TextureFormat mTextureFormat = backend::TextureFormat::DEPTH16;
|
||||
|
||||
mutable TypedUniformBuffer<ShadowUib> mShadowUb;
|
||||
backend::Handle<backend::HwBufferObject> mShadowUbh;
|
||||
|
||||
ShadowMappingUniforms mShadowMappingUniforms;
|
||||
|
||||
utils::FixedCapacityVector<ShadowMapEntry> mCascadeShadowMaps{
|
||||
|
||||
@@ -156,8 +156,8 @@ math::mat4 FCamera::getProjectionMatrix() const noexcept {
|
||||
// Note that this math ends up setting the projection matrix' p33 to 0, which is where we're
|
||||
// getting back a lot of precision in the depth buffer.
|
||||
const mat4 m{ mat4::row_major_init{
|
||||
mScaling.x, 0.0, 0.0, mShiftCS.x,
|
||||
0.0, mScaling.y, 0.0, mShiftCS.y,
|
||||
mScalingCS.x, 0.0, 0.0, mShiftCS.x,
|
||||
0.0, mScalingCS.y, 0.0, mShiftCS.y,
|
||||
0.0, 0.0, -0.5, 0.5, // GL to inverted DX convention
|
||||
0.0, 0.0, 0.0, 1.0
|
||||
}};
|
||||
@@ -167,8 +167,8 @@ math::mat4 FCamera::getProjectionMatrix() const noexcept {
|
||||
math::mat4 FCamera::getCullingProjectionMatrix() const noexcept {
|
||||
// The culling projection matrix stays in the GL convention
|
||||
const mat4 m{ mat4::row_major_init{
|
||||
mScaling.x, 0.0, 0.0, mShiftCS.x,
|
||||
0.0, mScaling.y, 0.0, mShiftCS.y,
|
||||
mScalingCS.x, 0.0, 0.0, mShiftCS.x,
|
||||
0.0, mScalingCS.y, 0.0, mShiftCS.y,
|
||||
0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0
|
||||
}};
|
||||
@@ -275,7 +275,7 @@ CameraInfo::CameraInfo(FCamera const& camera) noexcept {
|
||||
zn = camera.getNear();
|
||||
zf = camera.getCullingFar();
|
||||
ev100 = Exposure::ev100(camera);
|
||||
f = camera.getFocalLength();
|
||||
f = (float)camera.getFocalLength();
|
||||
A = f / camera.getAperture();
|
||||
d = std::max(zn, camera.getFocusDistance());
|
||||
}
|
||||
@@ -286,14 +286,13 @@ CameraInfo::CameraInfo(FCamera const& camera, const math::mat4& worldOriginCamer
|
||||
cullingProjection = mat4f{ camera.getCullingProjectionMatrix() };
|
||||
model = mat4f{ modelMatrix };
|
||||
view = mat4f{ inverse(modelMatrix) };
|
||||
worldOrigin = worldOriginCamera;
|
||||
zn = camera.getNear();
|
||||
zf = camera.getCullingFar();
|
||||
ev100 = Exposure::ev100(camera);
|
||||
f = camera.getFocalLength();
|
||||
f = (float)camera.getFocalLength();
|
||||
A = f / camera.getAperture();
|
||||
d = std::max(zn, camera.getFocusDistance());
|
||||
worldOffset = camera.getPosition();
|
||||
worldOrigin = worldOriginCamera;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -63,9 +63,9 @@ public:
|
||||
void setCustomProjection(math::mat4 const& projection,
|
||||
math::mat4 const& projectionForCulling, double near, double far) noexcept;
|
||||
|
||||
void setScaling(math::double2 scaling) noexcept { mScaling = scaling; }
|
||||
void setScaling(math::double2 scaling) noexcept { mScalingCS = scaling; }
|
||||
|
||||
math::double4 getScaling() const noexcept { return math::double4{ mScaling, 1.0, 1.0 }; }
|
||||
math::double4 getScaling() const noexcept { return math::double4{ mScalingCS, 1.0, 1.0 }; }
|
||||
|
||||
void setShift(math::double2 shift) noexcept { mShiftCS = shift * 2.0; }
|
||||
|
||||
@@ -189,8 +189,8 @@ private:
|
||||
|
||||
math::mat4 mProjection; // projection matrix (infinite far)
|
||||
math::mat4 mProjectionForCulling; // projection matrix (with far plane)
|
||||
math::double2 mScaling = { 1.0f }; // additional scaling applied to projection
|
||||
math::double2 mShiftCS = { 0.0f }; // additional translation applied to projection
|
||||
math::double2 mScalingCS = { 1.0 }; // additional scaling applied to projection
|
||||
math::double2 mShiftCS = { 0.0 }; // additional translation applied to projection
|
||||
|
||||
float mNear{};
|
||||
float mFar{};
|
||||
@@ -209,18 +209,18 @@ struct CameraInfo {
|
||||
math::mat4f projection; // projection matrix for drawing (infinite zfar)
|
||||
math::mat4f cullingProjection; // projection matrix for culling
|
||||
math::mat4f model; // camera model matrix
|
||||
math::mat4f view; // camera view matrix
|
||||
math::mat4f view; // camera view matrix (inverse(model))
|
||||
math::mat4 worldOrigin; // world origin transform (already applied to model and view)
|
||||
float zn{}; // distance (positive) to the near plane
|
||||
float zf{}; // distance (positive) to the far plane
|
||||
float ev100{}; // exposure
|
||||
float f{}; // focal length [m]
|
||||
float A{}; // f-number or f / aperture diameter [m]
|
||||
float d{}; // focus distance [m]
|
||||
math::float3 worldOffset{}; // world offset, API-level camera position
|
||||
math::mat4 worldOrigin; // this is already applied to model and view
|
||||
math::float3 const& getPosition() const noexcept { return model[3].xyz; }
|
||||
math::float3 getForwardVector() const noexcept { return normalize(-model[2].xyz); }
|
||||
|
||||
math::float3 getWorldOffset() const noexcept { return -worldOrigin[3].xyz; }
|
||||
math::mat4 getUserViewMatrix() const noexcept { return view * worldOrigin; }
|
||||
};
|
||||
|
||||
FILAMENT_UPCAST(Camera)
|
||||
|
||||
@@ -695,7 +695,6 @@ FRenderer* FEngine::createRenderer() noexcept {
|
||||
FRenderer* p = mHeapAllocator.make<FRenderer>(*this);
|
||||
if (p) {
|
||||
mRenderers.insert(p);
|
||||
p->init();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -139,17 +139,32 @@ FRenderTarget::FRenderTarget(FEngine& engine, const RenderTarget::Builder& build
|
||||
}
|
||||
};
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
if (mAttachments[i].texture) {
|
||||
mAttachmentMask |= getTargetBufferFlagsAt(i);
|
||||
Attachment const& attachment = mAttachments[i];
|
||||
if (attachment.texture) {
|
||||
TargetBufferFlags targetBufferBit = getTargetBufferFlagsAt(i);
|
||||
mAttachmentMask |= targetBufferBit;
|
||||
setAttachment(mrt[i], (AttachmentPoint)i);
|
||||
if (any(attachment.texture->getUsage() &
|
||||
(TextureUsage::SAMPLEABLE | Texture::Usage::SUBPASS_INPUT))) {
|
||||
mSampleableAttachmentsMask |= targetBufferBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mAttachments[(size_t)AttachmentPoint::DEPTH].texture) {
|
||||
|
||||
Attachment const& depthAttachment = mAttachments[(size_t)AttachmentPoint::DEPTH];
|
||||
if (depthAttachment.texture) {
|
||||
mAttachmentMask |= TargetBufferFlags::DEPTH;
|
||||
setAttachment(dinfo, AttachmentPoint::DEPTH);
|
||||
if (any(depthAttachment.texture->getUsage() &
|
||||
(TextureUsage::SAMPLEABLE | Texture::Usage::SUBPASS_INPUT))) {
|
||||
mSampleableAttachmentsMask |= TargetBufferFlags::DEPTH;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add stencil here when we support it
|
||||
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
mHandle = driver.createRenderTarget(mAttachmentMask,
|
||||
builder.mImpl->mWidth, builder.mImpl->mHeight, builder.mImpl->mSamples, mrt, dinfo, {});
|
||||
|
||||
@@ -55,6 +55,10 @@ public:
|
||||
return mAttachmentMask;
|
||||
}
|
||||
|
||||
backend::TargetBufferFlags getSampleableAttachmentsMask() const noexcept {
|
||||
return mSampleableAttachmentsMask;
|
||||
}
|
||||
|
||||
uint8_t getSupportedColorAttachmentsCount() const noexcept {
|
||||
return mSupportedColorAttachmentsCount;
|
||||
}
|
||||
@@ -67,6 +71,7 @@ private:
|
||||
Attachment mAttachments[ATTACHMENT_COUNT];
|
||||
HwHandle mHandle{};
|
||||
backend::TargetBufferFlags mAttachmentMask = {};
|
||||
backend::TargetBufferFlags mSampleableAttachmentsMask = {};
|
||||
const uint8_t mSupportedColorAttachmentsCount;
|
||||
};
|
||||
|
||||
|
||||
+420
-623
File diff suppressed because it is too large
Load Diff
@@ -33,17 +33,13 @@
|
||||
#include <fg/FrameGraphTexture.h>
|
||||
|
||||
#include <filament/Renderer.h>
|
||||
#include <filament/View.h>
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
#include <backend/PresentCallable.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/JobSystem.h>
|
||||
#include <utils/Slice.h>
|
||||
|
||||
#include <tsl/robin_set.h>
|
||||
|
||||
@@ -53,12 +49,9 @@ namespace backend {
|
||||
class Driver;
|
||||
} // namespace backend
|
||||
|
||||
class View;
|
||||
|
||||
class FEngine;
|
||||
class FRenderTarget;
|
||||
class FView;
|
||||
class ShadowMap;
|
||||
|
||||
/*
|
||||
* A concrete implementation of the Renderer Interface.
|
||||
@@ -68,39 +61,42 @@ class FRenderer : public Renderer {
|
||||
|
||||
public:
|
||||
explicit FRenderer(FEngine& engine);
|
||||
|
||||
~FRenderer() noexcept;
|
||||
|
||||
void init() noexcept;
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
FEngine& getEngine() const noexcept { return mEngine; }
|
||||
|
||||
math::float4 getShaderUserTime() const { return mShaderUserTime; }
|
||||
|
||||
// do all the work here!
|
||||
void renderJob(ArenaScope& arena, FView& view);
|
||||
void resetUserTime();
|
||||
|
||||
// renders a single standalone view. The view must have a a custom rendertarget.
|
||||
void renderStandaloneView(FView const* view);
|
||||
|
||||
// start a frame
|
||||
bool beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano);
|
||||
|
||||
// end a frame
|
||||
void endFrame();
|
||||
|
||||
// render a view. must be called between beginFrame/enfFrame.
|
||||
void render(FView const* view);
|
||||
|
||||
// read pixel from the current swapchain. must be called between beginFrame/enfFrame.
|
||||
void readPixels(uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
|
||||
backend::PixelBufferDescriptor&& buffer);
|
||||
|
||||
void copyFrame(FSwapChain* dstSwapChain, Viewport const& dstViewport,
|
||||
Viewport const& srcViewport, CopyFrameFlag flags);
|
||||
|
||||
void endFrame();
|
||||
|
||||
void renderStandaloneView(FView const* view);
|
||||
|
||||
// read pixel from a rendertarget. must be called between beginFrame/enfFrame.
|
||||
void readPixels(FRenderTarget* renderTarget,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
|
||||
backend::PixelBufferDescriptor&& buffer);
|
||||
|
||||
void resetUserTime();
|
||||
// blits the current swapchain to another one
|
||||
void copyFrame(FSwapChain* dstSwapChain, Viewport const& dstViewport,
|
||||
Viewport const& srcViewport, CopyFrameFlag flags);
|
||||
|
||||
// Clean-up everything, this is typically called when the client calls Engine::destroyRenderer()
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
void setDisplayInfo(DisplayInfo const& info) noexcept {
|
||||
mDisplayInfo = info;
|
||||
@@ -131,84 +127,45 @@ public:
|
||||
private:
|
||||
friend class Renderer;
|
||||
using Command = RenderPass::Command;
|
||||
using clock = std::chrono::steady_clock;
|
||||
using Epoch = clock::time_point;
|
||||
using duration = clock::duration;
|
||||
|
||||
void initializeClearFlags();
|
||||
|
||||
backend::TextureFormat getHdrFormat(const FView& view, bool translucent) const noexcept;
|
||||
backend::TextureFormat getLdrFormat(bool translucent) const noexcept;
|
||||
|
||||
Epoch getUserEpoch() const { return mUserEpoch; }
|
||||
duration getUserTime() const noexcept { return clock::now() - getUserEpoch(); }
|
||||
|
||||
void getRenderTarget(FView const& view,
|
||||
backend::TargetBufferFlags& outAttachementMask,
|
||||
backend::Handle<backend::HwRenderTarget>& outTarget) const noexcept;
|
||||
|
||||
void readPixels(backend::Handle<backend::HwRenderTarget> renderTargetHandle,
|
||||
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
|
||||
backend::PixelBufferDescriptor&& buffer);
|
||||
|
||||
void renderInternal(FView const* view);
|
||||
|
||||
struct ColorPassConfig {
|
||||
// User Viewport
|
||||
Viewport vp;
|
||||
// Scaled (down) viewport from dynamic resolution
|
||||
Viewport svp;
|
||||
// dynamic resolution scale
|
||||
math::float2 scale;
|
||||
// HDR format
|
||||
backend::TextureFormat hdrFormat;
|
||||
// MSAA sample count
|
||||
uint8_t msaa;
|
||||
// Clear flags
|
||||
backend::TargetBufferFlags clearFlags;
|
||||
// Clear color
|
||||
math::float4 clearColor = {};
|
||||
// Lod offset for the SSR passes
|
||||
float ssrLodOffset;
|
||||
// Contact shadow enabled?
|
||||
bool hasContactShadows;
|
||||
};
|
||||
|
||||
FrameGraphId<FrameGraphTexture> colorPass(FrameGraph& fg, const char* name,
|
||||
FrameGraphTexture::Descriptor const& colorBufferDesc,
|
||||
ColorPassConfig const& config,
|
||||
PostProcessManager::ColorGradingConfig colorGradingConfig,
|
||||
RenderPass::Executor const& passExecutor, FView const& view) const noexcept;
|
||||
|
||||
FrameGraphId<FrameGraphTexture> refractionPass(FrameGraph& fg,
|
||||
ColorPassConfig config,
|
||||
PostProcessManager::ScreenSpaceRefConfig const& ssrConfig,
|
||||
PostProcessManager::ColorGradingConfig colorGradingConfig,
|
||||
RenderPass const& pass, FView const& view) const noexcept;
|
||||
|
||||
void recordHighWatermark(size_t watermark) noexcept {
|
||||
mCommandsHighWatermark = std::max(mCommandsHighWatermark, watermark);
|
||||
}
|
||||
|
||||
size_t getCommandsHighWatermark() const noexcept {
|
||||
return mCommandsHighWatermark * sizeof(RenderPass::Command);
|
||||
return mCommandsHighWatermark;
|
||||
}
|
||||
|
||||
backend::TextureFormat getHdrFormat(const View& view, bool translucent) const noexcept;
|
||||
backend::TextureFormat getLdrFormat(bool translucent) const noexcept;
|
||||
|
||||
void initializeClearFlags();
|
||||
|
||||
using clock = std::chrono::steady_clock;
|
||||
using Epoch = clock::time_point;
|
||||
using duration = clock::duration;
|
||||
|
||||
Epoch getUserEpoch() const { return mUserEpoch; }
|
||||
duration getUserTime() const noexcept {
|
||||
return clock::now() - getUserEpoch();
|
||||
}
|
||||
void renderInternal(FView const* view);
|
||||
void renderJob(ArenaScope& arena, FView& view);
|
||||
|
||||
// keep a reference to our engine
|
||||
FEngine& mEngine;
|
||||
FrameSkipper mFrameSkipper;
|
||||
backend::Handle<backend::HwRenderTarget> mRenderTarget;
|
||||
backend::Handle<backend::HwRenderTarget> mRenderTargetHandle;
|
||||
FSwapChain* mSwapChain = nullptr;
|
||||
size_t mCommandsHighWatermark = 0;
|
||||
uint32_t mFrameId = 0;
|
||||
uint32_t mViewRenderedCount = 0;
|
||||
FrameInfoManager mFrameInfoManager;
|
||||
backend::TextureFormat mHdrTranslucent{};
|
||||
backend::TextureFormat mHdrQualityMedium{};
|
||||
backend::TextureFormat mHdrQualityHigh{};
|
||||
backend::TextureFormat mHdrTranslucent;
|
||||
backend::TextureFormat mHdrQualityMedium;
|
||||
backend::TextureFormat mHdrQualityHigh;
|
||||
bool mIsRGB8Supported : 1;
|
||||
Epoch mUserEpoch;
|
||||
math::float4 mShaderUserTime{};
|
||||
|
||||
@@ -92,9 +92,6 @@ FView::FView(FEngine& engine)
|
||||
mLightUbh = driver.createBufferObject(CONFIG_MAX_LIGHT_COUNT * sizeof(LightsUib),
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC);
|
||||
|
||||
mShadowUbh = driver.createBufferObject(mShadowUb.getSize(),
|
||||
BufferObjectBinding::UNIFORM, BufferUsage::DYNAMIC);
|
||||
|
||||
mIsDynamicResolutionSupported = driver.isFrameTimeSupported();
|
||||
|
||||
mDefaultColorGrading = mColorGrading = engine.getDefaultColorGrading();
|
||||
@@ -114,9 +111,9 @@ void FView::terminate(FEngine& engine) {
|
||||
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
driver.destroyBufferObject(mLightUbh);
|
||||
driver.destroyBufferObject(mShadowUbh);
|
||||
driver.destroyBufferObject(mRenderableUbh);
|
||||
drainFrameHistory(engine);
|
||||
mShadowMapManager.terminate(engine);
|
||||
mPerViewUniforms.terminate(driver);
|
||||
mFroxelizer.terminate(driver);
|
||||
}
|
||||
@@ -160,10 +157,6 @@ float2 FView::updateScale(FEngine& engine,
|
||||
Renderer::FrameRateOptions const& frameRateOptions,
|
||||
Renderer::DisplayInfo const& displayInfo) noexcept {
|
||||
|
||||
// scale factor returned to the caller is modified so the scaled viewport is rounded to
|
||||
// 8 pixels. The internal scale factor, mScale, doesn't have this rounding.
|
||||
float2 roundedScale = mScale;
|
||||
|
||||
DynamicResolutionOptions const& options = mDynamicResolution;
|
||||
if (options.enabled) {
|
||||
if (!UTILS_UNLIKELY(info.valid)) {
|
||||
@@ -241,14 +234,8 @@ float2 FView::updateScale(FEngine& engine,
|
||||
// (i.e. we clamped). This help not to have to wait too long for the Integral term
|
||||
// to kick in after a clamping event.
|
||||
mPidController.setIntegralInhibitionEnabled(mScale != s);
|
||||
|
||||
// now tweak the scaling factor to get multiples of 8 (to help quad-shading)
|
||||
// i.e. 8x8=64 fragments, to try to help with warp sizes.
|
||||
roundedScale.x = mScale.x == 1.0f ? 1.0f : (std::floor(mScale.x * float{ w } / 8) * 8) / float{ w };
|
||||
roundedScale.y = mScale.y == 1.0f ? 1.0f : (std::floor(mScale.y * float{ h } / 8) * 8) / float{ h };
|
||||
} else {
|
||||
mScale = 1.0f;
|
||||
roundedScale = 1.0f;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -270,7 +257,7 @@ float2 FView::updateScale(FEngine& engine,
|
||||
};
|
||||
#endif
|
||||
|
||||
return roundedScale;
|
||||
return mScale;
|
||||
}
|
||||
|
||||
void FView::setVisibleLayers(uint8_t select, uint8_t values) noexcept {
|
||||
@@ -340,7 +327,7 @@ void FView::prepareShadowing(FEngine& engine, DriverApi& driver,
|
||||
}
|
||||
|
||||
auto shadowTechnique = mShadowMapManager.update(engine, *this, cameraInfo,
|
||||
mShadowUb, renderableData, lightData);
|
||||
renderableData, lightData);
|
||||
|
||||
mHasShadowing = any(shadowTechnique);
|
||||
mNeedsShadowMap = any(shadowTechnique & ShadowMapManager::ShadowTechnique::SHADOW_MAP);
|
||||
@@ -407,8 +394,8 @@ void FView::prepareLighting(FEngine& engine, FEngine::DriverApi& driver, ArenaSc
|
||||
mHasDirectionalLight = directionalLight.isValid();
|
||||
}
|
||||
|
||||
CameraInfo FView::computeCameraInfo(FEngine& engine) noexcept {
|
||||
FScene* const scene = getScene();
|
||||
CameraInfo FView::computeCameraInfo(FEngine& engine) const noexcept {
|
||||
FScene const* const scene = getScene();
|
||||
|
||||
/*
|
||||
* We apply a "world origin" to "everything" in order to implement the IBL rotation.
|
||||
@@ -451,11 +438,25 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
|
||||
* and in particular their world-space AABB.
|
||||
*/
|
||||
|
||||
FScene* const scene = getScene();
|
||||
auto getFrustum = [this, &cameraInfo]() -> Frustum {
|
||||
if (UTILS_LIKELY(mViewingCamera == nullptr)) {
|
||||
// In the common case when we don't have a viewing camera, cameraInfo.view is
|
||||
// already the culling view matrix
|
||||
return Frustum{ mat4f{ highPrecisionMultiply(cameraInfo.projection, cameraInfo.view) }};
|
||||
} else {
|
||||
// Otherwise, we need to recalculate it from the culling camera.
|
||||
// Note: it is correct to always do the math from mCullingCamera, but it hides the
|
||||
// intent of the code, which is that we should only depend on CameraInfo here.
|
||||
// This is an extremely uncommon case.
|
||||
const mat4 projection = mCullingCamera->getCullingProjectionMatrix();
|
||||
const mat4 view = inverse(cameraInfo.worldOrigin * mCullingCamera->getModelMatrix());
|
||||
return Frustum{ mat4f{ projection * view }};
|
||||
}
|
||||
};
|
||||
|
||||
mCullingFrustum = Frustum(mat4f{
|
||||
mCullingCamera->getCullingProjectionMatrix() *
|
||||
inverse(cameraInfo.worldOrigin * mCullingCamera->getModelMatrix()) });
|
||||
const Frustum cullingFrustum = getFrustum();
|
||||
|
||||
FScene* const scene = getScene();
|
||||
|
||||
/*
|
||||
* Gather all information needed to render this scene. Apply the world origin to all
|
||||
@@ -470,9 +471,9 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
|
||||
JobSystem::Job* prepareVisibleLightsJob = nullptr;
|
||||
if (scene->getLightData().size() > FScene::DIRECTIONAL_LIGHTS_COUNT) {
|
||||
prepareVisibleLightsJob = js.runAndRetain(js.createJob(nullptr,
|
||||
[this, &engine, &arena, &cameraInfo, scene](JobSystem&, JobSystem::Job*) {
|
||||
[&cullingFrustum, &engine, &arena, &cameraInfo, scene](JobSystem&, JobSystem::Job*) {
|
||||
FView::prepareVisibleLights(engine.getLightManager(), arena,
|
||||
cameraInfo.view, mCullingFrustum, scene->getLightData());
|
||||
cameraInfo.view, cullingFrustum, scene->getLightData());
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -489,7 +490,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, ArenaScope& arena,
|
||||
* (this will set the VISIBLE_RENDERABLE bit)
|
||||
*/
|
||||
|
||||
prepareVisibleRenderables(js, mCullingFrustum, renderableData);
|
||||
prepareVisibleRenderables(js, cullingFrustum, renderableData);
|
||||
|
||||
|
||||
/*
|
||||
@@ -648,14 +649,17 @@ void FView::prepareUpscaler(float2 scale) const noexcept {
|
||||
mPerViewUniforms.prepareUpscaler(scale, mDynamicResolution);
|
||||
}
|
||||
|
||||
void FView::prepareCamera(const CameraInfo& camera) const noexcept {
|
||||
void FView::prepareCamera(const CameraInfo& cameraInfo) const noexcept {
|
||||
SYSTRACE_CALL();
|
||||
mPerViewUniforms.prepareCamera(camera);
|
||||
mPerViewUniforms.prepareCamera(cameraInfo);
|
||||
}
|
||||
|
||||
void FView::prepareViewport(const filament::Viewport& viewport) const noexcept {
|
||||
void FView::prepareViewport(const filament::Viewport& viewport,
|
||||
uint32_t xoffset, uint32_t yoffset) const noexcept {
|
||||
SYSTRACE_CALL();
|
||||
mPerViewUniforms.prepareViewport(viewport);
|
||||
// TODO: we should pass viewport.{left|bottom} to the backend so it can offset the
|
||||
// scissor properly.
|
||||
mPerViewUniforms.prepareViewport(viewport, xoffset, yoffset);
|
||||
}
|
||||
|
||||
void FView::prepareSSAO(Handle<HwTexture> ssao) const noexcept {
|
||||
@@ -680,25 +684,25 @@ void FView::prepareStructure(Handle<HwTexture> structure) const noexcept {
|
||||
}
|
||||
|
||||
void FView::prepareShadow(Handle<HwTexture> texture) const noexcept {
|
||||
const auto& uniforms = mShadowMapManager.getShadowMappingUniforms();
|
||||
switch (mShadowType) {
|
||||
case filament::ShadowType::PCF:
|
||||
mPerViewUniforms.prepareShadowPCF(texture);
|
||||
mPerViewUniforms.prepareShadowPCF(texture, uniforms);
|
||||
break;
|
||||
case filament::ShadowType::VSM:
|
||||
mPerViewUniforms.prepareShadowVSM(texture, mVsmShadowOptions);
|
||||
mPerViewUniforms.prepareShadowVSM(texture, uniforms, mVsmShadowOptions);
|
||||
break;
|
||||
case filament::ShadowType::DPCF:
|
||||
mPerViewUniforms.prepareShadowDPCF(texture, mSoftShadowOptions);
|
||||
mPerViewUniforms.prepareShadowDPCF(texture, uniforms, mSoftShadowOptions);
|
||||
break;
|
||||
case filament::ShadowType::PCSS:
|
||||
mPerViewUniforms.prepareShadowPCSS(texture, mSoftShadowOptions);
|
||||
mPerViewUniforms.prepareShadowPCSS(texture, uniforms, mSoftShadowOptions);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FView::prepareShadowMap() const noexcept {
|
||||
mPerViewUniforms.prepareShadowMapping(
|
||||
mShadowMapManager.getShadowMappingUniforms(), mVsmShadowOptions);
|
||||
mPerViewUniforms.prepareShadowMapping();
|
||||
}
|
||||
|
||||
void FView::cleanupRenderPasses() const noexcept {
|
||||
@@ -713,9 +717,6 @@ void FView::froxelize(FEngine& engine, mat4f const& viewMatrix) const noexcept {
|
||||
|
||||
void FView::commitUniforms(DriverApi& driver) const noexcept {
|
||||
mPerViewUniforms.commit(driver);
|
||||
if (mShadowUb.isDirty()) {
|
||||
driver.updateBufferObject(mShadowUbh, mShadowUb.toBufferDescriptor(driver), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void FView::commitFroxels(DriverApi& driverApi) const noexcept {
|
||||
|
||||
@@ -119,7 +119,7 @@ public:
|
||||
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
CameraInfo computeCameraInfo(FEngine& engine) noexcept;
|
||||
CameraInfo computeCameraInfo(FEngine& engine) const noexcept;
|
||||
|
||||
void prepare(FEngine& engine, backend::DriverApi& driver, ArenaScope& arena,
|
||||
filament::Viewport const& viewport, CameraInfo const& cameraInfo,
|
||||
@@ -161,12 +161,12 @@ public:
|
||||
|
||||
// returns the view's name. The pointer is owned by View.
|
||||
const char* getName() const noexcept {
|
||||
return mName.c_str();
|
||||
return mName.c_str_safe();
|
||||
}
|
||||
|
||||
void prepareUpscaler(math::float2 scale) const noexcept;
|
||||
void prepareCamera(const CameraInfo& camera) const noexcept;
|
||||
void prepareViewport(const Viewport& viewport) const noexcept;
|
||||
void prepareCamera(const CameraInfo& cameraInfo) const noexcept;
|
||||
void prepareViewport(const Viewport& viewport, uint32_t xoffset, uint32_t yoffset) const noexcept;
|
||||
void prepareShadowing(FEngine& engine, backend::DriverApi& driver,
|
||||
FScene::RenderableSoa& renderableData, FScene::LightSoa& lightData,
|
||||
CameraInfo const& cameraInfo) noexcept;
|
||||
@@ -410,8 +410,6 @@ public:
|
||||
static void cullRenderables(utils::JobSystem& js, FScene::RenderableSoa& renderableData,
|
||||
Frustum const& frustum, size_t bit) noexcept;
|
||||
|
||||
auto& getShadowUniforms() const { return mShadowUb; }
|
||||
|
||||
PerViewUniforms const& getPerViewUniforms() const noexcept { return mPerViewUniforms; }
|
||||
PerViewUniforms& getPerViewUniforms() noexcept { return mPerViewUniforms; }
|
||||
|
||||
@@ -479,7 +477,7 @@ private:
|
||||
void bindPerViewUniformsAndSamplers(FEngine::DriverApi& driver) const noexcept {
|
||||
mPerViewUniforms.bind(driver);
|
||||
driver.bindUniformBuffer(BindingPoints::LIGHTS, mLightUbh);
|
||||
driver.bindUniformBuffer(BindingPoints::SHADOW, mShadowUbh);
|
||||
driver.bindUniformBuffer(BindingPoints::SHADOW, mShadowMapManager.getShadowUniformsHandle());
|
||||
driver.bindUniformBuffer(BindingPoints::FROXEL_RECORDS, mFroxelizer.getRecordBuffer());
|
||||
}
|
||||
|
||||
@@ -496,15 +494,14 @@ private:
|
||||
|
||||
// these are accessed in the render loop, keep together
|
||||
backend::Handle<backend::HwBufferObject> mLightUbh;
|
||||
backend::Handle<backend::HwBufferObject> mShadowUbh;
|
||||
backend::Handle<backend::HwBufferObject> mRenderableUbh;
|
||||
|
||||
FScene* mScene = nullptr;
|
||||
// The camera set by the user, used for culling and viewing
|
||||
FCamera* mCullingCamera = nullptr;
|
||||
// The optional (debug) camera, used only for viewing
|
||||
FCamera* mViewingCamera = nullptr;
|
||||
|
||||
Frustum mCullingFrustum{};
|
||||
|
||||
mutable Froxelizer mFroxelizer;
|
||||
|
||||
Viewport mViewport;
|
||||
@@ -542,7 +539,6 @@ private:
|
||||
RenderQuality mRenderQuality;
|
||||
|
||||
mutable PerViewUniforms mPerViewUniforms;
|
||||
mutable TypedUniformBuffer<ShadowUib> mShadowUb;
|
||||
|
||||
mutable FrameHistory mFrameHistory{};
|
||||
|
||||
|
||||
@@ -39,14 +39,19 @@ using namespace math;
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
void FSR_ScalingSetup(FSRUniforms* outUniforms, FSRScalingConfig config) noexcept {
|
||||
FsrEasuCon( outUniforms->EasuCon0.v, outUniforms->EasuCon1.v,
|
||||
FsrEasuConOffset( outUniforms->EasuCon0.v, outUniforms->EasuCon1.v,
|
||||
outUniforms->EasuCon2.v, outUniforms->EasuCon3.v,
|
||||
// Viewport size (top left aligned) in the input image which is to be scaled.
|
||||
config.viewportWidth, config.viewportHeight,
|
||||
config.input.width, config.input.height,
|
||||
// The size of the input image.
|
||||
config.inputWidth, config.inputHeight,
|
||||
// The output resolution.
|
||||
config.outputWidth, config.outputHeight);
|
||||
config.outputWidth, config.outputHeight,
|
||||
// Input image offset
|
||||
config.input.left, config.input.bottom);
|
||||
// FIXME: FsrEasu api claims it needs the top,left offset, but when using that we get
|
||||
// some bad artifacts. To investigate. For reference:
|
||||
// top = config.inputHeight - (config.input.bottom + config.input.height)
|
||||
}
|
||||
|
||||
void FSR_SharpeningSetup(FSRUniforms* outUniforms, FSRSharpeningConfig config) noexcept {
|
||||
|
||||
+7
-6
@@ -17,6 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_FSR_H
|
||||
#define TNT_FILAMENT_FSR_H
|
||||
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <stdint.h>
|
||||
@@ -24,12 +26,11 @@
|
||||
namespace filament {
|
||||
|
||||
struct FSRScalingConfig {
|
||||
uint32_t viewportWidth;
|
||||
uint32_t viewportHeight;
|
||||
uint32_t inputWidth;
|
||||
uint32_t inputHeight;
|
||||
uint32_t outputWidth;
|
||||
uint32_t outputHeight;
|
||||
filament::Viewport input; // region of source to be scaled
|
||||
uint32_t inputWidth; // width of source
|
||||
uint32_t inputHeight; // height of source
|
||||
uint32_t outputWidth; // width of destination
|
||||
uint32_t outputHeight; // height of destination
|
||||
};
|
||||
|
||||
struct FSRSharpeningConfig {
|
||||
|
||||
@@ -5,6 +5,16 @@ material {
|
||||
type : sampler2d,
|
||||
name : colorBuffer,
|
||||
precision : medium
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : viewport,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
name : texelSize,
|
||||
precision : high
|
||||
}
|
||||
],
|
||||
depthWrite : false,
|
||||
@@ -18,7 +28,7 @@ material {
|
||||
vertex {
|
||||
|
||||
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
|
||||
postProcess.vertex.xy = postProcess.normalizedUV;
|
||||
postProcess.vertex.xy = materialParams.viewport.xy + postProcess.normalizedUV * materialParams.viewport.zw;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,40 +38,24 @@ fragment {
|
||||
#include "fxaa.fs"
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
// First, compute an exact upper bound for the area we need to sample from.
|
||||
// The render target may be larger than the viewport that was used for scene
|
||||
// rendering, so we cannot rely on the wrap mode alone.
|
||||
highp vec2 fboSize = vec2(textureSize(materialParams_colorBuffer, 0));
|
||||
highp vec2 invSize = 1.0 / fboSize;
|
||||
highp vec2 halfTexel = 0.5 * invSize;
|
||||
highp vec2 viewportSize = frameUniforms.resolution.xy;
|
||||
|
||||
// The clamp needs to be over-aggressive by a half-texel due to bilinear sampling.
|
||||
highp vec2 excessSize = 0.5 + fboSize - viewportSize;
|
||||
highp vec2 upperBound = 1.0 - excessSize * invSize;
|
||||
|
||||
// Next, compute the coordinates of the texel center and its bounding box.
|
||||
// There is no need to clamp the min corner since the wrap mode will do
|
||||
// it automatically.
|
||||
|
||||
// variable_vertex is already interpolated to pixel center by the GPU
|
||||
highp vec2 texelCenter = min(variable_vertex.xy, upperBound);
|
||||
highp vec2 texelMaxCorner = min(variable_vertex.xy + halfTexel, upperBound);
|
||||
highp vec2 texelMinCorner = variable_vertex.xy - halfTexel;
|
||||
highp vec2 texelCenter = variable_vertex.xy;
|
||||
highp vec2 texelMaxCorner = variable_vertex.xy + materialParams.texelSize * 0.5;
|
||||
highp vec2 texelMinCorner = variable_vertex.xy - materialParams.texelSize * 0.5;
|
||||
|
||||
vec4 color = fxaa(
|
||||
texelCenter,
|
||||
vec4(texelMinCorner, texelMaxCorner),
|
||||
materialParams_colorBuffer,
|
||||
invSize, // FxaaFloat4 fxaaConsoleRcpFrameOpt,
|
||||
2.0 * invSize, // FxaaFloat4 fxaaConsoleRcpFrameOpt2,
|
||||
8.0, // FxaaFloat fxaaConsoleEdgeSharpness,
|
||||
materialParams.texelSize, // FxaaFloat4 fxaaConsoleRcpFrameOpt,
|
||||
2.0 * materialParams.texelSize, // FxaaFloat4 fxaaConsoleRcpFrameOpt2,
|
||||
8.0, // FxaaFloat fxaaConsoleEdgeSharpness,
|
||||
#if defined(G3D_FXAA_PATCHES) && G3D_FXAA_PATCHES == 1
|
||||
0.08, // FxaaFloat fxaaConsoleEdgeThreshold,
|
||||
0.08, // FxaaFloat fxaaConsoleEdgeThreshold,
|
||||
#else
|
||||
0.125, // FxaaFloat fxaaConsoleEdgeThreshold,
|
||||
0.125, // FxaaFloat fxaaConsoleEdgeThreshold,
|
||||
#endif
|
||||
0.04 // FxaaFloat fxaaConsoleEdgeThresholdMin
|
||||
0.04 // FxaaFloat fxaaConsoleEdgeThresholdMin
|
||||
);
|
||||
#if POST_PROCESS_OPAQUE
|
||||
color.a = 1.0;
|
||||
|
||||
@@ -10,6 +10,11 @@ material {
|
||||
type : float4,
|
||||
name : resolution,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : viewport,
|
||||
precision: high
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
@@ -22,7 +27,7 @@ material {
|
||||
|
||||
vertex {
|
||||
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
|
||||
postProcess.vertex.xy = postProcess.normalizedUV;
|
||||
postProcess.vertex.xy = materialParams.viewport.xy + postProcess.normalizedUV * materialParams.viewport.zw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,11 @@ material {
|
||||
{
|
||||
type : float4,
|
||||
name : vignetteColor
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : viewport,
|
||||
precision : high
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
@@ -71,7 +76,7 @@ material {
|
||||
|
||||
vertex {
|
||||
void postProcessVertex(inout PostProcessVertexInputs postProcess) {
|
||||
postProcess.vertex.xy = postProcess.normalizedUV;
|
||||
postProcess.vertex.xy = materialParams.viewport.xy + postProcess.normalizedUV * materialParams.viewport.zw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +103,7 @@ float starburst(const vec2 uv) {
|
||||
return saturate(mask + (1.0 - smoothstep(0.0, 0.3, d)));
|
||||
}
|
||||
|
||||
vec3 bloom(const vec3 color) {
|
||||
highp vec2 uv = variable_vertex.xy;
|
||||
|
||||
vec3 bloom(highp vec2 uv, const vec3 color) {
|
||||
vec3 result = vec3(0.0);
|
||||
|
||||
if (materialParams.bloom.x > 0.0) {
|
||||
@@ -124,27 +127,28 @@ vec3 bloom(const vec3 color) {
|
||||
return result;
|
||||
}
|
||||
|
||||
vec4 resolveFragment(const ivec2 uv) {
|
||||
vec4 resolveFragment(const highp vec2 uv) {
|
||||
#if POST_PROCESS_OPAQUE
|
||||
return vec4(texelFetch(materialParams_colorBuffer, uv, 0).rgb, 1.0);
|
||||
return vec4(textureLod(materialParams_colorBuffer, uv, 0.0).rgb, 1.0);
|
||||
#else
|
||||
vec4 color = texelFetch(materialParams_colorBuffer, uv, 0);
|
||||
vec4 color = textureLod(materialParams_colorBuffer, uv, 0.0);
|
||||
color.rgb *= 1.0 / (color.a + FLT_EPS);
|
||||
return color;
|
||||
#endif
|
||||
}
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
vec4 color = resolveFragment(ivec2(getUV()));
|
||||
highp vec2 uv = variable_vertex.xy;
|
||||
|
||||
vec4 color = resolveFragment(uv);
|
||||
|
||||
// Bloom
|
||||
if (materialParams.bloom.x > 0.0) {
|
||||
color.rgb = bloom(color.rgb);
|
||||
color.rgb = bloom(uv, color.rgb);
|
||||
}
|
||||
|
||||
// Vignette
|
||||
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
|
||||
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
|
||||
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,11 +86,12 @@ vec4 resolveFragment() {
|
||||
}
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
highp vec2 uv = variable_vertex.xy;
|
||||
|
||||
vec4 color = resolveFragment();
|
||||
|
||||
// Vignette
|
||||
if (materialParams.vignette.x < MEDIUMP_FLT_MAX) {
|
||||
highp vec2 uv = getUV() * frameUniforms.resolution.zw;
|
||||
color.rgb = vignette(color.rgb, uv, materialParams.vignette, materialParams.vignetteColor);
|
||||
}
|
||||
|
||||
|
||||
@@ -599,8 +599,8 @@ void postProcess(inout PostProcessInputs postProcess) {
|
||||
#if KERNEL_USE_NOISE
|
||||
// A good random generator is essential, this one is okay.
|
||||
highp vec2 fragCoords = gl_FragCoord.xy;
|
||||
float randomAngle = random(fragCoords) * (2.0 * PI);
|
||||
float random01 = random(fragCoords * vec2(5099.0, 3499.0)); // large primes seem to work ok
|
||||
float randomAngle = interleavedGradientNoise(fragCoords) * (2.0 * PI);
|
||||
float random01 = interleavedGradientNoise(fragCoords * vec2(5099.0, 3499.0)); // large primes seem to work ok
|
||||
noiseState.randomUniformDiskRadius = 0.5 * sqrt(random01);
|
||||
noiseState.randomDisk = vec2(cos(randomAngle), sin(randomAngle));
|
||||
#endif
|
||||
|
||||
@@ -17,11 +17,6 @@
|
||||
// Currently our dilate pass is set-up for 32 max.
|
||||
#define MAX_COC_RADIUS 32.0
|
||||
|
||||
float random(const highp vec2 w) {
|
||||
const vec3 m = vec3(0.06711056, 0.00583715, 52.9829189);
|
||||
return fract(m.z * fract(dot(w, m.xy)));
|
||||
}
|
||||
|
||||
float min2(const vec2 v) {
|
||||
return min(v.x, v.y);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,11 @@ material {
|
||||
type : float4,
|
||||
name : EasuCon3,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : viewport,
|
||||
precision: high
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
|
||||
@@ -35,6 +35,11 @@ material {
|
||||
type : float4,
|
||||
name : EasuCon3,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : viewport,
|
||||
precision: high
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
|
||||
@@ -35,6 +35,11 @@ material {
|
||||
type : float4,
|
||||
name : EasuCon3,
|
||||
precision: high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : viewport,
|
||||
precision: high
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
|
||||
@@ -87,7 +87,7 @@ fragment {
|
||||
|
||||
// simple dithering helps a lot (assumes 8 bits target)
|
||||
// this is most useful with high quality/large blurs
|
||||
ao += ((random(gl_FragCoord.xy) - 0.5) / 255.0);
|
||||
ao += ((interleavedGradientNoise(gl_FragCoord.xy) - 0.5) / 255.0);
|
||||
|
||||
postProcess.color.r = ao;
|
||||
postProcess.color.gb = data.gb;
|
||||
|
||||
@@ -127,7 +127,7 @@ fragment {
|
||||
|
||||
// simple dithering helps a lot (assumes 8 bits target)
|
||||
// this is most useful with high quality/large blurs
|
||||
ao += ((random(gl_FragCoord.xy) - 0.5) / 255.0);
|
||||
ao += ((interleavedGradientNoise(gl_FragCoord.xy) - 0.5) / 255.0);
|
||||
|
||||
postProcess.aoData = vec3(ao, data.gb);
|
||||
postProcess.bnData = packBentNormal(bn);
|
||||
|
||||
@@ -107,7 +107,7 @@ void computeAmbientOcclusionSAO(inout float occlusion, inout vec3 bentNormal,
|
||||
|
||||
void scalableAmbientObscurance(out float obscurance, out vec3 bentNormal,
|
||||
highp vec2 uv, highp vec3 origin, vec3 normal) {
|
||||
float noise = random(getFragCoord(materialParams.resolution.xy));
|
||||
float noise = interleavedGradientNoise(getFragCoord(materialParams.resolution.xy));
|
||||
highp vec2 tapPosition = startPosition(noise);
|
||||
highp mat2 angleStep = tapAngleStep();
|
||||
|
||||
|
||||
@@ -19,22 +19,6 @@
|
||||
|
||||
#include "depthUtils.fs"
|
||||
|
||||
// random number between 0 and 1, using interleaved gradient noise
|
||||
float random(const highp vec2 w) {
|
||||
const vec3 m = vec3(0.06711056, 0.00583715, 52.9829189);
|
||||
return fract(m.z * fract(dot(w, m.xy)));
|
||||
}
|
||||
|
||||
// returns the frag coord in the GL convention with (0, 0) at the bottom-left
|
||||
// resolution : width, height
|
||||
highp vec2 getFragCoord(const highp vec2 resolution) {
|
||||
#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT)
|
||||
return vec2(gl_FragCoord.x, resolution.y - gl_FragCoord.y);
|
||||
#else
|
||||
return gl_FragCoord.xy;
|
||||
#endif
|
||||
}
|
||||
|
||||
vec2 pack(highp float normalizedDepth) {
|
||||
// we need 16-bits of precision
|
||||
highp float z = clamp(normalizedDepth, 0.0, 1.0);
|
||||
|
||||
@@ -143,8 +143,8 @@ float ssctDominantLightShadowing(highp vec2 uv, highp vec3 origin, vec3 normal,
|
||||
|
||||
float occlusion = 0.0;
|
||||
for (float i = 1.0; i <= rayCount.x; i += 1.0) {
|
||||
cone.jitterOffset.x = random(fragCoord * i) * 2.0 - 1.0; // direction
|
||||
cone.jitterOffset.y = random(fragCoord * i * vec2(3, 11)); // step
|
||||
cone.jitterOffset.x = interleavedGradientNoise(fragCoord * i) * 2.0 - 1.0; // direction
|
||||
cone.jitterOffset.y = interleavedGradientNoise(fragCoord * i * vec2(3, 11)); // step
|
||||
occlusion += coneTraceOcclusion(cone, depthTexture);
|
||||
}
|
||||
return occlusion * rayCount.y;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.21.2"
|
||||
spec.version = "1.21.3"
|
||||
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.21.2/filament-v1.21.2-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.21.3/filament-v1.21.3-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -424,6 +424,7 @@
|
||||
"-limage",
|
||||
"-lgeometry",
|
||||
"-lstb",
|
||||
"-lbasis_transcoder",
|
||||
"-lcamutils",
|
||||
"-ldracodec",
|
||||
"-lviewer",
|
||||
@@ -475,6 +476,7 @@
|
||||
"-limage",
|
||||
"-lgeometry",
|
||||
"-lstb",
|
||||
"-lbasis_transcoder",
|
||||
"-lcamutils",
|
||||
"-ldracodec",
|
||||
"-lviewer",
|
||||
@@ -588,6 +590,7 @@
|
||||
"-limage",
|
||||
"-lgeometry",
|
||||
"-lstb",
|
||||
"-lbasis_transcoder",
|
||||
"-lcamutils",
|
||||
"-ldracodec",
|
||||
"-lviewer",
|
||||
@@ -639,6 +642,7 @@
|
||||
"-limage",
|
||||
"-lgeometry",
|
||||
"-lstb",
|
||||
"-lbasis_transcoder",
|
||||
"-lcamutils",
|
||||
"-ldracodec",
|
||||
"-lviewer",
|
||||
|
||||
@@ -79,7 +79,8 @@ const float kSensitivity = 100.0f;
|
||||
ResourceLoader* _resourceLoader;
|
||||
|
||||
Manipulator<float>* _manipulator;
|
||||
TextureProvider* _decoder;
|
||||
TextureProvider* _stbDecoder;
|
||||
TextureProvider* _ktxDecoder;
|
||||
|
||||
FilamentAsset* _asset;
|
||||
|
||||
@@ -131,9 +132,11 @@ const float kSensitivity = 100.0f;
|
||||
_assetLoader = AssetLoader::create({_engine, _materialProvider, ncm, &em});
|
||||
_resourceLoader = new ResourceLoader(
|
||||
{.engine = _engine, .normalizeSkinningWeights = true, .recomputeBoundingBoxes = false});
|
||||
_decoder = createStbProvider(_engine);
|
||||
_resourceLoader->addTextureProvider("image/png", _decoder);
|
||||
_resourceLoader->addTextureProvider("image/jpeg", _decoder);
|
||||
_stbDecoder = createStbProvider(_engine);
|
||||
_ktxDecoder = createKtx2Provider(_engine);
|
||||
_resourceLoader->addTextureProvider("image/png", _stbDecoder);
|
||||
_resourceLoader->addTextureProvider("image/jpeg", _stbDecoder);
|
||||
_resourceLoader->addTextureProvider("image/ktx2", _ktxDecoder);
|
||||
|
||||
_manipulator =
|
||||
Manipulator<float>::Builder().orbitHomePosition(0.0f, 0.0f, 4.0f).build(Mode::ORBIT);
|
||||
@@ -252,7 +255,8 @@ const float kSensitivity = 100.0f;
|
||||
[self destroyModel];
|
||||
|
||||
delete _manipulator;
|
||||
delete _decoder;
|
||||
delete _stbDecoder;
|
||||
delete _ktxDecoder;
|
||||
|
||||
_materialProvider->destroyMaterials();
|
||||
delete _materialProvider;
|
||||
|
||||
@@ -19,7 +19,7 @@ targets:
|
||||
- FilamentApp
|
||||
settings:
|
||||
base:
|
||||
OTHER_LDFLAGS: ["-lgltfio_core", "-lgltfio_resources", "-limage", "-lgeometry", "-lstb",
|
||||
OTHER_LDFLAGS: ["-lgltfio_core", "-lgltfio_resources", "-limage", "-lgeometry", "-lstb", "-lbasis_transcoder",
|
||||
"-lcamutils", "-ldracodec", "-lviewer", "-lcivetweb", "-lktxreader"]
|
||||
preBuildScripts:
|
||||
- path: build-resources.sh
|
||||
|
||||
@@ -34,6 +34,10 @@ using namespace ktxreader;
|
||||
using namespace filament;
|
||||
using namespace filamesh;
|
||||
|
||||
const double kFov = 60.0;
|
||||
const double kNearPlane = 0.1;
|
||||
const double kFarPlane = 10.0;
|
||||
|
||||
void FilamentApp::initialize() {
|
||||
#if FILAMENT_APP_USE_OPENGL
|
||||
engine = Engine::create(filament::Engine::Backend::OPENGL);
|
||||
@@ -94,15 +98,9 @@ void FilamentApp::initialize() {
|
||||
|
||||
filaView->setScene(scene);
|
||||
filaView->setCamera(camera);
|
||||
filaView->setViewport(Viewport(0, 0, width, height));
|
||||
|
||||
const uint32_t w = filaView->getViewport().width;
|
||||
const uint32_t h = filaView->getViewport().height;
|
||||
const float aspect = (float) w / h;
|
||||
cameraManipulator.setCamera(camera);
|
||||
cameraManipulator.setViewport(w, h);
|
||||
cameraManipulator.lookAt(filament::math::double3(0, 0, 3), filament::math::double3(0, 0, 0));
|
||||
camera->setProjection(60, aspect, 0.1, 10);
|
||||
}
|
||||
|
||||
void FilamentApp::render() {
|
||||
@@ -116,6 +114,22 @@ void FilamentApp::pan(float deltaX, float deltaY) {
|
||||
cameraManipulator.rotate(filament::math::double2(deltaX, -deltaY), 10);
|
||||
}
|
||||
|
||||
void FilamentApp::updateViewportAndCameraProjection(uint32_t width, uint32_t height,
|
||||
float scaleFactor) {
|
||||
if (!filaView || !camera) {
|
||||
return;
|
||||
}
|
||||
|
||||
cameraManipulator.setViewport(width, height);
|
||||
|
||||
const uint32_t scaledWidth = width * scaleFactor;
|
||||
const uint32_t scaledHeight = height * scaleFactor;
|
||||
filaView->setViewport({0, 0, scaledWidth, scaledHeight});
|
||||
|
||||
const double aspect = (double)width / height;
|
||||
camera->setProjection(kFov, aspect, kNearPlane, kFarPlane);
|
||||
}
|
||||
|
||||
FilamentApp::~FilamentApp() {
|
||||
engine->destroy(app.materialInstance);
|
||||
engine->destroy(app.mat);
|
||||
|
||||
@@ -38,20 +38,20 @@ using utils::EntityManager;
|
||||
class FilamentApp {
|
||||
public:
|
||||
|
||||
FilamentApp(void* nativeLayer, uint32_t width, uint32_t height)
|
||||
: nativeLayer(nativeLayer), width(width), height(height) {}
|
||||
FilamentApp(void* nativeLayer)
|
||||
: nativeLayer(nativeLayer) {}
|
||||
~FilamentApp();
|
||||
|
||||
void initialize();
|
||||
void render();
|
||||
void pan(float deltaX, float deltaY);
|
||||
void updateViewportAndCameraProjection(uint32_t width, uint32_t height, float scaleFactor);
|
||||
|
||||
private:
|
||||
|
||||
void updateRotation();
|
||||
|
||||
void* nativeLayer = nullptr;
|
||||
uint32_t width, height;
|
||||
|
||||
Engine* engine = nullptr;
|
||||
Renderer* renderer = nullptr;
|
||||
|
||||
@@ -39,10 +39,11 @@
|
||||
#elif FILAMENT_APP_USE_METAL
|
||||
[self initializeMetalLayer];
|
||||
#endif
|
||||
CGRect nativeBounds = [[UIScreen mainScreen] nativeBounds];
|
||||
app = new FilamentApp((__bridge void*) self.layer, nativeBounds.size.width, nativeBounds.size.height);
|
||||
app->initialize();
|
||||
app = new FilamentApp((__bridge void*) self.layer);
|
||||
self.contentScaleFactor = UIScreen.mainScreen.nativeScale;
|
||||
app->initialize();
|
||||
app->updateViewportAndCameraProjection(self.bounds.size.width, self.bounds.size.height,
|
||||
self.contentScaleFactor);
|
||||
}
|
||||
|
||||
// Call renderloop 60 times a second.
|
||||
@@ -114,4 +115,32 @@
|
||||
app->pan(delta.x, delta.y);
|
||||
}
|
||||
|
||||
- (void)updateViewportAndCameraProjection {
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if FILAMENT_APP_USE_METAL
|
||||
CAMetalLayer* metalLayer = (CAMetalLayer*)self.layer;
|
||||
const uint32_t width = self.bounds.size.width * self.contentScaleFactor;
|
||||
const uint32_t height = self.bounds.size.height * self.contentScaleFactor;
|
||||
metalLayer.drawableSize = CGSizeMake(width, height);
|
||||
#endif
|
||||
|
||||
app->updateViewportAndCameraProjection(self.bounds.size.width, self.bounds.size.height,
|
||||
self.contentScaleFactor);
|
||||
}
|
||||
|
||||
#pragma mark UIView methods
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
[self updateViewportAndCameraProjection];
|
||||
}
|
||||
|
||||
- (void)setContentScaleFactor:(CGFloat)contentScaleFactor {
|
||||
[super setContentScaleFactor:contentScaleFactor];
|
||||
[self updateViewportAndCameraProjection];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -22,9 +22,4 @@
|
||||
|
||||
@implementation ViewController
|
||||
|
||||
- (BOOL)shouldAutorotate
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
namespace filament {
|
||||
|
||||
// update this when a new version of filament wouldn't work with older materials
|
||||
static constexpr size_t MATERIAL_VERSION = 21;
|
||||
static constexpr size_t MATERIAL_VERSION = 22;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace BindingPoints {
|
||||
constexpr uint8_t FROXEL_RECORDS = 6;
|
||||
constexpr uint8_t PER_MATERIAL_INSTANCE = 7; // uniforms/samplers updates per material
|
||||
constexpr uint8_t COUNT = 8;
|
||||
// These are limited by Program::UNIFORM_BINDING_COUNT (currently 8)
|
||||
// These are limited by CONFIG_BINDING_COUNT (currently 12)
|
||||
}
|
||||
|
||||
static_assert(BindingPoints::COUNT <= backend::CONFIG_BINDING_COUNT);
|
||||
|
||||
@@ -40,69 +40,108 @@ namespace filament {
|
||||
|
||||
struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
static constexpr utils::StaticString _name{ "FrameUniforms" };
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Values that can be accessed in both surface and post-process materials
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
math::mat4f viewFromWorldMatrix;
|
||||
math::mat4f worldFromViewMatrix;
|
||||
math::mat4f clipFromViewMatrix;
|
||||
math::mat4f viewFromClipMatrix;
|
||||
math::mat4f clipFromWorldMatrix;
|
||||
math::mat4f worldFromClipMatrix;
|
||||
std::array<math::mat4f, CONFIG_MAX_SHADOW_CASCADES> lightFromWorldMatrix;
|
||||
|
||||
// position of cascade splits, in world space (not including the near plane)
|
||||
// -Inf stored in unused components
|
||||
math::float4 cascadeSplits;
|
||||
math::float2 clipControl; // clip control
|
||||
float time; // time in seconds, with a 1 second period
|
||||
float temporalNoise; // noise [0,1] when TAA is used, 0 otherwise
|
||||
math::float4 userTime; // time(s), (double)time - (float)time, 0, 0
|
||||
|
||||
math::float4 resolution; // viewport width, height, 1/width, 1/height
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// values below should only be accessed in surface materials
|
||||
// (i.e.: not in the post-processing materials)
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
math::float2 origin; // viewport left, bottom (in pixels)
|
||||
math::float2 offset; // rendering offset left, bottom (in pixels)
|
||||
math::float4 resolution; // viewport width, height, 1/width, 1/height
|
||||
|
||||
float lodBias; // load bias to apply to user materials
|
||||
float refractionLodOffset;
|
||||
float padding1;
|
||||
float padding2;
|
||||
|
||||
// camera position in view space (when camera_at_origin is enabled), i.e. it's (0,0,0).
|
||||
// Always add worldOffset in the shader to get the true world-space position of the camera.
|
||||
math::float3 cameraPosition;
|
||||
|
||||
float time; // time in seconds, with a 1 second period
|
||||
|
||||
math::float4 lightColorIntensity; // directional light
|
||||
|
||||
math::float4 sun; // cos(sunAngle), sin(sunAngle), 1/(sunAngle*HALO_SIZE-sunAngle), HALO_EXP
|
||||
|
||||
math::float2 lightFarAttenuationParams; // a, a/far (a=1/pct-of-far)
|
||||
float needsAlphaChannel;
|
||||
uint32_t lightChannels;
|
||||
|
||||
math::float3 lightDirection;
|
||||
uint32_t fParamsX; // stride-x
|
||||
|
||||
float shadowBulbRadiusLs; // light radius in light-space
|
||||
float shadowBias; // normal bias
|
||||
float shadowPenumbraRatioScale; // For DPCF or PCSS, scale penumbra ratio for artistic use
|
||||
float oneOverFroxelDimensionY;
|
||||
|
||||
math::float4 zParams; // froxel Z parameters
|
||||
|
||||
math::uint2 fParams; // stride-y, stride-z
|
||||
math::float2 origin; // viewport left, viewport bottom
|
||||
|
||||
float oneOverFroxelDimensionX;
|
||||
float iblLuminance;
|
||||
float oneOverFarMinusNear; // 1 / (f-n), always positive
|
||||
math::float3 worldOffset; // this is (0,0,0) when camera_at_origin is disabled
|
||||
float nearOverFarMinusNear; // n / (f-n), always positive
|
||||
float cameraFar; // camera *culling* far-plane distance, always positive (projection far is at +inf)
|
||||
float exposure;
|
||||
float ev100;
|
||||
float needsAlphaChannel;
|
||||
|
||||
alignas(16) math::float4 iblSH[9]; // actually float3 entries (std140 requires float4 alignment)
|
||||
// AO
|
||||
float aoSamplingQualityAndEdgeDistance; // 0: bilinear, !0: bilateral edge distance
|
||||
float aoBentNormals; // 0: no AO bent normal, >0.0 AO bent normals
|
||||
float aoReserved0;
|
||||
float aoReserved1;
|
||||
|
||||
math::float4 userTime; // time(s), (double)time - (float)time, 0, 0
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Dynamic Lighting [variant: DYN]
|
||||
// --------------------------------------------------------------------------------------------
|
||||
math::float4 zParams; // froxel Z parameters
|
||||
math::uint3 fParams; // stride-x, stride-y, stride-z
|
||||
uint32_t lightChannels; // light channel bits
|
||||
math::float2 froxelCountXY;
|
||||
|
||||
float iblRoughnessOneLevel; // level for roughness == 1
|
||||
float cameraFar; // camera *culling* far-plane distance, always positive (projection far is at +inf)
|
||||
float refractionLodOffset;
|
||||
// IBL
|
||||
float iblLuminance;
|
||||
float iblRoughnessOneLevel; // level for roughness == 1
|
||||
math::float4 iblSH[9]; // actually float3 entries (std140 requires float4 alignment)
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Directional Lighting [variant: DIR]
|
||||
// --------------------------------------------------------------------------------------------
|
||||
math::float3 lightDirection; // directional light direction
|
||||
float padding0;
|
||||
math::float4 lightColorIntensity; // directional light
|
||||
math::float4 sun; // cos(sunAngle), sin(sunAngle), 1/(sunAngle*HALO_SIZE-sunAngle), HALO_EXP
|
||||
math::float2 lightFarAttenuationParams; // a, a/far (a=1/pct-of-far)
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Directional light shadowing [variant: SRE | DIR]
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// bit 0: directional (sun) shadow enabled
|
||||
// bit 1: directional (sun) screen-space contact shadow enabled
|
||||
// bit 8-15: screen-space contact shadows ray casting steps
|
||||
uint32_t directionalShadows;
|
||||
|
||||
math::float3 worldOffset; // this is (0,0,0) when camera_at_origin is disabled
|
||||
float ssContactShadowDistance;
|
||||
|
||||
// fog
|
||||
// position of cascade splits, in world space (not including the near plane)
|
||||
// -Inf stored in unused components
|
||||
math::float4 cascadeSplits;
|
||||
// bit 0-3: cascade count
|
||||
// bit 4: visualize cascades
|
||||
// bit 8-11: cascade has visible shadows
|
||||
uint32_t cascades;
|
||||
float shadowBulbRadiusLs; // light radius in light-space
|
||||
float shadowBias; // normal bias
|
||||
float shadowPenumbraRatioScale; // For DPCF or PCSS, scale penumbra ratio for artistic use
|
||||
std::array<math::mat4f, CONFIG_MAX_SHADOW_CASCADES> lightFromWorldMatrix;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// VSM shadows [variant: VSM]
|
||||
// --------------------------------------------------------------------------------------------
|
||||
float vsmExponent;
|
||||
float vsmDepthScale;
|
||||
float vsmLightBleedReduction;
|
||||
uint32_t shadowSamplingType; // 0: vsm, 1: dpcf
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Fog [variant: FOG]
|
||||
// --------------------------------------------------------------------------------------------
|
||||
float fogStart;
|
||||
float fogMaxOpacity;
|
||||
float fogHeight;
|
||||
@@ -112,31 +151,11 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
float fogInscatteringStart;
|
||||
float fogInscatteringSize;
|
||||
float fogColorFromIbl;
|
||||
float fogReserved0;
|
||||
|
||||
// bit 0-3: cascade count
|
||||
// bit 4: visualize cascades
|
||||
// bit 8-11: cascade has visible shadows
|
||||
uint32_t cascades;
|
||||
|
||||
float aoSamplingQualityAndEdgeDistance; // 0: bilinear, !0: bilateral edge distance
|
||||
float aoBentNormals; // 0: no AO bent normal, >0.0 AO bent normals
|
||||
float aoReserved2;
|
||||
float aoReserved3;
|
||||
|
||||
math::float2 clipControl;
|
||||
math::float2 padding1;
|
||||
|
||||
float vsmExponent;
|
||||
float vsmDepthScale;
|
||||
float vsmLightBleedReduction;
|
||||
uint32_t shadowSamplingType; // 0: vsm, 1: dpcf
|
||||
|
||||
float lodBias;
|
||||
float oneOverFarMinusNear; // 1 / (f-n), always positive
|
||||
float nearOverFarMinusNear; // n / (f-n), always positive
|
||||
float temporalNoise; // noise [0,1] when TAA is used, 0 otherwise
|
||||
|
||||
// Screen-space reflections
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Screen-space reflections [variant: SSR (i.e.: VSM | SRE)]
|
||||
// --------------------------------------------------------------------------------------------
|
||||
math::mat4f ssrReprojection;
|
||||
math::mat4f ssrUvFromViewMatrix;
|
||||
float ssrThickness; // ssr thickness, in world units
|
||||
@@ -145,7 +164,7 @@ struct PerViewUib { // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
float ssrStride; // ssr texel stride, >= 1.0
|
||||
|
||||
// bring PerViewUib to 2 KiB
|
||||
math::float4 padding3[49];
|
||||
math::float4 reserved[48];
|
||||
};
|
||||
|
||||
// 2 KiB == 128 float4s
|
||||
|
||||
@@ -40,51 +40,85 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept {
|
||||
.add("viewFromClipMatrix", 1, UniformInterfaceBlock::Type::MAT4, Precision::HIGH)
|
||||
.add("clipFromWorldMatrix", 1, UniformInterfaceBlock::Type::MAT4, Precision::HIGH)
|
||||
.add("worldFromClipMatrix", 1, UniformInterfaceBlock::Type::MAT4, Precision::HIGH)
|
||||
.add("lightFromWorldMatrix", 4, UniformInterfaceBlock::Type::MAT4, Precision::HIGH)
|
||||
.add("cascadeSplits", 1, UniformInterfaceBlock::Type::FLOAT4, Precision::HIGH)
|
||||
// view
|
||||
.add("resolution", 1, UniformInterfaceBlock::Type::FLOAT4, Precision::HIGH)
|
||||
// camera
|
||||
.add("cameraPosition", 1, UniformInterfaceBlock::Type::FLOAT3, Precision::HIGH)
|
||||
// time
|
||||
|
||||
.add("clipControl", 1, UniformInterfaceBlock::Type::FLOAT2)
|
||||
.add("time", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH)
|
||||
// directional light
|
||||
.add("temporalNoise", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH)
|
||||
.add("userTime", 1, UniformInterfaceBlock::Type::FLOAT4, Precision::HIGH)
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// values below should only be accessed in surface materials
|
||||
// ------------------------------------------------------------------------------------
|
||||
|
||||
.add("origin", 1, UniformInterfaceBlock::Type::FLOAT2, Precision::HIGH)
|
||||
.add("offset", 1, UniformInterfaceBlock::Type::FLOAT2, Precision::HIGH)
|
||||
.add("resolution", 1, UniformInterfaceBlock::Type::FLOAT4, Precision::HIGH)
|
||||
|
||||
.add("lodBias", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("refractionLodOffset", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("padding1", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("padding2", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
|
||||
.add("cameraPosition", 1, UniformInterfaceBlock::Type::FLOAT3, Precision::HIGH)
|
||||
.add("oneOverFarMinusNear", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH)
|
||||
.add("worldOffset", 1, UniformInterfaceBlock::Type::FLOAT3)
|
||||
.add("nearOverFarMinusNear", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH)
|
||||
.add("cameraFar", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("exposure", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH) // high precision to work around #3602 (qualcomm)
|
||||
.add("ev100", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("needsAlphaChannel", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
|
||||
// AO
|
||||
.add("aoSamplingQualityAndEdgeDistance", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("aoBentNormals", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("aoReserved0", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("aoReserved1", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Dynamic Lighting [variant: DYN]
|
||||
// ------------------------------------------------------------------------------------
|
||||
.add("zParams", 1, UniformInterfaceBlock::Type::FLOAT4)
|
||||
.add("fParams", 1, UniformInterfaceBlock::Type::UINT3)
|
||||
.add("lightChannels", 1, UniformInterfaceBlock::Type::UINT)
|
||||
.add("froxelCountXY", 1, UniformInterfaceBlock::Type::FLOAT2)
|
||||
|
||||
.add("iblLuminance", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("iblRoughnessOneLevel", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("iblSH", 9, UniformInterfaceBlock::Type::FLOAT3)
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Directional Lighting [variant: DIR]
|
||||
// ------------------------------------------------------------------------------------
|
||||
.add("lightDirection", 1, UniformInterfaceBlock::Type::FLOAT3)
|
||||
.add("padding0", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("lightColorIntensity", 1, UniformInterfaceBlock::Type::FLOAT4)
|
||||
.add("sun", 1, UniformInterfaceBlock::Type::FLOAT4)
|
||||
.add("lightFarAttenuationParams",1, UniformInterfaceBlock::Type::FLOAT2)
|
||||
.add("needsAlphaChannel", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("lightChannels", 1, UniformInterfaceBlock::Type::UINT)
|
||||
.add("lightDirection", 1, UniformInterfaceBlock::Type::FLOAT3)
|
||||
.add("fParamsX", 1, UniformInterfaceBlock::Type::UINT)
|
||||
// shadow
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Directional light shadowing [variant: SRE | DIR]
|
||||
// ------------------------------------------------------------------------------------
|
||||
.add("directionalShadows", 1, UniformInterfaceBlock::Type::UINT)
|
||||
.add("ssContactShadowDistance", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
|
||||
.add("cascadeSplits", 1, UniformInterfaceBlock::Type::FLOAT4, Precision::HIGH)
|
||||
.add("cascades", 1, UniformInterfaceBlock::Type::UINT)
|
||||
.add("shadowBulbRadiusLs", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("shadowBias", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("shadowPenumbraRatioScale",1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("oneOverFroxelDimensionY", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
// froxels
|
||||
.add("zParams", 1, UniformInterfaceBlock::Type::FLOAT4)
|
||||
.add("fParams", 1, UniformInterfaceBlock::Type::UINT2)
|
||||
.add("origin", 1, UniformInterfaceBlock::Type::FLOAT2)
|
||||
// froxels (again, for alignment purposes)
|
||||
.add("oneOverFroxelDimension", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
// ibl
|
||||
.add("iblLuminance", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
// camera
|
||||
.add("exposure", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH) // high precision to work around #3602 (qualcomm)
|
||||
.add("ev100", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
// ibl
|
||||
.add("iblSH", 9, UniformInterfaceBlock::Type::FLOAT3)
|
||||
// user time
|
||||
.add("userTime", 1, UniformInterfaceBlock::Type::FLOAT4)
|
||||
// ibl max mip level
|
||||
.add("iblRoughnessOneLevel", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("cameraFar", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("refractionLodOffset", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("directionalShadows", 1, UniformInterfaceBlock::Type::UINT)
|
||||
// view
|
||||
.add("worldOffset", 1, UniformInterfaceBlock::Type::FLOAT3)
|
||||
.add("ssContactShadowDistance", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
// fog
|
||||
.add("lightFromWorldMatrix", 4, UniformInterfaceBlock::Type::MAT4, Precision::HIGH)
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// VSM shadows [variant: VSM]
|
||||
// ------------------------------------------------------------------------------------
|
||||
.add("vsmExponent", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("vsmDepthScale", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("vsmLightBleedReduction", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("shadowSamplingType", 1, UniformInterfaceBlock::Type::UINT)
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Fog [variant: FOG]
|
||||
// ------------------------------------------------------------------------------------
|
||||
.add("fogStart", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("fogMaxOpacity", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("fogHeight", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
@@ -94,30 +128,11 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept {
|
||||
.add("fogInscatteringStart", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("fogInscatteringSize", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("fogColorFromIbl", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("fogReserved0", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
|
||||
// CSM information
|
||||
.add("cascades", 1, UniformInterfaceBlock::Type::UINT)
|
||||
|
||||
// SSAO sampling parameters
|
||||
.add("aoSamplingQualityAndEdgeDistance", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("aoBentNormals", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("aoReserved2", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("aoReserved3", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
|
||||
.add("clipControl", 1, UniformInterfaceBlock::Type::FLOAT2)
|
||||
.add("padding1", 1, UniformInterfaceBlock::Type::FLOAT2)
|
||||
|
||||
.add("vsmExponent", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("vsmDepthScale", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("vsmLightBleedReduction", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("shadowSamplingType", 1, UniformInterfaceBlock::Type::UINT)
|
||||
|
||||
.add("lodBias", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
.add("oneOverFarMinusNear", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH)
|
||||
.add("nearOverFarMinusNear", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH)
|
||||
.add("temporalNoise", 1, UniformInterfaceBlock::Type::FLOAT, Precision::HIGH)
|
||||
|
||||
// Screen-space reflection parameters
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Screen-space reflections [variant: SSR (i.e.: VSM | SRE)]
|
||||
// ------------------------------------------------------------------------------------
|
||||
.add("ssrReprojection", 1, UniformInterfaceBlock::Type::MAT4, Precision::HIGH)
|
||||
.add("ssrUvFromViewMatrix", 1, UniformInterfaceBlock::Type::MAT4, Precision::HIGH)
|
||||
.add("ssrThickness", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
@@ -126,7 +141,7 @@ UniformInterfaceBlock const& UibGenerator::getPerViewUib() noexcept {
|
||||
.add("ssrStride", 1, UniformInterfaceBlock::Type::FLOAT)
|
||||
|
||||
// bring PerViewUib to 2 KiB
|
||||
.add("padding3", 49, UniformInterfaceBlock::Type::FLOAT4)
|
||||
.add("reserved", sizeof(PerViewUib::reserved)/16, UniformInterfaceBlock::Type::FLOAT4)
|
||||
.build();
|
||||
return uib;
|
||||
}
|
||||
|
||||
@@ -391,8 +391,7 @@ io::sstream& CodeGenerator::generateSamplers(
|
||||
return out;
|
||||
}
|
||||
|
||||
io::sstream& CodeGenerator::generateSubpass(io::sstream& out,
|
||||
SubpassInfo subpass) {
|
||||
io::sstream& CodeGenerator::generateSubpass(io::sstream& out, SubpassInfo subpass) {
|
||||
if (!subpass.isValid) {
|
||||
return out;
|
||||
}
|
||||
@@ -522,8 +521,8 @@ io::sstream& CodeGenerator::generateQualityDefine(io::sstream& out, ShaderQualit
|
||||
}
|
||||
|
||||
io::sstream& CodeGenerator::generateCommon(io::sstream& out, ShaderType type) {
|
||||
out << SHADERS_COMMON_MATH_FS_DATA;
|
||||
out << SHADERS_COMMON_SHADOWING_FS_DATA;
|
||||
out << SHADERS_COMMON_MATH_GLSL_DATA;
|
||||
out << SHADERS_COMMON_SHADOWING_GLSL_DATA;
|
||||
if (type == ShaderType::VERTEX) {
|
||||
} else if (type == ShaderType::FRAGMENT) {
|
||||
out << SHADERS_COMMON_SHADING_FS_DATA;
|
||||
@@ -533,6 +532,16 @@ io::sstream& CodeGenerator::generateCommon(io::sstream& out, ShaderType type) {
|
||||
return out;
|
||||
}
|
||||
|
||||
io::sstream& CodeGenerator::generatePostProcessCommon(io::sstream& out, ShaderType type) {
|
||||
out << SHADERS_COMMON_MATH_GLSL_DATA;
|
||||
if (type == ShaderType::VERTEX) {
|
||||
} else if (type == ShaderType::FRAGMENT) {
|
||||
out << SHADERS_COMMON_SHADING_FS_DATA;
|
||||
out << SHADERS_COMMON_GRAPHICS_FS_DATA;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
io::sstream& CodeGenerator::generateFog(io::sstream& out, ShaderType type) {
|
||||
if (type == ShaderType::VERTEX) {
|
||||
} else if (type == ShaderType::FRAGMENT) {
|
||||
@@ -561,15 +570,16 @@ io::sstream& CodeGenerator::generatePostProcessInputs(io::sstream& out, ShaderTy
|
||||
|
||||
io::sstream& CodeGenerator::generatePostProcessGetters(io::sstream& out,
|
||||
ShaderType type) {
|
||||
out << SHADERS_COMMON_GETTERS_FS_DATA;
|
||||
out << SHADERS_COMMON_GETTERS_GLSL_DATA;
|
||||
if (type == ShaderType::VERTEX) {
|
||||
out << SHADERS_POST_PROCESS_GETTERS_VS_DATA;
|
||||
} else if (type == ShaderType::FRAGMENT) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
io::sstream& CodeGenerator::generateGetters(io::sstream& out, ShaderType type) {
|
||||
out << SHADERS_COMMON_GETTERS_FS_DATA;
|
||||
out << SHADERS_COMMON_GETTERS_GLSL_DATA;
|
||||
if (type == ShaderType::VERTEX) {
|
||||
out << SHADERS_GETTERS_VS_DATA;
|
||||
} else if (type == ShaderType::FRAGMENT) {
|
||||
|
||||
@@ -65,6 +65,7 @@ public:
|
||||
|
||||
// generate common functions for the given shader
|
||||
static utils::io::sstream& generateCommon(utils::io::sstream& out, ShaderType type) ;
|
||||
static utils::io::sstream& generatePostProcessCommon(utils::io::sstream& out, ShaderType type) ;
|
||||
static utils::io::sstream& generateCommonMaterial(utils::io::sstream& out, ShaderType type) ;
|
||||
|
||||
static utils::io::sstream& generateFog(utils::io::sstream& out, ShaderType type) ;
|
||||
|
||||
@@ -537,9 +537,6 @@ std::string ShaderGenerator::createPostProcessVertexProgram(
|
||||
|
||||
CodeGenerator::generateDefine(vs, "LOCATION_POSITION", uint32_t(VertexAttribute::POSITION));
|
||||
|
||||
// The UVs are at the location immediately following the custom variables.
|
||||
CodeGenerator::generateDefine(vs, "LOCATION_UVS", uint32_t(MaterialBuilder::MATERIAL_VARIABLES_COUNT));
|
||||
|
||||
// custom material variables
|
||||
size_t variableIndex = 0;
|
||||
for (const auto& variable : mVariables) {
|
||||
@@ -558,7 +555,7 @@ std::string ShaderGenerator::createPostProcessVertexProgram(
|
||||
material.samplerBindings.getBlockOffset(BindingPoints::PER_MATERIAL_INSTANCE),
|
||||
material.sib);
|
||||
|
||||
CodeGenerator::generateCommon(vs, ShaderType::VERTEX);
|
||||
CodeGenerator::generatePostProcessCommon(vs, ShaderType::VERTEX);
|
||||
CodeGenerator::generatePostProcessGetters(vs, ShaderType::VERTEX);
|
||||
|
||||
appendShader(vs, mMaterialVertexCode, mMaterialVertexLineOffset);
|
||||
@@ -579,9 +576,6 @@ std::string ShaderGenerator::createPostProcessFragmentProgram(
|
||||
|
||||
cg.generateQualityDefine(fs, material.quality);
|
||||
|
||||
// The UVs are at the location immediately following the custom variables.
|
||||
CodeGenerator::generateDefine(fs, "LOCATION_UVS", uint32_t(MaterialBuilder::MATERIAL_VARIABLES_COUNT));
|
||||
|
||||
generatePostProcessMaterialVariantDefines(fs, PostProcessVariant(variant));
|
||||
|
||||
// custom material variables
|
||||
@@ -602,7 +596,7 @@ std::string ShaderGenerator::createPostProcessFragmentProgram(
|
||||
// subpass
|
||||
CodeGenerator::generateSubpass(fs, material.subpass);
|
||||
|
||||
CodeGenerator::generateCommon(fs, ShaderType::FRAGMENT);
|
||||
CodeGenerator::generatePostProcessCommon(fs, ShaderType::FRAGMENT);
|
||||
CodeGenerator::generatePostProcessGetters(fs, ShaderType::FRAGMENT);
|
||||
|
||||
// Generate post-process outputs.
|
||||
|
||||
@@ -30,6 +30,7 @@ set(SRCS
|
||||
src/FFilamentInstance.h
|
||||
src/FilamentInstance.cpp
|
||||
src/GltfEnums.h
|
||||
src/Ktx2Provider.cpp
|
||||
src/MaterialProvider.cpp
|
||||
src/ResourceLoader.cpp
|
||||
src/StbProvider.cpp
|
||||
@@ -165,7 +166,7 @@ add_library(gltfio_resources_lite ${DUMMY_SRC} ${RESGEN_SOURCE})
|
||||
# ==================================================================================================
|
||||
|
||||
include_directories(${PUBLIC_HDR_DIR} ${RESOURCE_DIR})
|
||||
link_libraries(math utils filament cgltf stb geometry gltfio_resources tsl trie)
|
||||
link_libraries(math utils filament cgltf stb ktxreader geometry gltfio_resources tsl trie)
|
||||
|
||||
add_library(gltfio_core STATIC ${PUBLIC_HDRS} ${SRCS})
|
||||
|
||||
|
||||
@@ -169,6 +169,12 @@ public:
|
||||
*/
|
||||
TextureProvider* createStbProvider(filament::Engine* engine);
|
||||
|
||||
/**
|
||||
* Creates a decoder that can handle certain types of "image/ktx2" content as specified in
|
||||
* the KHR_texture_basisu specification.
|
||||
*/
|
||||
TextureProvider* createKtx2Provider(filament::Engine* engine);
|
||||
|
||||
} // namespace gltfio
|
||||
|
||||
#endif // GLTFIO_TEXTUREPROVIDER_H
|
||||
|
||||
@@ -1305,13 +1305,10 @@ MaterialInstance* FAssetLoader::createMaterialInstance(const cgltf_data* srcAsse
|
||||
|
||||
void FAssetLoader::addTextureBinding(MaterialInstance* materialInstance, const char* parameterName,
|
||||
const cgltf_texture* srcTexture, bool srgb) {
|
||||
if (!srcTexture->image) {
|
||||
if (srcTexture->basisu_image) {
|
||||
const char* name = srcTexture->name ? srcTexture->name : srcTexture->basisu_image->uri;
|
||||
slog.w << "BasisU is not yet supported (" << name << ")." << io::endl;
|
||||
} else {
|
||||
slog.w << "Texture is missing image (" << srcTexture->name << ")." << io::endl;
|
||||
}
|
||||
if (!srcTexture->image && !srcTexture->basisu_image) {
|
||||
#ifndef NDEBUG
|
||||
slog.w << "Texture is missing image (" << srcTexture->name << ")." << io::endl;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
TextureSampler dstSampler;
|
||||
@@ -1326,7 +1323,7 @@ void FAssetLoader::addTextureBinding(MaterialInstance* materialInstance, const c
|
||||
dstSampler.setWrapModeS(TextureSampler::WrapMode::REPEAT);
|
||||
dstSampler.setWrapModeT(TextureSampler::WrapMode::REPEAT);
|
||||
|
||||
// These defaults are up the implementation but since we generate mipmaps unconditionally,
|
||||
// These defaults are up the implementation but since we try to provide mipmaps,
|
||||
// we might as well use them. In practice the conformance models look awful without
|
||||
// using mipmapping by default.
|
||||
dstSampler.setMagFilter(TextureSampler::MagFilter::LINEAR);
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 <gltfio/TextureProvider.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <utils/JobSystem.h>
|
||||
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/Texture.h>
|
||||
|
||||
#include <ktxreader/Ktx2Reader.h>
|
||||
|
||||
using namespace filament;
|
||||
using namespace utils;
|
||||
|
||||
using std::atomic;
|
||||
using std::vector;
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace gltfio {
|
||||
|
||||
class Ktx2Provider final : public TextureProvider {
|
||||
public:
|
||||
Ktx2Provider(Engine* engine);
|
||||
~Ktx2Provider();
|
||||
|
||||
Texture* pushTexture(const uint8_t* data, size_t byteCount,
|
||||
const char* mimeType, uint64_t flags) final;
|
||||
|
||||
Texture* popTexture() final;
|
||||
void updateQueue() final;
|
||||
void waitForCompletion() final;
|
||||
void cancelDecoding() final;
|
||||
const char* getPushMessage() const final;
|
||||
const char* getPopMessage() const final;
|
||||
size_t getPushedCount() const final { return mPushedCount; }
|
||||
size_t getPoppedCount() const final { return mPoppedCount; }
|
||||
size_t getDecodedCount() const final { return mDecodedCount; }
|
||||
|
||||
private:
|
||||
enum class QueueItemState {
|
||||
TRANSCODING, // Texture has been pushed, mipmap levels are not yet complete.
|
||||
READY, // Mipmap levels are available but texture has not been popped yet.
|
||||
POPPED, // Client has popped the texture from the queue.
|
||||
};
|
||||
|
||||
enum class TranscoderState {
|
||||
NOT_STARTED,
|
||||
ERROR,
|
||||
SUCCESS,
|
||||
};
|
||||
|
||||
struct QueueItem {
|
||||
ktxreader::Ktx2Reader::Async* async;
|
||||
QueueItemState state;
|
||||
atomic<TranscoderState> transcoderState;
|
||||
JobSystem::Job* job;
|
||||
};
|
||||
|
||||
void transcodeSingleTexture();
|
||||
|
||||
size_t mPushedCount = 0;
|
||||
size_t mPoppedCount = 0;
|
||||
size_t mDecodedCount = 0;
|
||||
vector<unique_ptr<QueueItem> > mQueueItems;
|
||||
JobSystem::Job* mDecoderRootJob;
|
||||
std::string mRecentPushMessage;
|
||||
std::string mRecentPopMessage;
|
||||
std::unique_ptr<ktxreader::Ktx2Reader> mKtxReader;
|
||||
Engine* const mEngine;
|
||||
};
|
||||
|
||||
Texture* Ktx2Provider::pushTexture(const uint8_t* data, size_t byteCount,
|
||||
const char* mimeType, FlagBits flags) {
|
||||
using InternalFormat = Texture::InternalFormat;
|
||||
using TransferFunction = ktxreader::Ktx2Reader::TransferFunction;
|
||||
const FlagBits sRGB = FlagBits(Flags::sRGB);
|
||||
|
||||
auto async = mKtxReader->asyncCreate(data, byteCount,
|
||||
(flags & sRGB) ? TransferFunction::sRGB : TransferFunction::LINEAR);
|
||||
|
||||
if (async == nullptr) {
|
||||
mRecentPushMessage = "Unable to build Texture object.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mRecentPushMessage.clear();
|
||||
QueueItem* item = mQueueItems.emplace_back(new QueueItem).get();
|
||||
++mPushedCount;
|
||||
|
||||
item->async = async;
|
||||
item->state = QueueItemState::TRANSCODING;
|
||||
item->transcoderState.store(TranscoderState::NOT_STARTED);
|
||||
|
||||
// On single threaded systems, it is usually fine to create jobs because the job system will
|
||||
// simply execute serially. However in our case, we wish to amortize the decoder cost across
|
||||
// several frames, so we instead use the updateQueue() method to perform decoding.
|
||||
if constexpr (!UTILS_HAS_THREADING) {
|
||||
item->job = nullptr;
|
||||
return async->getTexture();
|
||||
}
|
||||
|
||||
JobSystem* js = &mEngine->getJobSystem();
|
||||
item->job = jobs::createJob(*js, mDecoderRootJob, [this, item] {
|
||||
using Result = ktxreader::Ktx2Reader::Result;
|
||||
const bool success = Result::SUCCESS == item->async->doTranscoding();
|
||||
item->transcoderState.store(success ? TranscoderState::SUCCESS : TranscoderState::ERROR);
|
||||
});
|
||||
|
||||
js->runAndRetain(item->job);
|
||||
return async->getTexture();
|
||||
}
|
||||
|
||||
Texture* Ktx2Provider::popTexture() {
|
||||
// We don't bother shrinking the mQueueItems vector here, instead we periodically clean it up in
|
||||
// the updateQueue method, since popTexture is typically called more frequently. Textures
|
||||
// can become ready in non-deterministic order due to concurrency.
|
||||
for (auto& item : mQueueItems) {
|
||||
if (item->state == QueueItemState::READY) {
|
||||
item->state = QueueItemState::POPPED;
|
||||
++mPoppedCount;
|
||||
const TranscoderState state = item->transcoderState.load();
|
||||
if (state != TranscoderState::SUCCESS) {
|
||||
mRecentPopMessage = "Texture is incomplete";
|
||||
} else {
|
||||
mRecentPopMessage.clear();
|
||||
}
|
||||
Texture* texture = item->async->getTexture();
|
||||
mKtxReader->asyncDestroy(&item->async);
|
||||
item->async = nullptr;
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Ktx2Provider::updateQueue() {
|
||||
if (!UTILS_HAS_THREADING) {
|
||||
transcodeSingleTexture();
|
||||
}
|
||||
JobSystem* js = &mEngine->getJobSystem();
|
||||
for (auto& item : mQueueItems) {
|
||||
if (item->state != QueueItemState::TRANSCODING) {
|
||||
continue;
|
||||
}
|
||||
Texture* texture = item->async->getTexture();
|
||||
const TranscoderState state = item->transcoderState.load();
|
||||
if (state != TranscoderState::NOT_STARTED) {
|
||||
if (item->job) {
|
||||
js->waitAndRelease(item->job);
|
||||
}
|
||||
if (state == TranscoderState::ERROR) {
|
||||
item->state = QueueItemState::READY;
|
||||
++mDecodedCount;
|
||||
continue;
|
||||
}
|
||||
item->async->uploadImages();
|
||||
item->state = QueueItemState::READY;
|
||||
++mDecodedCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Here we periodically clean up the "queue" (which is really just a vector) by removing unused
|
||||
// items from the front. This might ignore a popped texture that occurs in the middle of the
|
||||
// vector, but that's okay, it will be cleaned up eventually.
|
||||
decltype(mQueueItems)::iterator last = mQueueItems.begin();
|
||||
while (last != mQueueItems.end() && (*last)->state == QueueItemState::POPPED) ++last;
|
||||
mQueueItems.erase(mQueueItems.begin(), last);
|
||||
}
|
||||
|
||||
void Ktx2Provider::waitForCompletion() {
|
||||
JobSystem& js = mEngine->getJobSystem();
|
||||
for (auto& item : mQueueItems) {
|
||||
if (item->job) {
|
||||
js.waitAndRelease(item->job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ktx2Provider::cancelDecoding() {
|
||||
// TODO: Currently, Ktx2Provider runs jobs eagerly and JobSystem does not allow cancellation of
|
||||
// in-flight jobs. We should consider throttling the number of simultaneous decoder jobs, which
|
||||
// would allow for actual cancellation.
|
||||
waitForCompletion();
|
||||
}
|
||||
|
||||
const char* Ktx2Provider::getPushMessage() const {
|
||||
return mRecentPushMessage.empty() ? nullptr : mRecentPushMessage.c_str();
|
||||
}
|
||||
|
||||
const char* Ktx2Provider::getPopMessage() const {
|
||||
return mRecentPopMessage.empty() ? nullptr : mRecentPopMessage.c_str();
|
||||
}
|
||||
|
||||
void Ktx2Provider::transcodeSingleTexture() {
|
||||
assert_invariant(!UTILS_HAS_THREADING);
|
||||
for (auto& item : mQueueItems) {
|
||||
if (item->state == QueueItemState::TRANSCODING) {
|
||||
using Result = ktxreader::Ktx2Reader::Result;
|
||||
bool success = Result::SUCCESS == item->async->doTranscoding();
|
||||
item->transcoderState.store(success ? TranscoderState::SUCCESS : TranscoderState::ERROR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ktx2Provider::Ktx2Provider(Engine* engine) : mEngine(engine) {
|
||||
mDecoderRootJob = mEngine->getJobSystem().createJob();
|
||||
#ifdef NDEBUG
|
||||
const bool quiet = true;
|
||||
#else
|
||||
const bool quiet = false;
|
||||
#endif
|
||||
mKtxReader.reset(new ktxreader::Ktx2Reader(*engine, quiet));
|
||||
|
||||
mKtxReader->requestFormat(Texture::InternalFormat::ETC2_EAC_SRGBA8);
|
||||
mKtxReader->requestFormat(Texture::InternalFormat::DXT5_SRGBA);
|
||||
mKtxReader->requestFormat(Texture::InternalFormat::DXT5_RGBA);
|
||||
|
||||
// Uncompressed formats are lower priority, so they get added last.
|
||||
mKtxReader->requestFormat(Texture::InternalFormat::SRGB8_A8);
|
||||
mKtxReader->requestFormat(Texture::InternalFormat::RGBA8);
|
||||
}
|
||||
|
||||
Ktx2Provider::~Ktx2Provider() {
|
||||
cancelDecoding();
|
||||
for (auto& item : mQueueItems) {
|
||||
mKtxReader->asyncDestroy(&item->async);
|
||||
}
|
||||
mEngine->getJobSystem().release(mDecoderRootJob);
|
||||
}
|
||||
|
||||
TextureProvider* createKtx2Provider(Engine* engine) {
|
||||
return new Ktx2Provider(engine);
|
||||
}
|
||||
|
||||
} // namespace gltfio
|
||||
@@ -571,7 +571,8 @@ void ResourceLoader::asyncUpdateLoad() {
|
||||
|
||||
Texture* ResourceLoader::Impl::getOrCreateTexture(FFilamentAsset* asset, const TextureSlot& tb) {
|
||||
const cgltf_texture* srcTexture = tb.texture;
|
||||
const cgltf_image* image = srcTexture->image;
|
||||
const cgltf_image* image = srcTexture->basisu_image ?
|
||||
srcTexture->basisu_image : srcTexture->image;
|
||||
const cgltf_buffer_view* bv = image->buffer_view;
|
||||
const char* uri = image->uri;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ using std::unique_ptr;
|
||||
|
||||
namespace gltfio {
|
||||
|
||||
class StbProvider : public TextureProvider {
|
||||
class StbProvider final : public TextureProvider {
|
||||
public:
|
||||
StbProvider(Engine* engine);
|
||||
~StbProvider();
|
||||
|
||||
@@ -40,6 +40,12 @@ fragment {
|
||||
|
||||
void dummy() {}
|
||||
|
||||
#if defined(TARGET_MOBILE)
|
||||
# define MIN_ROUGHNESS 0.007921
|
||||
#else
|
||||
# define MIN_ROUGHNESS 0.002025
|
||||
#endif
|
||||
|
||||
// we default to highp in this shader
|
||||
precision highp float;
|
||||
precision highp int;
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.
|
||||
*/
|
||||
|
||||
//! \file Functions and types related to block-compressed texture formats.
|
||||
|
||||
#ifndef IMAGEIO_BLOCKCOMPRESSION_H_
|
||||
#define IMAGEIO_BLOCKCOMPRESSION_H_
|
||||
|
||||
#include <image/LinearImage.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <math/vec2.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace image {
|
||||
|
||||
enum class CompressedFormat {
|
||||
INVALID = 0,
|
||||
|
||||
R11_EAC = 0x9270,
|
||||
SIGNED_R11_EAC = 0x9271,
|
||||
RG11_EAC = 0x9272,
|
||||
SIGNED_RG11_EAC = 0x9273,
|
||||
RGB8_ETC2 = 0x9274,
|
||||
SRGB8_ETC2 = 0x9275,
|
||||
RGB8_ALPHA1_ETC2 = 0x9276,
|
||||
SRGB8_ALPHA1_ETC = 0x9277,
|
||||
RGBA8_ETC2_EAC = 0x9278,
|
||||
SRGB8_ALPHA8_ETC2_EAC = 0x9279,
|
||||
|
||||
RGB_S3TC_DXT1 = 0x83F0,
|
||||
RGBA_S3TC_DXT1 = 0x83F1,
|
||||
RGBA_S3TC_DXT3 = 0x83F2,
|
||||
RGBA_S3TC_DXT5 = 0x83F3,
|
||||
SRGB_S3TC_DXT1 = 0x8C4C,
|
||||
SRGB_ALPHA_S3TC_DXT1 = 0x8C4D,
|
||||
SRGB_ALPHA_S3TC_DXT3 = 0x8C4E,
|
||||
SRGB_ALPHA_S3TC_DXT5 = 0x8C4F,
|
||||
|
||||
RGBA_ASTC_4x4 = 0x93B0,
|
||||
RGBA_ASTC_5x4 = 0x93B1,
|
||||
RGBA_ASTC_5x5 = 0x93B2,
|
||||
RGBA_ASTC_6x5 = 0x93B3,
|
||||
RGBA_ASTC_6x6 = 0x93B4,
|
||||
RGBA_ASTC_8x5 = 0x93B5,
|
||||
RGBA_ASTC_8x6 = 0x93B6,
|
||||
RGBA_ASTC_8x8 = 0x93B7,
|
||||
RGBA_ASTC_10x5 = 0x93B8,
|
||||
RGBA_ASTC_10x6 = 0x93B9,
|
||||
RGBA_ASTC_10x8 = 0x93BA,
|
||||
RGBA_ASTC_10x10 = 0x93BB,
|
||||
RGBA_ASTC_12x10 = 0x93BC,
|
||||
RGBA_ASTC_12x12 = 0x93BD,
|
||||
SRGB8_ALPHA8_ASTC_4x4 = 0x93D0,
|
||||
SRGB8_ALPHA8_ASTC_5x4 = 0x93D1,
|
||||
SRGB8_ALPHA8_ASTC_5x5 = 0x93D2,
|
||||
SRGB8_ALPHA8_ASTC_6x5 = 0x93D3,
|
||||
SRGB8_ALPHA8_ASTC_6x6 = 0x93D4,
|
||||
SRGB8_ALPHA8_ASTC_8x5 = 0x93D5,
|
||||
SRGB8_ALPHA8_ASTC_8x6 = 0x93D6,
|
||||
SRGB8_ALPHA8_ASTC_8x8 = 0x93D7,
|
||||
SRGB8_ALPHA8_ASTC_10x5 = 0x93D8,
|
||||
SRGB8_ALPHA8_ASTC_10x6 = 0x93D9,
|
||||
SRGB8_ALPHA8_ASTC_10x8 = 0x93DA,
|
||||
SRGB8_ALPHA8_ASTC_10x10 = 0x93DB,
|
||||
SRGB8_ALPHA8_ASTC_12x10 = 0x93DC,
|
||||
SRGB8_ALPHA8_ASTC_12x12 = 0x93DD,
|
||||
};
|
||||
|
||||
// Represents the opaque result of compression and the chosen texture format.
|
||||
struct CompressedTexture {
|
||||
const CompressedFormat format;
|
||||
const uint32_t size;
|
||||
std::unique_ptr<uint8_t[]> data;
|
||||
};
|
||||
|
||||
// ASTC ////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Controls how fast compression occurs at the cost of quality in the resulting image.
|
||||
enum class AstcPreset {
|
||||
VERYFAST,
|
||||
FAST,
|
||||
MEDIUM,
|
||||
THOROUGH,
|
||||
EXHAUSTIVE,
|
||||
};
|
||||
|
||||
// Informs the encoder what texels represent; this is especially crucial for normal maps.
|
||||
enum class AstcSemantic {
|
||||
COLORS_LDR,
|
||||
COLORS_HDR,
|
||||
NORMALS,
|
||||
};
|
||||
|
||||
// The encoder configuration controls the quality and speed of compression, as well as the resulting
|
||||
// format. The specified block size must be one of the 14 block sizes that can be consumed by ES 3.2
|
||||
// as per https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glCompressedTexImage2D.xhtml
|
||||
struct AstcConfig {
|
||||
AstcPreset quality;
|
||||
AstcSemantic semantic;
|
||||
filament::math::ushort2 blocksize;
|
||||
bool srgb;
|
||||
};
|
||||
|
||||
// Uses the CPU to compress a linear image (1 to 4 channels) into an ASTC texture. The 16-byte
|
||||
// header block that ARM uses in their file format is not included.
|
||||
CompressedTexture astcCompress(const LinearImage& source, AstcConfig config);
|
||||
|
||||
// Parses a simple underscore-delimited string to produce an ASTC compression configuration. This
|
||||
// makes it easy to incorporate the compression API into command-line tools. If the string is
|
||||
// malformed, this returns a config with a 0x0 blocksize. Example strings: fast_ldr_4x4,
|
||||
// thorough_normals_6x6, veryfast_hdr_12x10
|
||||
AstcConfig astcParseOptionString(const std::string& options);
|
||||
|
||||
// ETC /////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum class EtcErrorMetric {
|
||||
RGBA,
|
||||
RGBX,
|
||||
REC709,
|
||||
NUMERIC,
|
||||
NORMALXYZ,
|
||||
};
|
||||
|
||||
// Informs the ETC encoder of the desired output. Effort sets the quality / speed tradeoff with
|
||||
// a number between 0 and 100.
|
||||
struct EtcConfig {
|
||||
CompressedFormat format;
|
||||
EtcErrorMetric metric;
|
||||
int effort;
|
||||
};
|
||||
|
||||
// Uses the CPU to compress a linear image (1 to 4 channels) into an ETC texture.
|
||||
CompressedTexture etcCompress(const LinearImage& source, EtcConfig config);
|
||||
|
||||
// Converts a string into an ETC compression configuration where the string has the form
|
||||
// FORMAT_METRIC_EFFORT where:
|
||||
// - FORMAT is one of: r11, signed_r11, rg11, signed_rg11, rgb8, srgb8, rgb8_alpha,
|
||||
// srgb8_alpha, rgba8, and srgb8_alpha8
|
||||
// - METRIC is one of: rgba, rgbx, rec709, numeric, and normalxyz
|
||||
// - EFFORT is an integer between 0 and 100
|
||||
EtcConfig etcParseOptionString(const std::string& options);
|
||||
|
||||
// S3TC ////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Informs the S3TC encoder of the desired output.
|
||||
struct S3tcConfig {
|
||||
CompressedFormat format;
|
||||
bool srgb;
|
||||
};
|
||||
|
||||
// Uses the CPU to compress a linear image (1 to 4 channels) into an S3TC texture.
|
||||
CompressedTexture s3tcCompress(const LinearImage& source, S3tcConfig config);
|
||||
|
||||
// Parses an underscore-delimited string to produce an S3TC compression configuration. Currently
|
||||
// this only accepts "rgb_dxt1" and "rgba_dxt5". If the string is malformed, this returns a config
|
||||
// with an invalid format.
|
||||
S3tcConfig s3tcParseOptionString(const std::string& options);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct CompressionConfig {
|
||||
enum { INVALID, ASTC, S3TC, ETC } type;
|
||||
AstcConfig astc;
|
||||
S3tcConfig s3tc;
|
||||
EtcConfig etc;
|
||||
bool isLinear() const {
|
||||
if (type == ASTC) {
|
||||
return !astc.srgb;
|
||||
}
|
||||
switch (type == S3TC ? s3tc.format : etc.format) {
|
||||
case CompressedFormat::R11_EAC:
|
||||
case CompressedFormat::SIGNED_R11_EAC:
|
||||
case CompressedFormat::RG11_EAC:
|
||||
case CompressedFormat::SIGNED_RG11_EAC:
|
||||
case CompressedFormat::RGB8_ETC2:
|
||||
case CompressedFormat::RGB8_ALPHA1_ETC2:
|
||||
case CompressedFormat::RGBA8_ETC2_EAC:
|
||||
case CompressedFormat::RGB_S3TC_DXT1:
|
||||
case CompressedFormat::RGBA_S3TC_DXT1:
|
||||
case CompressedFormat::RGBA_S3TC_DXT3:
|
||||
case CompressedFormat::RGBA_S3TC_DXT5:
|
||||
return true;
|
||||
case CompressedFormat::SRGB8_ETC2:
|
||||
case CompressedFormat::SRGB8_ALPHA1_ETC:
|
||||
case CompressedFormat::SRGB8_ALPHA8_ETC2_EAC:
|
||||
case CompressedFormat::SRGB_S3TC_DXT1:
|
||||
case CompressedFormat::SRGB_ALPHA_S3TC_DXT1:
|
||||
case CompressedFormat::SRGB_ALPHA_S3TC_DXT3:
|
||||
case CompressedFormat::SRGB_ALPHA_S3TC_DXT5:
|
||||
return false;
|
||||
default:
|
||||
// This should be unreachable.
|
||||
assert(false && "ASTC formats are already handled");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool parseOptionString(const std::string& options, CompressionConfig* config);
|
||||
|
||||
UTILS_PUBLIC
|
||||
CompressedTexture compressTexture(const CompressionConfig& config, const LinearImage& image);
|
||||
|
||||
} // namespace image
|
||||
|
||||
#endif /* IMAGEIO_BLOCKCOMPRESSION_H_ */
|
||||
@@ -94,10 +94,18 @@ Builder& Builder::miplevel(size_t level, size_t layer, const LinearImage& floatI
|
||||
std::unique_ptr<uint8_t[]> data = applyTransferFunction ?
|
||||
fromLinearTosRGB<uint8_t, 1>(sourceImage) : fromLinearToGrayscale<uint8_t>(sourceImage);
|
||||
basisImage->init(data.get(), floatImage.getWidth(), floatImage.getHeight(), 1);
|
||||
} else {
|
||||
} else if (sourceImage.getChannels() == 4) {
|
||||
std::unique_ptr<uint8_t[]> data = applyTransferFunction ?
|
||||
fromLinearTosRGB<uint8_t, 4>(sourceImage) : fromLinearToRGB<uint8_t, 4>(sourceImage);
|
||||
basisImage->init(data.get(), floatImage.getWidth(), floatImage.getHeight(), 4);
|
||||
} else if (sourceImage.getChannels() == 3) {
|
||||
std::unique_ptr<uint8_t[]> data = applyTransferFunction ?
|
||||
fromLinearTosRGB<uint8_t, 3>(sourceImage) : fromLinearToRGB<uint8_t, 3>(sourceImage);
|
||||
basisImage->init(data.get(), floatImage.getWidth(), floatImage.getHeight(), 3);
|
||||
} else {
|
||||
assert_invariant(false);
|
||||
mImpl->error = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
return *this;
|
||||
|
||||
@@ -1,589 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 <imageio/BlockCompression.h>
|
||||
|
||||
#include <image/ImageOps.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <thread>
|
||||
|
||||
#include <astcenc.h>
|
||||
#include <Etc.h>
|
||||
|
||||
#define STB_DXT_IMPLEMENTATION
|
||||
#include <stb_dxt.h>
|
||||
|
||||
namespace image {
|
||||
|
||||
static LinearImage extendToFourChannels(LinearImage source);
|
||||
|
||||
CompressedTexture astcCompress(const LinearImage& original, AstcConfig config) {
|
||||
|
||||
// If this is the first time, initialize the ARM encoder tables.
|
||||
|
||||
static bool first = true;
|
||||
if (first) {
|
||||
test_inappropriate_extended_precision();
|
||||
prepare_angular_tables();
|
||||
build_quantization_mode_table();
|
||||
first = false;
|
||||
}
|
||||
|
||||
// Check the validity of the given block size.
|
||||
|
||||
using Format = CompressedFormat;
|
||||
Format format;
|
||||
if (config.blocksize == filament::math::ushort2 {4, 4}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_4x4 : Format::RGBA_ASTC_4x4;
|
||||
} else if (config.blocksize == filament::math::ushort2 {5, 4}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_5x4 : Format::RGBA_ASTC_5x4;
|
||||
} else if (config.blocksize == filament::math::ushort2 {5, 5}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_5x5 : Format::RGBA_ASTC_5x5;
|
||||
} else if (config.blocksize == filament::math::ushort2 {6, 5}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_6x5 : Format::RGBA_ASTC_6x5;
|
||||
} else if (config.blocksize == filament::math::ushort2 {6, 6}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_6x6 : Format::RGBA_ASTC_6x6;
|
||||
} else if (config.blocksize == filament::math::ushort2 {8, 5}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_8x5 : Format::RGBA_ASTC_8x5;
|
||||
} else if (config.blocksize == filament::math::ushort2 {8, 6}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_8x6 : Format::RGBA_ASTC_8x6;
|
||||
} else if (config.blocksize == filament::math::ushort2 {8, 8}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_8x8 : Format::RGBA_ASTC_8x8;
|
||||
} else if (config.blocksize == filament::math::ushort2 {10, 5}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_10x5 : Format::RGBA_ASTC_10x5;
|
||||
} else if (config.blocksize == filament::math::ushort2 {10, 6}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_10x6 : Format::RGBA_ASTC_10x6;
|
||||
} else if (config.blocksize == filament::math::ushort2 {10, 8}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_10x8 : Format::RGBA_ASTC_10x8;
|
||||
} else if (config.blocksize == filament::math::ushort2 {10, 10}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_10x10 : Format::RGBA_ASTC_10x10;
|
||||
} else if (config.blocksize == filament::math::ushort2 {12, 10}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_12x10 : Format::RGBA_ASTC_12x10;
|
||||
} else if (config.blocksize == filament::math::ushort2 {12, 12}) {
|
||||
format = config.srgb ? Format::SRGB8_ALPHA8_ASTC_12x12 : Format::RGBA_ASTC_12x12;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Create an input image for the ARM encoder in a format that it can consume.
|
||||
// It expects four-channel data, so we extend or curtail the channel count in a reasonable way.
|
||||
// The encoder can take half-floats or bytes, but we always give it half-floats.
|
||||
|
||||
LinearImage source = extendToFourChannels(original);
|
||||
const uint32_t width = source.getWidth();
|
||||
const uint32_t height = source.getHeight();
|
||||
astc_codec_image* input_image = allocate_image(16, width, height, 1, 0);
|
||||
for (int y = 0; y < height; y++) {
|
||||
auto imagedata16 = input_image->imagedata16[0][y];
|
||||
float const* src = source.getPixelRef(0, y);
|
||||
for (int x = 0; x < width; x++) {
|
||||
imagedata16[4 * x] = float_to_sf16(src[4 * x], SF_NEARESTEVEN);
|
||||
imagedata16[4 * x + 1] = float_to_sf16(src[4 * x + 1], SF_NEARESTEVEN);
|
||||
imagedata16[4 * x + 2] = float_to_sf16(src[4 * x + 2], SF_NEARESTEVEN);
|
||||
imagedata16[4 * x + 3] = float_to_sf16(src[4 * x + 3], SF_NEARESTEVEN);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the bitrate based on the specified block size.
|
||||
|
||||
int xdim_2d = config.blocksize.x, ydim_2d = config.blocksize.y;
|
||||
const float log10_texels_2d = std::log((float)(xdim_2d * ydim_2d)) / std::log(10.0f);
|
||||
const float bitrate = 128.0 / (xdim_2d * ydim_2d);
|
||||
|
||||
// We do not fully support 3D textures yet, but we include some of the 3D config params anyway.
|
||||
|
||||
int xdim_3d, ydim_3d, zdim_3d;
|
||||
find_closest_blockdim_3d(bitrate, &xdim_3d, &ydim_3d, &zdim_3d, 0);
|
||||
const float log10_texels_3d = std::log((float)(xdim_3d * ydim_3d * zdim_3d)) / log(10.0f);
|
||||
|
||||
// Set up presets.
|
||||
|
||||
int plimit_autoset;
|
||||
float oplimit_autoset;
|
||||
float dblimit_autoset_2d;
|
||||
float dblimit_autoset_3d;
|
||||
float bmc_autoset;
|
||||
float mincorrel_autoset;
|
||||
int maxiters_autoset;
|
||||
int pcdiv;
|
||||
|
||||
switch (config.quality) {
|
||||
case AstcPreset::VERYFAST:
|
||||
plimit_autoset = 2;
|
||||
oplimit_autoset = 1.0;
|
||||
dblimit_autoset_2d = fmax(70 - 35 * log10_texels_2d, 53 - 19 * log10_texels_2d);
|
||||
dblimit_autoset_3d = fmax(70 - 35 * log10_texels_3d, 53 - 19 * log10_texels_3d);
|
||||
bmc_autoset = 25;
|
||||
mincorrel_autoset = 0.5;
|
||||
maxiters_autoset = 1;
|
||||
switch (ydim_2d) {
|
||||
case 4: pcdiv = 240; break;
|
||||
case 5: pcdiv = 56; break;
|
||||
case 6: pcdiv = 64; break;
|
||||
case 8: pcdiv = 47; break;
|
||||
case 10: pcdiv = 36; break;
|
||||
case 12: pcdiv = 30; break;
|
||||
default: pcdiv = 30; break;
|
||||
}
|
||||
break;
|
||||
case AstcPreset::FAST:
|
||||
plimit_autoset = 4;
|
||||
oplimit_autoset = 1.0;
|
||||
dblimit_autoset_2d = fmax(85 - 35 * log10_texels_2d, 63 - 19 * log10_texels_2d);
|
||||
dblimit_autoset_3d = fmax(85 - 35 * log10_texels_3d, 63 - 19 * log10_texels_3d);
|
||||
bmc_autoset = 50;
|
||||
mincorrel_autoset = 0.5;
|
||||
maxiters_autoset = 1;
|
||||
switch (ydim_2d) {
|
||||
case 4: pcdiv = 60; break;
|
||||
case 5: pcdiv = 27; break;
|
||||
case 6: pcdiv = 30; break;
|
||||
case 8: pcdiv = 24; break;
|
||||
case 10: pcdiv = 16; break;
|
||||
case 12: pcdiv = 20; break;
|
||||
default: pcdiv = 20; break;
|
||||
}
|
||||
break;
|
||||
case AstcPreset::MEDIUM:
|
||||
plimit_autoset = 25;
|
||||
oplimit_autoset = 1.2;
|
||||
dblimit_autoset_2d = fmax(95 - 35 * log10_texels_2d, 70 - 19 * log10_texels_2d);
|
||||
dblimit_autoset_3d = fmax(95 - 35 * log10_texels_3d, 70 - 19 * log10_texels_3d);
|
||||
bmc_autoset = 75;
|
||||
mincorrel_autoset = 0.75;
|
||||
maxiters_autoset = 2;
|
||||
switch (ydim_2d) {
|
||||
case 4: pcdiv = 25; break;
|
||||
case 5: pcdiv = 15; break;
|
||||
case 6: pcdiv = 15; break;
|
||||
case 8: pcdiv = 10; break;
|
||||
case 10: pcdiv = 8; break;
|
||||
case 12: pcdiv = 6; break;
|
||||
default: pcdiv = 6; break;
|
||||
}
|
||||
break;
|
||||
case AstcPreset::THOROUGH:
|
||||
plimit_autoset = 100;
|
||||
oplimit_autoset = 2.5;
|
||||
dblimit_autoset_2d = fmax(105 - 35 * log10_texels_2d, 77 - 19 * log10_texels_2d);
|
||||
dblimit_autoset_3d = fmax(105 - 35 * log10_texels_3d, 77 - 19 * log10_texels_3d);
|
||||
bmc_autoset = 95;
|
||||
mincorrel_autoset = 0.95f;
|
||||
maxiters_autoset = 4;
|
||||
switch (ydim_2d) {
|
||||
case 4: pcdiv = 12; break;
|
||||
case 5: pcdiv = 7; break;
|
||||
case 6: pcdiv = 7; break;
|
||||
case 8: pcdiv = 5; break;
|
||||
case 10: pcdiv = 4; break;
|
||||
case 12: pcdiv = 3; break;
|
||||
default: pcdiv = 3; break;
|
||||
}
|
||||
break;
|
||||
case AstcPreset::EXHAUSTIVE:
|
||||
plimit_autoset = 1 << 10;
|
||||
oplimit_autoset = 1000.0;
|
||||
dblimit_autoset_2d = 999.0f;
|
||||
dblimit_autoset_3d = 999.0f;
|
||||
bmc_autoset = 100;
|
||||
mincorrel_autoset = 0.99;
|
||||
maxiters_autoset = 4;
|
||||
switch (ydim_2d) {
|
||||
case 4: pcdiv = 3; break;
|
||||
case 5: pcdiv = 1; break;
|
||||
case 6: pcdiv = 1; break;
|
||||
case 8: pcdiv = 1; break;
|
||||
case 10: pcdiv = 1; break;
|
||||
case 12: pcdiv = 1; break;
|
||||
default: pcdiv = 1; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (plimit_autoset < 1) {
|
||||
plimit_autoset = 1;
|
||||
} else if (plimit_autoset > PARTITION_COUNT) {
|
||||
plimit_autoset = PARTITION_COUNT;
|
||||
}
|
||||
|
||||
error_weighting_params ewp;
|
||||
ewp.rgb_power = 1.0f;
|
||||
ewp.alpha_power = 1.0f;
|
||||
ewp.rgb_base_weight = 1.0f;
|
||||
ewp.alpha_base_weight = 1.0f;
|
||||
ewp.rgb_mean_weight = 0.0f;
|
||||
ewp.rgb_stdev_weight = 0.0f;
|
||||
ewp.alpha_mean_weight = 0.0f;
|
||||
ewp.alpha_stdev_weight = 0.0f;
|
||||
ewp.rgb_mean_and_stdev_mixing = 0.0f;
|
||||
ewp.mean_stdev_radius = 0;
|
||||
ewp.enable_rgb_scale_with_alpha = 0;
|
||||
ewp.alpha_radius = 0;
|
||||
ewp.block_artifact_suppression = 0.0f;
|
||||
ewp.rgba_weights[0] = 1.0f;
|
||||
ewp.rgba_weights[1] = 1.0f;
|
||||
ewp.rgba_weights[2] = 1.0f;
|
||||
ewp.rgba_weights[3] = 1.0f;
|
||||
ewp.ra_normal_angular_scale = 0;
|
||||
ewp.max_refinement_iters = maxiters_autoset;
|
||||
ewp.block_mode_cutoff = bmc_autoset / 100.0f;
|
||||
ewp.texel_avg_error_limit = pow(0.1f, dblimit_autoset_2d * 0.1f) * 65535.0f * 65535.0f;
|
||||
ewp.partition_1_to_2_limit = oplimit_autoset;
|
||||
ewp.lowest_correlation_cutoff = mincorrel_autoset;
|
||||
ewp.partition_search_limit = plimit_autoset;
|
||||
|
||||
// For now we do not support 3D textures but we keep the variable names consistent
|
||||
// with what's found in the ARM standalone tool.
|
||||
int xdim = xdim_2d, ydim = ydim_2d, zdim = 1;
|
||||
expand_block_artifact_suppression(xdim, ydim, zdim, &ewp);
|
||||
|
||||
// Perform compression.
|
||||
|
||||
swizzlepattern swz_encode = { 0, 1, 2, 3 };
|
||||
swizzlepattern swz_decode = { 0, 1, 2, 3 };
|
||||
astc_decode_mode decode_mode;
|
||||
switch (config.semantic) {
|
||||
case AstcSemantic::COLORS_LDR:
|
||||
decode_mode = config.srgb ? DECODE_LDR_SRGB : DECODE_LDR;
|
||||
break;
|
||||
case AstcSemantic::COLORS_HDR:
|
||||
decode_mode = DECODE_HDR;
|
||||
break;
|
||||
case AstcSemantic::NORMALS:
|
||||
decode_mode = config.srgb ? DECODE_LDR_SRGB : DECODE_LDR;
|
||||
ewp.rgba_weights[0] = 1.0f;
|
||||
ewp.rgba_weights[1] = 0.0f;
|
||||
ewp.rgba_weights[2] = 0.0f;
|
||||
ewp.rgba_weights[3] = 1.0f;
|
||||
ewp.ra_normal_angular_scale = 1;
|
||||
swz_encode.r = 0;
|
||||
swz_encode.g = 0;
|
||||
swz_encode.b = 0;
|
||||
swz_encode.a = 1;
|
||||
swz_decode.r = 0;
|
||||
swz_decode.g = 3;
|
||||
swz_decode.b = 6;
|
||||
swz_decode.a = 5;
|
||||
ewp.block_artifact_suppression = 1.8f;
|
||||
ewp.mean_stdev_radius = 3;
|
||||
ewp.rgb_mean_weight = 0;
|
||||
ewp.rgb_stdev_weight = 50;
|
||||
ewp.rgb_mean_and_stdev_mixing = 0.0;
|
||||
ewp.alpha_mean_weight = 0;
|
||||
ewp.alpha_stdev_weight = 50;
|
||||
break;
|
||||
}
|
||||
|
||||
const int threadcount = std::thread::hardware_concurrency();
|
||||
|
||||
const int xsize = input_image->xsize;
|
||||
const int ysize = input_image->ysize;
|
||||
const int zsize = input_image->zsize;
|
||||
const int xblocks = (xsize + xdim - 1) / xdim;
|
||||
const int yblocks = (ysize + ydim - 1) / ydim;
|
||||
const int zblocks = (zsize + zdim - 1) / zdim;
|
||||
|
||||
uint32_t size = xblocks * yblocks * zblocks * 16;
|
||||
uint8_t* buffer = new uint8_t[size];
|
||||
|
||||
encode_astc_image(input_image, nullptr, xdim, ydim, zdim, &ewp, decode_mode,
|
||||
swz_encode, swz_decode, buffer, 0, threadcount);
|
||||
|
||||
destroy_image(input_image);
|
||||
|
||||
return {
|
||||
.format = format,
|
||||
.size = size,
|
||||
.data = decltype(CompressedTexture::data)(buffer)
|
||||
};
|
||||
}
|
||||
|
||||
AstcConfig astcParseOptionString(const std::string& configString) {
|
||||
const size_t _1 = configString.find('_');
|
||||
const size_t _2 = configString.find('_', _1 + 1);
|
||||
if (_1 == std::string::npos || _2 == std::string::npos) {
|
||||
return {};
|
||||
}
|
||||
std::string quality = configString.substr(0, _1);
|
||||
std::string semantic = configString.substr(_1 + 1, _2 - _1 - 1);
|
||||
std::string blocksize = configString.substr(_2 + 1);
|
||||
AstcConfig config;
|
||||
if (quality == "veryfast") {
|
||||
config.quality = AstcPreset::VERYFAST;
|
||||
} else if (quality == "fast") {
|
||||
config.quality = AstcPreset::FAST;
|
||||
} else if (quality == "medium") {
|
||||
config.quality = AstcPreset::MEDIUM;
|
||||
} else if (quality == "thorough") {
|
||||
config.quality = AstcPreset::THOROUGH;
|
||||
} else if (quality == "exhaustive") {
|
||||
config.quality = AstcPreset::EXHAUSTIVE;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
if (semantic == "ldr") {
|
||||
config.semantic = AstcSemantic::COLORS_LDR;
|
||||
} else if (semantic == "hdr") {
|
||||
config.semantic = AstcSemantic::COLORS_HDR;
|
||||
} else if (semantic == "normals") {
|
||||
config.semantic = AstcSemantic::NORMALS;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
const size_t _x = blocksize.find('x');
|
||||
if (_x == std::string::npos) {
|
||||
return {};
|
||||
}
|
||||
config.blocksize[0] = std::stoi(blocksize.substr(0, _x));
|
||||
config.blocksize[1] = std::stoi(blocksize.substr(_x + 1));
|
||||
return config;
|
||||
}
|
||||
|
||||
static uint32_t imin(uint32_t a, uint32_t b) {
|
||||
return (a < b) ? a : b;
|
||||
}
|
||||
|
||||
static void extract4x4RGBA(uint8_t* dst, const LinearImage& source, uint32_t x0, uint32_t y0) {
|
||||
const uint32_t maxx = source.getWidth() - 1;
|
||||
const uint32_t maxy = source.getHeight() - 1;
|
||||
for (uint32_t y = y0, y1 = y0 + 4; y < y1; ++y) {
|
||||
for (uint32_t x = x0, x1 = x0 + 4; x < x1; ++x, dst += 4) {
|
||||
int clamped_x = imin(maxx, x);
|
||||
int clamped_y = imin(maxy, y);
|
||||
float const* rgba = source.getPixelRef(clamped_x, clamped_y);
|
||||
dst[0] = (uint8_t) std::min(255.0f, std::max(0.0f, (rgba[0] * 255.0f)));
|
||||
dst[1] = (uint8_t) std::min(255.0f, std::max(0.0f, (rgba[1] * 255.0f)));
|
||||
dst[2] = (uint8_t) std::min(255.0f, std::max(0.0f, (rgba[2] * 255.0f)));
|
||||
dst[3] = (uint8_t) std::min(255.0f, std::max(0.0f, (rgba[3] * 255.0f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Our S3TC / DXT encoder uses the STB implementation by Fabian Giesen.
|
||||
//
|
||||
// Due to limitations in STB, this only supports the following formats:
|
||||
// - DXT1 with no alpha (16 input pixels in 64 bits of output, 6:1)
|
||||
// - DXT5 with alpha (16 input pixels into 128 bits of output, 4:1)
|
||||
//
|
||||
// TODO: remove this in favor of basisu
|
||||
CompressedTexture s3tcCompress(const LinearImage& original, S3tcConfig config) {
|
||||
const bool dxt5 = config.format == CompressedFormat::RGBA_S3TC_DXT5;
|
||||
LinearImage source = extendToFourChannels(original);
|
||||
uint8_t block[64];
|
||||
uint32_t xblocks = (source.getWidth() + 3) / 4;
|
||||
uint32_t yblocks = (source.getHeight() + 3) / 4;
|
||||
uint32_t size = xblocks * yblocks * (dxt5 ? 16 : 8);
|
||||
uint8_t* buffer = new uint8_t[size];
|
||||
uint8_t* dst = buffer;
|
||||
for (int y = 0, h = source.getHeight(); y < h; y += 4) {
|
||||
for (int x = 0, w = source.getWidth(); x < w; x += 4) {
|
||||
extract4x4RGBA(block, source, x, y);
|
||||
stb_compress_dxt_block(dst, block, dxt5, 8);
|
||||
dst += dxt5 ? 16 : 8;
|
||||
}
|
||||
}
|
||||
return {
|
||||
.format = config.format,
|
||||
.size = size,
|
||||
.data = decltype(CompressedTexture::data)(buffer)
|
||||
};
|
||||
}
|
||||
|
||||
S3tcConfig s3tcParseOptionString(const std::string& options) {
|
||||
if (options == "rgb_dxt1") {
|
||||
return {CompressedFormat::RGB_S3TC_DXT1, false};
|
||||
}
|
||||
if (options == "rgba_dxt5") {
|
||||
return {CompressedFormat::RGBA_S3TC_DXT5, false};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
CompressedTexture etcCompress(const LinearImage& original, EtcConfig config) {
|
||||
LinearImage source = extendToFourChannels(original);
|
||||
const int threadcount = std::thread::hardware_concurrency();
|
||||
Etc::Image::Format etcformat;
|
||||
switch (config.format) {
|
||||
case CompressedFormat::R11_EAC: etcformat = Etc::Image::Format::R11; break;
|
||||
case CompressedFormat::SIGNED_R11_EAC: etcformat = Etc::Image::Format::SIGNED_R11; break;
|
||||
case CompressedFormat::RG11_EAC: etcformat = Etc::Image::Format::RG11; break;
|
||||
case CompressedFormat::SIGNED_RG11_EAC: etcformat = Etc::Image::Format::SIGNED_RG11; break;
|
||||
case CompressedFormat::RGB8_ETC2: etcformat = Etc::Image::Format::RGB8; break;
|
||||
case CompressedFormat::SRGB8_ETC2: etcformat = Etc::Image::Format::SRGB8; break;
|
||||
case CompressedFormat::RGB8_ALPHA1_ETC2: etcformat = Etc::Image::Format::RGB8A1; break;
|
||||
case CompressedFormat::SRGB8_ALPHA1_ETC: etcformat = Etc::Image::Format::SRGB8A1; break;
|
||||
case CompressedFormat::RGBA8_ETC2_EAC: etcformat = Etc::Image::Format::RGBA8; break;
|
||||
case CompressedFormat::SRGB8_ALPHA8_ETC2_EAC: etcformat = Etc::Image::Format::SRGBA8; break;
|
||||
default: return {};
|
||||
}
|
||||
Etc::ErrorMetric etcmetric;
|
||||
switch (config.metric) {
|
||||
case EtcErrorMetric::RGBA: etcmetric = Etc::RGBA; break;
|
||||
case EtcErrorMetric::RGBX: etcmetric = Etc::RGBX; break;
|
||||
case EtcErrorMetric::REC709: etcmetric = Etc::REC709; break;
|
||||
case EtcErrorMetric::NUMERIC: etcmetric = Etc::NUMERIC; break;
|
||||
case EtcErrorMetric::NORMALXYZ: etcmetric = Etc::NORMALXYZ; break;
|
||||
default: return {};
|
||||
}
|
||||
unsigned char *paucEncodingBits;
|
||||
unsigned int uiEncodingBitsBytes;
|
||||
unsigned int uiExtendedWidth;
|
||||
unsigned int uiExtendedHeight;
|
||||
int iEncodingTime_ms;
|
||||
|
||||
// The etc2comp API doesn't tell you that you need to free paucEncodingBits, but they have a
|
||||
// commented-out "delete[] m_paucEncodingBits" in their Image destructor, which is essentially
|
||||
// what our unique_ptr wrapper does (CompressedTexture::data).
|
||||
|
||||
Etc::Encode(source.getPixelRef(0, 0),
|
||||
source.getWidth(), source.getHeight(),
|
||||
etcformat,
|
||||
etcmetric,
|
||||
config.effort,
|
||||
threadcount,
|
||||
1024,
|
||||
&paucEncodingBits, &uiEncodingBitsBytes,
|
||||
&uiExtendedWidth, &uiExtendedHeight,
|
||||
&iEncodingTime_ms);
|
||||
|
||||
return {
|
||||
.format = config.format,
|
||||
.size = uiEncodingBitsBytes,
|
||||
.data = decltype(CompressedTexture::data)(paucEncodingBits)
|
||||
};
|
||||
}
|
||||
|
||||
EtcConfig etcParseOptionString(const std::string& options) {
|
||||
EtcConfig result {};
|
||||
const size_t _2 = options.rfind('_');
|
||||
const size_t _1 = options.rfind('_', _2 - 1);
|
||||
std::string sformat = options.substr(0, _1);
|
||||
std::string smetric = options.substr(_1 + 1, _2 - _1 - 1);
|
||||
std::string seffort = options.substr(_2 + 1);
|
||||
if (sformat == "r11") {
|
||||
result.format = CompressedFormat::R11_EAC;
|
||||
} else if (sformat == "signed_r11") {
|
||||
result.format = CompressedFormat::SIGNED_R11_EAC;
|
||||
} else if (sformat == "rg11") {
|
||||
result.format = CompressedFormat::RG11_EAC;
|
||||
} else if (sformat == "signed_rg11") {
|
||||
result.format = CompressedFormat::SIGNED_RG11_EAC;
|
||||
} else if (sformat == "rgb8") {
|
||||
result.format = CompressedFormat::RGB8_ETC2;
|
||||
} else if (sformat == "srgb8") {
|
||||
result.format = CompressedFormat::SRGB8_ETC2;
|
||||
} else if (sformat == "rgb8_alpha") {
|
||||
result.format = CompressedFormat::RGB8_ALPHA1_ETC2;
|
||||
} else if (sformat == "srgb8_alpha") {
|
||||
result.format = CompressedFormat::SRGB8_ALPHA1_ETC;
|
||||
} else if (sformat == "rgba8") {
|
||||
result.format = CompressedFormat::RGBA8_ETC2_EAC;
|
||||
} else if (sformat == "srgb8_alpha8") {
|
||||
result.format = CompressedFormat::SRGB8_ALPHA8_ETC2_EAC;
|
||||
}
|
||||
if (smetric == "rgba") {
|
||||
result.metric = EtcErrorMetric::RGBA;
|
||||
} else if (smetric == "rgbx") {
|
||||
result.metric = EtcErrorMetric::RGBX;
|
||||
} else if (smetric == "rec709") {
|
||||
result.metric = EtcErrorMetric::REC709;
|
||||
} else if (smetric == "numeric") {
|
||||
result.metric = EtcErrorMetric::NUMERIC;
|
||||
} else if (smetric == "normalxyz") {
|
||||
result.metric = EtcErrorMetric::NORMALXYZ;
|
||||
}
|
||||
result.effort = std::stoi(seffort);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool parseOptionString(const std::string& options, CompressionConfig* config) {
|
||||
config->type = CompressionConfig::INVALID;
|
||||
if (options.substr(0, 5) == "astc_") {
|
||||
config->astc = astcParseOptionString(options.substr(5));
|
||||
if (config->astc.blocksize[0] != 0) {
|
||||
config->type = CompressionConfig::ASTC;
|
||||
}
|
||||
} else if (options.substr(0, 5) == "s3tc_") {
|
||||
config->s3tc = s3tcParseOptionString(options.substr(5));
|
||||
if (config->s3tc.format != CompressedFormat::INVALID) {
|
||||
config->type = CompressionConfig::S3TC;
|
||||
}
|
||||
} else if (options.substr(0, 4) == "etc_") {
|
||||
config->etc = etcParseOptionString(options.substr(4));
|
||||
if (config->etc.format != CompressedFormat::INVALID) {
|
||||
config->type = CompressionConfig::ETC;
|
||||
}
|
||||
}
|
||||
return config->type != CompressionConfig::INVALID;
|
||||
}
|
||||
|
||||
CompressedTexture compressTexture(const CompressionConfig& config, const LinearImage& image) {
|
||||
if (config.type == CompressionConfig::ASTC) {
|
||||
return astcCompress(image, config.astc);
|
||||
}
|
||||
if (config.type == CompressionConfig::S3TC) {
|
||||
return s3tcCompress(image, config.s3tc);
|
||||
}
|
||||
if (config.type == CompressionConfig::ETC) {
|
||||
return etcCompress(image, config.etc);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static LinearImage extendToFourChannels(LinearImage original) {
|
||||
LinearImage source = original;
|
||||
const uint32_t width = source.getWidth();
|
||||
const uint32_t height = source.getHeight();
|
||||
auto createEmptyImage = [width, height](float value) {
|
||||
auto result = LinearImage(width, height, 1);
|
||||
float* pixels = result.getPixelRef(0, 0);
|
||||
std::fill(pixels, pixels + width * height, value);
|
||||
return result;
|
||||
};
|
||||
switch (source.getChannels()) {
|
||||
case 4: break;
|
||||
case 1: {
|
||||
auto l = image::extractChannel(source, 0);
|
||||
auto a = createEmptyImage(1.0f);
|
||||
source = image::combineChannels({l, l, l, a});
|
||||
}
|
||||
case 2: {
|
||||
auto l = image::extractChannel(source, 0);
|
||||
auto a = image::extractChannel(source, 1);
|
||||
source = image::combineChannels({l, l, l, a});
|
||||
}
|
||||
case 3: {
|
||||
auto r = image::extractChannel(source, 0);
|
||||
auto g = image::extractChannel(source, 1);
|
||||
auto b = image::extractChannel(source, 2);
|
||||
auto a = createEmptyImage(1.0f);
|
||||
source = image::combineChannels({r, g, b, a});
|
||||
}
|
||||
default: {
|
||||
auto r = image::extractChannel(source, 0);
|
||||
auto g = image::extractChannel(source, 1);
|
||||
auto b = image::extractChannel(source, 2);
|
||||
auto a = image::extractChannel(source, 3);
|
||||
source = image::combineChannels({r, g, b, a});
|
||||
}
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
@@ -53,6 +53,55 @@ namespace Ktx1Reader {
|
||||
TextureFormat toTextureFormat(const KtxInfo& info);
|
||||
TextureFormat toSrgbTextureFormat(TextureFormat tex);
|
||||
|
||||
template<typename T>
|
||||
T toCompressedFilamentEnum(uint32_t format) {
|
||||
switch (format) {
|
||||
case Ktx1Bundle::RGB_S3TC_DXT1: return T::DXT1_RGB;
|
||||
case Ktx1Bundle::RGBA_S3TC_DXT1: return T::DXT1_RGBA;
|
||||
case Ktx1Bundle::RGBA_S3TC_DXT3: return T::DXT3_RGBA;
|
||||
case Ktx1Bundle::RGBA_S3TC_DXT5: return T::DXT5_RGBA;
|
||||
case Ktx1Bundle::RGBA_ASTC_4x4: return T::RGBA_ASTC_4x4;
|
||||
case Ktx1Bundle::RGBA_ASTC_5x4: return T::RGBA_ASTC_5x4;
|
||||
case Ktx1Bundle::RGBA_ASTC_5x5: return T::RGBA_ASTC_5x5;
|
||||
case Ktx1Bundle::RGBA_ASTC_6x5: return T::RGBA_ASTC_6x5;
|
||||
case Ktx1Bundle::RGBA_ASTC_6x6: return T::RGBA_ASTC_6x6;
|
||||
case Ktx1Bundle::RGBA_ASTC_8x5: return T::RGBA_ASTC_8x5;
|
||||
case Ktx1Bundle::RGBA_ASTC_8x6: return T::RGBA_ASTC_8x6;
|
||||
case Ktx1Bundle::RGBA_ASTC_8x8: return T::RGBA_ASTC_8x8;
|
||||
case Ktx1Bundle::RGBA_ASTC_10x5: return T::RGBA_ASTC_10x5;
|
||||
case Ktx1Bundle::RGBA_ASTC_10x6: return T::RGBA_ASTC_10x6;
|
||||
case Ktx1Bundle::RGBA_ASTC_10x8: return T::RGBA_ASTC_10x8;
|
||||
case Ktx1Bundle::RGBA_ASTC_10x10: return T::RGBA_ASTC_10x10;
|
||||
case Ktx1Bundle::RGBA_ASTC_12x10: return T::RGBA_ASTC_12x10;
|
||||
case Ktx1Bundle::RGBA_ASTC_12x12: return T::RGBA_ASTC_12x12;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_4x4: return T::SRGB8_ALPHA8_ASTC_4x4;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_5x4: return T::SRGB8_ALPHA8_ASTC_5x4;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_5x5: return T::SRGB8_ALPHA8_ASTC_5x5;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_6x5: return T::SRGB8_ALPHA8_ASTC_6x5;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_6x6: return T::SRGB8_ALPHA8_ASTC_6x6;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_8x5: return T::SRGB8_ALPHA8_ASTC_8x5;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_8x6: return T::SRGB8_ALPHA8_ASTC_8x6;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_8x8: return T::SRGB8_ALPHA8_ASTC_8x8;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x5: return T::SRGB8_ALPHA8_ASTC_10x5;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x6: return T::SRGB8_ALPHA8_ASTC_10x6;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x8: return T::SRGB8_ALPHA8_ASTC_10x8;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x10: return T::SRGB8_ALPHA8_ASTC_10x10;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_12x10: return T::SRGB8_ALPHA8_ASTC_12x10;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_12x12: return T::SRGB8_ALPHA8_ASTC_12x12;
|
||||
case Ktx1Bundle::R11_EAC: return T::EAC_R11;
|
||||
case Ktx1Bundle::SIGNED_R11_EAC: return T::EAC_R11_SIGNED;
|
||||
case Ktx1Bundle::RG11_EAC: return T::EAC_RG11;
|
||||
case Ktx1Bundle::SIGNED_RG11_EAC: return T::EAC_RG11_SIGNED;
|
||||
case Ktx1Bundle::RGB8_ETC2: return T::ETC2_RGB8;
|
||||
case Ktx1Bundle::SRGB8_ETC2: return T::ETC2_SRGB8;
|
||||
case Ktx1Bundle::RGB8_ALPHA1_ETC2: return T::ETC2_RGB8_A1;
|
||||
case Ktx1Bundle::SRGB8_ALPHA1_ETC: return T::ETC2_SRGB8_A1;
|
||||
case Ktx1Bundle::RGBA8_ETC2_EAC: return T::ETC2_EAC_RGBA8;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ETC2_EAC: return T::ETC2_EAC_SRGBA8;
|
||||
}
|
||||
return (T) 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Texture object from a KTX file and populates all of its faces and miplevels.
|
||||
*
|
||||
|
||||
@@ -40,6 +40,14 @@ class Ktx2Reader {
|
||||
using Texture = filament::Texture;
|
||||
enum class TransferFunction { LINEAR, sRGB };
|
||||
|
||||
enum class Result {
|
||||
SUCCESS,
|
||||
COMPRESSED_TRANSCODE_FAILURE,
|
||||
UNCOMPRESSED_TRANSCODE_FAILURE,
|
||||
FORMAT_UNSUPPORTED,
|
||||
FORMAT_ALREADY_REQUESTED,
|
||||
};
|
||||
|
||||
Ktx2Reader(Engine& engine, bool quiet = false);
|
||||
~Ktx2Reader();
|
||||
|
||||
@@ -55,18 +63,18 @@ class Ktx2Reader {
|
||||
* considered higher priority.
|
||||
*
|
||||
* If BasisU knows a priori that the given format is not available (e.g. if the build has
|
||||
* disabled it), this returns false and the format is not added to the list.
|
||||
* disabled it), the format is not added and FORMAT_UNSUPPORTED is returned.
|
||||
*
|
||||
* Returns false if the given format has already been requested.
|
||||
* Returns FORMAT_ALREADY_REQUESTED if the given format has already been requested.
|
||||
*
|
||||
* Hint: BasisU supports the following uncompressed formats: RGBA8, RGB565, RGBA4.
|
||||
*/
|
||||
bool requestFormat(Texture::InternalFormat format);
|
||||
Result requestFormat(Texture::InternalFormat format) noexcept;
|
||||
|
||||
/**
|
||||
* Removes the given format from the list, or does nothing if it hasn't been requested.
|
||||
*/
|
||||
void unrequestFormat(Texture::InternalFormat format);
|
||||
void unrequestFormat(Texture::InternalFormat format) noexcept;
|
||||
|
||||
/**
|
||||
* Attempts to create and load a Filament texture from the given KTX2 blob.
|
||||
@@ -83,7 +91,97 @@ class Ktx2Reader {
|
||||
* in the KTX2 blob. If they do not match, this method fails.
|
||||
* 2) It is used as a filter when determining the final internal format.
|
||||
*/
|
||||
Texture* load(const uint8_t* data, size_t size, TransferFunction transfer);
|
||||
Texture* load(const void* data, size_t size, TransferFunction transfer);
|
||||
|
||||
/**
|
||||
* Asynchronous Interface
|
||||
* ======================
|
||||
*
|
||||
* Alternative API suitable for asynchronous transcoding of mipmap levels.
|
||||
* If unsure that you need to use this, then don't, just call load() instead.
|
||||
* Usage pseudocode:
|
||||
*
|
||||
* auto async = reader->asyncCreate(data, size, TransferFunction::LINEAR);
|
||||
* mTexture = async->getTexture();
|
||||
* auto backgroundThread = spawnThread({ async->doTranscoding(); })
|
||||
* backgroundThread.wait();
|
||||
* async->uploadImages();
|
||||
* reader->asyncDestroy(async);
|
||||
*
|
||||
* In the documentation comments, "foreground thread" refers to the thread that the
|
||||
* Filament Engine was created on.
|
||||
*/
|
||||
class Async {
|
||||
public:
|
||||
/**
|
||||
* Retrieves the Texture object.
|
||||
*
|
||||
* The texture is available immediately, but does not have its miplevels ready until
|
||||
* after doTranscoding() and the subsequent uploadImages() have been completed. The
|
||||
* caller has ownership over this texture and is responsible for freeing it after all
|
||||
* miplevels have been uploaded.
|
||||
*/
|
||||
Texture* getTexture() const noexcept;
|
||||
|
||||
/**
|
||||
* Loads all mipmaps from the KTX2 file and transcodes them to the resolved format.
|
||||
*
|
||||
* This does not return until all mipmaps have been transcoded. This is typically
|
||||
* called from a background thread.
|
||||
*/
|
||||
Result doTranscoding();
|
||||
|
||||
/**
|
||||
* Uploads pending mipmaps to the texture.
|
||||
*
|
||||
* This can safely be called while doTranscoding() is still working in another thread.
|
||||
* Since this calls Texture::setImage(), it should be called from the foreground thread;
|
||||
* see "Thread safety" in the documentation for filament::Engine.
|
||||
*/
|
||||
void uploadImages();
|
||||
|
||||
protected:
|
||||
Async() noexcept = default;
|
||||
~Async() = default;
|
||||
|
||||
public:
|
||||
Async(Async const&) = delete;
|
||||
Async(Async&&) = delete;
|
||||
Async& operator=(Async const&) = delete;
|
||||
Async& operator=(Async&&) = delete;
|
||||
|
||||
friend class Ktx2Reader;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a texture without starting the transcode process.
|
||||
*
|
||||
* This method is an alternative to load() that allows users to populate mipmap levels
|
||||
* asynchronously. The texture object however is still created synchronously.
|
||||
*
|
||||
* - For a usage example, see the documentation for the Async object.
|
||||
* - Creates a copy of the given buffer, allowing clients to free it immediately.
|
||||
* - Returns null if none of the requested formats can be extracted from the data.
|
||||
*
|
||||
* This method iterates through the requested format list, checking each one against the
|
||||
* platform's capabilities and its availability from the transcoder. When a suitable format
|
||||
* is determined, it then performs lossless decompression (zstd) before transcoding the data
|
||||
* into the final format.
|
||||
*
|
||||
* The transfer function specified here is used in two ways:
|
||||
* 1) It is checked against the transfer function that was specified as metadata
|
||||
* in the KTX2 blob. If they do not match, this method fails.
|
||||
* 2) It is used as a filter when determining the final internal format.
|
||||
*/
|
||||
Async* asyncCreate(const void* data, size_t size, TransferFunction transfer);
|
||||
|
||||
/**
|
||||
* Frees the given async object and sets it to null.
|
||||
*
|
||||
* This frees the original source data (i.e. the raw content of the KTX2 file) but does not
|
||||
* free the associated Texture object. This can be done after transcoding has finished.
|
||||
*/
|
||||
void asyncDestroy(Async** async);
|
||||
|
||||
private:
|
||||
Ktx2Reader(const Ktx2Reader&) = delete;
|
||||
@@ -91,11 +189,13 @@ class Ktx2Reader {
|
||||
Ktx2Reader(Ktx2Reader&& that) noexcept = delete;
|
||||
Ktx2Reader& operator=(Ktx2Reader&& that) noexcept = delete;
|
||||
|
||||
Engine& mEngine;
|
||||
bool mQuiet;
|
||||
basist::ktx2_transcoder* const mTranscoder;
|
||||
Texture* createTexture(basist::ktx2_transcoder* transcoder, const void* data,
|
||||
size_t size, TransferFunction transfer);
|
||||
|
||||
Engine& mEngine;
|
||||
basist::ktx2_transcoder* const mTranscoder;
|
||||
utils::FixedCapacityVector<Texture::InternalFormat> mRequestedFormats;
|
||||
bool mQuiet;
|
||||
};
|
||||
|
||||
} // namespace ktxreader
|
||||
|
||||
@@ -23,55 +23,6 @@
|
||||
namespace ktxreader {
|
||||
namespace Ktx1Reader {
|
||||
|
||||
template<typename T>
|
||||
T toCompressedFilamentEnum(uint32_t format) {
|
||||
switch (format) {
|
||||
case Ktx1Bundle::RGB_S3TC_DXT1: return T::DXT1_RGB;
|
||||
case Ktx1Bundle::RGBA_S3TC_DXT1: return T::DXT1_RGBA;
|
||||
case Ktx1Bundle::RGBA_S3TC_DXT3: return T::DXT3_RGBA;
|
||||
case Ktx1Bundle::RGBA_S3TC_DXT5: return T::DXT5_RGBA;
|
||||
case Ktx1Bundle::RGBA_ASTC_4x4: return T::RGBA_ASTC_4x4;
|
||||
case Ktx1Bundle::RGBA_ASTC_5x4: return T::RGBA_ASTC_5x4;
|
||||
case Ktx1Bundle::RGBA_ASTC_5x5: return T::RGBA_ASTC_5x5;
|
||||
case Ktx1Bundle::RGBA_ASTC_6x5: return T::RGBA_ASTC_6x5;
|
||||
case Ktx1Bundle::RGBA_ASTC_6x6: return T::RGBA_ASTC_6x6;
|
||||
case Ktx1Bundle::RGBA_ASTC_8x5: return T::RGBA_ASTC_8x5;
|
||||
case Ktx1Bundle::RGBA_ASTC_8x6: return T::RGBA_ASTC_8x6;
|
||||
case Ktx1Bundle::RGBA_ASTC_8x8: return T::RGBA_ASTC_8x8;
|
||||
case Ktx1Bundle::RGBA_ASTC_10x5: return T::RGBA_ASTC_10x5;
|
||||
case Ktx1Bundle::RGBA_ASTC_10x6: return T::RGBA_ASTC_10x6;
|
||||
case Ktx1Bundle::RGBA_ASTC_10x8: return T::RGBA_ASTC_10x8;
|
||||
case Ktx1Bundle::RGBA_ASTC_10x10: return T::RGBA_ASTC_10x10;
|
||||
case Ktx1Bundle::RGBA_ASTC_12x10: return T::RGBA_ASTC_12x10;
|
||||
case Ktx1Bundle::RGBA_ASTC_12x12: return T::RGBA_ASTC_12x12;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_4x4: return T::SRGB8_ALPHA8_ASTC_4x4;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_5x4: return T::SRGB8_ALPHA8_ASTC_5x4;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_5x5: return T::SRGB8_ALPHA8_ASTC_5x5;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_6x5: return T::SRGB8_ALPHA8_ASTC_6x5;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_6x6: return T::SRGB8_ALPHA8_ASTC_6x6;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_8x5: return T::SRGB8_ALPHA8_ASTC_8x5;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_8x6: return T::SRGB8_ALPHA8_ASTC_8x6;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_8x8: return T::SRGB8_ALPHA8_ASTC_8x8;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x5: return T::SRGB8_ALPHA8_ASTC_10x5;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x6: return T::SRGB8_ALPHA8_ASTC_10x6;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x8: return T::SRGB8_ALPHA8_ASTC_10x8;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_10x10: return T::SRGB8_ALPHA8_ASTC_10x10;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_12x10: return T::SRGB8_ALPHA8_ASTC_12x10;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ASTC_12x12: return T::SRGB8_ALPHA8_ASTC_12x12;
|
||||
case Ktx1Bundle::R11_EAC: return T::EAC_R11;
|
||||
case Ktx1Bundle::SIGNED_R11_EAC: return T::EAC_R11_SIGNED;
|
||||
case Ktx1Bundle::RG11_EAC: return T::EAC_RG11;
|
||||
case Ktx1Bundle::SIGNED_RG11_EAC: return T::EAC_RG11_SIGNED;
|
||||
case Ktx1Bundle::RGB8_ETC2: return T::ETC2_RGB8;
|
||||
case Ktx1Bundle::SRGB8_ETC2: return T::ETC2_SRGB8;
|
||||
case Ktx1Bundle::RGB8_ALPHA1_ETC2: return T::ETC2_RGB8_A1;
|
||||
case Ktx1Bundle::SRGB8_ALPHA1_ETC: return T::ETC2_SRGB8_A1;
|
||||
case Ktx1Bundle::RGBA8_ETC2_EAC: return T::ETC2_EAC_RGBA8;
|
||||
case Ktx1Bundle::SRGB8_ALPHA8_ETC2_EAC: return T::ETC2_EAC_SRGBA8;
|
||||
}
|
||||
return (T) 0xffff;
|
||||
}
|
||||
|
||||
Texture* createTexture(Engine* engine, const Ktx1Bundle& ktx, bool srgb,
|
||||
Callback callback, void* userdata) {
|
||||
using Sampler = Texture::Sampler;
|
||||
@@ -82,11 +33,16 @@ Texture* createTexture(Engine* engine, const Ktx1Bundle& ktx, bool srgb,
|
||||
const auto dataformat = toPixelDataFormat(ktxinfo);
|
||||
|
||||
auto texformat = toTextureFormat(ktxinfo);
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (srgb && !isSrgbTextureFormat(texformat)) {
|
||||
utils::slog.w << "Requested sRGB format but KTX contains a linear format. ";
|
||||
utils::slog.w << "Requested sRGB format but KTX contains a linear format. "
|
||||
<< utils::io::endl;
|
||||
} else if (!srgb && isSrgbTextureFormat(texformat)) {
|
||||
utils::slog.w << "Requested linear format but KTX contains a sRGB format. ";
|
||||
utils::slog.w << "Requested linear format but KTX contains a sRGB format. "
|
||||
<< utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
Texture* texture = Texture::Builder()
|
||||
.width(ktxinfo.pixelWidth)
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warray-bounds"
|
||||
#include <basisu_transcoder.h>
|
||||
@@ -30,9 +32,13 @@ using namespace basist;
|
||||
using namespace filament;
|
||||
|
||||
using TransferFunction = ktxreader::Ktx2Reader::TransferFunction;
|
||||
using Result = ktxreader::Ktx2Reader::Result;
|
||||
using Async = ktxreader::Ktx2Reader::Async;
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
|
||||
namespace {
|
||||
struct FinalFormatInfo {
|
||||
const char* name; // <-- for debug purposes only
|
||||
bool isSupported;
|
||||
bool isCompressed;
|
||||
TransferFunction transferFunction;
|
||||
@@ -73,35 +79,118 @@ static FinalFormatInfo getFinalFormatInfo(Texture::InternalFormat fmt) {
|
||||
using tt = Texture::Type;
|
||||
using tf = Texture::Format;
|
||||
using ttf = transcoder_texture_format;
|
||||
const auto sRGB = ktxreader::Ktx2Reader::TransferFunction::sRGB;
|
||||
const auto LINEAR = ktxreader::Ktx2Reader::TransferFunction::LINEAR;
|
||||
const auto sRGB = TransferFunction::sRGB;
|
||||
const auto LINEAR = TransferFunction::LINEAR;
|
||||
switch (fmt) {
|
||||
case tif::ETC2_EAC_SRGBA8: return {true, true, sRGB, ttf::cTFETC2_RGBA, tct::ETC2_EAC_RGBA8};
|
||||
case tif::ETC2_EAC_RGBA8: return {true, true, LINEAR, ttf::cTFETC2_RGBA, tct::ETC2_EAC_SRGBA8};
|
||||
case tif::DXT1_SRGB: return {true, true, sRGB, ttf::cTFBC1_RGB, tct::DXT1_RGB};
|
||||
case tif::DXT1_RGB: return {true, true, LINEAR, ttf::cTFBC1_RGB, tct::DXT1_SRGB};
|
||||
case tif::DXT3_SRGBA: return {true, true, sRGB, ttf::cTFBC3_RGBA, tct::DXT3_RGBA};
|
||||
case tif::DXT3_RGBA: return {true, true, LINEAR, ttf::cTFBC3_RGBA, tct::DXT3_SRGBA};
|
||||
case tif::SRGB8_ALPHA8_ASTC_4x4: return {true, true, sRGB, ttf::cTFASTC_4x4_RGBA, tct::RGBA_ASTC_4x4};
|
||||
case tif::RGBA_ASTC_4x4: return {true, true, LINEAR, ttf::cTFASTC_4x4_RGBA, tct::SRGB8_ALPHA8_ASTC_4x4};
|
||||
case tif::EAC_R11: return {true, true, LINEAR, ttf::cTFETC2_EAC_R11, tct::EAC_R11};
|
||||
case tif::ETC2_EAC_SRGBA8: return {"ETC2_EAC_SRGBA8", true, true, sRGB, ttf::cTFETC2_RGBA, tct::ETC2_EAC_RGBA8};
|
||||
case tif::ETC2_EAC_RGBA8: return {"ETC2_EAC_RGBA8", true, true, LINEAR, ttf::cTFETC2_RGBA, tct::ETC2_EAC_SRGBA8};
|
||||
case tif::DXT1_SRGB: return {"DXT1_SRGB", true, true, sRGB, ttf::cTFBC1_RGB, tct::DXT1_RGB};
|
||||
case tif::DXT1_RGB: return {"DXT1_RGB", true, true, LINEAR, ttf::cTFBC1_RGB, tct::DXT1_SRGB};
|
||||
case tif::DXT5_SRGBA: return {"DXT5_SRGBA", true, true, sRGB, ttf::cTFBC3_RGBA, tct::DXT5_RGBA};
|
||||
case tif::DXT5_RGBA: return {"DXT5_RGBA", true, true, LINEAR, ttf::cTFBC3_RGBA, tct::DXT5_SRGBA};
|
||||
case tif::SRGB8_ALPHA8_ASTC_4x4: return {"SRGB8_ALPHA8_ASTC_4x4", true, true, sRGB, ttf::cTFASTC_4x4_RGBA, tct::RGBA_ASTC_4x4};
|
||||
case tif::RGBA_ASTC_4x4: return {"RGBA_ASTC_4x4", true, true, LINEAR, ttf::cTFASTC_4x4_RGBA, tct::SRGB8_ALPHA8_ASTC_4x4};
|
||||
case tif::EAC_R11: return {"EAC_R11", true, true, LINEAR, ttf::cTFETC2_EAC_R11, tct::EAC_R11};
|
||||
|
||||
// The following format is useful for normal maps.
|
||||
// Note that BasisU supports only the unsigned variant.
|
||||
case tif::EAC_RG11: return {true, true, LINEAR, ttf::cTFETC2_EAC_RG11, tct::EAC_RG11};
|
||||
case tif::EAC_RG11: return {"EAC_RG11", true, true, LINEAR, ttf::cTFETC2_EAC_RG11, tct::EAC_RG11};
|
||||
|
||||
// Uncompressed formats.
|
||||
case tif::SRGB8_A8: return {true, false, sRGB, ttf::cTFRGBA32, {}, tt::UBYTE, tf::RGBA};
|
||||
case tif::RGBA8: return {true, false, LINEAR, ttf::cTFRGBA32, {}, tt::UBYTE, tf::RGBA};
|
||||
case tif::RGB565: return {true, false, LINEAR, ttf::cTFRGB565, {}, tt::USHORT_565, tf::RGB};
|
||||
case tif::RGBA4: return {true, false, LINEAR, ttf::cTFRGBA4444, {}, tt::USHORT, tf::RGBA};
|
||||
case tif::SRGB8_A8: return {"SRGB8_A8", true, false, sRGB, ttf::cTFRGBA32, {}, tt::UBYTE, tf::RGBA};
|
||||
case tif::RGBA8: return {"RGBA8", true, false, LINEAR, ttf::cTFRGBA32, {}, tt::UBYTE, tf::RGBA};
|
||||
case tif::RGB565: return {"RGB565", true, false, LINEAR, ttf::cTFRGB565, {}, tt::USHORT_565, tf::RGB};
|
||||
case tif::RGBA4: return {"RGBA4", true, false, LINEAR, ttf::cTFRGBA4444, {}, tt::USHORT, tf::RGBA};
|
||||
|
||||
default: return {false};
|
||||
default: return {};
|
||||
}
|
||||
}
|
||||
|
||||
// In theory we could pass "free" directly into the callback but doing so triggers ASAN warnings.
|
||||
static void freeCallback(void* buf, size_t, void* userdata) {
|
||||
free(buf);
|
||||
}
|
||||
|
||||
// This helper is used by both the asynchronous and synchronous API's.
|
||||
static Result transcodeImageLevel(ktx2_transcoder& transcoder,
|
||||
ktx2_transcoder_state& transcoderState, Texture::InternalFormat format,
|
||||
uint32_t levelIndex, Texture::PixelBufferDescriptor** pbd) {
|
||||
using basisu::texture_format;
|
||||
assert_invariant(levelIndex < KTX2_MAX_SUPPORTED_LEVEL_COUNT);
|
||||
const FinalFormatInfo formatInfo = getFinalFormatInfo(format);
|
||||
const texture_format destFormat = basis_get_basisu_texture_format(formatInfo.basisFormat);
|
||||
const uint32_t layerIndex = 0;
|
||||
const uint32_t faceIndex = 0;
|
||||
const uint32_t decodeFlags = 0;
|
||||
const uint32_t outputRowPitch = 0;
|
||||
const uint32_t outputRowCount = 0;
|
||||
const int channel0 = 0;
|
||||
const int channel1 = 0;
|
||||
|
||||
basist::ktx2_image_level_info levelInfo;
|
||||
transcoder.get_image_level_info(levelInfo, levelIndex, layerIndex, faceIndex);
|
||||
|
||||
if (formatInfo.isCompressed) {
|
||||
const uint32_t qwordsPerBlock = basisu::get_qwords_per_block(destFormat);
|
||||
const size_t byteCount = sizeof(uint64_t) * qwordsPerBlock * levelInfo.m_total_blocks;
|
||||
uint64_t* const blocks = (uint64_t*) malloc(byteCount);
|
||||
if (!transcoder.transcode_image_level(levelIndex, layerIndex, faceIndex, blocks,
|
||||
levelInfo.m_total_blocks, formatInfo.basisFormat, decodeFlags,
|
||||
outputRowPitch, outputRowCount, channel0,
|
||||
channel1, &transcoderState)) {
|
||||
return Result::COMPRESSED_TRANSCODE_FAILURE;
|
||||
}
|
||||
*pbd = new Texture::PixelBufferDescriptor(blocks,
|
||||
byteCount, formatInfo.compressedPixelDataType, byteCount, freeCallback);
|
||||
return Result::SUCCESS;
|
||||
}
|
||||
|
||||
const uint32_t rowCount = levelInfo.m_orig_height;
|
||||
const uint32_t bytesPerPix = basis_get_bytes_per_block_or_pixel(formatInfo.basisFormat);
|
||||
const size_t byteCount = bytesPerPix * levelInfo.m_orig_width * rowCount;
|
||||
uint64_t* const rows = (uint64_t*) malloc(byteCount);
|
||||
if (!transcoder.transcode_image_level(levelIndex, layerIndex, faceIndex, rows,
|
||||
byteCount / bytesPerPix, formatInfo.basisFormat, decodeFlags,
|
||||
outputRowPitch, outputRowCount, channel0, channel1, &transcoderState)) {
|
||||
return Result::UNCOMPRESSED_TRANSCODE_FAILURE;
|
||||
}
|
||||
*pbd = new Texture::PixelBufferDescriptor(rows, byteCount,
|
||||
formatInfo.pixelDataFormat, formatInfo.pixelDataType, freeCallback);
|
||||
return Result::SUCCESS;
|
||||
}
|
||||
|
||||
namespace ktxreader {
|
||||
|
||||
class FAsync : public Async {
|
||||
public:
|
||||
FAsync(Texture* texture, Engine& engine, ktx2_transcoder* transcoder, Buffer&& buf) :
|
||||
mTexture(texture), mEngine(engine), mTranscoder(transcoder),
|
||||
mSourceBuffer(std::move(buf)) {}
|
||||
Texture* getTexture() const noexcept { return mTexture; }
|
||||
Result doTranscoding();
|
||||
void uploadImages();
|
||||
|
||||
private:
|
||||
using TranscoderResult = std::atomic<Texture::PixelBufferDescriptor*>;
|
||||
|
||||
// After each level is transcoded, the results are stashed in the following array until the
|
||||
// foreground thread calls uploadImages(). Each slot in the array corresponds to a single
|
||||
// miplevel in the texture.
|
||||
TranscoderResult mTranscoderResults[KTX2_MAX_SUPPORTED_LEVEL_COUNT] = {};
|
||||
|
||||
Texture* const mTexture;
|
||||
Engine& mEngine;
|
||||
|
||||
// We do not share the BasisU trancoder between Async objects. The BasisU transcoder
|
||||
// allows parallelization at "level" granularity, but does not permit parallelization at
|
||||
// "texture" granularity. i.e. the transcode_image_level() method is thread-safe but the
|
||||
// start_transcoding() method is not.
|
||||
std::unique_ptr<ktx2_transcoder> const mTranscoder;
|
||||
|
||||
// Storage for the content of the KTX2 file.
|
||||
Buffer mSourceBuffer;
|
||||
};
|
||||
|
||||
Ktx2Reader::Ktx2Reader(Engine& engine, bool quiet) :
|
||||
mEngine(engine),
|
||||
mQuiet(quiet),
|
||||
@@ -114,20 +203,20 @@ Ktx2Reader::~Ktx2Reader() {
|
||||
delete mTranscoder;
|
||||
}
|
||||
|
||||
bool Ktx2Reader::requestFormat(Texture::InternalFormat format) {
|
||||
Result Ktx2Reader::requestFormat(Texture::InternalFormat format) noexcept {
|
||||
if (!getFinalFormatInfo(format).isSupported) {
|
||||
return false;
|
||||
return Result::FORMAT_UNSUPPORTED;
|
||||
}
|
||||
for (Texture::InternalFormat fmt : mRequestedFormats) {
|
||||
if (fmt == format) {
|
||||
return false;
|
||||
return Result::FORMAT_ALREADY_REQUESTED;
|
||||
}
|
||||
}
|
||||
mRequestedFormats.push_back(format);
|
||||
return true;
|
||||
return Result::SUCCESS;
|
||||
}
|
||||
|
||||
void Ktx2Reader::unrequestFormat(Texture::InternalFormat format) {
|
||||
void Ktx2Reader::unrequestFormat(Texture::InternalFormat format) noexcept {
|
||||
for (auto iter = mRequestedFormats.begin(); iter != mRequestedFormats.end(); ++iter) {
|
||||
if (*iter == format) {
|
||||
mRequestedFormats.erase(iter);
|
||||
@@ -136,15 +225,102 @@ void Ktx2Reader::unrequestFormat(Texture::InternalFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
Texture* Ktx2Reader::load(const uint8_t* data, size_t size, TransferFunction transfer) {
|
||||
if (!mTranscoder->init(data, size)) {
|
||||
Texture* Ktx2Reader::load(const void* data, size_t size, TransferFunction transfer) {
|
||||
Texture* texture = createTexture(mTranscoder, data, size, transfer);
|
||||
if (texture == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!mTranscoder->start_transcoding()) {
|
||||
mEngine.destroy(texture);
|
||||
if (!mQuiet) {
|
||||
utils::slog.e << "BasisU start_transcoding failed." << utils::io::endl;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ktx2_transcoder_state basisThreadState;
|
||||
basisThreadState.clear();
|
||||
|
||||
for (uint32_t levelIndex = 0, n = mTranscoder->get_levels(); levelIndex < n; levelIndex++) {
|
||||
Texture::PixelBufferDescriptor* pbd;
|
||||
Result result = transcodeImageLevel(*mTranscoder, basisThreadState, texture->getFormat(),
|
||||
levelIndex, &pbd);
|
||||
if (UTILS_UNLIKELY(result != Result::SUCCESS)) {
|
||||
mEngine.destroy(texture);
|
||||
if (!mQuiet) {
|
||||
utils::slog.e << "Failed to transcode level " << levelIndex << utils::io::endl;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
texture->setImage(mEngine, levelIndex, std::move(*pbd));
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
Result FAsync::doTranscoding() {
|
||||
ktx2_transcoder_state basisThreadState;
|
||||
basisThreadState.clear();
|
||||
for (uint32_t levelIndex = 0, n = mTranscoder->get_levels(); levelIndex < n; levelIndex++) {
|
||||
Texture::PixelBufferDescriptor* pbd;
|
||||
Result result = transcodeImageLevel(*mTranscoder, basisThreadState, mTexture->getFormat(),
|
||||
levelIndex, &pbd);
|
||||
if (UTILS_UNLIKELY(result != Result::SUCCESS)) {
|
||||
return result;
|
||||
}
|
||||
mTranscoderResults[levelIndex].store(pbd);
|
||||
}
|
||||
return Result::SUCCESS;
|
||||
}
|
||||
|
||||
void FAsync::uploadImages() {
|
||||
size_t levelIndex = 0;
|
||||
UTILS_NOUNROLL
|
||||
for (TranscoderResult& level : mTranscoderResults) {
|
||||
Texture::PixelBufferDescriptor* pbd = level.load();
|
||||
if (pbd) {
|
||||
level.store(nullptr);
|
||||
mTexture->setImage(mEngine, levelIndex, std::move(*pbd));
|
||||
delete pbd;
|
||||
}
|
||||
++levelIndex;
|
||||
}
|
||||
}
|
||||
|
||||
Async* Ktx2Reader::asyncCreate(const void* data, size_t size, TransferFunction transfer) {
|
||||
Buffer ktx2content((uint8_t*)data, (uint8_t*)data + size);
|
||||
ktx2_transcoder* transcoder = new ktx2_transcoder();
|
||||
Texture* texture = createTexture(transcoder, ktx2content.data(), ktx2content.size(), transfer);
|
||||
if (texture == nullptr) {
|
||||
delete transcoder;
|
||||
return nullptr;
|
||||
}
|
||||
if (!transcoder->start_transcoding()) {
|
||||
delete transcoder;
|
||||
mEngine.destroy(texture);
|
||||
return nullptr;
|
||||
}
|
||||
// There's no need to do any further work at this point but it should be noted that this is the
|
||||
// point at which we first come to know the number of miplevels, dimensions, etc. If we had a
|
||||
// dynamically sized array to store decoder results, we would reserve it here.
|
||||
return new FAsync(texture, mEngine, transcoder, std::move(ktx2content));
|
||||
}
|
||||
|
||||
void Ktx2Reader::asyncDestroy(Async** async) {
|
||||
delete *async;
|
||||
*async = nullptr;
|
||||
}
|
||||
|
||||
Texture* Ktx2Reader::createTexture(ktx2_transcoder* transcoder, const void* data, size_t size,
|
||||
TransferFunction transfer) {
|
||||
if (!transcoder->init(data, size)) {
|
||||
if (!mQuiet) {
|
||||
utils::slog.e << "BasisU transcoder init failed." << utils::io::endl;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mTranscoder->get_dfd_transfer_func() == KTX2_KHR_DF_TRANSFER_LINEAR &&
|
||||
if (transcoder->get_dfd_transfer_func() == KTX2_KHR_DF_TRANSFER_LINEAR &&
|
||||
transfer == TransferFunction::sRGB) {
|
||||
if (!mQuiet) {
|
||||
utils::slog.e << "Source texture is marked linear, but client is requesting sRGB."
|
||||
@@ -153,7 +329,7 @@ Texture* Ktx2Reader::load(const uint8_t* data, size_t size, TransferFunction tra
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mTranscoder->get_dfd_transfer_func() == KTX2_KHR_DF_TRANSFER_SRGB &&
|
||||
if (transcoder->get_dfd_transfer_func() == KTX2_KHR_DF_TRANSFER_SRGB &&
|
||||
transfer == TransferFunction::LINEAR) {
|
||||
if (!mQuiet) {
|
||||
utils::slog.e << "Source texture is marked sRGB, but client is requesting linear."
|
||||
@@ -163,7 +339,7 @@ Texture* Ktx2Reader::load(const uint8_t* data, size_t size, TransferFunction tra
|
||||
}
|
||||
|
||||
// TODO: support cubemaps. For now we use KTX1 for cubemaps because basisu does not support HDR.
|
||||
if (mTranscoder->get_faces() == 6) {
|
||||
if (transcoder->get_faces() == 6) {
|
||||
if (!mQuiet) {
|
||||
utils::slog.e << "Cubemaps are not yet supported." << utils::io::endl;
|
||||
}
|
||||
@@ -171,32 +347,33 @@ Texture* Ktx2Reader::load(const uint8_t* data, size_t size, TransferFunction tra
|
||||
}
|
||||
|
||||
// TODO: support texture arrays.
|
||||
if (mTranscoder->get_layers() > 1) {
|
||||
if (transcoder->get_layers() > 1) {
|
||||
if (!mQuiet) {
|
||||
utils::slog.e << "Texture arrays are not yet supported." << utils::io::endl;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Fierst pass through, just to make sure we can transcode it.
|
||||
// First pass through, just to make sure we can transcode it.
|
||||
bool found = false;
|
||||
Texture::InternalFormat resolvedFormat;
|
||||
FinalFormatInfo info;
|
||||
for (Texture::InternalFormat requestedFormat : mRequestedFormats) {
|
||||
if (!Texture::isTextureFormatSupported(mEngine, requestedFormat)) {
|
||||
continue;
|
||||
}
|
||||
const auto info = getFinalFormatInfo(requestedFormat);
|
||||
info = getFinalFormatInfo(requestedFormat);
|
||||
if (!info.isSupported || info.transferFunction != transfer) {
|
||||
continue;
|
||||
}
|
||||
if (!basis_is_format_supported(info.basisFormat, mTranscoder->get_format())) {
|
||||
if (!basis_is_format_supported(info.basisFormat, transcoder->get_format())) {
|
||||
continue;
|
||||
}
|
||||
const uint32_t layerIndex = 0;
|
||||
const uint32_t faceIndex = 0;
|
||||
for (uint32_t levelIndex = 0; levelIndex < mTranscoder->get_levels(); levelIndex++) {
|
||||
for (uint32_t levelIndex = 0; levelIndex < transcoder->get_levels(); levelIndex++) {
|
||||
basist::ktx2_image_level_info info;
|
||||
if (!mTranscoder->get_image_level_info(info, levelIndex, layerIndex, faceIndex)) {
|
||||
if (!transcoder->get_image_level_info(info, levelIndex, layerIndex, faceIndex)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -212,75 +389,37 @@ Texture* Ktx2Reader::load(const uint8_t* data, size_t size, TransferFunction tra
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto formatInfo = getFinalFormatInfo(resolvedFormat);
|
||||
|
||||
Texture* texture = Texture::Builder()
|
||||
.width(mTranscoder->get_width())
|
||||
.height(mTranscoder->get_height())
|
||||
.levels(mTranscoder->get_levels())
|
||||
.width(transcoder->get_width())
|
||||
.height(transcoder->get_height())
|
||||
.levels(transcoder->get_levels())
|
||||
.sampler(Texture::Sampler::SAMPLER_2D)
|
||||
.format(resolvedFormat)
|
||||
.build(mEngine);
|
||||
|
||||
if (texture == nullptr) {
|
||||
if (!mQuiet) {
|
||||
utils::slog.e << "Unable to construct texture using BasisU info." << utils::io::endl;
|
||||
}
|
||||
return nullptr;
|
||||
if (texture == nullptr && !mQuiet) {
|
||||
utils::slog.e << "Unable to construct texture using BasisU info." << utils::io::endl;
|
||||
}
|
||||
|
||||
if (!mTranscoder->start_transcoding()) {
|
||||
mEngine.destroy(texture);
|
||||
if (!mQuiet) {
|
||||
utils::slog.e << "BasisU start_transcoding failed." << utils::io::endl;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// In theory we could pass "free" directly into the callback but that triggers ASAN warnings.
|
||||
Texture::PixelBufferDescriptor::Callback cb = [](void* buf, size_t, void* userdata) {
|
||||
free(buf);
|
||||
};
|
||||
|
||||
const uint32_t layerIndex = 0;
|
||||
const uint32_t faceIndex = 0;
|
||||
for (uint32_t levelIndex = 0; levelIndex < mTranscoder->get_levels(); levelIndex++) {
|
||||
basist::ktx2_image_level_info levelInfo;
|
||||
mTranscoder->get_image_level_info(levelInfo, levelIndex, layerIndex, faceIndex);
|
||||
const basisu::texture_format destFormat =
|
||||
basis_get_basisu_texture_format(formatInfo.basisFormat);
|
||||
if (formatInfo.isCompressed) {
|
||||
const uint32_t qwordsPerBlock = basisu::get_qwords_per_block(destFormat);
|
||||
const size_t byteCount = sizeof(uint64_t) * qwordsPerBlock * levelInfo.m_total_blocks;
|
||||
uint64_t* const blocks = (uint64_t*) malloc(byteCount);
|
||||
const uint32_t flags = 0;
|
||||
if (!mTranscoder->transcode_image_level(levelIndex, layerIndex, faceIndex, blocks,
|
||||
levelInfo.m_total_blocks, formatInfo.basisFormat, flags)) {
|
||||
utils::slog.e << "Failed to transcode level " << levelIndex << utils::io::endl;
|
||||
return nullptr;
|
||||
}
|
||||
Texture::PixelBufferDescriptor pbd(blocks, byteCount,
|
||||
formatInfo.compressedPixelDataType, byteCount, cb, nullptr);
|
||||
texture->setImage(mEngine, levelIndex, std::move(pbd));
|
||||
} else {
|
||||
// The transcoder still does work even for uncompressed formats, because of zstd.
|
||||
const uint32_t rowCount = levelInfo.m_orig_height;
|
||||
const uint32_t bytesPerPix = basis_get_bytes_per_block_or_pixel(formatInfo.basisFormat);
|
||||
const size_t byteCount = bytesPerPix * levelInfo.m_orig_width * rowCount;
|
||||
uint64_t* const rows = (uint64_t*) malloc(byteCount);
|
||||
const uint32_t flags = 0;
|
||||
if (!mTranscoder->transcode_image_level(levelIndex, layerIndex, faceIndex, rows,
|
||||
byteCount / bytesPerPix, formatInfo.basisFormat, flags)) {
|
||||
utils::slog.e << "Failed to transcode level " << levelIndex << utils::io::endl;
|
||||
return nullptr;
|
||||
}
|
||||
Texture::PixelBufferDescriptor pbd(rows, byteCount, formatInfo.pixelDataFormat,
|
||||
formatInfo.pixelDataType, cb, nullptr);
|
||||
texture->setImage(mEngine, levelIndex, std::move(pbd));
|
||||
}
|
||||
}
|
||||
#if BASISU_FORCE_DEVEL_MESSAGES
|
||||
utils::slog.e << "Ktx2Reader created "
|
||||
<< transcoder->get_width() << "x" << transcoder->get_height() << " texture with format "
|
||||
<< info.name << utils::io::endl;
|
||||
#endif
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
Texture* Async::getTexture() const noexcept {
|
||||
return static_cast<FAsync const*>(this)->getTexture();
|
||||
}
|
||||
|
||||
Result Async::doTranscoding() {
|
||||
return static_cast<FAsync*>(this)->doTranscoding();
|
||||
}
|
||||
|
||||
void Async::uploadImages() {
|
||||
return static_cast<FAsync*>(this)->uploadImages();
|
||||
}
|
||||
|
||||
} // namespace ktxreader
|
||||
|
||||
@@ -73,8 +73,8 @@ TEST_F(KtxReaderTest, Ktx2) {
|
||||
|
||||
ktxreader::Ktx2Reader reader(*engine);
|
||||
|
||||
reader.requestFormat(Texture::InternalFormat::DXT3_SRGBA);
|
||||
reader.requestFormat(Texture::InternalFormat::DXT3_RGBA);
|
||||
reader.requestFormat(Texture::InternalFormat::DXT5_SRGBA);
|
||||
reader.requestFormat(Texture::InternalFormat::DXT5_RGBA);
|
||||
|
||||
// Uncompressed formats are lower priority, so they get added last.
|
||||
reader.requestFormat(Texture::InternalFormat::SRGB8_A8);
|
||||
@@ -84,7 +84,7 @@ TEST_F(KtxReaderTest, Ktx2) {
|
||||
ktxreader::Ktx2Reader::TransferFunction::sRGB);
|
||||
|
||||
ASSERT_NE(tex, nullptr);
|
||||
ASSERT_EQ(tex->getFormat(), Texture::InternalFormat::DXT3_SRGBA);
|
||||
ASSERT_EQ(tex->getFormat(), Texture::InternalFormat::DXT5_SRGBA);
|
||||
ASSERT_EQ(tex->getWidth(), 1024);
|
||||
|
||||
engine->destroy(tex);
|
||||
|
||||
@@ -498,6 +498,7 @@ struct HighWatermark {
|
||||
void onFree(void* p, size_t size) noexcept;
|
||||
void onReset() noexcept;
|
||||
void onRewind(void const* addr) noexcept;
|
||||
uint32_t getHighWatermark() const noexcept { return mHighWaterMark; }
|
||||
protected:
|
||||
const char* mName = nullptr;
|
||||
void* mBase = nullptr;
|
||||
|
||||
@@ -12,7 +12,7 @@ set(PUBLIC_HDRS
|
||||
include/viewer/AutomationSpec.h
|
||||
include/viewer/RemoteServer.h
|
||||
include/viewer/Settings.h
|
||||
include/viewer/SimpleViewer.h
|
||||
include/viewer/ViewerGui.h
|
||||
)
|
||||
|
||||
set(SRCS
|
||||
@@ -21,7 +21,7 @@ set(SRCS
|
||||
src/AutomationSpec.cpp
|
||||
src/RemoteServer.cpp
|
||||
src/Settings.cpp
|
||||
src/SimpleViewer.cpp
|
||||
src/ViewerGui.cpp
|
||||
)
|
||||
|
||||
# ==================================================================================================
|
||||
|
||||
+11
-11
@@ -14,8 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef VIEWER_SIMPLEVIEWER_H
|
||||
#define VIEWER_SIMPLEVIEWER_H
|
||||
#ifndef VIEWER_VIEWERGUI_H
|
||||
#define VIEWER_VIEWERGUI_H
|
||||
|
||||
#include <filament/Box.h>
|
||||
#include <filament/DebugRegistry.h>
|
||||
@@ -45,15 +45,15 @@ namespace filament {
|
||||
namespace viewer {
|
||||
|
||||
/**
|
||||
* \class SimpleViewer SimpleViewer.h viewer/SimpleViewer.h
|
||||
* \brief Manages the state for a simple glTF viewer with imgui controls and a tree view.
|
||||
* \class ViewerGui ViewerGui.h viewer/ViewerGui.h
|
||||
* \brief Builds ImGui widgets for a simple glTF viewer and manages the associated state.
|
||||
*
|
||||
* This is a utility that can be used across multiple platforms, including web.
|
||||
*
|
||||
* \note If you don't need ImGui controls, there is no need to use this class, just use AssetLoader
|
||||
* instead.
|
||||
*/
|
||||
class UTILS_PUBLIC SimpleViewer {
|
||||
class UTILS_PUBLIC ViewerGui {
|
||||
public:
|
||||
using Animator = gltfio::Animator;
|
||||
using FilamentAsset = gltfio::FilamentAsset;
|
||||
@@ -62,18 +62,18 @@ public:
|
||||
static constexpr int DEFAULT_SIDEBAR_WIDTH = 350;
|
||||
|
||||
/**
|
||||
* Constructs a SimpleViewer that has a fixed association with the given Filament objects.
|
||||
* Constructs a ViewerGui that has a fixed association with the given Filament objects.
|
||||
*
|
||||
* Upon construction, the simple viewer may create some additional Filament objects (such as
|
||||
* light sources) that it owns.
|
||||
*/
|
||||
SimpleViewer(filament::Engine* engine, filament::Scene* scene, filament::View* view,
|
||||
ViewerGui(filament::Engine* engine, filament::Scene* scene, filament::View* view,
|
||||
int sidebarWidth = DEFAULT_SIDEBAR_WIDTH);
|
||||
|
||||
/**
|
||||
* Destroys the SimpleViewer and any Filament entities that it owns.
|
||||
* Destroys the ViewerGui and any Filament entities that it owns.
|
||||
*/
|
||||
~SimpleViewer();
|
||||
~ViewerGui();
|
||||
|
||||
/**
|
||||
* Adds the asset's ready-to-render entities into the scene.
|
||||
@@ -126,7 +126,7 @@ public:
|
||||
void renderUserInterface(float timeStepInSeconds, filament::View* guiView, float pixelRatio);
|
||||
|
||||
/**
|
||||
* Event-passing methods, useful only when SimpleViewer manages its own instance of ImGuiHelper.
|
||||
* Event-passing methods, useful only when ViewerGui manages its own instance of ImGuiHelper.
|
||||
* The key codes used in these methods are just normal ASCII/ANSI codes.
|
||||
* @{
|
||||
*/
|
||||
@@ -265,4 +265,4 @@ filament::math::mat4f fitIntoUnitCube(const filament::Aabb& bounds, float zoffse
|
||||
} // namespace viewer
|
||||
} // namespace filament
|
||||
|
||||
#endif // VIEWER_SIMPLEVIEWER_H
|
||||
#endif // VIEWER_VIEWERGUI_H
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <viewer/SimpleViewer.h>
|
||||
#include <viewer/ViewerGui.h>
|
||||
|
||||
#include <filament/RenderableManager.h>
|
||||
#include <filament/TransformManager.h>
|
||||
@@ -359,7 +359,7 @@ static void colorGradingUI(Settings& settings, float* rangePlot, float* curvePlo
|
||||
}
|
||||
}
|
||||
|
||||
SimpleViewer::SimpleViewer(filament::Engine* engine, filament::Scene* scene, filament::View* view,
|
||||
ViewerGui::ViewerGui(filament::Engine* engine, filament::Scene* scene, filament::View* view,
|
||||
int sidebarWidth) :
|
||||
mEngine(engine), mScene(scene), mView(view),
|
||||
mSunlight(utils::EntityManager::get().create()),
|
||||
@@ -389,12 +389,12 @@ SimpleViewer::SimpleViewer(filament::Engine* engine, filament::Scene* scene, fil
|
||||
view->setAmbientOcclusionOptions({ .upsampling = View::QualityLevel::HIGH });
|
||||
}
|
||||
|
||||
SimpleViewer::~SimpleViewer() {
|
||||
ViewerGui::~ViewerGui() {
|
||||
mEngine->destroy(mSunlight);
|
||||
delete mImGuiHelper;
|
||||
}
|
||||
|
||||
void SimpleViewer::populateScene(FilamentAsset* asset, FilamentInstance* instanceToAnimate) {
|
||||
void ViewerGui::populateScene(FilamentAsset* asset, FilamentInstance* instanceToAnimate) {
|
||||
if (mAsset != asset) {
|
||||
removeAsset();
|
||||
mAsset = asset;
|
||||
@@ -421,7 +421,7 @@ void SimpleViewer::populateScene(FilamentAsset* asset, FilamentInstance* instan
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleViewer::removeAsset() {
|
||||
void ViewerGui::removeAsset() {
|
||||
if (!isRemoteMode()) {
|
||||
mScene->removeEntities(mAsset->getEntities(), mAsset->getEntityCount());
|
||||
mAsset = nullptr;
|
||||
@@ -429,7 +429,7 @@ void SimpleViewer::removeAsset() {
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleViewer::setIndirectLight(filament::IndirectLight* ibl,
|
||||
void ViewerGui::setIndirectLight(filament::IndirectLight* ibl,
|
||||
filament::math::float3 const* sh3) {
|
||||
using namespace filament::math;
|
||||
mSettings.view.fog.color = sh3[0];
|
||||
@@ -446,7 +446,7 @@ void SimpleViewer::setIndirectLight(filament::IndirectLight* ibl,
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleViewer::updateRootTransform() {
|
||||
void ViewerGui::updateRootTransform() {
|
||||
if (isRemoteMode()) {
|
||||
return;
|
||||
}
|
||||
@@ -459,7 +459,7 @@ void SimpleViewer::updateRootTransform() {
|
||||
tcm.setTransform(root, transform);
|
||||
}
|
||||
|
||||
void SimpleViewer::updateIndirectLight() {
|
||||
void ViewerGui::updateIndirectLight() {
|
||||
using namespace filament::math;
|
||||
if (mIndirectLight) {
|
||||
mIndirectLight->setIntensity(mSettings.lighting.iblIntensity);
|
||||
@@ -467,7 +467,7 @@ void SimpleViewer::updateIndirectLight() {
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleViewer::applyAnimation(double currentTime) {
|
||||
void ViewerGui::applyAnimation(double currentTime) {
|
||||
assert_invariant(!isRemoteMode());
|
||||
static double startTime = 0;
|
||||
const size_t numAnimations = mAnimator->getAnimationCount();
|
||||
@@ -488,7 +488,7 @@ void SimpleViewer::applyAnimation(double currentTime) {
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleViewer::renderUserInterface(float timeStepInSeconds, View* guiView, float pixelRatio) {
|
||||
void ViewerGui::renderUserInterface(float timeStepInSeconds, View* guiView, float pixelRatio) {
|
||||
if (mImGuiHelper == nullptr) {
|
||||
mImGuiHelper = new ImGuiHelper(mEngine, guiView, "");
|
||||
|
||||
@@ -529,7 +529,7 @@ void SimpleViewer::renderUserInterface(float timeStepInSeconds, View* guiView, f
|
||||
});
|
||||
}
|
||||
|
||||
void SimpleViewer::mouseEvent(float mouseX, float mouseY, bool mouseButton, float mouseWheelY,
|
||||
void ViewerGui::mouseEvent(float mouseX, float mouseY, bool mouseButton, float mouseWheelY,
|
||||
bool control) {
|
||||
if (mImGuiHelper) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
@@ -543,25 +543,25 @@ void SimpleViewer::mouseEvent(float mouseX, float mouseY, bool mouseButton, floa
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleViewer::keyDownEvent(int keyCode) {
|
||||
void ViewerGui::keyDownEvent(int keyCode) {
|
||||
if (mImGuiHelper && keyCode < IM_ARRAYSIZE(ImGui::GetIO().KeysDown)) {
|
||||
ImGui::GetIO().KeysDown[keyCode] = true;
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleViewer::keyUpEvent(int keyCode) {
|
||||
void ViewerGui::keyUpEvent(int keyCode) {
|
||||
if (mImGuiHelper && keyCode < IM_ARRAYSIZE(ImGui::GetIO().KeysDown)) {
|
||||
ImGui::GetIO().KeysDown[keyCode] = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleViewer::keyPressEvent(int charCode) {
|
||||
void ViewerGui::keyPressEvent(int charCode) {
|
||||
if (mImGuiHelper) {
|
||||
ImGui::GetIO().AddInputCharacter(charCode);
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleViewer::updateUserInterface() {
|
||||
void ViewerGui::updateUserInterface() {
|
||||
using namespace filament;
|
||||
|
||||
auto& tm = mEngine->getTransformManager();
|
||||
@@ -906,7 +906,7 @@ void SimpleViewer::updateUserInterface() {
|
||||
|
||||
// We do not yet support camera selection in the remote UI. To support this feature, we
|
||||
// would need to send a message from DebugServer to the WebSockets client.
|
||||
if (isRemoteMode()) {
|
||||
if (!isRemoteMode()) {
|
||||
|
||||
const utils::Entity* cameras = mAsset->getCameraEntities();
|
||||
const size_t cameraCount = mAsset->getCameraEntityCount();
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <gltfio/FilamentAsset.h>
|
||||
#include <gltfio/ResourceLoader.h>
|
||||
|
||||
#include <viewer/SimpleViewer.h>
|
||||
#include <viewer/ViewerGui.h>
|
||||
|
||||
#include <camutils/Manipulator.h>
|
||||
|
||||
@@ -58,7 +58,7 @@ enum MaterialSource {
|
||||
|
||||
struct App {
|
||||
Engine* engine;
|
||||
SimpleViewer* viewer;
|
||||
ViewerGui* viewer;
|
||||
Config config;
|
||||
AssetLoader* loader;
|
||||
FilamentAsset* asset = nullptr;
|
||||
@@ -242,7 +242,7 @@ int main(int argc, char** argv) {
|
||||
auto setup = [&](Engine* engine, View* view, Scene* scene) {
|
||||
app.engine = engine;
|
||||
app.names = new NameComponentManager(EntityManager::get());
|
||||
app.viewer = new SimpleViewer(engine, scene, view, app.instanceToAnimate);
|
||||
app.viewer = new ViewerGui(engine, scene, view, app.instanceToAnimate);
|
||||
app.materials = (app.materialSource == GENERATE_SHADERS) ?
|
||||
createMaterialGenerator(engine) : createUbershaderLoader(engine);
|
||||
app.loader = AssetLoader::create({engine, app.materials, app.names });
|
||||
|
||||
+13
-8
@@ -37,7 +37,7 @@
|
||||
|
||||
#include <viewer/AutomationEngine.h>
|
||||
#include <viewer/AutomationSpec.h>
|
||||
#include <viewer/SimpleViewer.h>
|
||||
#include <viewer/ViewerGui.h>
|
||||
|
||||
#include <camutils/Manipulator.h>
|
||||
|
||||
@@ -73,7 +73,7 @@ enum MaterialSource {
|
||||
|
||||
struct App {
|
||||
Engine* engine;
|
||||
SimpleViewer* viewer;
|
||||
ViewerGui* viewer;
|
||||
Config config;
|
||||
Camera* mainCamera;
|
||||
|
||||
@@ -85,7 +85,8 @@ struct App {
|
||||
MaterialSource materialSource = GENERATE_SHADERS;
|
||||
|
||||
gltfio::ResourceLoader* resourceLoader = nullptr;
|
||||
gltfio::TextureProvider* decoder = nullptr;
|
||||
gltfio::TextureProvider* stbDecoder = nullptr;
|
||||
gltfio::TextureProvider* ktxDecoder = nullptr;
|
||||
bool recomputeAabb = false;
|
||||
bool ignoreBindTransform = false;
|
||||
|
||||
@@ -425,9 +426,11 @@ int main(int argc, char** argv) {
|
||||
configuration.normalizeSkinningWeights = true;
|
||||
if (!app.resourceLoader) {
|
||||
app.resourceLoader = new gltfio::ResourceLoader(configuration);
|
||||
app.decoder = createStbProvider(app.engine);
|
||||
app.resourceLoader->addTextureProvider("image/png", app.decoder);
|
||||
app.resourceLoader->addTextureProvider("image/jpeg", app.decoder);
|
||||
app.stbDecoder = createStbProvider(app.engine);
|
||||
app.ktxDecoder = createKtx2Provider(app.engine);
|
||||
app.resourceLoader->addTextureProvider("image/png", app.stbDecoder);
|
||||
app.resourceLoader->addTextureProvider("image/jpeg", app.stbDecoder);
|
||||
app.resourceLoader->addTextureProvider("image/ktx2", app.ktxDecoder);
|
||||
}
|
||||
app.resourceLoader->asyncBeginLoad(app.asset);
|
||||
app.asset->releaseSourceData();
|
||||
@@ -441,7 +444,7 @@ int main(int argc, char** argv) {
|
||||
auto setup = [&](Engine* engine, View* view, Scene* scene) {
|
||||
app.engine = engine;
|
||||
app.names = new NameComponentManager(EntityManager::get());
|
||||
app.viewer = new SimpleViewer(engine, scene, view, 410);
|
||||
app.viewer = new ViewerGui(engine, scene, view, 410);
|
||||
app.viewer->getSettings().viewer.autoScaleEnabled = !app.actualSize;
|
||||
|
||||
const bool batchMode = !app.batchFile.empty();
|
||||
@@ -580,6 +583,7 @@ int main(int argc, char** argv) {
|
||||
debug.getPropertyAddress<bool>("d.renderer.doFrameCapture");
|
||||
*captureFrame = true;
|
||||
}
|
||||
ImGui::Checkbox("Camera at origin", debug.getPropertyAddress<bool>("d.view.camera_at_origin"));
|
||||
auto dataSource = debug.getDataSource("d.view.frame_info");
|
||||
if (dataSource.data) {
|
||||
ImGuiExt::PlotLinesSeries("FrameInfo", 6,
|
||||
@@ -649,7 +653,8 @@ int main(int argc, char** argv) {
|
||||
delete app.viewer;
|
||||
delete app.materials;
|
||||
delete app.names;
|
||||
delete app.decoder;
|
||||
delete app.stbDecoder;
|
||||
delete app.ktxDecoder;
|
||||
|
||||
AssetLoader::destroy(&app.assetLoader);
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
#include <utils/EntityManager.h>
|
||||
|
||||
#include <viewer/SimpleViewer.h>
|
||||
#include <viewer/ViewerGui.h>
|
||||
|
||||
#include <camutils/Manipulator.h>
|
||||
|
||||
@@ -63,7 +63,7 @@ using namespace utils;
|
||||
|
||||
struct App {
|
||||
Engine* engine;
|
||||
SimpleViewer* viewer;
|
||||
ViewerGui* viewer;
|
||||
Config config;
|
||||
Camera* mainCamera;
|
||||
|
||||
@@ -290,7 +290,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
auto setup = [&](Engine* engine, View* view, Scene* scene) {
|
||||
app.engine = engine;
|
||||
app.viewer = new SimpleViewer(engine, scene, view, 410);
|
||||
app.viewer = new ViewerGui(engine, scene, view, 410);
|
||||
app.viewer->getSettings().viewer.autoScaleEnabled = false;
|
||||
app.viewer->getSettings().view.bloom.enabled = false;
|
||||
app.viewer->getSettings().view.ssao.enabled = false;
|
||||
|
||||
@@ -14,13 +14,13 @@ set(SHADERS
|
||||
src/ambient_occlusion.fs
|
||||
src/attributes.vs
|
||||
src/brdf.fs
|
||||
src/common_getters.fs
|
||||
src/common_getters.glsl
|
||||
src/common_graphics.fs
|
||||
src/common_lighting.fs
|
||||
src/common_material.fs
|
||||
src/common_math.fs
|
||||
src/common_math.glsl
|
||||
src/common_shading.fs
|
||||
src/common_shadowing.fs
|
||||
src/common_shadowing.glsl
|
||||
src/common_types.fs
|
||||
src/depth_main.fs
|
||||
src/fog.fs
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
// Uniforms access
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#define FILAMENT_OBJECT_SKINNING_ENABLED_BIT 0x1u
|
||||
#define FILAMENT_OBJECT_MORPHING_ENABLED_BIT 0x2u
|
||||
#define FILAMENT_OBJECT_CONTACT_SHADOWS_BIT 0x4u
|
||||
|
||||
/** @public-api */
|
||||
highp mat4 getViewFromWorldMatrix() {
|
||||
return frameUniforms.viewFromWorldMatrix;
|
||||
@@ -36,6 +32,27 @@ highp mat4 getWorldFromClipMatrix() {
|
||||
return frameUniforms.worldFromClipMatrix;
|
||||
}
|
||||
|
||||
/** @public-api */
|
||||
float getTime() {
|
||||
return frameUniforms.time;
|
||||
}
|
||||
|
||||
/** @public-api */
|
||||
highp vec4 getUserTime() {
|
||||
return frameUniforms.userTime;
|
||||
}
|
||||
|
||||
/** @public-api **/
|
||||
highp float getUserTimeMod(float m) {
|
||||
return mod(mod(frameUniforms.userTime.x, m) + mod(frameUniforms.userTime.y, m), m);
|
||||
}
|
||||
|
||||
// TODO: below shouldn't be accessible from post-process materials
|
||||
|
||||
#define FILAMENT_OBJECT_SKINNING_ENABLED_BIT 0x1u
|
||||
#define FILAMENT_OBJECT_MORPHING_ENABLED_BIT 0x2u
|
||||
#define FILAMENT_OBJECT_CONTACT_SHADOWS_BIT 0x4u
|
||||
|
||||
/** @public-api */
|
||||
highp vec4 getResolution() {
|
||||
return frameUniforms.resolution;
|
||||
@@ -51,21 +68,6 @@ highp vec3 getWorldOffset() {
|
||||
return frameUniforms.worldOffset;
|
||||
}
|
||||
|
||||
/** @public-api */
|
||||
float getTime() {
|
||||
return frameUniforms.time;
|
||||
}
|
||||
|
||||
/** @public-api */
|
||||
highp vec4 getUserTime() {
|
||||
return frameUniforms.userTime;
|
||||
}
|
||||
|
||||
/** @public-api **/
|
||||
highp float getUserTimeMod(float m) {
|
||||
return mod(mod(frameUniforms.userTime.x, m) + mod(frameUniforms.userTime.y, m), m);
|
||||
}
|
||||
|
||||
/** @public-api */
|
||||
float getExposure() {
|
||||
// NOTE: this is a highp uniform only to work around #3602 (qualcomm)
|
||||
@@ -88,6 +88,20 @@ vec3 decodeRGBM(vec4 c) {
|
||||
return c.rgb * c.rgb;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Common screen-space operations
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// returns the frag coord in the GL convention with (0, 0) at the bottom-left
|
||||
// resolution : width, height
|
||||
highp vec2 getFragCoord(const highp vec2 resolution) {
|
||||
#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT)
|
||||
return vec2(gl_FragCoord.x, resolution.y - gl_FragCoord.y);
|
||||
#else
|
||||
return gl_FragCoord.xy;
|
||||
#endif
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Common debug
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -192,7 +192,7 @@ highp mat3 cofactor(const highp mat3 m) {
|
||||
* Random number between 0 and 1, using interleaved gradient noise.
|
||||
* w must not be normalized (e.g. window coordinates)
|
||||
*/
|
||||
float interleavedGradientNoise(const highp vec2 w) {
|
||||
float interleavedGradientNoise(highp vec2 w) {
|
||||
const vec3 m = vec3(0.06711056, 0.00583715, 52.9829189);
|
||||
return fract(m.z * fract(dot(w, m.xy)));
|
||||
}
|
||||
@@ -31,7 +31,7 @@ void main() {
|
||||
|
||||
#if defined(MATERIAL_HAS_TRANSPARENT_SHADOW)
|
||||
// Interleaved gradient noise, see dithering.fs
|
||||
float noise = fract(52.982919 * fract(dot(vec2(0.06711, 0.00584), gl_FragCoord.xy)));
|
||||
float noise = interleavedGradientNoise(gl_FragCoord.xy);
|
||||
if (noise >= alpha) {
|
||||
discard;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user