Compare commits

...

42 Commits

Author SHA1 Message Date
Andy Hovingh
3e6fd9d044 TEMP fix constants presence (even though not used at this point) 2025-05-30 15:25:30 -05:00
bridgewaterrobbie
d943293fd8 Hack do not merge: Make metal have the same hack to compare linear only 2025-05-30 15:18:48 -05:00
bridgewaterrobbie
acc6c801ec Allow srgb support to be signaled 2025-05-30 15:18:48 -05:00
Syed Idris Shah
a09ab9625d fix the render target usage
bindPipeline should be able to handle both default and custom render targets
2025-05-30 15:18:45 -05:00
Syed Idris Shah
2c65946217 temp code and prints 2025-05-30 15:16:19 -05:00
Juan Caldas
c166bea8fc Dynamically update the Spec Constant value 2025-05-30 15:16:15 -05:00
Juan Caldas
b9be9b4f2d Set Render Pipeline 2025-05-30 15:06:50 -05:00
Powei Feng
ce88a56579 Fix leaking dummy depth array texture (#8796) 2025-05-30 17:19:55 +00:00
Powei Feng
689e769f9a utils: Fix FixedCircularBufferTest.Exceptions (#8794)
Switching from assert_invariant to assert produced a different
error message.
2025-05-30 10:02:31 -07:00
Jeremy Nelson
2f0a8b19b5 use params in draw call 2025-05-30 11:49:05 -05:00
Andy Hovingh
1bed68afb7 webgpu: support user-defined spec constants in addition to native Filament ones 2025-05-30 11:33:14 -05:00
Konrad Piascik
130e02da5c Add helper script to linux documentation (#8783) 2025-05-30 14:16:36 +00:00
bridgewaterrobbie
8e06a68446 Relocate calling mRenderPassEncoder.SetBindGroup to draw call, deferring it to handle non-default render targets being used. 2025-05-29 20:23:06 -04:00
bridgewaterrobbie
656b50b304 Manually remove Tint annotations that Dawn complains about when consuming.
Dawn complains when consuming these lines with
WebGPU device error: ErrorType::Validation Error while parsing WGSL: :24:4 error: expected attribute
Did you mean 'size'?
Possible values: 'align', 'binding', 'blend_src', 'builtin', 'color', 'compute', 'diagnostic', 'fragment', 'group', 'id', 'input_attachment_index', 'interpolate', 'invariant', 'location', 'must_use', 'size', 'vertex', 'workgroup_size'
  @stride(16) @internal(disable_validation__ignore_stride)

And we don't see a better workaround at this time.
2025-05-29 17:59:12 -04:00
Mathias Agopian
880db4ec43 fix a buffer overflow during init
the default cubemap has RGBA pixels (4 bytes per pixel).
2025-05-29 14:27:38 -07:00
Syed Idris Shah
2ce294720d vertex buffer info hardening.
Implement vertexbufferinfo taking both interleaved and block attributes into account.
Handle the unused buffers by dedicating a slot for that.
Update the usage of vertexbufferinfo for setVertexBuffer call
2025-05-29 15:51:55 -05:00
Juan Caldas
eb07decb4b wgpu: Ignore spec constants greater than 10 (#8789) 2025-05-29 15:58:35 -04:00
Mathias Agopian
47930edf70 don't use assert_invariant in public headers
this is to eventually suppress the dependency on utils/debug.h from
public headers
2025-05-29 09:56:52 -07:00
Mathias Agopian
05b89905d8 fix a use after free of texture data during init (#8786) 2025-05-28 22:32:24 -07:00
Powei Feng
3e0df36c1c Add option to disable GTAO (#8785) 2025-05-28 22:35:14 +00:00
Powei Feng
c9ce384f14 vk: add missing header (#8781) 2025-05-28 22:17:55 +00:00
Sungun Park
6a6bdd5be7 Fix broken android samples (#8784)
that use DescriptorType::SAMPLER_EXTERNAL.
2025-05-28 20:56:20 +00:00
Sungun Park
d56ade924e Add checks for MSAA (#8782) 2025-05-28 19:48:12 +00:00
Andy Hovingh
fe561e3e27 webgpu: support selection from multiple adapters/graphics devices 2025-05-28 12:52:04 -05:00
Jeremy Nelson
1d98e350e4 Set stencil ops to undefined if nonexistent 2025-05-27 16:28:01 -07:00
Matthew Hoffman
d34e018acb Replace 512 magic number in backend tests. (#8775) 2025-05-27 21:39:50 +00:00
Mathias Agopian
169c8d57ff assert vertex attribute alignment
offset and size must now be a multiple of 4
2025-05-27 10:19:53 -07:00
Mathias Agopian
a1e0cfa33c fix a few noexcept through out the code base 2025-05-27 10:18:12 -07:00
Sungun Park
6d44db3ca0 Fix a compile error
This is a fix for the compile error caused by
86a500c846
2025-05-23 10:33:14 -07:00
Sungun Park
a261429d06 Add multiview MSAA support for custom swapchain (#8767)
BUGS=[417311684]
2025-05-23 00:42:04 +00:00
Matthew Hoffman
69d0de3c6d In backend tests replace calls to draw with draw2. (#8765)
BUGS=[398199600]
2025-05-22 13:14:52 -05:00
bridgewaterrobbie
1305303f2e Fix typo in getVertexFormat 2025-05-20 18:13:19 -04:00
bridgewaterrobbie
8532511107 Add enforced check for compressed texture block width/height offset 2025-05-20 16:10:17 -04:00
bridgewaterrobbie
b762ccb78c Use the aspect to skip drawing for depth only. TODO is why this is even coming up? 2025-05-20 16:10:17 -04:00
bridgewaterrobbie
3296eb153c If necessary, apply padding to uploaded texture. 2025-05-20 16:10:17 -04:00
bridgewaterrobbie
ae6d1028f7 Initial update3DImage implementation for webgpu 2025-05-20 16:10:17 -04:00
bridgewaterrobbie
92579b6307 Expose bytes per pixel function separately from computeDataSize 2025-05-20 16:10:17 -04:00
Benjamin Doherty
315b53c339 Release Filament 1.60.1 2025-05-20 12:42:33 -07:00
Andy Hovingh
c55d459986 webgpu: samples/hellopbr.cpp CLI allows for selecting webgpu backend api 2025-05-20 14:40:48 -05:00
Andy Hovingh
7876695545 webgpu: add CLI to texturedquad.cpp sample to select backend + updated WebGPU warning message. 2025-05-20 14:40:48 -05:00
Matthew Hoffman
e1a1464c5d Add helpers for creating RenderPipelineParams for backend tests (#8751)
* Add helpers for creating RenderPipelineParams for backend tests

BUGS=[398199600]

* Move all backend tests over to use cyan as the clear color
2025-05-20 19:17:12 +00:00
bridgewaterrobbie
67ed5141bf Avoid using paramater name type which is reserved in WGSL and thus can't be compiled for WebGPU 2025-05-20 14:09:23 -04:00
150 changed files with 2567 additions and 1173 deletions

View File

@@ -97,6 +97,10 @@ Make sure you've installed the following dependencies:
- `libxcomposite-dev` (`libXcomposite-devel` on Fedora)
- `libxxf86vm-dev` (`libXxf86vm-devel` on Fedora)
```shell
sudo apt install clang-14 libglu1-mesa-dev libc++-14-dev libc++abi-14-dev ninja-build libxi-dev libxcomposite-dev libxxf86vm-dev -y
```
After dependencies have been installed, we highly recommend using the [easy build](#easy-build)
script.

View File

@@ -49,6 +49,10 @@ option(FILAMENT_SUPPORTS_OSMESA "Enable OSMesa (headless GL context) for Filamen
option(FILAMENT_ENABLE_FGVIEWER "Enable the frame graph viewer" OFF)
# This is to disable GTAO for the short-term while we investigate a way to better manage size increases.
# On the regular filament build (where size is of less concern), we enable GTAO by default.
option(FILAMENT_DISABLE_GTAO "Disable GTAO" OFF)
set(FILAMENT_NDK_VERSION "" CACHE STRING
"Android NDK version or version prefix to be used when building for Android."
)

View File

@@ -7,5 +7,3 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
- materials: sampler now export their type in the material binary [⚠️ **New Material Version**]

View File

@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.60.0'
implementation 'com.google.android.filament:filament-android:1.60.1'
}
```
@@ -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:
```shell
pod 'Filament', '~> 1.60.0'
pod 'Filament', '~> 1.60.1'
```
## Documentation

View File

@@ -7,6 +7,12 @@ A new header is inserted each time a *tag* is created.
Instead, if you are authoring a PR for the main branch, add your release note to
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
## v1.61.0
- materials: sampler now export their type in the material binary [⚠️ **New Material Version**]
- samples/texturedquad.cpp now has CLI to select backend api
- samples/hellopbr.cpp CLI now allows for selecting webgpu
## v1.60.1

View File

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

View File

@@ -262,11 +262,16 @@ set(MATERIAL_SRCS
src/materials/ssao/mipmapDepth.mat
src/materials/ssao/sao.mat
src/materials/ssao/saoBentNormals.mat
src/materials/ssao/gtao.mat
src/materials/ssao/gtaoBentNormals.mat
src/materials/vsmMipmap.mat
)
if (NOT FILAMENT_DISABLE_GTAO)
list(APPEND MATERIAL_SRCS
src/materials/ssao/gtao.mat
src/materials/ssao/gtaoBentNormals.mat
)
endif()
set(MATERIAL_FL0_SRCS
src/materials/defaultMaterial.mat
src/materials/skybox.mat
@@ -316,6 +321,10 @@ if (FILAMENT_FORCE_PROFILING_MODE)
add_definitions(-DFILAMENT_FORCE_PROFILING_MODE)
endif()
if (FILAMENT_DISABLE_GTAO)
add_definitions(-DFILAMENT_DISABLE_GTAO)
endif()
# ==================================================================================================
# Definitions
# ==================================================================================================
@@ -463,27 +472,29 @@ add_custom_command(
APPEND
)
add_custom_command(
OUTPUT "${MATERIAL_DIR}/gtao.filamat"
DEPENDS src/materials/ssao/ssaoUtils.fs
DEPENDS src/materials/ssao/ssct.fs
DEPENDS src/materials/utils/depthUtils.fs
DEPENDS src/materials/utils/geometry.fs
DEPENDS src/materials/ssao/gtaoImpl.fs
DEPENDS src/materials/ssao/ssctImpl.fs
APPEND
)
if (NOT FILAMENT_DISABLE_GTAO)
add_custom_command(
OUTPUT "${MATERIAL_DIR}/gtao.filamat"
DEPENDS src/materials/ssao/ssaoUtils.fs
DEPENDS src/materials/ssao/ssct.fs
DEPENDS src/materials/utils/depthUtils.fs
DEPENDS src/materials/utils/geometry.fs
DEPENDS src/materials/ssao/gtaoImpl.fs
DEPENDS src/materials/ssao/ssctImpl.fs
APPEND
)
add_custom_command(
OUTPUT "${MATERIAL_DIR}/gtaoBentNormals.filamat"
DEPENDS src/materials/ssao/ssaoUtils.fs
DEPENDS src/materials/ssao/ssct.fs
DEPENDS src/materials/utils/depthUtils.fs
DEPENDS src/materials/utils/geometry.fs
DEPENDS src/materials/ssao/gtaoImpl.fs
DEPENDS src/materials/ssao/ssctImpl.fs
APPEND
)
add_custom_command(
OUTPUT "${MATERIAL_DIR}/gtaoBentNormals.filamat"
DEPENDS src/materials/ssao/ssaoUtils.fs
DEPENDS src/materials/ssao/ssct.fs
DEPENDS src/materials/utils/depthUtils.fs
DEPENDS src/materials/utils/geometry.fs
DEPENDS src/materials/ssao/gtaoImpl.fs
DEPENDS src/materials/ssao/ssctImpl.fs
APPEND
)
endif()
add_custom_command(
OUTPUT "${MATERIAL_DIR}/bilateralBlur.filamat"

View File

@@ -261,6 +261,7 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPUHandles.h
src/webgpu/WebGPUPipelineCreation.cpp
src/webgpu/WebGPUPipelineCreation.h
src/webgpu/WebGPUStrings.h
src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h
src/webgpu/WGPUProgram.cpp

View File

@@ -204,23 +204,15 @@ public:
}, new T(std::forward<T>(functor))
};
}
// --------------------------------------------------------------------------------------------
/**
* Computes the size in bytes needed to fit an image of given dimensions and format
* Computes the size in bytes for a pixel of given dimensions and format
*
* @param format Format of the image pixels
* @param type Type of the image pixels
* @param stride Stride of a row in pixels
* @param height Height of the image in rows
* @param alignment Alignment in bytes of pixel rows
* @return The buffer size needed to fit this image in bytes
* @return The size of the specified pixel in bytes
*/
static constexpr size_t computeDataSize(PixelDataFormat format, PixelDataType type,
size_t stride, size_t height, size_t alignment) noexcept {
assert_invariant(alignment);
static constexpr size_t computePixelSize(PixelDataFormat format, PixelDataType type) noexcept {
if (type == PixelDataType::COMPRESSED) {
return 0;
}
@@ -242,7 +234,7 @@ public:
case PixelDataFormat::RGB_INTEGER:
n = 3;
break;
case PixelDataFormat::UNUSED: // shouldn't happen (used to be rgbm)
case PixelDataFormat::UNUSED:// shouldn't happen (used to be rgbm)
case PixelDataFormat::RGBA:
case PixelDataFormat::RGBA_INTEGER:
n = 4;
@@ -251,7 +243,7 @@ public:
size_t bpp = n;
switch (type) {
case PixelDataType::COMPRESSED: // Impossible -- to squash the IDE warnings
case PixelDataType::COMPRESSED:// Impossible -- to squash the IDE warnings
case PixelDataType::UBYTE:
case PixelDataType::BYTE:
// nothing to do
@@ -282,16 +274,35 @@ public:
bpp = 2;
break;
}
return bpp;
}
// --------------------------------------------------------------------------------------------
/**
* Computes the size in bytes needed to fit an image of given dimensions and format
*
* @param format Format of the image pixels
* @param type Type of the image pixels
* @param stride Stride of a row in pixels
* @param height Height of the image in rows
* @param alignment Alignment in bytes of pixel rows
* @return The buffer size needed to fit this image in bytes
*/
static constexpr size_t computeDataSize(PixelDataFormat format, PixelDataType type,
size_t stride, size_t height, size_t alignment) noexcept {
assert_invariant(alignment);
size_t bpp = computePixelSize(format, type);
size_t const bpr = bpp * stride;
size_t const bprAligned = (bpr + (alignment - 1)) & (~alignment + 1);
return bprAligned * height;
}
//! left coordinate in pixels
uint32_t left = 0;
uint32_t left = 0;
//! top coordinate in pixels
uint32_t top = 0;
uint32_t top = 0;
union {
struct {
//! stride in pixels

View File

@@ -199,7 +199,7 @@ public:
*/
virtual bool makeCurrent(ContextType type,
SwapChain* UTILS_NONNULL drawSwapChain,
SwapChain* UTILS_NONNULL readSwapChain) noexcept = 0;
SwapChain* UTILS_NONNULL readSwapChain) = 0;
/**
* Called by the driver to make the OpenGL context active on the calling thread and bind
@@ -219,7 +219,7 @@ public:
SwapChain* UTILS_NONNULL drawSwapChain,
SwapChain* UTILS_NONNULL readSwapChain,
utils::Invocable<void()> preContextChange,
utils::Invocable<void(size_t index)> postContextChange) noexcept;
utils::Invocable<void(size_t index)> postContextChange);
/**
* Called by the backend just before calling commit()

View File

@@ -58,7 +58,7 @@ protected:
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override;
void commit(SwapChain* swapChain) noexcept override;
ExternalTexture* createExternalImageTexture() noexcept override;
void destroyExternalImageTexture(ExternalTexture* texture) noexcept override;

View File

@@ -55,7 +55,7 @@ public:
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override;
void commit(SwapChain* swapChain) noexcept override;
ExternalTexture* createExternalImageTexture() noexcept override;

View File

@@ -109,11 +109,11 @@ protected:
bool makeCurrent(ContextType type,
SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept override;
SwapChain* readSwapChain) override;
void makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain,
utils::Invocable<void()> preContextChange,
utils::Invocable<void(size_t index)> postContextChange) noexcept override;
utils::Invocable<void(size_t index)> postContextChange) override;
void commit(SwapChain* swapChain) noexcept override;
@@ -148,12 +148,12 @@ protected:
EGLContext getContextForType(ContextType type) const noexcept;
// makes the draw and read surface current without changing the current context
EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept {
EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) {
return egl.makeCurrent(drawSurface, readSurface);
}
// makes context current and set draw and read surfaces to EGL_NO_SURFACE
EGLBoolean makeCurrent(EGLContext context) noexcept {
EGLBoolean makeCurrent(EGLContext context) {
return egl.makeCurrent(context, mEGLDummySurface, mEGLDummySurface);
}
@@ -211,9 +211,9 @@ private:
public:
explicit EGL(EGLDisplay& dpy) : mEGLDisplay(dpy) {}
EGLBoolean makeCurrent(EGLContext context,
EGLSurface drawSurface, EGLSurface readSurface) noexcept;
EGLSurface drawSurface, EGLSurface readSurface);
EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept {
EGLBoolean makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) {
return makeCurrent(mCurrentContext, drawSurface, readSurface);
}
} egl{ mEGLDisplay };

View File

@@ -127,7 +127,7 @@ protected:
protected:
bool makeCurrent(ContextType type,
SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept override;
SwapChain* readSwapChain) override;
private:
struct InitializeJvmForPerformanceManagerIfNeeded {

View File

@@ -51,7 +51,7 @@ protected:
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override;
void commit(SwapChain* swapChain) noexcept override;
private:

View File

@@ -56,7 +56,7 @@ protected:
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept override;
SwapChain* readSwapChain) override;
void commit(SwapChain* swapChain) noexcept override;
private:

View File

@@ -53,7 +53,7 @@ protected:
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override;
void commit(SwapChain* swapChain) noexcept override;
protected:

View File

@@ -46,7 +46,7 @@ protected:
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept override;
bool makeCurrent(ContextType type, SwapChain* drawSwapChain, SwapChain* readSwapChain) override;
void commit(SwapChain* swapChain) noexcept override;
};

View File

@@ -22,6 +22,7 @@
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <vector>
namespace filament::backend {
@@ -56,6 +57,10 @@ protected:
const Platform::DriverConfig& driverConfig) noexcept override;
private:
// returns adapter request option variations applicable for the particular
// platform
[[nodiscard]] static std::vector<wgpu::RequestAdapterOptions> getAdapterOptions();
// we may consider having the driver own this in the future
wgpu::Instance mInstance;
};

View File

@@ -72,7 +72,7 @@ public:
Range getBuffer() noexcept;
private:
void* alloc(size_t size) noexcept;
void* alloc(size_t size);
void dealloc() noexcept;
// pointer to the beginning of the circular buffer (constant)

View File

@@ -76,7 +76,7 @@ public:
// all commands buffers (Slices) written to this point are returned by waitForCommand(). This
// call blocks until the CircularBuffer has at least mRequiredSize bytes available.
void flush() noexcept;
void flush();
// returns from waitForCommands() immediately.
void requestExit();

View File

@@ -27,13 +27,13 @@ namespace filament {
class VirtualMachineEnv {
public:
// must be called before VirtualMachineEnv::get() from a thread that is attached to the JavaVM
static jint JNI_OnLoad(JavaVM* vm) noexcept;
static jint JNI_OnLoad(JavaVM* vm);
// must be called on backend thread
static VirtualMachineEnv& get() noexcept;
// can be called from any thread that already has a JniEnv
static JNIEnv* getThreadEnvironment() noexcept;
static JNIEnv* getThreadEnvironment();
// must be called from the backend thread
JNIEnv* getEnvironment() noexcept {
@@ -49,7 +49,7 @@ public:
private:
explicit VirtualMachineEnv(JavaVM* vm) noexcept;
~VirtualMachineEnv() noexcept;
JNIEnv* getEnvironmentSlow() noexcept;
JNIEnv* getEnvironmentSlow();
static utils::Mutex sLock;
static JavaVM* sVirtualMachine;

View File

@@ -65,7 +65,7 @@ CircularBuffer::~CircularBuffer() noexcept {
// to each others and a special case in circularize()
UTILS_NOINLINE
void* CircularBuffer::alloc(size_t size) noexcept {
void* CircularBuffer::alloc(size_t size) {
#if HAS_MMAP
void* data = nullptr;
void* vaddr = MAP_FAILED;

View File

@@ -53,18 +53,18 @@ CommandBufferQueue::~CommandBufferQueue() {
}
void CommandBufferQueue::requestExit() {
std::lock_guard<utils::Mutex> const lock(mLock);
std::lock_guard const lock(mLock);
mExitRequested = EXIT_REQUESTED;
mCondition.notify_one();
}
bool CommandBufferQueue::isPaused() const noexcept {
std::lock_guard<utils::Mutex> const lock(mLock);
std::lock_guard const lock(mLock);
return mPaused;
}
void CommandBufferQueue::setPaused(bool paused) {
std::lock_guard<utils::Mutex> const lock(mLock);
std::lock_guard const lock(mLock);
if (paused) {
mPaused = true;
} else {
@@ -74,12 +74,12 @@ void CommandBufferQueue::setPaused(bool paused) {
}
bool CommandBufferQueue::isExitRequested() const {
std::lock_guard<utils::Mutex> const lock(mLock);
return (bool)mExitRequested;
std::lock_guard const lock(mLock);
return bool(mExitRequested);
}
void CommandBufferQueue::flush() noexcept {
void CommandBufferQueue::flush() {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
CircularBuffer& circularBuffer = mCircularBuffer;
@@ -103,7 +103,7 @@ void CommandBufferQueue::flush() noexcept {
static_cast<char const*>(begin), static_cast<char const*>(end));
std::unique_lock<utils::Mutex> lock(mLock);
std::unique_lock lock(mLock);
// circular buffer is too small, we corrupted the stream
FILAMENT_CHECK_POSTCONDITION(used <= mFreeSpace) <<
@@ -147,7 +147,7 @@ std::vector<CommandBufferQueue::Range> CommandBufferQueue::waitForCommands() con
if (!UTILS_HAS_THREADING) {
return std::move(mCommandBuffersToExecute);
}
std::unique_lock<utils::Mutex> lock(mLock);
std::unique_lock lock(mLock);
while ((mCommandBuffersToExecute.empty() || mPaused) && !mExitRequested) {
mCondition.wait(lock);
}
@@ -157,7 +157,7 @@ std::vector<CommandBufferQueue::Range> CommandBufferQueue::waitForCommands() con
void CommandBufferQueue::releaseBuffer(CommandBufferQueue::Range const& buffer) {
size_t const used = std::distance(
static_cast<char const*>(buffer.begin), static_cast<char const*>(buffer.end));
std::lock_guard<utils::Mutex> const lock(mLock);
std::lock_guard const lock(mLock);
mFreeSpace += used;
mCondition.notify_one();
}

View File

@@ -50,7 +50,7 @@ JavaVM* VirtualMachineEnv::getVirtualMachine() {
*/
UTILS_PUBLIC
UTILS_NOINLINE
jint VirtualMachineEnv::JNI_OnLoad(JavaVM* vm) noexcept {
jint VirtualMachineEnv::JNI_OnLoad(JavaVM* vm) {
std::lock_guard const lock(sLock);
if (sVirtualMachine) {
// It doesn't make sense for JNI_OnLoad() to be called more than once
@@ -77,7 +77,7 @@ VirtualMachineEnv& VirtualMachineEnv::get() noexcept {
}
UTILS_NOINLINE
JNIEnv* VirtualMachineEnv::getThreadEnvironment() noexcept {
JNIEnv* VirtualMachineEnv::getThreadEnvironment() {
JavaVM* const vm = getVirtualMachine();
JNIEnv* env = nullptr;
jint const result = vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
@@ -101,7 +101,7 @@ VirtualMachineEnv::~VirtualMachineEnv() noexcept {
}
UTILS_NOINLINE
JNIEnv* VirtualMachineEnv::getEnvironmentSlow() noexcept {
JNIEnv* VirtualMachineEnv::getEnvironmentSlow() {
FILAMENT_CHECK_PRECONDITION(mVirtualMachine)
<< "JNI_OnLoad() has not been called";

View File

@@ -218,11 +218,11 @@ void MetalBlitter::blitDepthPlane(id<MTLCommandBuffer> cmdBuffer, const BlitArgs
}
[encoder setFragmentTexture:srcTextureColor atIndex:0];
SamplerMinFilter filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST;
SamplerMinFilter filterMin = SamplerMinFilter::LINEAR;
if (args.filter == SamplerMagFilter::NEAREST) {
filterMin = SamplerMinFilter::NEAREST_MIPMAP_NEAREST;
filterMin = SamplerMinFilter::LINEAR;
} else if (args.filter == SamplerMagFilter::LINEAR) {
filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
filterMin = SamplerMinFilter::LINEAR;
}
SamplerState const s {

View File

@@ -1533,7 +1533,7 @@ void MetalDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
id<MTLTexture> readPixelsTexture = [mContext->device newTextureWithDescriptor:textureDescriptor];
MetalBlitter::BlitArgs args{};
args.filter = SamplerMagFilter::NEAREST;
args.filter = SamplerMagFilter::LINEAR;
args.source.level = miplevel;
args.source.region = MTLRegionMake2D(0, 0, srcTexture.width >> miplevel, srcTexture.height >> miplevel);
args.source.texture = srcTexture;
@@ -1650,7 +1650,7 @@ void MetalDriver::blit(
<< "Metal does not support blitting to/from non-2D textures.";
MetalBlitter::BlitArgs args{};
args.filter = SamplerMagFilter::NEAREST;
args.filter = SamplerMagFilter::LINEAR;
args.source.region = MTLRegionMake2D(
(NSUInteger)srcOrigin.x,
std::max(srcTexture->height - (int64_t)srcOrigin.y - size.y, (int64_t)0),

View File

@@ -348,7 +348,7 @@ constexpr inline MTLSamplerMinMagFilter getFilter(SamplerMinFilter filter) noexc
case SamplerMinFilter::NEAREST:
case SamplerMinFilter::NEAREST_MIPMAP_NEAREST:
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR:
return MTLSamplerMinMagFilterNearest;
return MTLSamplerMinMagFilterLinear;
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST:
case SamplerMinFilter::LINEAR:
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
@@ -359,7 +359,7 @@ constexpr inline MTLSamplerMinMagFilter getFilter(SamplerMinFilter filter) noexc
constexpr inline MTLSamplerMinMagFilter getFilter(SamplerMagFilter filter) noexcept {
switch (filter) {
case SamplerMagFilter::NEAREST:
return MTLSamplerMinMagFilterNearest;
return MTLSamplerMinMagFilterLinear;
case SamplerMagFilter::LINEAR:
return MTLSamplerMinMagFilterLinear;
}
@@ -369,10 +369,10 @@ constexpr inline MTLSamplerMipFilter getMipFilter(SamplerMinFilter filter) noexc
switch (filter) {
case SamplerMinFilter::NEAREST:
case SamplerMinFilter::LINEAR:
return MTLSamplerMipFilterNotMipmapped;
return MTLSamplerMipFilterLinear;
case SamplerMinFilter::NEAREST_MIPMAP_NEAREST:
case SamplerMinFilter::LINEAR_MIPMAP_NEAREST:
return MTLSamplerMipFilterNearest;
return MTLSamplerMipFilterLinear;
case SamplerMinFilter::NEAREST_MIPMAP_LINEAR:
case SamplerMinFilter::LINEAR_MIPMAP_LINEAR:
return MTLSamplerMipFilterLinear;

View File

@@ -943,7 +943,7 @@ void MetalTexture::loadWithBlit(uint32_t level, uint32_t slice, MTLRegion region
}
MetalBlitter::BlitArgs args{};
args.filter = SamplerMagFilter::NEAREST;
args.filter = SamplerMagFilter::LINEAR;
args.source.level = 0;
args.source.slice = 0;
args.source.region = sourceRegion;

View File

@@ -932,7 +932,12 @@ void OpenGLDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
if (depth <= 1) {
// We forcibly change the target to 2D-multisample only for flat texture.
// A depth value greater than 1 may indicate multiview usage, which requires
// GL_TEXTURE_2D_ARRAY. Also 2D MSAA won't work with non-flat texture anyway.
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
}
} else {
// Turn off multi-sampling for that texture. It's just not supported.
}
@@ -1208,7 +1213,12 @@ void OpenGLDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
if (gl.features.multisample_texture) {
// multi-sample texture on GL 3.2 / GLES 3.1 and above
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
if (depth <= 1) {
// We forcibly change the target to 2D-multisample only for flat texture.
// A depth value greater than 1 may indicate multiview usage, which requires
// GL_TEXTURE_2D_ARRAY. Also 2D MSAA won't work with non-flat texture anyway.
t->gl.target = GL_TEXTURE_2D_MULTISAMPLE;
}
} else {
// Turn off multi-sampling for that texture. It's just not supported.
}
@@ -1453,8 +1463,15 @@ void OpenGLDriver::framebufferTexture(TargetBufferInfo const& binfo,
#if !defined(__EMSCRIPTEN__) && !defined(FILAMENT_IOS)
if (layerCount > 1) {
// if layerCount > 1, it means we use the multiview extension.
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, attachment,
t->gl.id, 0, binfo.layer, layerCount);
if (rt->gl.samples > 1) {
// For MSAA
glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, attachment,
t->gl.id, 0, rt->gl.samples, binfo.layer, layerCount);
}
else {
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, attachment, t->gl.id, 0,
binfo.layer, layerCount);
}
} else
#endif // !defined(__EMSCRIPTEN__) && !defined(FILAMENT_IOS)
{

View File

@@ -64,7 +64,7 @@ utils::CString OpenGLPlatform::getRendererString(Driver const* driver) {
}
void OpenGLPlatform::makeCurrent(SwapChain* drawSwapChain, SwapChain* readSwapChain,
utils::Invocable<void()>, utils::Invocable<void(size_t)>) noexcept {
utils::Invocable<void()>, utils::Invocable<void(size_t)>) {
makeCurrent(getCurrentContextType(), drawSwapChain, readSwapChain);
}

View File

@@ -70,6 +70,9 @@ PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR;
#ifdef GL_OVR_multiview
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR;
#endif
#ifdef GL_OVR_multiview_multisampled_render_to_texture
PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC glFramebufferTextureMultisampleMultiviewOVR;
#endif
#if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
// On Android, If we want to support a build system less than ANDROID_API 21, we need to
@@ -123,6 +126,9 @@ void importGLESExtensionsEntryPoints() {
#ifdef GL_OVR_multiview
getProcAddress(glFramebufferTextureMultiviewOVR, "glFramebufferTextureMultiviewOVR");
#endif
#ifdef GL_OVR_multiview_multisampled_render_to_texture
getProcAddress(glFramebufferTextureMultisampleMultiviewOVR, "glFramebufferTextureMultisampleMultiviewOVR");
#endif
#if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
getProcAddress(glDispatchCompute, "glDispatchCompute");
#endif

View File

@@ -154,6 +154,9 @@ extern PFNGLMAXSHADERCOMPILERTHREADSKHRPROC glMaxShaderCompilerThreadsKHR;
#ifdef GL_OVR_multiview
extern PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC glFramebufferTextureMultiviewOVR;
#endif
#ifdef GL_OVR_multiview_multisampled_render_to_texture
extern PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC glFramebufferTextureMultisampleMultiviewOVR;
#endif
#if defined(__ANDROID__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
extern PFNGLDISPATCHCOMPUTEPROC glDispatchCompute;
#endif

View File

@@ -255,7 +255,7 @@ void PlatformCocoaGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept
}
bool PlatformCocoaGL::makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept {
SwapChain* readSwapChain) {
ASSERT_PRECONDITION_NON_FATAL(drawSwapChain == readSwapChain,
"ContextManagerCocoa does not support using distinct draw/read swap chains.");
CocoaGLSwapChain* swapChain = (CocoaGLSwapChain*)drawSwapChain;

View File

@@ -155,7 +155,7 @@ uint32_t PlatformCocoaTouchGL::getDefaultFramebufferObject() noexcept {
}
bool PlatformCocoaTouchGL::makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept {
SwapChain* readSwapChain) {
ASSERT_PRECONDITION_NON_FATAL(drawSwapChain == readSwapChain,
"PlatformCocoaTouchGL does not support using distinct draw/read swap chains.");
CAEAGLLayer* const glLayer = (__bridge CAEAGLLayer*) drawSwapChain;

View File

@@ -577,18 +577,18 @@ OpenGLPlatform::ContextType PlatformEGL::getCurrentContextType() const noexcept
}
bool PlatformEGL::makeCurrent(ContextType type,
SwapChain* drawSwapChain, SwapChain* readSwapChain) noexcept {
SwapChain* drawSwapChain, SwapChain* readSwapChain) {
SwapChainEGL const* const dsc = static_cast<SwapChainEGL const*>(drawSwapChain);
SwapChainEGL const* const rsc = static_cast<SwapChainEGL const*>(readSwapChain);
EGLContext context = getContextForType(type);
EGLBoolean const success = egl.makeCurrent(context, dsc->sur, rsc->sur);
return success == EGL_TRUE ? true : false;
return success == EGL_TRUE;
}
void PlatformEGL::makeCurrent(SwapChain* drawSwapChain,
SwapChain* readSwapChain,
Invocable<void()> preContextChange,
Invocable<void(size_t index)> postContextChange) noexcept {
Invocable<void(size_t index)> postContextChange) {
assert_invariant(drawSwapChain);
assert_invariant(readSwapChain);
@@ -796,7 +796,7 @@ void PlatformEGL::Config::erase(EGLint name) noexcept {
// ------------------------------------------------------------------------------------------------
EGLBoolean PlatformEGL::EGL::makeCurrent(EGLContext context, EGLSurface drawSurface,
EGLSurface readSurface) noexcept {
EGLSurface readSurface) {
if (UTILS_UNLIKELY((
mCurrentContext != context ||
drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) {

View File

@@ -135,7 +135,7 @@ static constexpr const std::string_view kNativeWindowInvalidMsg =
bool PlatformEGLAndroid::makeCurrent(ContextType type,
SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept {
SwapChain* readSwapChain) {
// fast & safe path
if (UTILS_LIKELY(!mAssertNativeWindowIsValid)) {

View File

@@ -266,7 +266,7 @@ void PlatformGLX::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
}
bool PlatformGLX::makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept {
SwapChain* readSwapChain) {
g_glx.setCurrentContext(mGLXDisplay,
(GLXDrawable)drawSwapChain, (GLXDrawable)readSwapChain, mGLXContext);
return true;

View File

@@ -169,7 +169,7 @@ void PlatformOSMesa::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
}
bool PlatformOSMesa::makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept {
SwapChain* readSwapChain) {
OSMesaAPI* api = (OSMesaAPI*) mOsMesaApi;
OSMesaSwapchain* impl = (OSMesaSwapchain*) drawSwapChain;

View File

@@ -262,7 +262,7 @@ void PlatformWGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
}
bool PlatformWGL::makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept {
SwapChain* readSwapChain) {
ASSERT_PRECONDITION_NON_FATAL(drawSwapChain == readSwapChain,
"PlatformWGL does not support distinct draw/read swap chains.");

View File

@@ -47,7 +47,7 @@ void PlatformWebGL::destroySwapChain(Platform::SwapChain* swapChain) noexcept {
}
bool PlatformWebGL::makeCurrent(ContextType type, SwapChain* drawSwapChain,
SwapChain* readSwapChain) noexcept {
SwapChain* readSwapChain) {
return true;
}

View File

@@ -21,6 +21,7 @@
#include <utils/bitset.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Panic.h>
#include <bluevk/BlueVK.h>

View File

@@ -17,6 +17,7 @@
#include "WebGPUHandles.h"
#include "WebGPUConstants.h"
#include "WebGPUStrings.h"
#include "DriverBase.h"
#include <backend/DriverEnums.h>
@@ -28,39 +29,116 @@
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <cstdint>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
#include <unordered_map>
#include <variant>
namespace filament::backend {
namespace {
[[nodiscard]] constexpr std::string_view toString(ShaderStage stage) {
switch (stage) {
case ShaderStage::VERTEX:
return "vertex";
case ShaderStage::FRAGMENT:
return "fragment";
case ShaderStage::COMPUTE:
return "compute";
[[nodiscard]] std::string replaceSpecConstants(std::string_view shaderLabel,
std::string_view shaderSource,
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>> const&
specConstants) {
assert_invariant(!specConstants.empty());
constexpr std::string_view specConstantPrefix = "FILAMENT_SPEC_CONST_";
constexpr size_t specConstantPrefixSize = specConstantPrefix.size();
const char* const sourceData = shaderSource.data();
std::stringstream processedShaderSourceStr{};
size_t pos = 0;
while (pos < shaderSource.size()) {
const size_t posOfNextSpecConstant = shaderSource.find(specConstantPrefix, pos);
if (posOfNextSpecConstant == std::string::npos) {
// no more spec constants, so just stream the rest of the source code string
processedShaderSourceStr
<< std::string_view(sourceData + pos, shaderSource.size() - pos);
break;
}
const size_t posOfId = posOfNextSpecConstant + specConstantPrefixSize;
const size_t posAfterId = shaderSource.find('_', posOfId);
FILAMENT_CHECK_POSTCONDITION(posAfterId != std::string::npos)
<< "malformed " << shaderLabel << ". Found spec constant prefix '"
<< specConstantPrefix << "' without an id or '_' after it.";
const std::string_view idStr =
std::string_view(sourceData + posOfId, posAfterId - posOfId);
const size_t posEndOfStatement = shaderSource.find(';', posAfterId);
FILAMENT_CHECK_POSTCONDITION(posEndOfStatement != std::string::npos)
<< "malformed " << shaderLabel << ". Found spec constant assignment with id "
<< idStr << " without a terminating ';' character?";
const std::string_view statementSegment =
std::string_view(sourceData + posAfterId, posEndOfStatement - posAfterId);
size_t posOfEqual = statementSegment.find('=');
if (posOfEqual == std::string::npos) {
// not an assignment statement, so stream to the end of the statement and continue...
processedShaderSourceStr
<< std::string_view(sourceData + pos, posEndOfStatement + 1 - pos);
pos = posEndOfStatement + 1;
continue;
}
posOfEqual += posAfterId; // position in original source, not just the segment
int id = 0;
try {
id = std::stoi(idStr.data());
} catch (const std::invalid_argument& e) {
PANIC_POSTCONDITION("Invalid spec constant id '%s' in %s (not a valid integer?): %s",
idStr.data(), shaderLabel.data(), e.what());
} catch (const std::out_of_range& e) {
PANIC_POSTCONDITION(
"Invalid spec constant id '%s' in %s (not an integer? out of range?): %s",
idStr.data(), shaderLabel.data(), e.what());
}
const auto newValueItr = specConstants.find(static_cast<uint32_t>(id));
if (newValueItr == specConstants.end()) {
// not going to override the constant (stream to the end of the statement)...
processedShaderSourceStr
<< std::string_view(sourceData + pos, posEndOfStatement + 1 - pos);
pos = posEndOfStatement + 1;
continue;
}
// need to override the constant...
const std::variant<int32_t, float, bool> newValue = newValueItr->second;
// stream up to the equal sign...
processedShaderSourceStr << std::string_view(sourceData + pos, posOfEqual + 1 - pos);
// stream the new value...
if (auto* v = std::get_if<int32_t>(&newValue)) {
processedShaderSourceStr << " " << *v << "i";
} else if (auto* f = std::get_if<float>(&newValue)) {
processedShaderSourceStr << " " << *f << "f";
} else if (auto* b = std::get_if<bool>(&newValue)) {
processedShaderSourceStr << " " << ((*b) ? "true" : "false");
}
processedShaderSourceStr << ";";
// and skip to after the end of the statement in the original source...
pos = posEndOfStatement + 1;
}
return processedShaderSourceStr.str();
}
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device& device, const char* programName,
std::array<utils::FixedCapacityVector<uint8_t>, Program::SHADER_TYPE_COUNT> const&
shaderSource,
ShaderStage stage) {
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device& device, Program& program,
ShaderStage stage,
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>> const& specConstants) {
const char* const programName = program.getName().c_str_safe();
std::array<utils::FixedCapacityVector<uint8_t>, Program::SHADER_TYPE_COUNT> const&
shaderSource = program.getShadersSource();
utils::FixedCapacityVector<uint8_t> const& sourceBytes =
shaderSource[static_cast<size_t>(stage)];
if (sourceBytes.empty()) {
return nullptr;// nothing to compile, the shader was not provided
}
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
wgslDescriptor.code = wgpu::StringView(reinterpret_cast<const char*>(sourceBytes.data()));
std::stringstream labelStream;
labelStream << programName << " " << toString(stage) << " shader";
labelStream << programName << " " << filamentShaderStageToString(stage) << " shader";
auto label = labelStream.str();
const std::string processedShaderSource =
specConstants.empty()
? reinterpret_cast<const char*>(sourceBytes.data())
: replaceSpecConstants(label, reinterpret_cast<const char*>(sourceBytes.data()),
specConstants);
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
wgslDescriptor.code = wgpu::StringView(processedShaderSource);
wgpu::ShaderModuleDescriptor descriptor{
.nextInChain = &wgslDescriptor,
.label = label.data()
@@ -119,85 +197,33 @@ namespace {
<< ":\n"
<< errorStream.str();
}
#if FWGPU_ENABLED(FWGPU_DEBUG_VALIDATION)
FWGPU_LOGD << descriptor.label << " compiled successfully"
<< utils::io::endl;
#endif
}),
UINT16_MAX);
SHADER_COMPILATION_TIMEOUT_NANOSECONDS);
return module;
}
// This is a 1 to 1 mapping of the ReservedSpecializationConstants enum in EngineEnums.h
// The _hack is a workaround until https://issues.chromium.org/issues/42250586 is resolved
// This workaround is the same one being used on the generateSpecializationConstant() function
wgpu::StringView getSpecConstantStringId(uint32_t id) {
switch (id) {
case 0:
return "0";// BACKEND_FEATURE_LEVEL_hack
case 1:
return "1";// CONFIG_MAX_INSTANCES_hack
case 2:
return "2";// ONFIG_STATIC_TEXTURE_TARGET_WORKAROUND_hack
case 3:
return "3";// CONFIG_SRGB_SWAPCHAIN_EMULATION_hack
case 4:
return "4";// CONFIG_FROXEL_BUFFER_HEIGHT_hack
case 5:
return "5";// CONFIG_POWER_VR_SHADER_WORKAROUNDS_hack
case 6:
return "6";// CONFIG_DEBUG_DIRECTIONAL_SHADOWMAP_hack
case 7:
return "7";// CONFIG_DEBUG_FROXEL_VISUALIZATION_hack
case 8:
return "8";// CONFIG_STEREO_EYE_COUNT_hack
case 9:
return "9";// CONFIG_SH_BANDS_COUNT_hack
case 10:
return "10";// CONFIG_SHADOW_SAMPLING_METHOD_hack
default:
PANIC_POSTCONDITION("Unknown/unhandled spec constant key/id: %d", id);
void toMap(utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants,
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>>& constantById) {
constantById.reserve(specConstants.size());
for (auto const& specConstant: specConstants) {
constantById.emplace(specConstant.id, specConstant.value);
}
}
std::vector<wgpu::ConstantEntry> convertConstants(
utils::FixedCapacityVector<filament::backend::Program::SpecializationConstant> const&
constantsInfo) {
std::vector<wgpu::ConstantEntry> constants;
constants.reserve(constantsInfo.size());
for (filament::backend::Program::SpecializationConstant const& constant: constantsInfo) {
// CONFIG_MAX_INSTANCES (1) and CONFIG_FROXEL_BUFFER_HEIGHT (4) will not be present
// as constant overrides in the generated WGSL, because WGSL doesn't support specialization
// constants as an array length
// More information at https://github.com/gpuweb/gpuweb/issues/572#issuecomment-649760005
// CONFIG_SRGB_SWAPCHAIN_EMULATION (3) is being skipped all together since it's only
// included for the case of mFeatureLevel == FeatureLevel::FEATURE_LEVEL_0, which should
// not be possible for WebGPU
if (constant.id == 1 || constant.id == 3 || constant.id == 4) {
continue;
}
double value = 0.0;
if (auto* v = std::get_if<int32_t>(&constant.value)) {
value = static_cast<double>(*v);
} else if (auto* f = std::get_if<float>(&constant.value)) {
value = static_cast<double>(*f);
} else if (auto* b = std::get_if<bool>(&constant.value)) {
value = *b ? 0.0 : 1.0;
}
constants.push_back(
wgpu::ConstantEntry{ .key = getSpecConstantStringId(constant.id), .value = value });
}
return constants;
}
}// namespace
WGPUProgram::WGPUProgram(wgpu::Device& device, Program& program)
: HwProgram(program.getName()),
vertexShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
ShaderStage::VERTEX)),
fragmentShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
ShaderStage::FRAGMENT)),
computeShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
ShaderStage::COMPUTE)),
constants(convertConstants(program.getSpecializationConstants())) {}
: HwProgram(program.getName()) {
std::unordered_map<uint32_t, std::variant<int32_t, float, bool>> specConstants;
toMap(program.getSpecializationConstants(), specConstants);
vertexShaderModule = createShaderModule(device, program, ShaderStage::VERTEX, specConstants);
fragmentShaderModule =
createShaderModule(device, program, ShaderStage::FRAGMENT, specConstants);
computeShaderModule = createShaderModule(device, program, ShaderStage::COMPUTE, specConstants);
}
}// namespace filament::backend

View File

@@ -63,4 +63,13 @@
#define FWGPU_LOGI (utils::slog.i)
#endif
constexpr uint64_t REQUEST_ADAPTER_TIMEOUT_NANOSECONDS =
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
constexpr uint64_t REQUEST_DEVICE_TIMEOUT_NANOSECONDS =
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
constexpr uint64_t SHADER_COMPILATION_TIMEOUT_NANOSECONDS =
/* milliseconds */ 1000u * /* converted to ns */ 1000000u;
#endif// TNT_FILAMENT_BACKEND_WEBGPUCONSTANTS_H

View File

@@ -26,193 +26,24 @@
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/TargetBufferInfo.h>
#include <private/backend/BackendUtils.h>
#include <math/mat3.h>
#include <utils/CString.h>
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <sstream>
#include <string_view>
#include <utility>
#include <variant>
namespace filament::backend {
namespace {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printInstanceDetails(wgpu::Instance const& instance) {
wgpu::SupportedWGSLLanguageFeatures supportedWGSLLanguageFeatures{};
if (!instance.GetWGSLLanguageFeatures(&supportedWGSLLanguageFeatures)) {
FWGPU_LOGW << "Failed to get WebGPU instance supported WGSL language features"
<< utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU instance supported WGSL language features ("
<< supportedWGSLLanguageFeatures.featureCount << "):" << utils::io::endl;
if (supportedWGSLLanguageFeatures.featureCount > 0 &&
supportedWGSLLanguageFeatures.features != nullptr) {
std::for_each(supportedWGSLLanguageFeatures.features,
supportedWGSLLanguageFeatures.features +
supportedWGSLLanguageFeatures.featureCount,
[](wgpu::WGSLLanguageFeatureName const featureName) {
std::stringstream nameStream{};
nameStream << featureName;
FWGPU_LOGI << " " << nameStream.str() << utils::io::endl;
});
}
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printLimit(std::string_view name, const std::variant<uint32_t, uint64_t> value) {
FWGPU_LOGI << " " << name.data() << ": ";
bool undefined = true;
if (std::holds_alternative<uint32_t>(value)) {
if (std::get<uint32_t>(value) != WGPU_LIMIT_U32_UNDEFINED) {
undefined = false;
FWGPU_LOGI << std::get<uint32_t>(value);
}
} else if (std::holds_alternative<uint64_t>(value)) {
if (std::get<uint64_t>(value) != WGPU_LIMIT_U64_UNDEFINED) {
undefined = false;
FWGPU_LOGI << std::get<uint64_t>(value);
}
}
if (undefined) {
FWGPU_LOGI << "UNDEFINED";
}
FWGPU_LOGI << utils::io::endl;
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printLimits(wgpu::Limits const& limits) {
printLimit("maxTextureDimension1D", limits.maxTextureDimension1D);
printLimit("maxTextureDimension2D", limits.maxTextureDimension2D);
printLimit("maxTextureDimension3D", limits.maxTextureDimension3D);
printLimit("maxTextureArrayLayers", limits.maxTextureArrayLayers);
printLimit("maxBindGroups", limits.maxBindGroups);
printLimit("maxBindGroupsPlusVertexBuffers", limits.maxBindGroupsPlusVertexBuffers);
printLimit("maxBindingsPerBindGroup", limits.maxBindingsPerBindGroup);
printLimit("maxDynamicUniformBuffersPerPipelineLayout",
limits.maxDynamicUniformBuffersPerPipelineLayout);
printLimit("maxDynamicStorageBuffersPerPipelineLayout",
limits.maxDynamicStorageBuffersPerPipelineLayout);
printLimit("maxSampledTexturesPerShaderStage", limits.maxSampledTexturesPerShaderStage);
printLimit("maxSamplersPerShaderStage", limits.maxSamplersPerShaderStage);
printLimit("maxStorageBuffersPerShaderStage", limits.maxStorageBuffersPerShaderStage);
printLimit("maxStorageTexturesPerShaderStage", limits.maxStorageTexturesPerShaderStage);
printLimit("maxUniformBuffersPerShaderStage", limits.maxUniformBuffersPerShaderStage);
printLimit("maxUniformBufferBindingSize", limits.maxUniformBufferBindingSize);
printLimit("maxStorageBufferBindingSize", limits.maxStorageBufferBindingSize);
printLimit("minUniformBufferOffsetAlignment", limits.minUniformBufferOffsetAlignment);
printLimit("minStorageBufferOffsetAlignment", limits.minStorageBufferOffsetAlignment);
printLimit("maxVertexBuffers", limits.maxVertexBuffers);
printLimit("maxBufferSize", limits.maxBufferSize);
printLimit("maxVertexAttributes", limits.maxVertexAttributes);
printLimit("maxVertexBufferArrayStride", limits.maxVertexBufferArrayStride);
printLimit("maxInterStageShaderVariables", limits.maxInterStageShaderVariables);
printLimit("maxColorAttachments", limits.maxColorAttachments);
printLimit("maxColorAttachmentBytesPerSample", limits.maxColorAttachmentBytesPerSample);
printLimit("maxComputeWorkgroupStorageSize", limits.maxComputeWorkgroupStorageSize);
printLimit("maxComputeInvocationsPerWorkgroup", limits.maxComputeInvocationsPerWorkgroup);
printLimit("maxComputeWorkgroupSizeX", limits.maxComputeWorkgroupSizeX);
printLimit("maxComputeWorkgroupSizeY", limits.maxComputeWorkgroupSizeY);
printLimit("maxComputeWorkgroupSizeZ", limits.maxComputeWorkgroupSizeZ);
printLimit("maxComputeWorkgroupsPerDimension", limits.maxComputeWorkgroupsPerDimension);
printLimit("maxStorageBuffersInVertexStage", limits.maxStorageBuffersInVertexStage);
printLimit("maxStorageTexturesInVertexStage", limits.maxStorageTexturesInVertexStage);
printLimit("maxStorageBuffersInFragmentStage", limits.maxStorageBuffersInFragmentStage);
printLimit("maxStorageTexturesInFragmentStage", limits.maxStorageTexturesInFragmentStage);
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printAdapterDetails(wgpu::Adapter const& adapter) {
wgpu::DawnAdapterPropertiesPowerPreference powerPreferenceProperties{};
wgpu::AdapterInfo adapterInfo{};
adapterInfo.nextInChain = &powerPreferenceProperties;
if (!adapter.GetInfo(&adapterInfo)) {
FWGPU_LOGW << "Failed to get WebGPU adapter info" << utils::io::endl;
} else {
std::stringstream backendTypeStream{};
backendTypeStream << adapterInfo.backendType;
std::stringstream adapterTypeStream{};
adapterTypeStream << adapterInfo.adapterType;
std::stringstream powerPreferenceStream{};
powerPreferenceStream << powerPreferenceProperties.powerPreference;
FWGPU_LOGI << "WebGPU adapter info:" << utils::io::endl;
FWGPU_LOGI << " vendor: " << adapterInfo.vendor.data << utils::io::endl;
FWGPU_LOGI << " architecture: " << adapterInfo.architecture.data << utils::io::endl;
FWGPU_LOGI << " device: " << adapterInfo.device.data << utils::io::endl;
FWGPU_LOGI << " description: " << adapterInfo.description.data << utils::io::endl;
FWGPU_LOGI << " backend type: " << backendTypeStream.str().data() << utils::io::endl;
FWGPU_LOGI << " adapter type: " << adapterTypeStream.str().data() << utils::io::endl;
FWGPU_LOGI << " device ID: " << adapterInfo.deviceID << utils::io::endl;
FWGPU_LOGI << " vendor ID: " << adapterInfo.vendorID << utils::io::endl;
FWGPU_LOGI << " subgroup min size: " << adapterInfo.subgroupMinSize << utils::io::endl;
FWGPU_LOGI << " subgroup max size: " << adapterInfo.subgroupMaxSize << utils::io::endl;
FWGPU_LOGI << " power preference: " << powerPreferenceStream.str() << utils::io::endl;
}
wgpu::SupportedFeatures supportedFeatures{};
adapter.GetFeatures(&supportedFeatures);
FWGPU_LOGI << "WebGPU adapter supported features (" << supportedFeatures.featureCount
<< "):" << utils::io::endl;
if (supportedFeatures.featureCount > 0 && supportedFeatures.features != nullptr) {
std::for_each(supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount,
[](wgpu::FeatureName const name) {
std::stringstream nameStream{};
nameStream << name;
FWGPU_LOGI << " " << nameStream.str().data() << utils::io::endl;
});
}
wgpu::Limits supportedLimits{};
if (!adapter.GetLimits(&supportedLimits)) {
FWGPU_LOGW << "Failed to get WebGPU adapter supported limits" << utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU adapter supported limits:" << utils::io::endl;
printLimits(supportedLimits);
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printDeviceDetails(wgpu::Device const& device) {
wgpu::SupportedFeatures supportedFeatures{};
device.GetFeatures(&supportedFeatures);
FWGPU_LOGI << "WebGPU device supported features (" << supportedFeatures.featureCount
<< "):" << utils::io::endl;
if (supportedFeatures.featureCount > 0 && supportedFeatures.features != nullptr) {
std::for_each(supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount,
[](wgpu::FeatureName const name) {
std::stringstream nameStream{};
nameStream << name;
FWGPU_LOGI << " " << nameStream.str().data() << utils::io::endl;
});
}
wgpu::Limits supportedLimits{};
if (!device.GetLimits(&supportedLimits)) {
FWGPU_LOGW << "Failed to get WebGPU supported device limits" << utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU device supported limits:" << utils::io::endl;
printLimits(supportedLimits);
}
}
#endif
}// namespace
Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept {
constexpr size_t defaultSize = FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
Platform::DriverConfig validConfig {driverConfig};
@@ -220,26 +51,16 @@ Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverCon
return new WebGPUDriver(platform, validConfig);
}
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
const Platform::DriverConfig& driverConfig) noexcept
: mPlatform(platform),
mHandleAllocator("Handles",
driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck,
driverConfig.disableHeapHandleTags) {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printInstanceDetails(mPlatform.getInstance());
#endif
mHandleAllocator("Handles", driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags) {
mAdapter = mPlatform.requestAdapter(nullptr);
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printAdapterDetails(mAdapter);
#endif
mDevice = mPlatform.requestDevice(mAdapter);
wgpu::Limits supportedLimits{};
mDevice.GetLimits(&supportedLimits);
mMinUniformBufferOffsetAlignment = supportedLimits.minUniformBufferOffsetAlignment;
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printDeviceDetails(mDevice);
#endif
mQueue = mDevice.GetQueue();
}
@@ -353,6 +174,20 @@ void WebGPUDriver::destroyProgram(Handle<HwProgram> ph) {
}
void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
if (rth) {
WGPURenderTarget* rt = handleCast<WGPURenderTarget>(rth);
if (rt == mDefaultRenderTarget) {
mDefaultRenderTarget = nullptr;
}
if (rt == mCurrentRenderTarget) {
mCurrentRenderTarget = nullptr;
}
// WGPURenderTarget destructor is trivial.
// The HwTexture handles stored within WGPURenderTarget (via MRT, TargetBufferInfo)
// are not owned by WGPURenderTarget, so they are not destroyed here.
// They are destroyed via WebGPUDriver::destroyTexture.
destructHandle<WGPURenderTarget>(rth);
}
}
void WebGPUDriver::destroySwapChain(Handle<HwSwapChain> sch) {
@@ -375,6 +210,19 @@ void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh)
}
void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
auto* bindGroup = handleCast<WebGPUDescriptorSet>(tqh);
assert_invariant(bindGroup);
if (bindGroup->getBindGroup() != nullptr) {
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
if (mCurrentDescriptorSets[i].bindGroup != nullptr &&
bindGroup->getBindGroup().Get() == mCurrentDescriptorSets[i].bindGroup.Get()) {
// Clear this from our current entries
mCurrentDescriptorSets[i].bindGroup = nullptr;
mCurrentDescriptorSets[i].offsets.clear();
mCurrentDescriptorSets[i].offsetCount = 0;
}
}
}
if (tqh) {
destructHandle<WebGPUDescriptorSet>(tqh);
}
@@ -472,9 +320,9 @@ void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
mDevice, flags);
assert_invariant(mSwapChain);
FWGPU_LOGW << "WebGPU support is still essentially a no-op at this point in development (only "
"background components have been instantiated/selected, such as surface/screen, "
"graphics device/GPU, etc.), thus nothing is being drawn to the screen."
FWGPU_LOGW << "WebGPU support is highly experimental, in development, and tested for only a "
"small set of simple samples (e.g. hellotriangle and texturedquad), thus issues "
"are likely to be encountered at this stage."
<< utils::io::endl;
#if !FWGPU_ENABLED(FWGPU_PRINT_SYSTEM) && !defined(NDEBUG)
FWGPU_LOGI << "If the FILAMENT_BACKEND_DEBUG_FLAG variable were set with the " << utils::io::hex
@@ -579,7 +427,21 @@ void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> rth, TargetBufferFlags targets,
uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount, MRT color,
TargetBufferInfo depth, TargetBufferInfo stencil) {}
TargetBufferInfo depth, TargetBufferInfo stencil) {
// The `targets` flags indicate which of the `color`, `depth`, `stencil` TargetBufferInfo
// are actually active for this render target.
// We'll pass all TargetBufferInfo to WGPURenderTarget; it will use them if their handles are valid.
// Ensure that textures intended for use as attachments were created with
// wgpu::TextureUsage::RenderAttachment. This check should ideally be in createTextureR
// or validated here if possible.
// The `layerCount` parameter might be for creating array textures that this RT targets.
// Individual attachments (color[i].layer, depth.layer, stencil.layer) specify which layer
// of an array texture to bind. For now, we assume textures are pre-configured.
constructHandle<WGPURenderTarget>(rth, width, height, samples, color, depth, stencil);
}
void WebGPUDriver::createFenceR(Handle<HwFence> fh, int) {}
@@ -662,7 +524,7 @@ bool WebGPUDriver::isAutoDepthResolveSupported() {
}
bool WebGPUDriver::isSRGBSwapChainSupported() {
return false;
return true;
}
bool WebGPUDriver::isProtectedContentSupported() {
@@ -752,11 +614,72 @@ void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t in
vertexBuffer->buffers[index] = bufferObject->getBuffer();
}
void WebGPUDriver::update3DImage(Handle<HwTexture> th,
uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset,
uint32_t width, uint32_t height, uint32_t depth,
PixelBufferDescriptor&& data) {
scheduleDestroy(std::move(data));
void WebGPUDriver::update3DImage(Handle<HwTexture> th, uint32_t level, uint32_t xoffset,
uint32_t yoffset, uint32_t zoffset, uint32_t width, uint32_t height, uint32_t depth,
PixelBufferDescriptor&& p) {
PixelBufferDescriptor* data = &p;
PixelBufferDescriptor reshapedData;
if (reshape(p, reshapedData)) {
data = &reshapedData;
}
auto texture = handleCast<WGPUTexture>(th);
// TODO: Writing to a depth texture is illegal and errors. I'm not sure why Filament is trying
// to do so, but early returning is working?
if(texture->getAspect() == wgpu::TextureAspect::DepthOnly){
scheduleDestroy(std::move(p));
return;
}
size_t blockWidth = texture->getBlockWidth();
size_t blockHeight = texture->getBlockHeight();
// WebGPU specification requires that for compressed textures, the x and y offsets
// must be a multiple of the compressed texture format's block width and height.
// See: https://www.w3.org/TR/webgpu/#abstract-opdef-validating-gputexelcopytextureinfo
if (blockWidth > 1 || blockHeight > 1) {
FILAMENT_CHECK_PRECONDITION(xoffset % blockWidth == 0)
<< "xoffset must be aligned to blockwidth, but offset is " << blockWidth
<< "and offset is " << xoffset;
FILAMENT_CHECK_PRECONDITION(yoffset % blockHeight == 0)
<< "yoffset must be aligned to blockHeight, but offset is " << blockHeight
<< "and offset is " << yoffset;
}
auto copyInfo = wgpu::TexelCopyTextureInfo{ .texture = texture->getTexture(),
.mipLevel = level,
.origin = { .x = xoffset, .y = yoffset, .z = zoffset },
.aspect = texture->getAspect() };
uint32_t bytesPerRow = static_cast<uint32_t>(
PixelBufferDescriptor::computePixelSize(data->format, data->type) * width);
auto extent = wgpu::Extent3D{ .width = width, .height = height, .depthOrArrayLayers = depth };
const uint8_t* dataBuff = static_cast<const uint8_t*>(data->buffer);
size_t dataSize = data->size;
std::unique_ptr<uint8_t[]> paddedBuffer;
if (bytesPerRow % 256 != 0) {
uint32_t padding = 256 - (bytesPerRow % 256);
uint32_t paddedBytesPerRow = bytesPerRow + padding;
size_t paddedBufferSize = static_cast<size_t>(paddedBytesPerRow) * height * depth;
paddedBuffer = std::make_unique<uint8_t[]>(paddedBufferSize);
uint8_t* dest = paddedBuffer.get();
for (uint32_t z = 0; z < depth; ++z) {
for (uint32_t y = 0; y < height; ++y) {
std::memcpy(dest, dataBuff, bytesPerRow);
dest += paddedBytesPerRow;
dataBuff += bytesPerRow;
}
}
dataBuff = paddedBuffer.get();
dataSize = paddedBufferSize;
bytesPerRow = paddedBytesPerRow;
}
auto layout = wgpu::TexelCopyBufferLayout{ .bytesPerRow = bytesPerRow, .rowsPerImage = height };
mQueue.WriteTexture(&copyInfo, dataBuff, dataSize, &layout, &extent);
scheduleDestroy(std::move(p));
}
void WebGPUDriver::setupExternalImage(void* image) {
@@ -783,35 +706,81 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> rth, RenderPassParams const& params) {
assert_invariant(mCommandEncoder);
auto* renderTarget = handleCast<WGPURenderTarget>(rth);
// if (renderTarget == mDefaultRenderTarget) {
// FWGPU_LOGW << "Default render target"
// << utils::io::endl;
// } else {
// FWGPU_LOGW << "Non Default render target"
// << utils::io::endl;
// }
wgpu::RenderPassDescriptor renderPassDescriptor;
wgpu::RenderPassDepthStencilAttachment depthStencilAttachment{
.view = mSwapChain->getDepthTextureView(),
.depthLoadOp = WGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH),
.depthStoreOp = WGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH),
.depthClearValue = static_cast<float>(params.clearDepth),
.depthReadOnly = (params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0,
.stencilLoadOp = WGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL),
.stencilStoreOp = WGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL),
.stencilClearValue = params.clearStencil,
.stencilReadOnly = (params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0
};
renderTarget->setUpRenderPassAttachments(renderPassDescriptor, mTextureView, params);
renderPassDescriptor.depthStencilAttachment = &depthStencilAttachment;
assert_invariant(mTextureView);
wgpu::TextureView defaultColorView = nullptr;
wgpu::TextureView defaultDepthStencilView = nullptr;
std::array<wgpu::TextureView, MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT> customColorViews{};
uint32_t customColorViewCount = 0;
wgpu::TextureView customDepthView = nullptr;
wgpu::TextureFormat customDepthFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureFormat customStencilFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureView customStencilView = nullptr;
mCurrentRenderTarget = renderTarget;
if (renderTarget->isDefaultRenderTarget()) {
assert_invariant(mSwapChain && mTextureView);
defaultColorView = mTextureView;
defaultDepthStencilView = mSwapChain->getDepthTextureView();
} else {
// Resolve views for custom render target
const auto& colorInfos = renderTarget->getColorAttachmentInfos();
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
if (colorInfos[i].handle) {
auto* hwTexture = handleCast<WGPUTexture>(colorInfos[i].handle);
if (hwTexture) {
// TODO: Consider colorInfos[i].level and colorInfos[i].layer for view creation
// if WGPUTexture::getTextureView() isn't sufficient or needs parameters.
customColorViews[customColorViewCount++] = hwTexture->getTextureView();
}
}
}
const auto& depthInfo = renderTarget->getDepthAttachmentInfo();
if (depthInfo.handle) {
auto* hwTexture = handleCast<WGPUTexture>(depthInfo.handle);
if (hwTexture) {
customDepthView = hwTexture->getTextureView();
customDepthFormat = hwTexture->getFormat();
}
}
const auto& stencilInfo = renderTarget->getStencilAttachmentInfo();
if (stencilInfo.handle) {
// If depth and stencil use the same texture handle, this will re-cast but that's fine.
auto* hwTexture = handleCast<WGPUTexture>(stencilInfo.handle);
if (hwTexture) {
customStencilView = hwTexture->getTextureView();
customStencilFormat = hwTexture->getFormat();
}
}
}
wgpu::RenderPassDescriptor renderPassDescriptor{};
renderTarget->setUpRenderPassAttachments(renderPassDescriptor,
params,
defaultColorView,
defaultDepthStencilView,
customColorViews.data(),
customColorViewCount,
customDepthView,
customStencilView,
customDepthFormat,
customStencilFormat);
mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor);
mRenderPassEncoder.SetViewport(params.viewport.left, params.viewport.bottom,
params.viewport.width, params.viewport.height, params.depthRange.near, params.depthRange.far);
// Ensure viewport dimensions are not 0
FILAMENT_CHECK_POSTCONDITION(params.viewport.width > 0) << "viewport width is 0?";
FILAMENT_CHECK_POSTCONDITION(params.viewport.height > 0) << "viewport height is 0?";
mRenderPassEncoder.SetViewport(
static_cast<float>(params.viewport.left),
static_cast<float>(params.viewport.bottom),
static_cast<float>(params.viewport.width),
static_cast<float>(params.viewport.height),
params.depthRange.near,
params.depthRange.far);
}
void WebGPUDriver::endRenderPass(int /* dummy */) {
@@ -904,7 +873,7 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
// VulkanPipelineCache to handle this, may be missing nuance
static auto pipleineStateHasher = utils::hash::MurmurHashFn<filament::backend::PipelineState>();
auto hash = pipleineStateHasher(pipelineState);
if(mPipelineMap.find(hash) != mPipelineMap.end()){
if (mPipelineMap.find(hash) != mPipelineMap.end()) {
mRenderPassEncoder.SetPipeline(mPipelineMap[hash]);
return;
}
@@ -940,24 +909,67 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
<< layoutDescriptor.label;
auto const* vertexBufferInfo = handleCast<WGPUVertexBufferInfo>(pipelineState.vertexBufferInfo);
assert_invariant(vertexBufferInfo);
const wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program,
*vertexBufferInfo, layout, pipelineState.rasterState, pipelineState.stencilState,
pipelineState.polygonOffset, pipelineState.primitiveType, mSwapChain->getColorFormat(),
mSwapChain->getDepthFormat());
std::vector<wgpu::TextureFormat> pipelineColorFormats;
wgpu::TextureFormat pipelineDepthFormat = wgpu::TextureFormat::Undefined;
uint8_t pipelineSamples = 1;
if (mCurrentRenderTarget->isDefaultRenderTarget()) {
pipelineColorFormats.push_back(mSwapChain->getColorFormat());
pipelineDepthFormat = mSwapChain->getDepthFormat();
pipelineSamples =
mCurrentRenderTarget->getSamples();// Default RT should have samples (usually 1)
} else {
const auto& mrtColorAttachments = mCurrentRenderTarget->getColorAttachmentInfos();
for (size_t i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
if (mrtColorAttachments[i].handle) {
const auto* colorTexture = handleCast<WGPUTexture>(mrtColorAttachments[i].handle);
if (colorTexture) {
pipelineColorFormats.push_back(colorTexture->getTexture().GetFormat());
}
}
}
const auto& depthInfo = mCurrentRenderTarget->getDepthAttachmentInfo();
const auto& stencilInfo = mCurrentRenderTarget->getStencilAttachmentInfo();
if (depthInfo.handle) {
FILAMENT_CHECK_POSTCONDITION(!stencilInfo.handle)
<< "depth and stencil attachments cannot both be provided for WebGPU";
const auto* dfTexture = handleCast<WGPUTexture>(depthInfo.handle);
if (dfTexture) pipelineDepthFormat = dfTexture->getTexture().GetFormat();
} else {
if (stencilInfo.handle) {
const auto* sfTexture = handleCast<WGPUTexture>(stencilInfo.handle);
// Assuming combined depth/stencil format if only stencil is present
if (sfTexture) {
pipelineDepthFormat = sfTexture->getTexture().GetFormat();
}
}
}
pipelineSamples = mCurrentRenderTarget->getSamples();
}
if (program->fragmentShaderModule != nullptr) {
FILAMENT_CHECK_POSTCONDITION(!pipelineColorFormats.empty())
<< "Render pipeline with fragment shader must have at least one color target "
"format.";
}
wgpu::RenderPipeline pipeline = createWebGPURenderPipeline(mDevice, *program, *vertexBufferInfo,
layout, pipelineState.rasterState, pipelineState.stencilState,
pipelineState.polygonOffset, pipelineState.primitiveType, pipelineColorFormats,
pipelineDepthFormat, pipelineSamples);
assert_invariant(pipeline);
mPipelineMap[hash] = pipeline;
mRenderPassEncoder.SetPipeline(pipeline);
}
void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
auto* renderPrimitive = handleCast<WGPURenderPrimitive>(rph);
// This *must* match the WGPUVertexBufferInfo that was bound in bindPipeline(). But we want
// to allow to call this before bindPipeline(), so the validation can only happen in draw()
auto vbi = handleCast<WGPUVertexBufferInfo>(renderPrimitive->vertexBuffer->vbih);
assert_invariant(
vbi->getVertexBufferLayoutSize() == renderPrimitive->vertexBuffer->buffers.size());
for (uint32_t i = 0; i < vbi->getVertexBufferLayoutSize(); i++) {
mRenderPassEncoder.SetVertexBuffer(i, renderPrimitive->vertexBuffer->buffers[i]);
for (const auto& webGPUSlotBindings: vbi->getWebGPUSlotBindingInfos()) {
mRenderPassEncoder.SetVertexBuffer(webGPUSlotBindings.slot,
renderPrimitive->vertexBuffer->buffers[webGPUSlotBindings.sourceBuffer],
webGPUSlotBindings.bufferOffset);
}
mRenderPassEncoder.SetIndexBuffer(renderPrimitive->indexBuffer->getBuffer(),
@@ -965,11 +977,27 @@ void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
}
void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
// We defer actually binding until we actually draw
for (size_t i = 0; i < MAX_DESCRIPTOR_SET_COUNT; i++) {
auto& binding = mCurrentDescriptorSets[i];
if (binding.bindGroup) {
mRenderPassEncoder.SetBindGroup(i, binding.bindGroup, binding.offsetCount,
binding.offsets.data());
}
}
mRenderPassEncoder.DrawIndexed(indexCount, instanceCount, indexOffset, 0, 0);
}
void WebGPUDriver::draw(PipelineState, Handle<HwRenderPrimitive>, uint32_t indexOffset,
uint32_t indexCount, uint32_t instanceCount) {
void WebGPUDriver::draw(PipelineState pipelineState,
Handle<HwRenderPrimitive> renderPrimitiveHandle, uint32_t indexOffset, uint32_t indexCount,
uint32_t instanceCount) {
WGPURenderPrimitive const* const renderPrimitive =
handleCast<WGPURenderPrimitive>(renderPrimitiveHandle);
pipelineState.primitiveType = renderPrimitive->type;
pipelineState.vertexBufferInfo = renderPrimitive->vertexBuffer->vbih;
bindPipeline(pipelineState);
bindRenderPrimitive(renderPrimitiveHandle);
draw2(indexOffset, indexCount, instanceCount);
}
@@ -1018,7 +1046,7 @@ void WebGPUDriver::updateDescriptorSetTexture(Handle<HwDescriptorSet> dsh,
auto sampler = makeSampler(params);
// TODO making assumptions that size and offset mean the same thing here.
wgpu::BindGroupEntry tEntry{ .binding = static_cast<uint32_t>(binding * 2),
.textureView = texture->getTexView() };
.textureView = texture->getTextureView() };
bindGroup->addEntry(tEntry.binding, std::move(tEntry));
wgpu::BindGroupEntry sEntry{ .binding = static_cast<uint32_t>(binding * 2 + 1),
@@ -1029,15 +1057,18 @@ void WebGPUDriver::updateDescriptorSetTexture(Handle<HwDescriptorSet> dsh,
void WebGPUDriver::bindDescriptorSet(Handle<HwDescriptorSet> dsh,
backend::descriptor_set_t setIndex, backend::DescriptorSetOffsetArray&& offsets) {
assert_invariant(setIndex < MAX_DESCRIPTOR_SET_COUNT);
const auto bindGroup = handleCast<WebGPUDescriptorSet>(dsh);
const auto wbg = bindGroup->lockAndReturn(mDevice);
assert_invariant(mRenderPassEncoder);
const size_t dynamicOffsetCount = bindGroup->countEntitiesWithDynamicOffsets();
mRenderPassEncoder.SetBindGroup(setIndex, wbg, dynamicOffsetCount, offsets.data());
mCurrentDescriptorSets[setIndex] = { .bindGroup = wbg,
.offsetCount = bindGroup->countEntitiesWithDynamicOffsets(),
.offsets = std::move(offsets) };
}
void WebGPUDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
}
wgpu::Sampler WebGPUDriver::makeSampler(SamplerParams const& params) {
wgpu::SamplerDescriptor desc{};

View File

@@ -74,8 +74,16 @@ private:
wgpu::RenderPassEncoder mRenderPassEncoder = nullptr;
wgpu::CommandBuffer mCommandBuffer = nullptr;
WGPURenderTarget* mDefaultRenderTarget = nullptr;
WGPURenderTarget* mCurrentRenderTarget = nullptr;
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineMap;
struct DescriptorSetBindingInfo{
wgpu::BindGroup bindGroup;
size_t offsetCount;
backend::DescriptorSetOffsetArray offsets;
};
std::array<DescriptorSetBindingInfo,MAX_DESCRIPTOR_SET_COUNT> mCurrentDescriptorSets;
/*
* Driver interface
*/

View File

@@ -20,7 +20,7 @@
#include <utils/BitmaskEnum.h>
#include <utils/Panic.h>
#include <private/backend/BackendUtils.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
@@ -68,7 +68,7 @@ wgpu::VertexFormat getVertexFormat(filament::backend::ElementType type, bool nor
case ElementType::BYTE4: return VertexFormat::Snorm8x4;
case ElementType::UBYTE4: return VertexFormat::Unorm8x4;
case ElementType::SHORT4: return VertexFormat::Snorm16x4;
case ElementType::USHORT4: return VertexFormat::Unorm8x4;
case ElementType::USHORT4: return VertexFormat::Unorm16x4;
default:
FILAMENT_CHECK_POSTCONDITION(false) << "Normalized format does not exist.";
return VertexFormat::Float32x3;
@@ -196,43 +196,90 @@ void WGPUBufferBase::updateGPUBuffer(BufferDescriptor& bufferDescriptor, uint32_
queue.WriteBuffer(buffer, byteOffset + legalSize, &mRemainderChunk, 4);
}
}
static constexpr uint32_t DUMMY_WEBGPU_SLOT = 0;
WGPUVertexBufferInfo::WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
AttributeArray const& attributes)
: HwVertexBufferInfo(bufferCount, attributeCount),
mVertexBufferLayout(bufferCount),
mAttributes(bufferCount) {
assert_invariant(attributeCount > 0);
assert_invariant(bufferCount > 0);
for (uint32_t attribIndex = 0; attribIndex < attributes.size(); attribIndex++) {
Attribute const& attrib = attributes[attribIndex];
// Ignore the attributes which are not bind to vertex buffers.
if (attrib.buffer == Attribute::BUFFER_UNUSED) {
// TODO: max limits may not be supported by webgpu driver. This should be addressed in the
// hardening part.
mVertexBufferLayouts(MAX_VERTEX_BUFFER_COUNT),
mVertexAttributes(MAX_VERTEX_BUFFER_COUNT) {
// It starts from 1 because slot 0 is now reserved for the dummy.
uint32_t currentWebGPUSlotIndex = 1;
// A reasonable dummy stride (e.g., for vec4)
const uint32_t DUMMY_STRIDE = 16;
// Initialize the layout for the dummy slot (slot 0)
mVertexBufferLayouts[DUMMY_WEBGPU_SLOT].arrayStride = DUMMY_STRIDE;
mVertexBufferLayouts[DUMMY_WEBGPU_SLOT].stepMode = wgpu::VertexStepMode::Vertex;
mVertexBufferLayouts[DUMMY_WEBGPU_SLOT].attributeCount = 0;
mWebGPUSlotBindingInfos.push_back({
.sourceBuffer = 0,
.slot = DUMMY_WEBGPU_SLOT,
.bufferOffset = 0,
.stride = DUMMY_STRIDE,
});
for (uint32_t attributeIndex = 0; attributeIndex < attributes.size(); ++attributeIndex) {
const auto& attribute = attributes[attributeIndex];
bool const isInteger = attribute.flags & Attribute::FLAG_INTEGER_TARGET;
bool const isNormalized = attribute.flags & Attribute::FLAG_NORMALIZED;
wgpu::VertexFormat vertexFormat = getVertexFormat(attribute.type, isNormalized, isInteger);
if (attribute.buffer == Attribute::BUFFER_UNUSED) {
// Use some dummy format
vertexFormat = isInteger ? wgpu::VertexFormat::Uint8x4 : wgpu::VertexFormat::Unorm8x4;
mVertexAttributes[DUMMY_WEBGPU_SLOT].push_back({
.format = vertexFormat,
.offset = 0,
.shaderLocation = attributeIndex,
});
mVertexBufferLayouts[DUMMY_WEBGPU_SLOT].attributeCount++;
continue;
}
assert_invariant(attrib.buffer < bufferCount);
bool const isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET;
bool const isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED;
wgpu::VertexFormat vertexFormat = getVertexFormat(attrib.type, isNormalized, isInteger);
auto it = std::find_if(mWebGPUSlotBindingInfos.begin(), mWebGPUSlotBindingInfos.end(),
[&](const auto& info) {
return info.sourceBuffer == attribute.buffer && info.stride == attribute.stride;
});
// Attributes are sequential per buffer
mAttributes[attrib.buffer].push_back({
.format = vertexFormat,
.offset = attrib.offset,
.shaderLocation = attribIndex,
});
mVertexBufferLayout[attrib.buffer].stepMode = wgpu::VertexStepMode::Vertex;
if (mVertexBufferLayout[attrib.buffer].arrayStride == 0) {
mVertexBufferLayout[attrib.buffer].arrayStride = attrib.stride;
uint32_t assignedSlot;
if (it != mWebGPUSlotBindingInfos.end()) {
assignedSlot = it->slot;
} else {
assert_invariant(mVertexBufferLayout[attrib.buffer].arrayStride == attrib.stride);
// New combination, allocate a new WebGPU slot
assert_invariant(currentWebGPUSlotIndex < MAX_VERTEX_BUFFER_COUNT);
assignedSlot = currentWebGPUSlotIndex++;
mWebGPUSlotBindingInfos.push_back({
.sourceBuffer = attribute.buffer,
.slot = assignedSlot,
.bufferOffset = attribute.offset - (attribute.offset % attribute.stride),
.stride = attribute.stride,
});
mVertexBufferLayouts[assignedSlot].arrayStride = attribute.stride;
mVertexBufferLayouts[assignedSlot].stepMode = wgpu::VertexStepMode::Vertex;
mVertexBufferLayouts[assignedSlot].attributeCount = 0;
}
mVertexAttributes[assignedSlot].push_back({
.format = vertexFormat,
.offset = attribute.offset % attribute.stride,
.shaderLocation = attributeIndex,
});
mVertexBufferLayouts[assignedSlot].attributeCount++;
}
for (uint32_t bufferIndex = 0; bufferIndex < bufferCount; bufferIndex++) {
mVertexBufferLayout[bufferIndex].attributeCount = mAttributes[bufferIndex].size();
mVertexBufferLayout[bufferIndex].attributes = mAttributes[bufferIndex].data();
mVertexBufferLayouts.resize(currentWebGPUSlotIndex);
for (const auto& info: mWebGPUSlotBindingInfos) {
mVertexBufferLayouts[info.slot].attributes = mVertexAttributes[info.slot].data();
}
}
@@ -502,6 +549,8 @@ WGPUTexture::WGPUTexture(SamplerType target, uint8_t levels, TextureFormat forma
mUsage = fToWGPUTextureUsage(usage);
mFormat = fToWGPUTextureFormat(format);
mAspect = fToWGPUTextureViewAspect(usage, format);
mBlockWidth = filament::backend::getBlockWidth(format);
mBlockHeight = filament::backend::getBlockHeight(format);
wgpu::TextureDescriptor textureDescriptor{
.label = getUserTextureLabel(target),
.usage = mUsage,
@@ -543,12 +592,16 @@ WGPUTexture::WGPUTexture(SamplerType target, uint8_t levels, TextureFormat forma
FILAMENT_CHECK_POSTCONDITION(mTexture)
<< "Failed to create texture for " << textureDescriptor.label;
// Second, the texture view aspect
mTexView = makeTextureView(0, levels, target);
mTexureView = makeTextureView(0, levels, target);
}
WGPUTexture::WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept {
mTexture = src->mTexture;
mTexView = makeTextureView(baseLevel, levelCount, target);
mAspect = src->mAspect;
mBlockWidth = src->mBlockWidth;
mBlockHeight = src->mBlockHeight;
mTexureView = makeTextureView(baseLevel, levelCount, target);
}
wgpu::TextureUsage WGPUTexture::fToWGPUTextureUsage(TextureUsage const& fUsage) {
@@ -917,58 +970,153 @@ wgpu::TextureView WGPUTexture::makeTextureView(const uint8_t& baseLevel, const u
return textureView;
}
WGPURenderTarget::Attachment WGPURenderTarget::getDrawColorAttachment(size_t index) {
assert_invariant( index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
auto result = color[index];
if (index == 0 && defaultRenderTarget) {
}
return result;
WGPURenderTarget::WGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples,
const MRT& colorAttachmentsMRT,
const Attachment& depthAttachmentInfo,
const Attachment& stencilAttachmentInfo)
: HwRenderTarget(width, height),
defaultRenderTarget(false),
samples(samples),
mColorAttachments(colorAttachmentsMRT),
mDepthAttachment(depthAttachmentInfo),
mStencilAttachment(stencilAttachmentInfo) {
// TODO Make this an array
mColorAttachmentDescriptors.reserve(MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
}
wgpu::LoadOp WGPURenderTarget::getLoadOperation(RenderPassParams const& params,
TargetBufferFlags buffer) {
auto clearFlags = params.flags.clear;
auto discardStartFlags = params.flags.discardStart;
if (any(clearFlags & buffer)) {
return wgpu::LoadOp::Clear;
} else if (any(discardStartFlags & buffer)) {
TargetBufferFlags bufferToOperateOn) {
if (any(params.flags.clear & bufferToOperateOn)) {
return wgpu::LoadOp::Clear;
}
if (any(params.flags.discardStart & bufferToOperateOn)) {
return wgpu::LoadOp::Clear; // Or wgpu::LoadOp::Undefined if clear is not desired on discard
}
return wgpu::LoadOp::Load;
}
wgpu::StoreOp WGPURenderTarget::getStoreOperation(RenderPassParams const& params,
TargetBufferFlags buffer) {
const auto discardEndFlags = params.flags.discardEnd;
if (any(discardEndFlags & buffer)) {
TargetBufferFlags bufferToOperateOn) {
if (any(params.flags.discardEnd & bufferToOperateOn)) {
return wgpu::StoreOp::Discard;
}
return wgpu::StoreOp::Store;
}
void WGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor& descriptor,
wgpu::TextureView const& textureView, RenderPassParams const& params) {
// auto discardFlags = params.flags.discardEnd;
// (void) discardFlags;
// std::vector<wgpu::RenderPassColorAttachment> colorAttachments;
colorAttachments.clear();
for (size_t i = 0; i < 1/*MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT*/; i++) {
// auto attachment = getDrawColorAttachment(i);
// if (attachment) {
wgpu::RenderPassColorAttachment colorAttachment;
colorAttachment.view = textureView;
colorAttachment.loadOp = getLoadOperation(params, getTargetBufferFlagsAt(i));
colorAttachment.storeOp = getStoreOperation(params, getTargetBufferFlagsAt(i));
colorAttachment.clearValue = { params.clearColor.r, params.clearColor.g, params.clearColor.b, params.clearColor.a };
colorAttachments.emplace_back(colorAttachment);
// }
RenderPassParams const& params, wgpu::TextureView const& defaultColorTextureView,
wgpu::TextureView const& defaultDepthStencilTextureView,
wgpu::TextureView const* customColorTextureViews, uint32_t customColorTextureViewCount,
wgpu::TextureView const& customDepthTextureView,
wgpu::TextureView const& customStencilTextureView, wgpu::TextureFormat customDepthFormat,
wgpu::TextureFormat customStencilFormat) {
mColorAttachmentDescriptors.clear();
mHasDepthStencilAttachment = false;
if (defaultRenderTarget) {
assert_invariant(defaultColorTextureView);
mColorAttachmentDescriptors.push_back({ .view = defaultColorTextureView,
.resolveTarget = nullptr,
.loadOp = WGPURenderTarget::getLoadOperation(params, TargetBufferFlags::COLOR0),
.storeOp = WGPURenderTarget::getStoreOperation(params, TargetBufferFlags::COLOR0),
.clearValue = { params.clearColor.r, params.clearColor.g, params.clearColor.b,
params.clearColor.a } });
if (defaultDepthStencilTextureView) {
mDepthStencilAttachmentDescriptor = {
.view = defaultDepthStencilTextureView,
.depthLoadOp = WGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH),
.depthStoreOp =
WGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH),
.depthClearValue = static_cast<float>(params.clearDepth),
.depthReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0,
.stencilLoadOp =
WGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL),
.stencilStoreOp =
WGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL),
.stencilClearValue = params.clearStencil,
.stencilReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0,
};
mHasDepthStencilAttachment = true;
}
} else {// Custom Render Target
for (uint32_t i = 0; i < customColorTextureViewCount; ++i) {
if (customColorTextureViews[i]) {
mColorAttachmentDescriptors.push_back({ .view = customColorTextureViews[i],
// .resolveTarget = nullptr; // TODO: MSAA resolve for custom RT
.loadOp = WGPURenderTarget::getLoadOperation(params, getTargetBufferFlagsAt(i)),
.storeOp =
WGPURenderTarget::getStoreOperation(params, getTargetBufferFlagsAt(i)),
.clearValue = { .r = params.clearColor.r,
.g = params.clearColor.g,
.b = params.clearColor.b,
.a = params.clearColor.a } });
}
}
FILAMENT_CHECK_POSTCONDITION(!(customDepthTextureView && customStencilTextureView))
<< "WebGPU CANNOT support separate texture views for depth + stencil. depth + "
"stencil needs to be in one texture view";
const bool hasStencil =
customStencilTextureView ||
(customDepthFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
customDepthFormat == wgpu::TextureFormat::Depth32FloatStencil8);
const bool hasDepth =
customDepthTextureView ||
(customStencilFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
customDepthFormat == wgpu::TextureFormat::Depth32FloatStencil8);
if (customDepthTextureView || customStencilTextureView) {
assert_invariant((hasDepth || hasStencil) &&
"Depth or Texture view without a valid texture format");
mDepthStencilAttachmentDescriptor = {};
mDepthStencilAttachmentDescriptor.view =
customDepthTextureView ? customDepthTextureView : customStencilTextureView;
if (hasDepth) {
mDepthStencilAttachmentDescriptor.depthLoadOp =
WGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH);
mDepthStencilAttachmentDescriptor.depthStoreOp =
WGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH);
mDepthStencilAttachmentDescriptor.depthClearValue =
static_cast<float>(params.clearDepth);
mDepthStencilAttachmentDescriptor.depthReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0;
} else {
mDepthStencilAttachmentDescriptor.depthLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDescriptor.depthStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDescriptor.depthReadOnly = true;
}
if (hasStencil) {
mDepthStencilAttachmentDescriptor.stencilLoadOp =
WGPURenderTarget::getLoadOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDescriptor.stencilStoreOp =
WGPURenderTarget::getStoreOperation(params, TargetBufferFlags::STENCIL);
mDepthStencilAttachmentDescriptor.stencilClearValue = params.clearStencil;
mDepthStencilAttachmentDescriptor.stencilReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_STENCIL) > 0;
} else {
mDepthStencilAttachmentDescriptor.stencilLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDescriptor.stencilStoreOp = wgpu::StoreOp::Undefined;
mDepthStencilAttachmentDescriptor.stencilReadOnly = true;
}
mHasDepthStencilAttachment = true;
}
}
descriptor.colorAttachments = colorAttachments.data();
descriptor.colorAttachmentCount = colorAttachments.size();
descriptor.depthStencilAttachment = nullptr;
descriptor.timestampWrites = nullptr;
descriptor.colorAttachmentCount = mColorAttachmentDescriptors.size();
descriptor.colorAttachments = mColorAttachmentDescriptors.data();
descriptor.depthStencilAttachment =
mHasDepthStencilAttachment ? &mDepthStencilAttachmentDescriptor : nullptr;
// descriptor.sampleCount was removed from the core spec. If your webgpu.h still has it,
// and your Dawn version expects it, you might need to set it here based on this->samples.
// e.g., descriptor.sampleCount = this->samples;
}
}// namespace filament::backend

View File

@@ -44,36 +44,46 @@ public:
};
// VertexBufferInfo contains layout info for Vertex Buffer based on WebGPU structs. In WebGPU each
// VertexBufferLayout is associated with a single vertex buffer. So number of mVertexBufferLayout
// is equal to bufferCount. Each VertexBufferLayout can contain multiple VertexAttribute. Bind index
// of vertex buffer is implicitly calculated by the position of VertexBufferLayout in an array.
// WGPUVertexBufferInfo maps Filament vertex attributes to WebGPU buffer binding model.
class WGPUVertexBufferInfo : public HwVertexBufferInfo {
public:
WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
AttributeArray const& attributes);
inline wgpu::VertexBufferLayout const* getVertexBufferLayout() const {
return mVertexBufferLayout.data();
inline wgpu::VertexBufferLayout const* getVertexBufferLayouts() const {
return mVertexBufferLayouts.data();
}
inline uint32_t getVertexBufferLayoutCount() const {
return static_cast<uint32_t>(mVertexBufferLayouts.size());
}
inline uint32_t getVertexBufferLayoutSize() const {
return mVertexBufferLayout.size();
inline wgpu::VertexAttribute const* getVertexAttributes(uint32_t i) const {
return mVertexAttributes[i].data();
}
inline uint32_t getVertexAttributeCount(uint32_t i) const {
return static_cast<uint32_t>(mVertexAttributes[i].size());
}
inline wgpu::VertexAttribute const* getVertexAttributeForIndex(uint32_t index) const {
assert_invariant(index < mAttributes.size());
return mAttributes[index].data();
}
inline uint32_t getVertexAttributeSize(uint32_t index) const {
assert_invariant(index < mAttributes.size());
return mAttributes[index].size();
struct WebGPUSlotBindingInfo {
uint8_t sourceBuffer;
uint32_t slot;
uint64_t bufferOffset;
uint32_t stride;
};
inline const std::vector<WebGPUSlotBindingInfo>& getWebGPUSlotBindingInfos() const {
return mWebGPUSlotBindingInfos;
}
private:
// TODO: can we do better in terms on heap management.
std::vector<wgpu::VertexBufferLayout> mVertexBufferLayout{};
std::vector<std::vector<wgpu::VertexAttribute>> mAttributes{};
// This stores the final wgpu::VertexBufferLayout objects, one per WebGPU slot.
std::vector<wgpu::VertexBufferLayout> mVertexBufferLayouts;
// This stores all wgpu::VertexAttribute structs, indexed by their original
// Filament attributeIndex.
std::vector<std::vector<wgpu::VertexAttribute>> mVertexAttributes;
// Stores information for the driver to perform setVertexBuffer calls
std::vector<WebGPUSlotBindingInfo> mWebGPUSlotBindingInfos;
};
struct WGPUVertexBuffer : public HwVertexBuffer {
@@ -145,6 +155,9 @@ public:
[[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; }
[[nodiscard]] size_t countEntitiesWithDynamicOffsets() const;
// May be nullptr. Use lockAndReturn to create the bind group when appropriate
[[nodiscard]] const wgpu::BindGroup& getBindGroup() const { return mBindGroup; }
private:
wgpu::BindGroupLayout mLayout = nullptr;
static constexpr uint8_t INVALID_INDEX = MAX_DESCRIPTOR_COUNT + 1;
@@ -161,9 +174,13 @@ public:
wgpu::Device const& device) noexcept;
WGPUTexture(WGPUTexture* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
wgpu::TextureAspect getAspect() const { return mAspect; }
size_t getBlockWidth() const { return mBlockWidth; }
size_t getBlockHeight() const { return mBlockHeight; }
[[nodiscard]] const wgpu::Texture& getTexture() const { return mTexture; }
[[nodiscard]] const wgpu::TextureView& getTexView() const { return mTexView; }
[[nodiscard]] const wgpu::TextureView& getTextureView() const { return mTexureView; }
[[nodiscard]] wgpu::TextureFormat getFormat() const { return mFormat; }
static wgpu::TextureFormat fToWGPUTextureFormat(
filament::backend::TextureFormat const& fFormat);
@@ -182,8 +199,10 @@ private:
wgpu::TextureFormat mFormat = wgpu::TextureFormat::Undefined;
wgpu::TextureAspect mAspect = wgpu::TextureAspect::Undefined;
uint32_t mArrayLayerCount = 1;
wgpu::TextureView mTexView = nullptr;
wgpu::TextureView mTexureView = nullptr;
wgpu::TextureUsage fToWGPUTextureUsage(filament::backend::TextureUsage const& fUsage);
size_t mBlockWidth;
size_t mBlockHeight;
};
struct WGPURenderPrimitive : public HwRenderPrimitive {
@@ -194,55 +213,60 @@ struct WGPURenderPrimitive : public HwRenderPrimitive {
class WGPURenderTarget : public HwRenderTarget {
public:
class Attachment {
public:
friend class WGPURenderTarget;
Attachment() = default;
Attachment(WGPUTexture* gpuTexture, uint8_t level = 0, uint16_t layer = 0)
: level(level),
layer(layer),
texture(gpuTexture->getTexture()),
mWGPUTexture(gpuTexture) {}
operator bool() const {
return mWGPUTexture != nullptr;
}
uint8_t level = 0;
uint16_t layer = 0;
private:
wgpu::Texture texture = nullptr;
WGPUTexture* mWGPUTexture = nullptr;
};
using Attachment = TargetBufferInfo; // Using TargetBufferInfo directly for attachments
WGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples,
Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]);
const MRT& colorAttachments,
const Attachment& depthAttachment,
const Attachment& stencilAttachment);
// Default constructor for the default render target
WGPURenderTarget()
: HwRenderTarget(0, 0),
defaultRenderTarget(true) {}
defaultRenderTarget(true),
samples(1) {}
void setUpRenderPassAttachments(wgpu::RenderPassDescriptor& descriptor,
wgpu::TextureView const& textureView, RenderPassParams const& params);
// Updated signature: takes resolved views for custom RTs, and default views for default RT
void setUpRenderPassAttachments(
wgpu::RenderPassDescriptor& descriptor,
RenderPassParams const& params,
// For default render target:
wgpu::TextureView const& defaultColorTextureView,
wgpu::TextureView const& defaultDepthStencilTextureView,
// For custom render targets:
wgpu::TextureView const* customColorTextureViews, // Array of views
uint32_t customColorTextureViewCount,
wgpu::TextureView const& customDepthTextureView,
wgpu::TextureView const& customStencilTextureView,
wgpu::TextureFormat customDepthFormat,
wgpu::TextureFormat customStencilFormat);
math::uint2 getAttachmentSize() noexcept;
bool isDefaultRenderTarget() const { return defaultRenderTarget; }
uint8_t getSamples() const { return samples; }
Attachment getDrawColorAttachment(size_t index);
Attachment getReadColorAttachment(size_t index);
// Accessors for the driver to get stored attachment info
const MRT& getColorAttachmentInfos() const { return mColorAttachments; }
const Attachment& getDepthAttachmentInfo() const { return mDepthAttachment; }
const Attachment& getStencilAttachmentInfo() const { return mStencilAttachment; }
// Static helpers for load/store operations
static wgpu::LoadOp getLoadOperation(const RenderPassParams& params, TargetBufferFlags buffer);
static wgpu::StoreOp getStoreOperation(const RenderPassParams& params, TargetBufferFlags buffer);
private:
bool defaultRenderTarget = false;
uint8_t samples = 1;
Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
math::uint2 attachmentSize = {};
std::vector<wgpu::RenderPassColorAttachment> colorAttachments{};
};
MRT mColorAttachments{};
// TODO WebGPU only supports a DepthStencil attachment, should this be just mDepthStencilAttachment?
Attachment mDepthAttachment{};
Attachment mStencilAttachment{};
// Cached descriptors for the render pass
std::vector<wgpu::RenderPassColorAttachment> mColorAttachmentDescriptors;
wgpu::RenderPassDepthStencilAttachment mDepthStencilAttachmentDescriptor{};
bool mHasDepthStencilAttachment = false;
};
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H

View File

@@ -154,9 +154,9 @@ constexpr wgpu::BlendFactor toWebGPU(BlendFunction blendFunction) {
wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
WGPUProgram const& program, WGPUVertexBufferInfo const& vertexBufferInfo,
wgpu::PipelineLayout const& layout, RasterState const& rasterState,
StencilState const& stencilState, PolygonOffset const& polygonOffset,
PrimitiveType primitiveType, wgpu::TextureFormat colorFormat,
wgpu::TextureFormat depthFormat) {
StencilState const& stencilState, PolygonOffset const& polygonOffset, PrimitiveType primitiveType,
std::vector<wgpu::TextureFormat> const& colorFormats,
wgpu::TextureFormat depthFormat, uint8_t samplesCount) {
assert_invariant(program.vertexShaderModule);
const wgpu::DepthStencilState depthStencilState {
.format = depthFormat,
@@ -191,8 +191,8 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
.entryPoint = "main",
.constantCount = program.constants.size(),
.constants = program.constants.data(),
.bufferCount = vertexBufferInfo.getVertexBufferLayoutSize(),
.buffers = vertexBufferInfo.getVertexBufferLayout()
.bufferCount = vertexBufferInfo.getVertexBufferLayoutCount(),
.buffers = vertexBufferInfo.getVertexBufferLayouts()
},
.primitive = {
.topology = toWebGPU(primitiveType),
@@ -207,9 +207,9 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
.unclippedDepth = !rasterState.depthClamp &&
device.HasFeature(wgpu::FeatureName::DepthClipControl)
},
.depthStencil = &depthStencilState,
.depthStencil = depthFormat != wgpu::TextureFormat::Undefined ? &depthStencilState: nullptr,
.multisample = {
.count = 1, // TODO need to get this from the render target
.count = samplesCount,
.mask = 0xFFFFFFFF,
.alphaToCoverageEnabled = rasterState.alphaToCoverage
},
@@ -234,12 +234,15 @@ wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const& device,
fragmentState.entryPoint = "main";
fragmentState.constantCount = program.constants.size(),
fragmentState.constants = program.constants.data(),
fragmentState.targetCount = 1; // TODO need to get this from the render target
fragmentState.targetCount = colorFormats.size();
fragmentState.targets = colorTargets.data();
assert_invariant(fragmentState.targetCount <= MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT);
// A fragment shader implies at least one color target if it outputs color.
// This should be guaranteed by the caller ensuring colorFormats is not empty.
assert_invariant(fragmentState.targetCount > 0);
for (size_t targetIndex = 0; targetIndex < fragmentState.targetCount; targetIndex++) {
auto& colorTarget = colorTargets[targetIndex];
colorTarget.format = colorFormat;
colorTarget.format = colorFormats[targetIndex];
colorTarget.blend = rasterState.hasBlending() ? &blendState : nullptr;
colorTarget.writeMask =
rasterState.colorWrite ? wgpu::ColorWriteMask::All : wgpu::ColorWriteMask::None;

View File

@@ -18,6 +18,7 @@
#define TNT_FILAMENT_BACKEND_WEBGPUPIPELINECREATION_H
#include <cstdint>
#include <vector>
namespace wgpu {
class Device;
@@ -38,8 +39,9 @@ class WGPUProgram;
[[nodiscard]] wgpu::RenderPipeline createWebGPURenderPipeline(wgpu::Device const&,
WGPUProgram const&, WGPUVertexBufferInfo const&, wgpu::PipelineLayout const&,
RasterState const&, StencilState const&, PolygonOffset const&, PrimitiveType,
wgpu::TextureFormat colorFormat, wgpu::TextureFormat depthFormat);
RasterState const&, StencilState const&, PolygonOffset const&, PrimitiveType primitiveType,
std::vector<wgpu::TextureFormat> const& colorFormats,
wgpu::TextureFormat depthFormat, uint8_t samplesCount);
}// namespace filament::backend

View File

@@ -0,0 +1,188 @@
/*
* Copyright (C) 2025 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_BACKEND_WEBGPUSTRINGS_H
#define TNT_FILAMENT_BACKEND_WEBGPUSTRINGS_H
#include "WebGPUConstants.h"
#include <backend/DriverEnums.h>
#include <utils/Panic.h>
#include <utils/ostream.h>
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
#include <dawn/webgpu_cpp_print.h>
#endif
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
#include <sstream>
#endif
#include <string_view>
/**
* Reusable set of convenience functions for strings -- generally string views, literals, &
* streaming -- used in the WebGPU backend
*/
namespace filament::backend {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
/**
* Convenience template to print WGPU types (which don't support Filament's utils::io::ostream)
* @tparam WebGPUPrintable e.g. wgpu::FeatureName type
* @param out stream to print to
* @param printable the instance to print
* @return the stream printed to
*
* Example usage (because including the template operator override itself does not compile):
* #if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
* utils::io::ostream& operator<<(utils::io::ostream& out,
* const wgpu::FeatureName featureName) noexcept {
* return streamInsertWebGPUPrintable(out, featureName);
* }
* #endif
*/
template<typename WebGPUPrintable>
utils::io::ostream& streamInsertWebGPUPrintable(utils::io::ostream& out,
const WebGPUPrintable printable) {
std::stringstream printableStream;
printableStream << printable;
out << printableStream.str();
return out;
}
#endif
[[nodiscard]] constexpr std::string_view errorTypeToString(const wgpu::ErrorType errorType) {
switch (errorType) {
case wgpu::ErrorType::NoError: return "NO_ERROR";
case wgpu::ErrorType::Validation: return "VALIDATION";
case wgpu::ErrorType::OutOfMemory: return "OUT_OF_MEMORY";
case wgpu::ErrorType::Internal: return "INTERNAL";
case wgpu::ErrorType::Unknown: return "UNKNOWN";
}
}
[[nodiscard]] constexpr std::string_view powerPreferenceToString(
const wgpu::PowerPreference powerPreference) {
switch (powerPreference) {
case wgpu::PowerPreference::Undefined: return "UNDEFINED";
case wgpu::PowerPreference::LowPower: return "LOW_POWER";
case wgpu::PowerPreference::HighPerformance: return "HIGH_PERFORMANCE";
}
}
[[nodiscard]] static inline std::string_view powerPreferenceToString(
const wgpu::DawnAdapterPropertiesPowerPreference powerPreference) {
return powerPreferenceToString(powerPreference.powerPreference);
}
[[nodiscard]] constexpr std::string_view backendTypeToString(const wgpu::BackendType backendType) {
switch (backendType) {
case wgpu::BackendType::Undefined: return "UNDEFINED";
case wgpu::BackendType::Null: return "NULL";
case wgpu::BackendType::WebGPU: return "WEBGPU";
case wgpu::BackendType::D3D11: return "D3D11";
case wgpu::BackendType::D3D12: return "D3D12";
case wgpu::BackendType::Metal: return "METAL";
case wgpu::BackendType::Vulkan: return "VULKAN";
case wgpu::BackendType::OpenGL: return "OPENGL";
case wgpu::BackendType::OpenGLES: return "OPENGLES";
}
}
[[nodiscard]] constexpr std::string_view adapterTypeToString(const wgpu::AdapterType adapterType) {
switch (adapterType) {
case wgpu::AdapterType::DiscreteGPU: return "DISCRETE_GPU";
case wgpu::AdapterType::IntegratedGPU: return "INTEGRATED_GPU";
case wgpu::AdapterType::CPU: return "CPU";
case wgpu::AdapterType::Unknown: return "UNKNOWN";
}
}
/**
* Convenience template to print wgpu::RequestAdapterOptions
* @tparam STREAM_TYPE useful namely for streaming to either an utils::io::ostream OR
* utils::details::PanicStream, but also for any kind of stream
* @param out stream to print to
* @param options instance to be printed
* @return stream printed to
*
* Example usage (because including the template operator override itself does not compile):
* template<typename STREAM_TYPE>
* STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::RequestAdapterOptions const& options) noexcept {
* return streamInsertRequestAdapterOptions(out, options);
* }
*/
template<typename STREAM_TYPE>
STREAM_TYPE& streamInsertRequestAdapterOptions(STREAM_TYPE& out,
wgpu::RequestAdapterOptions const& options) noexcept {
out << "power preference " << powerPreferenceToString(options.powerPreference)
<< " force fallback adapter " << bool(options.forceFallbackAdapter) << " backend type "
<< backendTypeToString(options.backendType);
return out;
}
/**
* Convenience template to print wgpu::AdapterInfo
* @tparam STREAM_TYPE useful namely for streaming to either an utils::io::ostream OR
* utils::details::PanicStream, but also for any kind of stream
* @param out stream to print to
* @param options instance to be printed
* @return stream printed to
*
* Example usage (because including the template operator override itself does not compile):
* template<typename STREAM_TYPE>
* STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::AdapterInfo const& info) noexcept {
* return streamInsertRequestAdapterInfo(out, info);
* }
*/
template<class STREAM_TYPE>
STREAM_TYPE& streamInsertRequestAdapterInfo(STREAM_TYPE& out,
wgpu::AdapterInfo const& info) noexcept {
out << "vendor (" << info.vendorID << ") '" << info.vendor
<< "' device (" << info.deviceID << ") '" << info.device
<< "' adapter " << adapterTypeToString(info.adapterType)
<< " backend " << backendTypeToString(info.backendType)
<< " architecture '" << info.architecture
<< "' subgroupMinSize " << info.subgroupMinSize
<< " subgroupMaxSize " << info.subgroupMaxSize;
return out;
}
[[nodiscard]] constexpr std::string_view deviceLostReasonToString(
const wgpu::DeviceLostReason reason) {
switch (reason) {
case wgpu::DeviceLostReason::Unknown: return "UNKNOWN";
case wgpu::DeviceLostReason::Destroyed: return "DESTROYED";
case wgpu::DeviceLostReason::CallbackCancelled: return "CALLBACK_CANCELLED";
case wgpu::DeviceLostReason::FailedCreation: return "FAILED_CREATION";
}
}
[[nodiscard]] constexpr std::string_view filamentShaderStageToString(ShaderStage stage) {
switch (stage) {
case ShaderStage::VERTEX: return "vertex";
case ShaderStage::FRAGMENT: return "fragment";
case ShaderStage::COMPUTE: return "compute";
}
}
}// namespace filament::backend
#endif// TNT_FILAMENT_BACKEND_WEBGPUSTRINGS_H

View File

@@ -17,34 +17,50 @@
#include "webgpu/WebGPUSwapChain.h"
#include "webgpu/WebGPUConstants.h"
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
#include "webgpu/WebGPUStrings.h"
#endif
#include "backend/DriverEnums.h"
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <cstdint>
#include <sstream>
namespace {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
utils::io::ostream& operator<<(utils::io::ostream& out, const wgpu::TextureFormat format) noexcept {
return filament::backend::streamInsertWebGPUPrintable(out, format);
}
utils::io::ostream& operator<<(utils::io::ostream& out,
const wgpu::TextureUsage textureUsage) noexcept {
return filament::backend::streamInsertWebGPUPrintable(out, textureUsage);
}
utils::io::ostream& operator<<(utils::io::ostream& out,
const wgpu::PresentMode presentMode) noexcept {
return filament::backend::streamInsertWebGPUPrintable(out, presentMode);
}
utils::io::ostream& operator<<(utils::io::ostream& out,
const wgpu::CompositeAlphaMode alphaMode) noexcept {
return filament::backend::streamInsertWebGPUPrintable(out, alphaMode);
}
void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabilities) {
std::stringstream usagesStream{};
usagesStream << capabilities.usages;
FWGPU_LOGI << "WebGPU surface capabilities:" << utils::io::endl;
FWGPU_LOGI << " surface usages: " << usagesStream.str().data() << utils::io::endl;
FWGPU_LOGI << " surface usages: " << capabilities.usages << utils::io::endl;
FWGPU_LOGI << " surface formats (" << capabilities.formatCount << "):" << utils::io::endl;
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
[](wgpu::TextureFormat const format) {
std::stringstream formatStream{};
formatStream << format;
FWGPU_LOGI << " " << formatStream.str().data() << utils::io::endl;
FWGPU_LOGI << " " << format << utils::io::endl;
});
}
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount
@@ -53,9 +69,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
std::for_each(capabilities.presentModes,
capabilities.presentModes + capabilities.presentModeCount,
[](wgpu::PresentMode const presentMode) {
std::stringstream presentModeStream{};
presentModeStream << presentMode;
FWGPU_LOGI << " " << presentModeStream.str().data() << utils::io::endl;
FWGPU_LOGI << " " << presentMode << utils::io::endl;
});
}
FWGPU_LOGI << " surface alpha modes (" << capabilities.alphaModeCount
@@ -64,46 +78,30 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
std::for_each(capabilities.alphaModes,
capabilities.alphaModes + capabilities.alphaModeCount,
[](wgpu::CompositeAlphaMode const alphaMode) {
std::stringstream alphaModeStream{};
alphaModeStream << alphaMode;
FWGPU_LOGI << " " << alphaModeStream.str().data() << utils::io::endl;
FWGPU_LOGI << " " << alphaMode << utils::io::endl;
});
}
}
#endif
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
wgpu::TextureFormat depthFormat) {
std::stringstream formatStream{};
formatStream << config.format;
std::stringstream usageStream{};
usageStream << config.usage;
std::stringstream alphaModeStream{};
alphaModeStream << config.alphaMode;
std::stringstream presentModeStream{};
presentModeStream << config.presentMode;
std::stringstream depthFormatStream;
depthFormatStream << depthFormat;
FWGPU_LOGI << "WebGPU surface configuration:" << utils::io::endl;
FWGPU_LOGI << " surface format: " << formatStream.str() << utils::io::endl;
FWGPU_LOGI << " surface usage: " << usageStream.str() << utils::io::endl;
FWGPU_LOGI << " surface format: " << config.format << utils::io::endl;
FWGPU_LOGI << " surface usage: " << config.usage << utils::io::endl;
FWGPU_LOGI << " surface view formats (" << config.viewFormatCount << "):" << utils::io::endl;
if (config.viewFormatCount > 0 && config.viewFormats != nullptr) {
std::for_each(config.viewFormats, config.viewFormats + config.viewFormatCount,
[](wgpu::TextureFormat const viewFormat) {
std::stringstream viewFormatStream{};
viewFormatStream << viewFormat;
FWGPU_LOGI << " " << viewFormatStream.str().data() << utils::io::endl;
FWGPU_LOGI << " " << viewFormat << utils::io::endl;
});
}
FWGPU_LOGI << " surface alpha mode: " << alphaModeStream.str() << utils::io::endl;
FWGPU_LOGI << " surface alpha mode: " << config.alphaMode << utils::io::endl;
FWGPU_LOGI << " surface width: " << config.width << utils::io::endl;
FWGPU_LOGI << " surface height: " << config.height << utils::io::endl;
FWGPU_LOGI << " surface present mode: " << presentModeStream.str() << utils::io::endl;
FWGPU_LOGI << "WebGPU selected depth format: " << depthFormatStream.str() << utils::io::endl;
FWGPU_LOGI << " surface present mode: " << config.presentMode << utils::io::endl;
FWGPU_LOGI << "WebGPU selected depth format: " << depthFormat << utils::io::endl;
}
#endif
#endif// FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
[[nodiscard]] constexpr wgpu::TextureFormat selectColorFormat(size_t availableFormatsCount,
wgpu::TextureFormat const* availableFormats, bool useSRGBColorSpace) {

View File

@@ -18,16 +18,30 @@
#include "webgpu/WebGPUConstants.h"
#include "webgpu/WebGPUDriver.h"
#include "webgpu/WebGPUStrings.h"
#include <backend/DriverEnums.h>
#include <backend/Platform.h>
#include <utils/Hash.h>
#include <utils/Panic.h>
#include <utils/ostream.h>
#include <dawn/webgpu_cpp_print.h>
#include <webgpu/webgpu_cpp.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <sstream>
#include <functional>
#include <mutex>
#include <sstream>// for one-time-ish setup string concatenation, namely error messaging
#include <unordered_set>
#include <utility>
#include <vector>
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
#include <string_view>
#include <variant>
#endif
/**
* WebGPU Backend implementation common across platforms or operating systems (at least for now).
@@ -39,6 +53,213 @@ namespace filament::backend {
namespace {
constexpr std::array REQUIRED_FEATURES = {
wgpu::FeatureName::TransientAttachments };
constexpr std::array OPTIONAL_FEATURES = {
wgpu::FeatureName::DepthClipControl,
wgpu::FeatureName::Depth32FloatStencil8,
wgpu::FeatureName::CoreFeaturesAndLimits };
enum class LimitToValidate : uint8_t {
begin = 0,// needs to be first for iterating through all possible values in the enum
MAX_BIND_GROUPS,
MAX_BINDINGS_PER_BIND_GROUP,
MAX_SAMPLERS_PER_SHADER_STAGE,
MAX_STORAGE_BUFFERS_PER_SHADER_STAGE,
MAX_VERTEX_BUFFERS,
MAX_VERTEX_ATTRIBUTES,
end// needs to be last for iterating through all possible values in the enum
// (Sentinel value)
};
// Only the attributes identified by the LimitToValidate enum above will actually be validated
// at runtime. Thus, if you add a limit here add the associated enum value in LimitToValidate!
constexpr wgpu::Limits REQUIRED_LIMITS = {
.maxBindGroups = filament::backend::MAX_DESCRIPTOR_SET_COUNT,
.maxBindingsPerBindGroup = filament::backend::MAX_DESCRIPTOR_COUNT,
.maxSamplersPerShaderStage = 16, // TODO should be set to filament::backend::MAX_SAMPLER_COUNT,
.maxStorageBuffersPerShaderStage = filament::backend::MAX_SSBO_COUNT,
.maxVertexBuffers = 8, // TODO should be set to filament::backend::MAX_VERTEX_BUFFER_COUNT,
.maxVertexAttributes = filament::backend::MAX_VERTEX_ATTRIBUTE_COUNT,
};
constexpr void forEachLimitToValidate(std::function<bool(LimitToValidate const)> const& func) {
for (auto limit = static_cast<uint8_t>(LimitToValidate::begin) + 1;
limit < static_cast<uint8_t>(LimitToValidate::end); limit++) {
auto castedLimit = static_cast<LimitToValidate>(limit);
bool castedToValidValue = false;
switch (castedLimit) {
case LimitToValidate::begin:
case LimitToValidate::MAX_BIND_GROUPS:
case LimitToValidate::MAX_BINDINGS_PER_BIND_GROUP:
case LimitToValidate::MAX_SAMPLERS_PER_SHADER_STAGE:
case LimitToValidate::MAX_STORAGE_BUFFERS_PER_SHADER_STAGE:
case LimitToValidate::MAX_VERTEX_BUFFERS:
case LimitToValidate::MAX_VERTEX_ATTRIBUTES:
case LimitToValidate::end:
castedToValidValue = true;
break;
}
assert_invariant(castedToValidValue &&
"LimitToValidate enum values are not sequentially incremented by 1? "
"Compilers normally do this by default; which compiler was used here?");
if (func(castedLimit)) {
break;
}
}
}
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
utils::io::ostream& operator<<(utils::io::ostream& out,
const wgpu::WGSLLanguageFeatureName languageFeatureName) noexcept {
return streamInsertWebGPUPrintable(out, languageFeatureName);
}
utils::io::ostream& operator<<(utils::io::ostream& out,
const wgpu::FeatureName featureName) noexcept {
return streamInsertWebGPUPrintable(out, featureName);
}
#endif
template<typename STREAM_TYPE>
STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::RequestAdapterOptions const& options) noexcept {
return streamInsertRequestAdapterOptions(out, options);
}
template<class STREAM_TYPE>
STREAM_TYPE& operator<<(STREAM_TYPE& out, wgpu::AdapterInfo const& info) noexcept {
return streamInsertRequestAdapterInfo(out, info);
}
constexpr bool isDefined(uint32_t limit) { return limit < wgpu::kLimitU32Undefined; }
[[maybe_unused]] constexpr bool isDefined(uint64_t limit) {
return limit < wgpu::kLimitU64Undefined;
}
void assertLimitsAreExpressedInRequirementsStruct() {
forEachLimitToValidate([](LimitToValidate const limit) {
switch (limit) {
case LimitToValidate::MAX_BIND_GROUPS:
static_assert(isDefined(REQUIRED_LIMITS.maxBindGroups));
break;
case LimitToValidate::MAX_BINDINGS_PER_BIND_GROUP:
static_assert(isDefined(REQUIRED_LIMITS.maxBindingsPerBindGroup));
break;
case LimitToValidate::MAX_SAMPLERS_PER_SHADER_STAGE:
static_assert(isDefined(REQUIRED_LIMITS.maxSamplersPerShaderStage));
break;
case LimitToValidate::MAX_STORAGE_BUFFERS_PER_SHADER_STAGE:
static_assert(isDefined(REQUIRED_LIMITS.maxStorageBuffersPerShaderStage));
break;
case LimitToValidate::MAX_VERTEX_BUFFERS:
static_assert(isDefined(REQUIRED_LIMITS.maxVertexBuffers));
break;
case LimitToValidate::MAX_VERTEX_ATTRIBUTES:
static_assert(isDefined(REQUIRED_LIMITS.maxVertexAttributes));
break;
case LimitToValidate::begin:
case LimitToValidate::end:
break;
}
return false; // should not break
});
}
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
[[nodiscard]] std::string_view toString(LimitToValidate limit) {
switch (limit) {
case LimitToValidate::MAX_BIND_GROUPS:
return "MAX_BIND_GROUPS";
case LimitToValidate::MAX_BINDINGS_PER_BIND_GROUP:
return "MAX_BINDINGS_PER_BIND_GROUP";
case LimitToValidate::MAX_SAMPLERS_PER_SHADER_STAGE:
return "MAX_SAMPLERS_PER_SHADER_STAGE";
case LimitToValidate::MAX_STORAGE_BUFFERS_PER_SHADER_STAGE:
return "MAX_STORAGE_BUFFERS_PER_SHADER_STAGE";
case LimitToValidate::MAX_VERTEX_BUFFERS:
return "MAX_VERTEX_BUFFERS";
case LimitToValidate::MAX_VERTEX_ATTRIBUTES:
return "MAX_VERTEX_ATTRIBUTES";
case LimitToValidate::begin:
case LimitToValidate::end:
return "UNKNOWN";
}
}
#endif
[[nodiscard]] uint64_t valueForLimit(wgpu::Limits const& limits, LimitToValidate limit) {
switch (limit) {
case LimitToValidate::MAX_BIND_GROUPS:
return limits.maxBindGroups;
case LimitToValidate::MAX_BINDINGS_PER_BIND_GROUP:
return limits.maxBindingsPerBindGroup;
case LimitToValidate::MAX_SAMPLERS_PER_SHADER_STAGE:
return limits.maxSamplersPerShaderStage;
case LimitToValidate::MAX_STORAGE_BUFFERS_PER_SHADER_STAGE:
return limits.maxStorageBuffersPerShaderStage;
case LimitToValidate::MAX_VERTEX_BUFFERS:
return limits.maxVertexBuffers;
case LimitToValidate::MAX_VERTEX_ATTRIBUTES:
return limits.maxVertexAttributes;
case LimitToValidate::begin:
case LimitToValidate::end:
return 0;
}
}
[[nodiscard]] bool satisfiesLimits(wgpu::Limits const& limits) {
size_t failedLimitCount = 0;
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
std::stringstream failedLimitsStream;
#endif
forEachLimitToValidate([&](LimitToValidate const limit) {
uint64_t supportedValue = valueForLimit(limits, limit);
uint64_t requiredValue = valueForLimit(REQUIRED_LIMITS, limit);
if (supportedValue < requiredValue) {
failedLimitCount++;
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
failedLimitsStream << toString(limit) << " required " << requiredValue << " but found "
<< supportedValue << ". ";
#else
return true;// break early if not printing anything
#endif
}
return false;// don't break, keep going
});
if (failedLimitCount == 0) {
return true;
}
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
FWGPU_LOGI << "Failed to satisfy " << failedLimitCount
<< " limit(s): " << failedLimitsStream.str();
#endif
return false;
}
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printInstanceDetails(wgpu::Instance const& instance) {
wgpu::SupportedWGSLLanguageFeatures supportedWGSLLanguageFeatures{};
if (!instance.GetWGSLLanguageFeatures(&supportedWGSLLanguageFeatures)) {
FWGPU_LOGW << "Failed to get WebGPU instance supported WGSL language features"
<< utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU instance supported WGSL language features ("
<< supportedWGSLLanguageFeatures.featureCount << "):" << utils::io::endl;
if (supportedWGSLLanguageFeatures.featureCount > 0 &&
supportedWGSLLanguageFeatures.features != nullptr) {
std::for_each(supportedWGSLLanguageFeatures.features,
supportedWGSLLanguageFeatures.features +
supportedWGSLLanguageFeatures.featureCount,
[](wgpu::WGSLLanguageFeatureName const featureName) {
FWGPU_LOGI << " " << featureName << utils::io::endl;
});
}
}
}
#endif
//either returns a valid instance or panics
[[nodiscard]] wgpu::Instance createInstance() {
wgpu::DawnTogglesDescriptor dawnTogglesDescriptor{};
@@ -63,119 +284,409 @@ namespace {
}
};
wgpu::Instance instance = wgpu::CreateInstance(&instanceDescriptor);
FILAMENT_CHECK_POSTCONDITION(instance != nullptr) << "Unable to create webgpu instance.";
FILAMENT_CHECK_POSTCONDITION(instance != nullptr) << "Unable to create WebGPU instance.";
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printInstanceDetails(instance);
#endif
return instance;
}
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printLimit(std::string_view name, const std::variant<uint32_t, uint64_t> value) {
FWGPU_LOGI << " " << name.data() << ": ";
bool undefined = true;
if (std::holds_alternative<uint32_t>(value)) {
if (std::get<uint32_t>(value) != WGPU_LIMIT_U32_UNDEFINED) {
undefined = false;
FWGPU_LOGI << std::get<uint32_t>(value);
}
} else if (std::holds_alternative<uint64_t>(value)) {
if (std::get<uint64_t>(value) != WGPU_LIMIT_U64_UNDEFINED) {
undefined = false;
FWGPU_LOGI << std::get<uint64_t>(value);
}
}
if (undefined) {
FWGPU_LOGI << "UNDEFINED";
}
FWGPU_LOGI << utils::io::endl;
}
#endif// FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printLimits(wgpu::Limits const& limits) {
printLimit("maxTextureDimension1D", limits.maxTextureDimension1D);
printLimit("maxTextureDimension2D", limits.maxTextureDimension2D);
printLimit("maxTextureDimension3D", limits.maxTextureDimension3D);
printLimit("maxTextureArrayLayers", limits.maxTextureArrayLayers);
printLimit("maxBindGroups", limits.maxBindGroups);
printLimit("maxBindGroupsPlusVertexBuffers", limits.maxBindGroupsPlusVertexBuffers);
printLimit("maxBindingsPerBindGroup", limits.maxBindingsPerBindGroup);
printLimit("maxDynamicUniformBuffersPerPipelineLayout",
limits.maxDynamicUniformBuffersPerPipelineLayout);
printLimit("maxDynamicStorageBuffersPerPipelineLayout",
limits.maxDynamicStorageBuffersPerPipelineLayout);
printLimit("maxSampledTexturesPerShaderStage", limits.maxSampledTexturesPerShaderStage);
printLimit("maxSamplersPerShaderStage", limits.maxSamplersPerShaderStage);
printLimit("maxStorageBuffersPerShaderStage", limits.maxStorageBuffersPerShaderStage);
printLimit("maxStorageTexturesPerShaderStage", limits.maxStorageTexturesPerShaderStage);
printLimit("maxUniformBuffersPerShaderStage", limits.maxUniformBuffersPerShaderStage);
printLimit("maxUniformBufferBindingSize", limits.maxUniformBufferBindingSize);
printLimit("maxStorageBufferBindingSize", limits.maxStorageBufferBindingSize);
printLimit("minUniformBufferOffsetAlignment", limits.minUniformBufferOffsetAlignment);
printLimit("minStorageBufferOffsetAlignment", limits.minStorageBufferOffsetAlignment);
printLimit("maxVertexBuffers", limits.maxVertexBuffers);
printLimit("maxBufferSize", limits.maxBufferSize);
printLimit("maxVertexAttributes", limits.maxVertexAttributes);
printLimit("maxVertexBufferArrayStride", limits.maxVertexBufferArrayStride);
printLimit("maxInterStageShaderVariables", limits.maxInterStageShaderVariables);
printLimit("maxColorAttachments", limits.maxColorAttachments);
printLimit("maxColorAttachmentBytesPerSample", limits.maxColorAttachmentBytesPerSample);
printLimit("maxComputeWorkgroupStorageSize", limits.maxComputeWorkgroupStorageSize);
printLimit("maxComputeInvocationsPerWorkgroup", limits.maxComputeInvocationsPerWorkgroup);
printLimit("maxComputeWorkgroupSizeX", limits.maxComputeWorkgroupSizeX);
printLimit("maxComputeWorkgroupSizeY", limits.maxComputeWorkgroupSizeY);
printLimit("maxComputeWorkgroupSizeZ", limits.maxComputeWorkgroupSizeZ);
printLimit("maxComputeWorkgroupsPerDimension", limits.maxComputeWorkgroupsPerDimension);
printLimit("maxStorageBuffersInVertexStage", limits.maxStorageBuffersInVertexStage);
printLimit("maxStorageTexturesInVertexStage", limits.maxStorageTexturesInVertexStage);
printLimit("maxStorageBuffersInFragmentStage", limits.maxStorageBuffersInFragmentStage);
printLimit("maxStorageTexturesInFragmentStage", limits.maxStorageTexturesInFragmentStage);
}
#endif
struct AdapterDetails final {
AdapterDetails()
: AdapterDetails(nullptr) {}
explicit AdapterDetails(wgpu::Adapter adapter)
: adapter(std::move(adapter)) {
info.nextInChain = &powerPreference;
}
AdapterDetails(wgpu::AdapterInfo&& info,
wgpu::DawnAdapterPropertiesPowerPreference& powerPreference, wgpu::Adapter&& adapter)
: info(std::move(info)),
powerPreference(powerPreference),
adapter(std::move(adapter)) {}
AdapterDetails& operator=(AdapterDetails&& other) noexcept {
adapter = std::exchange(other.adapter, nullptr);
info = std::exchange(other.info, {});
powerPreference = std::exchange(other.powerPreference, {});
return *this;
}
wgpu::AdapterInfo info{};
wgpu::DawnAdapterPropertiesPowerPreference powerPreference{};
wgpu::Adapter adapter = nullptr;
bool operator==(AdapterDetails const& other) const {
return info.vendorID == other.info.vendorID &&
info.deviceID == other.info.deviceID &&
info.backendType == other.info.backendType &&
info.adapterType == other.info.adapterType &&
powerPreference.powerPreference == other.powerPreference.powerPreference;
}
};
template<class STREAM_TYPE>
STREAM_TYPE& operator<<(STREAM_TYPE& out, AdapterDetails const& details) noexcept {
out << details.info
<< " power preference " << powerPreferenceToString(details.powerPreference);
return out;
}
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printAdapterDetails(AdapterDetails const& details) {
FWGPU_LOGI << "Selected WebGPU adapter info: " << details << utils::io::endl;
wgpu::SupportedFeatures supportedFeatures{};
details.adapter.GetFeatures(&supportedFeatures);
FWGPU_LOGI << "WebGPU adapter supported features (" << supportedFeatures.featureCount
<< "):" << utils::io::endl;
if (supportedFeatures.featureCount > 0 && supportedFeatures.features != nullptr) {
std::for_each(supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount,
[](wgpu::FeatureName const featureName) {
FWGPU_LOGI << " " << featureName << utils::io::endl;
});
}
wgpu::Limits supportedLimits{};
if (!details.adapter.GetLimits(&supportedLimits)) {
FWGPU_LOGW << "Failed to get WebGPU adapter supported limits" << utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU adapter supported limits:" << utils::io::endl;
printLimits(supportedLimits);
}
}
#endif
struct AdapterDetailsHash final {
size_t operator()(AdapterDetails const& details) const {
using utils::hash::combine;
size_t hash = 0;
combine(hash, details.info.vendorID);
combine(hash, details.info.deviceID);
combine(hash, static_cast<uint32_t>(details.info.backendType));
combine(hash, static_cast<uint32_t>(details.info.adapterType));
combine(hash, static_cast<uint32_t>(details.powerPreference.powerPreference));
return hash;
}
};
[[nodiscard]] bool adapterMeetsMinimumRequirements(AdapterDetails const& details) {
// check if the adapter has all required features...
if (!std::all_of(REQUIRED_FEATURES.begin(), REQUIRED_FEATURES.end(),
[&details](auto const& featureName) {
return details.adapter.HasFeature(featureName);
})) {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
FWGPU_LOGI << "WebGPU adapter " << details
<< " does not have the minimum required features." << utils::io::endl;
FWGPU_LOGI << " Missing required feature(s): ";
for (wgpu::FeatureName const& requiredFeature: REQUIRED_FEATURES) {
if (!details.adapter.HasFeature(requiredFeature)) {
FWGPU_LOGI << requiredFeature << " ";
}
}
FWGPU_LOGI << utils::io::endl;
#endif
return false;
}
wgpu::Limits supportedLimits {};
FILAMENT_CHECK_POSTCONDITION(details.adapter.GetLimits(&supportedLimits))
<< "Failed to get limits for WebGPU adapter: " << details;
if (!satisfiesLimits(supportedLimits)) {
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
FWGPU_LOGI << " (for WebGPU adapter " << details << ")" << utils::io::endl;
#endif
return false;
}
return true;
}
// trys to get a set of compatible adapters (whether the meet minimum requirements or not).
// It panics if none are found.
[[nodiscard]] std::unordered_set<AdapterDetails, AdapterDetailsHash> requestCompatibleAdapters(
wgpu::Instance const& instance, std::vector<wgpu::RequestAdapterOptions> const& requests) {
// make the series of requests asynchronously, collecting compatible adapter results...
std::unordered_set<AdapterDetails, AdapterDetailsHash> compatibleAdapters;
compatibleAdapters.reserve(requests.size());
std::mutex adaptersMutex;
std::vector<wgpu::Future> futures(requests.size());
for (size_t i = 0; i < requests.size(); i++) {
wgpu::RequestAdapterOptions const& options = requests[i];
futures[i] = instance.RequestAdapter(&options, wgpu::CallbackMode::WaitAnyOnly,
[&options, &compatibleAdapters,
&adaptersMutex](wgpu::RequestAdapterStatus const status,
wgpu::Adapter const& readyAdapter, wgpu::StringView const message) {
FILAMENT_CHECK_POSTCONDITION(
status != wgpu::RequestAdapterStatus::CallbackCancelled)
<< "Failed to request a WebGPU adapter due to the request callback "
"being cancelled? Options: "
<< options << " " << message.data;
FILAMENT_CHECK_POSTCONDITION(status != wgpu::RequestAdapterStatus::Error)
<< "Failed to request a WebGPU adapter due to an error. Options: "
<< options << " Error: " << message.data;
if (status == wgpu::RequestAdapterStatus::Success) {
AdapterDetails details = AdapterDetails(readyAdapter);
FILAMENT_CHECK_POSTCONDITION(readyAdapter.GetInfo(&details.info))
<< "Failed to get info for adapter (options: " << options << ")";
const std::lock_guard<std::mutex> lock(adaptersMutex);
compatibleAdapters.emplace(std::move(details.info), details.powerPreference,
std::move(details.adapter));
return;
}
assert_invariant(status == wgpu::RequestAdapterStatus::Unavailable);
});
}
// wait for all the results to return...
for (size_t i = 0; i < futures.size(); i++) {
wgpu::RequestAdapterOptions const& options = requests[i];
wgpu::Future& future = futures[i];
wgpu::WaitStatus status = instance.WaitAny(future, REQUEST_ADAPTER_TIMEOUT_NANOSECONDS);
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::TimedOut)
<< "Timed out requesting a WebGPU adapter with options " << options;
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::Error)
<< "Failed to request a WebGPU adapter with options " << options
<< " due to an error (as request was made synchronous)";
assert_invariant(status == wgpu::WaitStatus::Success);
}
FILAMENT_CHECK_POSTCONDITION(!compatibleAdapters.empty()) << "No WebGPU adapters found!";
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
FWGPU_LOGI << compatibleAdapters.size() << " WebGPU adapter(s) found:" << utils::io::endl;
for (auto& details: compatibleAdapters) {
FWGPU_LOGI << " WebGPU adapter: " << details << utils::io::endl;
}
#endif
return compatibleAdapters;
}
// selects one preferred adapter or panics if none can be found
wgpu::Adapter selectPreferredAdapter(
std::unordered_set<AdapterDetails, AdapterDetailsHash> const& compatibleAdapters) {
// for each unique adapter...
AdapterDetails const* selectedAdapter = nullptr;
size_t selectedOptionalFeaturesCount = 0;
// choose the most desirable adapter that meets the minimum requirements...
for (AdapterDetails const& details: compatibleAdapters) {
if (!adapterMeetsMinimumRequirements(details)) {
continue;
}
size_t supportedOptionalFeaturesCount = std::count_if(OPTIONAL_FEATURES.begin(),
OPTIONAL_FEATURES.end(), [&details](auto const& featureName) {
return details.adapter.HasFeature(featureName);
});
// select this one if it is the first (perhaps it is the only one anyway)...
if (selectedAdapter == nullptr) {
selectedAdapter = &details;
selectedOptionalFeaturesCount = supportedOptionalFeaturesCount;
continue;
}
// NOTE: we can make this selection logic more dynamic, configurable,
// and robust in the future as needed, such as the app preferring lower power
// (prioritizing battery life) or weighting specific optional features in its decision, etc.
// This is just a good start that works for now.
//
// first, prefer higher performance...
if (details.powerPreference.powerPreference >
selectedAdapter->powerPreference.powerPreference) {
selectedAdapter = &details;
selectedOptionalFeaturesCount = supportedOptionalFeaturesCount;
continue;
}
// second, prefer more optional features supported...
if (supportedOptionalFeaturesCount > selectedOptionalFeaturesCount) {
selectedAdapter = &details;
selectedOptionalFeaturesCount = supportedOptionalFeaturesCount;
continue;
}
// otherwise keep the selected adapter
assert_invariant(selectedAdapter != nullptr);
}
FILAMENT_CHECK_POSTCONDITION(selectedAdapter != nullptr)
<< "Could not find a WebGPU adapter that meets the minimum requirements.";
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printAdapterDetails(*selectedAdapter);
#endif
return selectedAdapter->adapter;
}
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
void printDeviceDetails(wgpu::Device const& device) {
wgpu::SupportedFeatures supportedFeatures{};
device.GetFeatures(&supportedFeatures);
FWGPU_LOGI << "WebGPU device supported features (" << supportedFeatures.featureCount
<< "):" << utils::io::endl;
if (supportedFeatures.featureCount > 0 && supportedFeatures.features != nullptr) {
std::for_each(supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount,
[](wgpu::FeatureName const featureName) {
FWGPU_LOGI << " " << featureName << utils::io::endl;
});
}
wgpu::Limits supportedLimits{};
if (!device.GetLimits(&supportedLimits)) {
FWGPU_LOGW << "Failed to get WebGPU supported device limits" << utils::io::endl;
} else {
FWGPU_LOGI << "WebGPU device supported limits:" << utils::io::endl;
printLimits(supportedLimits);
}
}
#endif
}// namespace
wgpu::Adapter WebGPUPlatform::requestAdapter(wgpu::Surface const& surface) {
// TODO consider power preference etc. (can be custom preferences passed to the platform or
// based on whether this is a Mobile or Desktop system,
// etc...)
wgpu::RequestAdapterOptions adaptorOptions{ .compatibleSurface = surface };
// note this just gets the first adapter
wgpu::Adapter adapter = nullptr;
wgpu::WaitStatus status = mInstance.WaitAny(
mInstance.RequestAdapter(&adaptorOptions, wgpu::CallbackMode::WaitAnyOnly,
[&adapter](wgpu::RequestAdapterStatus const status,
wgpu::Adapter const& readyAdapter, wgpu::StringView const message) {
// TODO consider more robust error handling
FILAMENT_CHECK_POSTCONDITION(status == wgpu::RequestAdapterStatus::Success)
<< "Unable to request a WebGPU adapter. Status "
<< static_cast<uint32_t>(status)
<< " with message: " << message.data;
adapter = readyAdapter;
}),
UINT16_MAX);// TODO define reasonable timeout (or do this asynchronously)
FILAMENT_CHECK_POSTCONDITION(status == wgpu::WaitStatus::Success)
<< "Non-successful wait status requesting a WebGPU adapter "
<< static_cast<uint32_t>(status);
FILAMENT_CHECK_POSTCONDITION(adapter != nullptr)
<< "Failed to get a WebGPU adapter for the platform";
// TODO consider validating adapter has required features and/or limits
return adapter;
assertLimitsAreExpressedInRequirementsStruct();
std::vector<wgpu::RequestAdapterOptions> requests = getAdapterOptions();
for (auto& request: requests) {
request.compatibleSurface = surface;
}
const std::unordered_set<AdapterDetails, AdapterDetailsHash> compatibleAdapters =
requestCompatibleAdapters(mInstance, requests);
return selectPreferredAdapter(compatibleAdapters);
}
wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
// TODO consider passing limits
constexpr std::array optionalFeatures = { wgpu::FeatureName::DepthClipControl,
wgpu::FeatureName::Depth32FloatStencil8, wgpu::FeatureName::CoreFeaturesAndLimits };
constexpr std::array requiredFeatures = { wgpu::FeatureName::TransientAttachments };
wgpu::SupportedFeatures supportedFeatures;
adapter.GetFeatures(&supportedFeatures);
std::vector<wgpu::FeatureName> enabledFeatures;
enabledFeatures.reserve(requiredFeatures.size() + optionalFeatures.size());
std::set_intersection(supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount, requiredFeatures.begin(),
requiredFeatures.end(), std::back_inserter(enabledFeatures));
if (enabledFeatures.size() != requiredFeatures.size()) {
std::vector<wgpu::FeatureName> missingFeatures;
std::set_difference(requiredFeatures.begin(), requiredFeatures.end(),
supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount,
std::back_inserter(missingFeatures));
std::stringstream missingFeaturesStream{};
for (const auto& entry: missingFeatures) {
missingFeaturesStream << std::to_string(static_cast<uint32_t>(entry)) << " ";
}
PANIC_POSTCONDITION("Some required features are not available %s/n",
missingFeaturesStream.str().c_str());
enabledFeatures.reserve(REQUIRED_FEATURES.size() + OPTIONAL_FEATURES.size());
for (auto const& requiredFeature : REQUIRED_FEATURES) {
enabledFeatures.push_back(requiredFeature);
}
for (auto const& optionalFeature : OPTIONAL_FEATURES) {
if (adapter.HasFeature(optionalFeature)) {
enabledFeatures.push_back(optionalFeature);
}
}
std::set_intersection(supportedFeatures.features,
supportedFeatures.features + supportedFeatures.featureCount, optionalFeatures.begin(),
optionalFeatures.end(), std::back_inserter(enabledFeatures));
wgpu::DeviceDescriptor deviceDescriptor{};
deviceDescriptor.label = "graphics_device";
deviceDescriptor.defaultQueue.label = "default_queue";
deviceDescriptor.requiredFeatureCount = enabledFeatures.size();
deviceDescriptor.requiredFeatures = enabledFeatures.data();
deviceDescriptor.requiredLimits = &REQUIRED_LIMITS;
deviceDescriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::Device const&, wgpu::DeviceLostReason const& reason,
wgpu::StringView message) {
if (reason == wgpu::DeviceLostReason::Destroyed) {
#if FWGPU_ENABLED(FWGPU_DEBUG_VALIDATION)
FWGPU_LOGD << "WebGPU device lost due to being destroyed (expected)"
<< utils::io::endl;
#endif
return;
}
// TODO try recreating the device instead of just panicking
std::stringstream reasonStream{};
reasonStream << reason;
FILAMENT_CHECK_POSTCONDITION(reason != wgpu::DeviceLostReason::Destroyed)
<< "WebGPU device lost: " << reasonStream.str() << " " << message.data;
<< "WebGPU device lost: " << deviceLostReasonToString(reason) << " "
<< message.data;
});
deviceDescriptor.SetUncapturedErrorCallback(
[](wgpu::Device const&, wgpu::ErrorType errorType, wgpu::StringView message) {
std::stringstream typeStream{};
typeStream << errorType;
FWGPU_LOGE << "WebGPU device error: " << typeStream.str() << " " << message.data
<< utils::io::endl;
FWGPU_LOGE << "WebGPU device error: " << errorTypeToString(errorType) << " "
<< message.data << utils::io::endl;
});
wgpu::Device device = nullptr;
wgpu::WaitStatus status = mInstance.WaitAny(
adapter.RequestDevice(&deviceDescriptor, wgpu::CallbackMode::WaitAnyOnly,
[&device](wgpu::RequestDeviceStatus const status,
wgpu::Device const& readyDevice, wgpu::StringView const message) {
FILAMENT_CHECK_POSTCONDITION(status == wgpu::RequestDeviceStatus::Success)
<< "Unable to request a WebGPU device. Status: "
<< static_cast<uint32_t>(status)
<< " with message: " << message.data;
FILAMENT_CHECK_POSTCONDITION(
status != wgpu::RequestDeviceStatus::CallbackCancelled)
<< "Failed to request a WebGPU device due to the callback being "
"cancelled? "
<< message;
FILAMENT_CHECK_POSTCONDITION(status != wgpu::RequestDeviceStatus::Error)
<< "Failed to request a WebGPU device due to en error: " << message;
assert_invariant(status == wgpu::RequestDeviceStatus::Success);
device = readyDevice;
}),
UINT64_MAX);// TODO define reasonable timeout (or do this asynchronously)
FILAMENT_CHECK_POSTCONDITION(status == wgpu::WaitStatus::Success)
<< "Non-successful wait status requesting a WebGPU device "
<< static_cast<uint32_t>(status);
REQUEST_DEVICE_TIMEOUT_NANOSECONDS);
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::TimedOut)
<< "Failed to request a WebGPU device due to a timeout.";
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::Error)
<< "Failed to request a WebGPU device due to an error.";
assert_invariant(status == wgpu::WaitStatus::Success);
FILAMENT_CHECK_POSTCONDITION(device != nullptr)
<< "Failed to get a WebGPU device for the platform.";
<< "Failed to get a WebGPU device for the platform. null device returned?";
size_t missingFeatures = 0;
std::stringstream featureNamesStream;
for (wgpu::FeatureName const& enabledFeature : enabledFeatures) {
if (!device.HasFeature(enabledFeature)) {
missingFeatures += 1;
featureNamesStream << enabledFeature << " ";
}
}
if (missingFeatures > 0) {
PANIC_POSTCONDITION("WebGPU device is missing %d requested feature(s) even though the "
"adapter should support them: %s\n",
missingFeatures, featureNamesStream.str().data());
}
wgpu::Limits supportedLimits {};
FILAMENT_CHECK_POSTCONDITION(device.GetLimits(&supportedLimits))
<< "Failed to get limits for the device?";
FILAMENT_CHECK_POSTCONDITION(satisfiesLimits(supportedLimits))
<< "WebGPU device failed to statify required limits.";
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
printDeviceDetails(device);
#endif
return device;
}

View File

@@ -21,7 +21,9 @@
#include <android/native_window.h>
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstdint>
#include <vector>
/**
* Android OS specific implementation aspects of the WebGPU backend
@@ -29,6 +31,30 @@
namespace filament::backend {
std::vector<wgpu::RequestAdapterOptions> WebGPUPlatform::getAdapterOptions() {
constexpr std::array powerPreferences = {
wgpu::PowerPreference::HighPerformance,
wgpu::PowerPreference::LowPower };
constexpr std::array backendTypes = { wgpu::BackendType::Vulkan, wgpu::BackendType::OpenGLES };
constexpr std::array forceFallbackAdapters = { false, true };
constexpr size_t totalCombinations =
powerPreferences.size() * backendTypes.size() * forceFallbackAdapters.size();
std::vector<wgpu::RequestAdapterOptions> requests;
requests.reserve(totalCombinations);
for (auto powerPreference: powerPreferences) {
for (auto backendType: backendTypes) {
for (auto forceFallbackAdapter: forceFallbackAdapters) {
requests.emplace_back(
wgpu::RequestAdapterOptions{
.powerPreference = powerPreference,
.forceFallbackAdapter = forceFallbackAdapter,
.backendType = backendType });
}
}
}
return requests;
}
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
ANativeWindow* window = static_cast<ANativeWindow*>(nativeWindow);
return wgpu::Extent2D{

View File

@@ -21,7 +21,9 @@
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstdint>
#include <vector>
// Platform specific includes and defines
#include <Cocoa/Cocoa.h>
@@ -33,6 +35,30 @@
namespace filament::backend {
std::vector<wgpu::RequestAdapterOptions> WebGPUPlatform::getAdapterOptions() {
constexpr std::array powerPreferences = {
wgpu::PowerPreference::HighPerformance,
wgpu::PowerPreference::LowPower };
constexpr std::array backendTypes = { wgpu::BackendType::Metal };
constexpr std::array forceFallbackAdapters = { false, true };
constexpr size_t totalCombinations =
powerPreferences.size() * backendTypes.size() * forceFallbackAdapters.size();
std::vector<wgpu::RequestAdapterOptions> requests;
requests.reserve(totalCombinations);
for (auto powerPreference: powerPreferences) {
for (auto backendType: backendTypes) {
for (auto forceFallbackAdapter: forceFallbackAdapters) {
requests.emplace_back(
wgpu::RequestAdapterOptions{
.powerPreference = powerPreference,
.forceFallbackAdapter = forceFallbackAdapter,
.backendType = backendType });
}
}
}
return requests;
}
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
// Both IOS and MacOS expects CAMetalLayer.
CAMetalLayer* metalLayer = (__bridge CAMetalLayer*) nativeWindow;

View File

@@ -21,8 +21,10 @@
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>
#if defined(__linux__) || defined(__FreeBSD__)
#define LINUX_OR_FREEBSD 1
@@ -79,6 +81,33 @@
namespace filament::backend {
std::vector<wgpu::RequestAdapterOptions> WebGPUPlatform::getAdapterOptions() {
constexpr std::array powerPreferences = {
wgpu::PowerPreference::HighPerformance,
wgpu::PowerPreference::LowPower };
constexpr std::array backendTypes = {
wgpu::BackendType::Vulkan,
wgpu::BackendType::OpenGL,
wgpu::BackendType::OpenGLES };
constexpr std::array forceFallbackAdapters = { false, true };
constexpr size_t totalCombinations =
powerPreferences.size() * backendTypes.size() * forceFallbackAdapters.size();
std::vector<wgpu::RequestAdapterOptions> requests;
requests.reserve(totalCombinations);
for (auto powerPreference: powerPreferences) {
for (auto backendType: backendTypes) {
for (auto forceFallbackAdapter: forceFallbackAdapters) {
requests.emplace_back(
wgpu::RequestAdapterOptions{
.powerPreference = powerPreference,
.forceFallbackAdapter = forceFallbackAdapter,
.backendType = backendType });
}
}
}
return requests;
}
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
auto surfaceExtent = wgpu::Extent2D{};
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)

View File

@@ -20,7 +20,9 @@
#include <webgpu/webgpu_cpp.h>
#include <array>
#include <cstdint>
#include <vector>
#include <Windows.h>
@@ -30,6 +32,35 @@
namespace filament::backend {
std::vector<wgpu::RequestAdapterOptions> WebGPUPlatform::getAdapterOptions() {
constexpr std::array powerPreferences = {
wgpu::PowerPreference::HighPerformance,
wgpu::PowerPreference::LowPower };
constexpr std::array backendTypes = {
wgpu::BackendType::Vulkan,
wgpu::BackendType::OpenGL,
wgpu::BackendType::OpenGLES,
wgpu::BackendType::D3D12,
wgpu::BackendType::D3D11 };
constexpr std::array forceFallbackAdapters = { false, true };
constexpr size_t totalCombinations =
powerPreferences.size() * backendTypes.size() * forceFallbackAdapters.size();
std::vector<wgpu::RequestAdapterOptions> requests;
requests.reserve(totalCombinations);
for (auto powerPreference: powerPreferences) {
for (auto backendType: backendTypes) {
for (auto forceFallbackAdapter: forceFallbackAdapters) {
requests.emplace_back(
wgpu::RequestAdapterOptions{
.powerPreference = powerPreference,
.forceFallbackAdapter = forceFallbackAdapter,
.backendType = backendType });
}
}
}
return requests;
}
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
HWND window = static_cast<HWND>(nativeWindow);
RECT windowRect;

View File

@@ -32,6 +32,7 @@ static constexpr size_t CONFIG_COMMAND_BUFFERS_SIZE = 3 * CONFIG_MIN_COMMAND
using namespace filament;
using namespace filament::backend;
using namespace filament::math;
#ifndef FILAMENT_IOS
#include <imageio/ImageEncoder.h>
@@ -57,6 +58,8 @@ BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE,
CONFIG_COMMAND_BUFFERS_SIZE, /*mPaused=*/false) {
initializeDriver();
mImageExpectations.emplace(getDriverApi());
NativeView nativeView = getNativeView();
mScreenSize = {nativeView.width, nativeView.height};
}
BackendTest::~BackendTest() {
@@ -114,59 +117,35 @@ PipelineState BackendTest::getColorWritePipelineState() {
return result;
}
void BackendTest::fullViewport(RenderPassParams& params) {
fullViewport(params.viewport);
filament::backend::Viewport BackendTest::getFullViewport() const {
const NativeView& view = getNativeView();
return Viewport {
.left = 0,
.bottom = 0,
.width = static_cast<uint32_t>(view.width),
.height = static_cast<uint32_t>(view.height)
};
}
void BackendTest::fullViewport(Viewport& viewport) {
const NativeView& view = getNativeView();
viewport.left = 0;
viewport.bottom = 0;
viewport.width = view.width;
viewport.height = view.height;
}
void BackendTest::renderTriangle(
PipelineLayout const& pipelineLayout,
Handle<filament::backend::HwRenderTarget> renderTarget,
Handle<filament::backend::HwSwapChain> swapChain,
Handle<filament::backend::HwProgram> program) {
filament::backend::RenderPassParams BackendTest::getClearColorRenderPass(float4 color) {
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = {0.f, 0.f, 1.f, 1.f};
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
params.viewport.height = 512;
params.viewport.width = 512;
renderTriangle(pipelineLayout, renderTarget, swapChain, program, params);
params.clearColor = color;
return params;
}
void BackendTest::renderTriangle(
PipelineLayout const& pipelineLayout,
Handle<HwRenderTarget> renderTarget,
Handle<HwSwapChain> swapChain,
Handle<HwProgram> program,
const RenderPassParams& params) {
auto& api = getDriverApi();
filament::backend::RenderPassParams BackendTest::getNoClearRenderPass() {
return RenderPassParams{};
}
TrianglePrimitive triangle(api);
std::size_t BackendTest::screenWidth() const {
return mScreenSize[0];
}
api.makeCurrent(swapChain, swapChain);
api.beginRenderPass(renderTarget, params);
PipelineState state;
state.program = program;
state.pipelineLayout = pipelineLayout;
state.rasterState.colorWrite = true;
state.rasterState.depthWrite = false;
state.rasterState.depthFunc = RasterState::DepthFunc::A;
state.rasterState.culling = CullingMode::NONE;
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
api.endRenderPass();
std::size_t BackendTest::screenHeight() const {
return mScreenSize[1];
}
bool BackendTest::matchesEnvironment(Backend backend) {

View File

@@ -54,28 +54,21 @@ protected:
static filament::backend::PipelineState getColorWritePipelineState();
// Helper methods to set the viewport to the full extent of the swap chain.
static void fullViewport(filament::backend::RenderPassParams& params);
static void fullViewport(filament::backend::Viewport& viewport);
void renderTriangle(
filament::backend::PipelineLayout const& pipelineLayout,
filament::backend::Handle<filament::backend::HwRenderTarget> renderTarget,
filament::backend::Handle<filament::backend::HwSwapChain> swapChain,
filament::backend::Handle<filament::backend::HwProgram> program);
void renderTriangle(
filament::backend::PipelineLayout const& pipelineLayout,
filament::backend::Handle<filament::backend::HwRenderTarget> renderTarget,
filament::backend::Handle<filament::backend::HwSwapChain> swapChain,
filament::backend::Handle<filament::backend::HwProgram> program,
const filament::backend::RenderPassParams& params);
// Gets the full back buffer's viewport
filament::backend::Viewport getFullViewport() const;
// If color is unset this defaults to using opaque cyan
static filament::backend::RenderPassParams getClearColorRenderPass(
filament::math::float4 color = filament::math::float4(0, 1, 1, 1));
static filament::backend::RenderPassParams getNoClearRenderPass();
filament::backend::DriverApi& getDriverApi() { return *commandStream; }
filament::backend::Driver& getDriver() { return *driver; }
ImageExpectations& getExpectations() { return *mImageExpectations; }
std::size_t screenWidth() const;
std::size_t screenHeight() const;
static bool matchesEnvironment(Backend backend);
static bool matchesEnvironment(OperatingSystem operatingSystem);
private:
@@ -95,6 +88,8 @@ private:
// This isn't truly optional, it just needs to delay construction until after the driver has
// been initialized
std::optional<ImageExpectations> mImageExpectations;
std::array<size_t, 2> mScreenSize;
};
} // namespace test

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -151,3 +151,6 @@ if __name__ == "__main__":
results.show_images(file_prefix)
else:
results.batch_move(args.batch)
print("--------------------------------------------------------")
print("REMEMBER TO RESYNC CMAKE AND UPDATE HASHES IN TEST FILES")
print("--------------------------------------------------------")

View File

@@ -305,11 +305,7 @@ TEST_F(BlitTest, ColorResolve) {
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::COLOR;
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
params.clearColor = float4(1, 1, 0, 1);
RenderPassParams params = getClearColorRenderPass();
params.viewport.width = kSrcTexWidth;
params.viewport.height = kSrcTexHeight;
@@ -326,7 +322,11 @@ TEST_F(BlitTest, ColorResolve) {
{
RenderFrame frame(api);
api.beginRenderPass(srcRenderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
}
@@ -337,7 +337,7 @@ TEST_F(BlitTest, ColorResolve) {
SamplerMagFilter::NEAREST);
EXPECT_IMAGE(dstRenderTarget, getExpectations(),
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 531759687));
}
TEST_F(BlitTest, Blit2DTextureArray) {
@@ -493,8 +493,8 @@ TEST_F(BlitTest, BlitRegionToSwapChain) {
constexpr int kSrcTexWidth = 1024;
constexpr int kSrcTexHeight = 1024;
constexpr auto kSrcTexFormat = TextureFormat::RGBA8;
constexpr int kDstTexWidth = 512;
constexpr int kDstTexHeight = 512;
const uint32_t kDstTexWidth = screenWidth();
const uint32_t kDstTexHeight = screenHeight();
constexpr int kNumLevels = 3;
// Create a SwapChain and make it current.

View File

@@ -79,12 +79,8 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = { 0.f, 1.f, 0.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
RenderPassParams params = getClearColorRenderPass();
params.viewport = getFullViewport();
// Create a uniform buffer.
// We use STATIC here, even though the buffer is updated, to force the Metal backend to use
@@ -136,7 +132,11 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
}
api.beginRenderPass(defaultRenderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
triangleIndex++;
@@ -157,9 +157,12 @@ TEST_F(BufferUpdatesTest, VertexBufferUpdate) {
TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
NONFATAL_FAIL_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::VULKAN),
"All values including alpha are written as 0, see b/417254943");
auto& api = getDriverApi();
Cleanup cleanup(api);
const TrianglePrimitive triangle(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
@@ -176,10 +179,11 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
shader.bindUniform<SimpleMaterialParams>(api, ubuffer, kBindingConfig);
// Create a render target.
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT));
auto renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0, 512, 512, 1, 0, {{ colorTexture }}, {}, {}));
auto colorTexture =
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
screenWidth(), screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT));
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0, screenWidth(),
screenHeight(), 1, 0, { { colorTexture } }, {}, {}));
// Upload uniforms for the first triangle.
// Upload the uniform, but with an offset to accommodate the padding in the shader's
@@ -190,38 +194,52 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
.offset = { 0.0f, 0.0f, 0.0f, 0.0f }
});
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = { 0.f, 0.f, 1.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
params.viewport.height = 512;
params.viewport.width = 512;
renderTriangle({ { shader.getDescriptorSetLayout() } }, renderTarget, swapChain,
shader.getProgram(), params);
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
// Upload uniforms for the second triangle. To test partial buffer updates, we'll only update
// color.b, color.a, scaleMinusOne, offset.x, and offset.y.
const UniformBindingConfig partialBindingConfig = {
.dataSize = sizeof(float) * 8,
.bufferSize = sizeof(SimpleMaterialParams) + 64,
.byteOffset = 64 + offsetof(SimpleMaterialParams, color.b)
};
shader.uploadUniform(api, ubuffer, partialBindingConfig,
std::array<float, 8>{
1.0f, 1.0f, // color.b, color.a
0.0f, 0.0f, 0.0f, 0.0f, // scale
0.5f, 0.5f // offset.x, offset.y
});
{
RenderFrame frame(api);
params.flags.clear = TargetBufferFlags::NONE;
params.flags.discardStart = TargetBufferFlags::NONE;
renderTriangle({{ shader.getDescriptorSetLayout() }},
renderTarget, swapChain, shader.getProgram(), params);
RenderPassParams clearParams = getClearColorRenderPass();
clearParams.viewport.height = screenWidth();
clearParams.viewport.width = screenHeight();
api.beginRenderPass(renderTarget, clearParams);
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
// Upload uniforms for the second triangle. To test partial buffer updates, we'll only
// update color.b, color.a, scaleMinusOne, offset.x, and offset.y.
const UniformBindingConfig partialBindingConfig = {
.dataSize = sizeof(float) * 8,
.bufferSize = sizeof(SimpleMaterialParams) + 64,
.byteOffset = 64 + offsetof(SimpleMaterialParams, color.b) };
shader.uploadUniform(api, ubuffer, partialBindingConfig,
std::array<float, 8>{
1.0f, 1.0f, // color.b, color.a
0.0f, 0.0f, 0.0f, 0.0f, // scale
0.5f, 0.5f // offset.x, offset.y
});
RenderPassParams noClearParams = getNoClearRenderPass();
noClearParams.viewport.height = screenWidth();
noClearParams.viewport.width = screenHeight();
api.beginRenderPass(renderTarget, noClearParams);
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
}
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "BufferObjectUpdateWithOffset", 91322442));
ScreenshotParams(screenWidth(), screenHeight(), "BufferObjectUpdateWithOffset",
2320747245));
api.flush();
api.commit(swapChain);

View File

@@ -161,12 +161,11 @@ TEST_F(BackendTest, FeedbackLoops) {
for (int frame = 0; frame < kNumFrames; frame++) {
// Prep for rendering.
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::NONE;
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
RenderPassParams params = getNoClearRenderPass();
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
@@ -198,7 +197,11 @@ TEST_F(BackendTest, FeedbackLoops) {
});
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
}
@@ -231,7 +234,11 @@ TEST_F(BackendTest, FeedbackLoops) {
});
api.beginRenderPass(renderTargets[targetLevel], params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
}

View File

@@ -25,6 +25,7 @@
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include "TrianglePrimitive.h"
#include "private/filament/SamplerInterfaceBlock.h"
#include <vector>
@@ -50,7 +51,7 @@ layout(location = 0) out vec4 fragColor;
layout(location = 0, set = 0) uniform {samplerType} test_tex;
void main() {
vec2 fbsize = vec2(512);
vec2 fbsize = vec2({texSize});
vec2 uv = gl_FragCoord.xy / fbsize;
#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) || defined(TARGET_WEBGPU_ENVIRONMENT)
uv.y = 1.0 - uv.y;
@@ -71,7 +72,7 @@ float getLayer(in sampler3D s) { return 2.5f / 4.0f; }
float getLayer(in sampler2DArray s) { return 2.0f; }
void main() {
vec2 fbsize = vec2(512);
vec2 fbsize = vec2({texSize});
vec2 uv = gl_FragCoord.xy / fbsize;
#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) || defined(TARGET_WEBGPU_ENVIRONMENT)
uv.y = 1.0 - uv.y;
@@ -89,7 +90,7 @@ layout(location = 0) out vec4 fragColor;
layout(location = 0, set = 0) uniform sampler2D test_tex;
void main() {
vec2 fbsize = vec2(512);
vec2 fbsize = vec2({texSize});
vec2 uv = gl_FragCoord.xy / fbsize;
#if defined(TARGET_METAL_ENVIRONMENT) || defined(TARGET_VULKAN_ENVIRONMENT) || defined(TARGET_WEBGPU_ENVIRONMENT)
uv.y = 1.0 - uv.y;
@@ -105,17 +106,6 @@ namespace test {
template<typename componentType> inline componentType getMaxValue();
class LoadImageTest : public BackendTest {
public:
LoadImageTest() {
mVertexShader = SharedShaders::getVertexShaderText(VertexShaderType::Noop,
ShaderUniformType::None);
}
std::string mVertexShader;
};
inline std::string stringReplace(const std::string& find, const std::string& replace,
std::string source) {
std::string::size_type pos = source.find(find);
@@ -211,11 +201,40 @@ static SamplerFormat getSamplerFormat(TextureFormat textureFormat) {
}
}
class LoadImageTest : public BackendTest {
public:
LoadImageTest() : mTriangle(getDriverApi()) {
mVertexShader = SharedShaders::getVertexShaderText(VertexShaderType::Noop,
ShaderUniformType::None);
// Checkerboard utils require square textures
EXPECT_THAT(screenWidth(), testing::Eq(screenWidth()));
}
uint32_t kTexSize = screenWidth();
uint32_t kHalfTexSize = kTexSize / 2;
uint32_t kDoubleTexSize = kTexSize * 2;
std::string mVertexShader;
TrianglePrimitive mTriangle;
std::string getFormattedFragment(const std::string& fragment, TextureFormat textureFormat) {
std::string withSampler =
stringReplace("{samplerType}", getSamplerTypeName(textureFormat), fragment);
return stringReplace("{texSize}", std::to_string(kTexSize), withSampler);
}
std::string getFormattedFragment(const std::string& fragment, SamplerType samplerType) {
std::string withSampler =
stringReplace("{samplerType}", getSamplerTypeName(samplerType), fragment);
return stringReplace("{texSize}", std::to_string(kTexSize), withSampler);
}
};
TEST_F(LoadImageTest, UpdateImage2D) {
FAIL_IF(Backend::VULKAN, "Multiple test cases crash, see b/417481434");
// All of these test cases should result in the same rendered image, and thus the same hash.
static const uint32_t expectedHash = 3644679986;
static const uint32_t expectedHash = 1875922935;
struct TestCase {
const char* name;
@@ -303,8 +322,7 @@ TEST_F(LoadImageTest, UpdateImage2D) {
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(t.textureFormat), Precision::HIGH, false };
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(t.textureFormat), fragmentTemplate);
std::string const fragment = getFormattedFragment(fragmentTemplate, t.textureFormat);
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = mVertexShader,
.fragmentShader= fragment,
@@ -314,21 +332,26 @@ TEST_F(LoadImageTest, UpdateImage2D) {
// Create a Texture.
auto usage = TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE;
Handle<HwTexture> const texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
t.textureFormat, 1, 512, 512, 1u, usage));
t.textureFormat, 1, kTexSize, kTexSize, 1u, usage));
// Upload some pixel data.
if (t.uploadSubregions) {
api.update3DImage(texture, 0, 0, 0, 0, 256, 256, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding));
api.update3DImage(texture, 0, 256, 0, 0, 256, 256, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding));
api.update3DImage(texture, 0, 0, 256, 0, 256, 256, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding));
api.update3DImage(texture, 0, 256, 256, 0, 256, 256, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 256, t.bufferPadding));
api.update3DImage(texture, 0, 0, 0, 0, kHalfTexSize, kHalfTexSize, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, kHalfTexSize,
t.bufferPadding));
api.update3DImage(texture, 0, kHalfTexSize, 0, 0, kHalfTexSize, kHalfTexSize, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, kHalfTexSize,
t.bufferPadding));
api.update3DImage(texture, 0, 0, kHalfTexSize, 0, kHalfTexSize, kHalfTexSize, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, kHalfTexSize,
t.bufferPadding));
api.update3DImage(texture, 0, kHalfTexSize, kHalfTexSize, 0, kHalfTexSize, kHalfTexSize,
1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, kHalfTexSize,
t.bufferPadding));
} else {
api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, 512, t.bufferPadding));
api.update3DImage(texture, 0, 0, 0, 0, kTexSize, kTexSize, 1,
checkerboardPixelBuffer(t.pixelFormat, t.pixelType, kTexSize, t.bufferPadding));
}
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
@@ -338,11 +361,21 @@ TEST_F(LoadImageTest, UpdateImage2D) {
api.bindDescriptorSet(descriptorSet, 0, {});
renderTriangle({{ shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
RenderPassParams params = getClearColorRenderPass();
params.viewport.width = kTexSize;
params.viewport.height = kTexSize;
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = mTriangle.getVertexBufferInfo();
api.beginRenderPass(defaultRenderTarget, params);
api.bindPipeline(state);
api.bindRenderPrimitive(mTriangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, t.name, expectedHash));
ScreenshotParams(kTexSize, kTexSize, t.name, expectedHash));
api.commit(swapChain);
api.endFrame(0);
@@ -368,39 +401,39 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false };
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(textureFormat), fragmentTemplate);
std::string const fragment = getFormattedFragment(fragmentTemplate, textureFormat);
Shader shader(api, cleanup, ShaderConfig{
.vertexShader = mVertexShader, .fragmentShader = fragment, .uniforms = {{
"test_tex", DescriptorType::SAMPLER_2D_FLOAT, samplerInfo
}}});
// Create a texture.
Handle<HwTexture> const texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
textureFormat, 1, 512, 512, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE));
Handle<HwTexture> const texture =
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, textureFormat, 1, kTexSize,
kTexSize, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE));
// Create image data.
size_t components; int bpp;
getPixelInfo(pixelFormat, pixelType, components, bpp);
size_t bpl = 512 * 512 * bpp;
size_t bpl = kTexSize * kTexSize * bpp;
size_t bufferSize = bpl;
void* buffer = calloc(1, bufferSize);
PixelBufferDescriptor descriptor(buffer, bufferSize, pixelFormat, pixelType,
1, 0, 0, 512, [](void* buffer, size_t size, void* user) {
1, 0, 0, kTexSize, [](void* buffer, size_t size, void* user) {
free(buffer);
}, nullptr);
// Add a gradient.
uint8_t* pixel = (uint8_t*) buffer;
for (int r = 0; r < 512; r++) {
for (int c = 0; c < 512; c++) {
for (int r = 0; r < kTexSize; r++) {
for (int c = 0; c < kTexSize; c++) {
for (int n = 0; n < components; n++) {
*pixel++ = (c / 512.0f) * 255;
*pixel++ = (c / static_cast<float>(kTexSize)) * 255;
}
}
}
api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 1, std::move(descriptor));
api.update3DImage(texture, 0, 0, 0, 0, kTexSize, kTexSize, 1, std::move(descriptor));
api.beginFrame(0, 0, 0);
@@ -413,11 +446,21 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
api.bindDescriptorSet(descriptorSet, 0, {});
renderTriangle({{ shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
RenderPassParams params = getClearColorRenderPass();
params.viewport.width = kTexSize;
params.viewport.height = kTexSize;
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = mTriangle.getVertexBufferInfo();
api.beginRenderPass(defaultRenderTarget, params);
api.bindPipeline(state);
api.bindRenderPrimitive(mTriangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, "UpdateImageSRGB", 359858623));
ScreenshotParams(kTexSize, kTexSize, "UpdateImageSRGB", 3300305265));
api.commit(swapChain);
api.endFrame(0);
@@ -442,8 +485,7 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D, getSamplerFormat(textureFormat), Precision::HIGH, false };
std::string const fragment = stringReplace("{samplerType}",
getSamplerTypeName(textureFormat), fragmentUpdateImageMip);
std::string const fragment = getFormattedFragment(fragmentUpdateImageMip, textureFormat);
Shader shader(api, cleanup, ShaderConfig {
.vertexShader = mVertexShader,
.fragmentShader = fragment,
@@ -454,12 +496,13 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
// Base level: 1024
// Level 1: 512 <-- upload data and sample from this level
// Level 2: 256
Handle<HwTexture> texture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 3,
textureFormat, 1, 1024, 1024, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE));
Handle<HwTexture> texture = cleanup.add(
api.createTexture(SamplerType::SAMPLER_2D, 3, textureFormat, 1, kDoubleTexSize,
kDoubleTexSize, 1, TextureUsage::SAMPLEABLE | TextureUsage::UPLOADABLE));
// Create image data.
PixelBufferDescriptor descriptor = checkerboardPixelBuffer(pixelFormat, pixelType, 512);
api.update3DImage(texture, /* level*/ 1, 0, 0, 0, 512, 512, 1, std::move(descriptor));
PixelBufferDescriptor descriptor = checkerboardPixelBuffer(pixelFormat, pixelType, kTexSize);
api.update3DImage(texture, /* level*/ 1, 0, 0, 0, kTexSize, kTexSize, 1, std::move(descriptor));
api.beginFrame(0, 0, 0);
@@ -472,11 +515,24 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
api.bindDescriptorSet(descriptorSet, 0, {});
renderTriangle({{ shader.getDescriptorSetLayout() }},
defaultRenderTarget, swapChain, shader.getProgram());
{
RenderFrame frame(api);
RenderPassParams params = getClearColorRenderPass();
params.viewport.width = kTexSize;
params.viewport.height = kTexSize;
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = mTriangle.getVertexBufferInfo();
api.beginRenderPass(defaultRenderTarget, params);
api.bindPipeline(state);
api.bindRenderPrimitive(mTriangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
}
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, "UpdateImageMipLevel", 3644679986));
ScreenshotParams(kTexSize, kTexSize, "UpdateImageMipLevel", 1875922935));
api.commit(swapChain);
api.endFrame(0);
@@ -506,8 +562,7 @@ TEST_F(LoadImageTest, UpdateImage3D) {
// Create a program.
filament::SamplerInterfaceBlock::SamplerInfo samplerInfo { "test", "tex", 0,
SamplerType::SAMPLER_2D_ARRAY, getSamplerFormat(textureFormat), Precision::HIGH, false };
std::string fragment = stringReplace("{samplerType}",
getSamplerTypeName(samplerType), fragmentUpdateImage3DTemplate);
std::string fragment = getFormattedFragment(fragmentUpdateImage3DTemplate, samplerType);
Shader shader(api, cleanup, ShaderConfig {
.vertexShader = mVertexShader,
.fragmentShader = fragment,
@@ -516,24 +571,24 @@ TEST_F(LoadImageTest, UpdateImage3D) {
// Create a texture.
Handle<HwTexture> texture = cleanup.add(api.createTexture(samplerType, 1,
textureFormat, 1, 512, 512, 4, usage));
textureFormat, 1, kTexSize, kTexSize, 4, usage));
// Create image data for all 4 layers.
size_t components; int bpp;
getPixelInfo(pixelFormat, pixelType, components, bpp);
size_t bpl = 512 * 512 * bpp;
size_t bpl = kTexSize * kTexSize * bpp;
size_t bufferSize = bpl * 4;
void* buffer = calloc(1, bufferSize);
PixelBufferDescriptor descriptor(buffer, bufferSize, pixelFormat, pixelType,
1, 0, 0, 512, [](void* buffer, size_t size, void* user) {
1, 0, 0, kTexSize, [](void* buffer, size_t size, void* user) {
free(buffer);
}, nullptr);
// Only add checkerboard data to the 3rd layer, which we'll sample from.
uint8_t* thirdLayer = (uint8_t*) buffer + (bpl * 2);
fillCheckerboard<float>(thirdLayer, 512, 512, components, 1.0f);
fillCheckerboard<float>(thirdLayer, kTexSize, kTexSize, components, 1.0f);
api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 4, std::move(descriptor));
api.update3DImage(texture, 0, 0, 0, 0, kTexSize, kTexSize, 4, std::move(descriptor));
{
RenderFrame frame(api);
@@ -546,11 +601,21 @@ TEST_F(LoadImageTest, UpdateImage3D) {
api.bindDescriptorSet(descriptorSet, 0, {});
renderTriangle({ { shader.getDescriptorSetLayout() } },
defaultRenderTarget, swapChain, shader.getProgram());
RenderPassParams params = getClearColorRenderPass();
params.viewport.width = kTexSize;
params.viewport.height = kTexSize;
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = mTriangle.getVertexBufferInfo();
api.beginRenderPass(defaultRenderTarget, params);
api.bindPipeline(state);
api.bindRenderPrimitive(mTriangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, "UpdateImage3D", 3644679986));
ScreenshotParams(kTexSize, kTexSize, "UpdateImage3D", 1875922935));
}
api.stopCapture();

View File

@@ -75,41 +75,38 @@ TEST_F(BackendTest, MRT) {
1, // levels
TextureFormat::RGBA8, // format
1, // samples
512, // width
512, // height
screenWidth(), // width
screenHeight(), // height
1, // depth
usage)); // usage
usage)); // usage
Handle<HwTexture> textureB = cleanup.add(api.createTexture(
SamplerType::SAMPLER_2D, // target
1, // levels
TextureFormat::RGBA8, // format
1, // samples
512, // width
512, // height
screenWidth(), // width
screenHeight(), // height
1, // depth
usage)); // usage
usage)); // usage
// Create a RenderTarget with two attachments.
Handle<HwRenderTarget> renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0 | TargetBufferFlags::COLOR1,
// The width and height must match the width and height of the respective mip
// level (at least for OpenGL).
512, // width
512, // height
1, // samples
0, // layerCount
{{textureA },{textureB }}, // color
{}, // depth
{})); // stencil
screenWidth(), // width
screenHeight(), // height
1, // samples
0, // layerCount
{{textureA },{textureB }}, // color
{}, // depth
{})); // stencil
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = {0.f, 1.f, 0.f, 1.f};
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
RenderPassParams params = getClearColorRenderPass();
params.viewport = getFullViewport();
api.startCapture(0);
@@ -118,7 +115,11 @@ TEST_F(BackendTest, MRT) {
// Draw a triangle.
api.beginRenderPass(renderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
api.flush();

View File

@@ -138,30 +138,30 @@ TEST_F(BackendTest, TextureViewLod) {
TargetBufferFlags::COLOR, 32, 32, 1, 0,
{texture, 2 /* level */, 0 /* layer */}, {}, {}));
{
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::NONE;
params.flags.discardStart = TargetBufferFlags::NONE;
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState state = getColorWritePipelineState();
whiteShader.addProgramToPipelineState(state);
RenderPassParams params = getNoClearRenderPass();
params.viewport = getFullViewport();
api.beginRenderPass(renderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
}
backend::Handle<HwRenderTarget> defaultRenderTarget =
cleanup.add(api.createDefaultRenderTarget(0));
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = {0.f, 0.f, 0.5f, 1.f};
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState state = getColorWritePipelineState();
texturedShader.addProgramToPipelineState(state);
RenderPassParams params = getClearColorRenderPass();
params.viewport = getFullViewport();
DescriptorSetHandle descriptorSet13 = texturedShader.createDescriptorSet(api);
api.updateDescriptorSetTexture(descriptorSet13, 0, texture13, {
.filterMag = SamplerMagFilter::NEAREST,
@@ -174,7 +174,11 @@ TEST_F(BackendTest, TextureViewLod) {
// previous pass.
api.beginRenderPass(defaultRenderTarget, params);
api.scissor(params.viewport);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
// Adjust the base mip to 2.
@@ -199,7 +203,11 @@ TEST_F(BackendTest, TextureViewLod) {
params.flags.discardStart = TargetBufferFlags::NONE;
api.beginRenderPass(defaultRenderTarget, params);
api.scissor(params.viewport);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
api.commit(swapChain);

View File

@@ -81,12 +81,9 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = { 0.f, 1.f, 0.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
RenderPassParams params = getClearColorRenderPass();
params.viewport = getFullViewport();
api.startCapture(0);
@@ -95,7 +92,11 @@ TEST_F(BackendTest, MissingRequiredAttributes) {
// Render a triangle.
api.beginRenderPass(defaultRenderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
api.flush();

View File

@@ -130,12 +130,8 @@ TEST_F(BackendTest, PushConstants) {
TrianglePrimitive triangle(api);
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::COLOR0;
params.viewport = { 0, 0, 512, 512 };
params.clearColor = math::float4(0.0f, 0.0f, 1.0f, 1.0f);
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
RenderPassParams params = getClearColorRenderPass();
params.viewport = getFullViewport();
PipelineState ps = {};
ps.program = program;
@@ -183,7 +179,8 @@ TEST_F(BackendTest, PushConstants) {
api.endRenderPass();
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "pushConstants", 1957275826));
ScreenshotParams(params.viewport.width, params.viewport.height, "pushConstants",
3575588741));
api.commit(swapChain);
api.endFrame(0);

View File

@@ -71,6 +71,11 @@ namespace test {
class ReadPixelsTest : public BackendTest {
public:
ReadPixelsTest() {
// Expect a square screen
EXPECT_THAT(screenWidth(), ::testing::Eq(screenHeight()));
}
bool readPixelsFinished = false;
};
@@ -82,9 +87,14 @@ TEST_F(ReadPixelsTest, ReadPixels) {
// deterministic results. Take this test with a grain of salt, however, as other platform / GPU
// combinations may vary ever-so-slightly, which would cause this test to fail.
const size_t renderTargetBaseSize = 512;
const size_t renderTargetBaseSize = screenWidth();
struct TestCase {
explicit TestCase(size_t renderTargetBaseSize)
: renderTargetBaseSize(renderTargetBaseSize) {}
size_t renderTargetBaseSize;
const char* testName = "readPixels_normal";
// The murmur3 hash of the read pixel buffer result, used to determine success.
@@ -163,10 +173,10 @@ TEST_F(ReadPixelsTest, ReadPixels) {
// The normative read pixels test case. Render a white triangle over a blue background and read
// the full viewport into a pixel buffer.
TestCase const t0;
TestCase const t0(renderTargetBaseSize);
// Check that a subregion of the render target can be read into a pixel buffer.
TestCase t2;
TestCase t2(renderTargetBaseSize);
t2.testName = "readPixels_subregion";
t2.readRect.x = 90;
t2.readRect.y = 403;
@@ -176,7 +186,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
t2.hash = 0xcba7675a;
// Check that readPixels works when rendering into and reading from a mip level.
TestCase t3;
TestCase t3(renderTargetBaseSize);
t3.testName = "readPixels_mip";
t3.mipLevels = 4;
t3.mipLevel = 2;
@@ -186,7 +196,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
t3.hash = 0xe6fa6c55;
// Check that readPixels can return pixels in floating point RGBA format.
TestCase t4;
TestCase t4(renderTargetBaseSize);
t4.testName = "readPixels_float";
t4.format = PixelDataFormat::RGBA;
t4.type = PixelDataType::FLOAT;
@@ -194,7 +204,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
// Check that readPixels can read a region of the render target into a subregion of a large
// buffer.
TestCase t5;
TestCase t5(renderTargetBaseSize);
t5.testName = "readPixels_subbuffer";
t5.readRect.x = 90;
t5.readRect.y = 403;
@@ -206,7 +216,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
t5.hash = 0xbaefdb54;
// Check that readPixels works with integer formats.
TestCase t6;
TestCase t6(renderTargetBaseSize);
t6.testName = "readPixels_UINT";
t6.format = PixelDataFormat::R_INTEGER;
t6.type = PixelDataType::UINT;
@@ -214,7 +224,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
t6.hash = 0x9d91227;
// Check that readPixels works with half formats.
TestCase t7;
TestCase t7(renderTargetBaseSize);
t7.testName = "readPixels_half";
t7.format = PixelDataFormat::RG;
t7.type = PixelDataType::HALF;
@@ -223,7 +233,7 @@ TEST_F(ReadPixelsTest, ReadPixels) {
// Check that readPixels works when rendering into the SwapChain.
// This requires that the test runner's native window size is 512x512.
TestCase t8;
TestCase t8(renderTargetBaseSize);
t8.testName = "readPixels_swapchain";
t8.useDefaultRT = true;
@@ -279,14 +289,9 @@ TEST_F(ReadPixelsTest, ReadPixels) {
TrianglePrimitive const triangle(api);
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = { 0.f, 0.f, 1.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
params.viewport.height = t.getRenderTargetSize();
RenderPassParams params = getClearColorRenderPass(math::float4(0, 0, 1, 1));
params.viewport.width = t.getRenderTargetSize();
params.viewport.height = t.getRenderTargetSize();
api.makeCurrent(swapChain, swapChain);
api.beginFrame(0, 0, 0);
@@ -300,7 +305,11 @@ TEST_F(ReadPixelsTest, ReadPixels) {
} else {
floatShader.addProgramToPipelineState(state);
}
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
@@ -399,14 +408,10 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = { 0.f, 0.f, 1.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
params.viewport.height = renderTargetSize;
RenderPassParams params = getClearColorRenderPass(math::float4(0, 0, 1, 1));
params.viewport.width = renderTargetSize;
params.viewport.height = renderTargetSize;
void* buffer = calloc(1, renderTargetSize * renderTargetSize * 4);
@@ -422,7 +427,11 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
// Render some content, just so we don't read back uninitialized data.
api.beginRenderPass(renderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
PixelBufferDescriptor descriptor(buffer, renderTargetSize * renderTargetSize * 4,

View File

@@ -75,12 +75,9 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = { 0.f, 1.f, 0.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
RenderPassParams params = getClearColorRenderPass();
params.viewport = getFullViewport();
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
@@ -93,7 +90,11 @@ TEST_F(BackendTest, RenderExternalImageWithoutSet) {
// Render a triangle.
api.beginRenderPass(defaultRenderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
api.flush();
@@ -175,12 +176,9 @@ TEST_F(BackendTest, RenderExternalImage) {
PipelineState state = getColorWritePipelineState();
shader.addProgramToPipelineState(state);
RenderPassParams params = {};
fullViewport(params);
params.flags.clear = TargetBufferFlags::COLOR;
params.clearColor = { 0.f, 1.f, 0.f, 1.f };
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
RenderPassParams params = getClearColorRenderPass();
params.viewport = getFullViewport();
api.startCapture(0);
api.makeCurrent(swapChain, swapChain);
@@ -191,14 +189,18 @@ TEST_F(BackendTest, RenderExternalImage) {
// Render a triangle.
api.beginRenderPass(defaultRenderTarget, params);
api.draw(state, triangle.getRenderPrimitive(), 0, 3, 1);
state.primitiveType = PrimitiveType::TRIANGLES;
state.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(state);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
api.flush();
api.commit(swapChain);
api.endFrame(0);
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
ScreenshotParams(512, 512, "RenderExternalImage", 267229901));
ScreenshotParams(screenWidth(), screenHeight(), "RenderExternalImage", 1206264951));
api.stopCapture(0);
api.finish();

View File

@@ -114,12 +114,8 @@ TEST_F(BackendTest, ScissorViewportRegion) {
TrianglePrimitive triangle(api);
// Render a white triangle over blue.
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::COLOR0;
RenderPassParams params = getClearColorRenderPass();
params.viewport = srcRect;
params.clearColor = math::float4(0.0f, 0.0f, 1.0f, 1.0f);
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState ps = getColorWritePipelineState();
shader.addProgramToPipelineState(ps);
@@ -129,11 +125,15 @@ TEST_F(BackendTest, ScissorViewportRegion) {
api.beginRenderPass(srcRenderTarget, params);
api.scissor(scissor);
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
ps.primitiveType = PrimitiveType::TRIANGLES;
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(ps);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
EXPECT_IMAGE(fullRenderTarget, getExpectations(),
ScreenshotParams(kSrcTexWidth >> 1, kSrcTexHeight >> 1, "scissor", 0xAB3D1C53));
ScreenshotParams(kSrcTexWidth >> 1, kSrcTexHeight >> 1, "scissor", 15842520));
api.commit(swapChain);
api.endFrame(0);
@@ -194,12 +194,8 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
TrianglePrimitive triangle(api);
// Render a white triangle over blue.
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::COLOR0;
RenderPassParams params = getClearColorRenderPass();
params.viewport = bottomLeftViewport;
params.clearColor = math::float4(0.0f, 0.0f, 1.0f, 1.0f);
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
PipelineState ps = getColorWritePipelineState();
shader.addProgramToPipelineState(ps);
@@ -209,7 +205,11 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
api.beginRenderPass(renderTarget, params);
api.scissor(scissor);
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
ps.primitiveType = PrimitiveType::TRIANGLES;
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(ps);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
params.viewport = topLeftViewport;
@@ -217,11 +217,15 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
params.flags.discardStart = TargetBufferFlags::NONE;
api.beginRenderPass(renderTarget, params);
api.scissor(scissor);
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
ps.primitiveType = PrimitiveType::TRIANGLES;
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(ps);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "ScissorViewportEdgeCases", 0x6BF00F31));
ScreenshotParams(512, 512, "ScissorViewportEdgeCases", 2199186852));
api.commit(swapChain);
api.endFrame(0);

View File

@@ -74,7 +74,8 @@ public:
// Render a small triangle only to the stencil buffer, increasing the stencil buffer to 1.
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL;
params.viewport = { 0, 0, 512, 512 };
params.viewport = { 0, 0, static_cast<uint32_t>(screenWidth()),
static_cast<uint32_t>(screenHeight()) };
params.clearColor = math::float4(0.0f, 0.0f, 1.0f, 1.0f);
params.clearStencil = 0u;
params.flags.discardStart = TargetBufferFlags::ALL;
@@ -91,7 +92,11 @@ public:
api.beginFrame(0, 0, 0);
api.beginRenderPass(renderTarget, params);
api.draw(ps, smallTriangle.getRenderPrimitive(), 0, 3, 1);
ps.primitiveType = PrimitiveType::TRIANGLES;
ps.vertexBufferInfo = smallTriangle.getVertexBufferInfo();
api.bindPipeline(ps);
api.bindRenderPrimitive(smallTriangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
// Step 2: Render a larger triangle with the stencil test enabled.
@@ -104,7 +109,11 @@ public:
ps.stencilState.front.ref = 0u;
api.beginRenderPass(renderTarget, params);
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
ps.primitiveType = PrimitiveType::TRIANGLES;
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(ps);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
api.commit(mSwapChain);
@@ -123,18 +132,20 @@ TEST_F(BasicStencilBufferTest, StencilBuffer) {
Cleanup cleanup(api);
// Create two textures: a color and a stencil, and an associated RenderTarget.
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT));
auto stencilTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::STENCIL8, 1, 512, 512, 1, TextureUsage::STENCIL_ATTACHMENT));
auto renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, 512, 512, 1, 0,
{{ colorTexture }}, {}, {{ stencilTexture }}));
auto colorTexture =
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
screenWidth(), screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT));
auto stencilTexture =
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::STENCIL8, 1,
screenWidth(), screenHeight(), 1, TextureUsage::STENCIL_ATTACHMENT));
auto renderTarget = cleanup.add(api.createRenderTarget(TargetBufferFlags::COLOR0 |
TargetBufferFlags::STENCIL,
screenWidth(), screenHeight(), 1, 0, { { colorTexture } }, {}, { { stencilTexture } }));
RunTest(renderTarget);
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "StencilBuffer", 0x3B1AEF0F));
ScreenshotParams(screenWidth(), screenHeight(), "StencilBuffer", 0x3B1AEF0F));
flushAndWait();
getDriver().purge();
@@ -147,19 +158,20 @@ TEST_F(BasicStencilBufferTest, DepthAndStencilBuffer) {
Cleanup cleanup(api);
// Create two textures: a color and a stencil, and an associated RenderTarget.
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 512, 512, 1, TextureUsage::COLOR_ATTACHMENT));
auto colorTexture =
cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1,
screenWidth(), screenHeight(), 1, TextureUsage::COLOR_ATTACHMENT));
auto depthStencilTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::DEPTH24_STENCIL8, 1, 512, 512, 1,
TextureFormat::DEPTH24_STENCIL8, 1, screenWidth(), screenHeight(), 1,
TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT));
auto renderTarget = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, 512, 512, 1, 0,
{{ colorTexture }}, { depthStencilTexture }, {{ depthStencilTexture }}));
TargetBufferFlags::COLOR0 | TargetBufferFlags::STENCIL, screenWidth(), screenHeight(),
1, 0, { { colorTexture } }, { depthStencilTexture }, { { depthStencilTexture } }));
RunTest(renderTarget);
EXPECT_IMAGE(renderTarget, getExpectations(),
ScreenshotParams(512, 512, "DepthAndStencilBuffer", 0x3B1AEF0F));
ScreenshotParams(screenWidth(), screenHeight(), "DepthAndStencilBuffer", 0x3B1AEF0F));
flushAndWait();
getDriver().purge();
@@ -178,17 +190,18 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
// Pass 1: Render a triangle into (an auto-created) MSAA color buffer using the stencil test.
// Performs an auto-resolve on the color.
auto colorTexture = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::RGBA8, 1, 512, 512, 1,
TextureFormat::RGBA8, 1, screenWidth(), screenHeight(), 1,
TextureUsage::COLOR_ATTACHMENT | TextureUsage::SAMPLEABLE));
auto depthStencilTextureMSAA = cleanup.add(api.createTexture(SamplerType::SAMPLER_2D, 1,
TextureFormat::DEPTH24_STENCIL8, 4, 512, 512, 1,
TextureFormat::DEPTH24_STENCIL8, 4, screenWidth(), screenHeight(), 1,
TextureUsage::STENCIL_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT));
auto renderTarget0 = cleanup.add(api.createRenderTarget(
TargetBufferFlags::DEPTH_AND_STENCIL, 512, 512, 4, 0,
TargetBufferFlags::DEPTH_AND_STENCIL, screenWidth(), screenHeight(), 4, 0,
{{}}, { depthStencilTextureMSAA }, { depthStencilTextureMSAA }));
auto renderTarget1 = cleanup.add(api.createRenderTarget(
TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH_AND_STENCIL, 512, 512, 4, 0,
{{ colorTexture }}, { depthStencilTextureMSAA }, { depthStencilTextureMSAA }));
auto renderTarget1 = cleanup.add(
api.createRenderTarget(TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH_AND_STENCIL,
screenWidth(), screenHeight(), 4, 0, { { colorTexture } },
{ depthStencilTextureMSAA }, { depthStencilTextureMSAA }));
api.startCapture(0);
@@ -206,7 +219,8 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
// Render a small triangle only to the stencil buffer, increasing the stencil buffer to 1.
RenderPassParams params = {};
params.flags.clear = TargetBufferFlags::STENCIL;
params.viewport = { 0, 0, 512, 512 };
params.viewport = { 0, 0, static_cast<uint32_t>(screenWidth()),
static_cast<uint32_t>(screenHeight()) };
params.clearStencil = 0u;
params.flags.discardStart = TargetBufferFlags::ALL;
params.flags.discardEnd = TargetBufferFlags::NONE;
@@ -222,7 +236,11 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
api.beginFrame(0, 0, 0);
api.beginRenderPass(renderTarget0, params);
api.draw(ps, smallTriangle.getRenderPrimitive(), 0, 3, 1);
ps.primitiveType = PrimitiveType::TRIANGLES;
ps.vertexBufferInfo = smallTriangle.getVertexBufferInfo();
api.bindPipeline(ps);
api.bindRenderPrimitive(smallTriangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
// Step 2: Render a larger triangle with the stencil test enabled.
@@ -237,7 +255,11 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
ps.stencilState.front.ref = 0u;
api.beginRenderPass(renderTarget1, params);
api.draw(ps, triangle.getRenderPrimitive(), 0, 3, 1);
ps.primitiveType = PrimitiveType::TRIANGLES;
ps.vertexBufferInfo = triangle.getVertexBufferInfo();
api.bindPipeline(ps);
api.bindRenderPrimitive(triangle.getRenderPrimitive());
api.draw2(0, 3, 1);
api.endRenderPass();
api.commit(mSwapChain);
@@ -245,7 +267,8 @@ TEST_F(BasicStencilBufferTest, StencilBufferMSAA) {
api.endFrame(0);
EXPECT_IMAGE(renderTarget1, getExpectations(),
ScreenshotParams(512, 512, "StencilBufferAutoResolve", 3353562179));
ScreenshotParams(screenWidth(), screenHeight(), "StencilBufferAutoResolve",
3353562179));
flushAndWait();
getDriver().purge();

View File

@@ -1017,7 +1017,7 @@ public:
*
* @see setPaused
*/
bool isPaused() const noexcept;
bool isPaused() const noexcept(UTILS_HAS_THREADING);
/**
* Pause or resume rendering thread.

View File

@@ -153,6 +153,14 @@ public:
*/
Builder& multiview(AttachmentPoint attachment, uint8_t layerCount, uint8_t baseLayer = 0) noexcept;
/**
* Sets the number of samples used for MSAA (Multisample Anti-Aliasing).
*
* @param samples The number of samples used for multisampling.
* @return A reference to this Builder for chaining calls.
*/
Builder& samples(uint8_t samples) noexcept;
/**
* Creates the RenderTarget object and returns a pointer to it.
*

Some files were not shown because too many files have changed in this diff Show More