Compare commits
22 Commits
v1.61.2
...
jc/enableT
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7da1c0ad5 | ||
|
|
04ff88fab2 | ||
|
|
7d242341f2 | ||
|
|
e625c7024c | ||
|
|
192a61a06b | ||
|
|
6a93e3a765 | ||
|
|
e1fb1391f9 | ||
|
|
a3922ac3a9 | ||
|
|
5f1112d85e | ||
|
|
09545690bf | ||
|
|
3ce752c47b | ||
|
|
1f9f3ddb91 | ||
|
|
8e94688fbf | ||
|
|
438ee40f40 | ||
|
|
7f22d733d7 | ||
|
|
77d77f0a5c | ||
|
|
0aabd3dd6a | ||
|
|
72a54b30f9 | ||
|
|
00b2ab7ac4 | ||
|
|
39e15d67fc | ||
|
|
921dbddad9 | ||
|
|
6406d4602c |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.61.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.61.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.61.2'
|
||||
pod 'Filament', '~> 1.61.1'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.61.2
|
||||
VERSION_NAME=1.61.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
13
build/common/upload-release-assets/package-lock.json
generated
13
build/common/upload-release-assets/package-lock.json
generated
@@ -204,9 +204,10 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -409,9 +410,9 @@
|
||||
"integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
||||
@@ -257,6 +257,7 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/WebGPUPlatform.h
|
||||
src/webgpu/platform/WebGPUPlatform.cpp
|
||||
src/webgpu/SpdMipmapGenerator/SpdMipmapGenerator.cpp
|
||||
src/webgpu/WebGPUBufferBase.cpp
|
||||
src/webgpu/WebGPUBufferBase.h
|
||||
src/webgpu/WebGPUBufferObject.cpp
|
||||
@@ -282,6 +283,8 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
src/webgpu/WebGPUSwapChain.h
|
||||
src/webgpu/WebGPUTexture.cpp
|
||||
src/webgpu/WebGPUTexture.h
|
||||
src/webgpu/WebGPUTimerQuery.cpp
|
||||
src/webgpu/WebGPUTimerQuery.h
|
||||
src/webgpu/WebGPUVertexBuffer.cpp
|
||||
src/webgpu/WebGPUVertexBuffer.h
|
||||
src/webgpu/WebGPUVertexBufferInfo.cpp
|
||||
|
||||
@@ -495,18 +495,12 @@ struct DescriptorSetLayoutBinding {
|
||||
DescriptorFlags flags = DescriptorFlags::NONE;
|
||||
uint16_t count = 0;
|
||||
|
||||
// TODO: uncomment when needed. Note that this class is used as hash key. We need to ensure
|
||||
// no uninitialized padding bytes.
|
||||
// uint8_t externalSamplerDataIndex = EXTERNAL_SAMPLER_DATA_INDEX_UNUSED;
|
||||
|
||||
friend bool operator==(DescriptorSetLayoutBinding const& lhs,
|
||||
DescriptorSetLayoutBinding const& rhs) noexcept {
|
||||
return lhs.type == rhs.type &&
|
||||
lhs.flags == rhs.flags &&
|
||||
lhs.count == rhs.count &&
|
||||
lhs.stageFlags == rhs.stageFlags;
|
||||
// lhs.stageFlags == rhs.stageFlags &&
|
||||
// lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1254,26 +1248,6 @@ enum class SamplerCompareFunc : uint8_t {
|
||||
N //!< Never. The depth / stencil test always fails.
|
||||
};
|
||||
|
||||
//! this API is copied from (and only applies to) the Vulkan spec.
|
||||
//! These specify YUV to RGB conversions.
|
||||
enum class SamplerYcbcrModelConversion : uint8_t {
|
||||
RGB_IDENTITY = 0,
|
||||
YCBCR_IDENTITY = 1,
|
||||
YCBCR_709 = 2,
|
||||
YCBCR_601 = 3,
|
||||
YCBCR_2020 = 4,
|
||||
};
|
||||
|
||||
enum class SamplerYcbcrRange : uint8_t {
|
||||
ITU_FULL = 0,
|
||||
ITU_NARROW = 1,
|
||||
};
|
||||
|
||||
enum class ChromaLocation : uint8_t {
|
||||
COSITED_EVEN = 0,
|
||||
MIDPOINT = 1,
|
||||
};
|
||||
|
||||
//! Sampler parameters
|
||||
struct SamplerParams { // NOLINT
|
||||
SamplerMagFilter filterMag : 1; //!< magnification filter (NEAREST)
|
||||
@@ -1342,94 +1316,9 @@ static_assert(sizeof(SamplerParams) == 4);
|
||||
static_assert(sizeof(SamplerParams) <= sizeof(uint64_t),
|
||||
"SamplerParams must be no more than 64 bits");
|
||||
|
||||
//! Sampler parameters
|
||||
struct SamplerYcbcrConversion {// NOLINT
|
||||
SamplerYcbcrModelConversion ycbcrModel : 4;
|
||||
TextureSwizzle r : 4;
|
||||
TextureSwizzle g : 4;
|
||||
TextureSwizzle b : 4;
|
||||
TextureSwizzle a : 4;
|
||||
SamplerYcbcrRange ycbcrRange : 1;
|
||||
ChromaLocation xChromaOffset : 1;
|
||||
ChromaLocation yChromaOffset : 1;
|
||||
SamplerMagFilter chromaFilter : 1;
|
||||
uint8_t padding;
|
||||
|
||||
struct Hasher {
|
||||
size_t operator()(const SamplerYcbcrConversion p) const noexcept {
|
||||
// we don't use std::hash<> here, so we don't have to include <functional>
|
||||
return *reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&p));
|
||||
}
|
||||
};
|
||||
|
||||
struct EqualTo {
|
||||
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
|
||||
assert_invariant(lhs.padding == 0);
|
||||
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs == *pRhs;
|
||||
}
|
||||
};
|
||||
|
||||
struct LessThan {
|
||||
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
|
||||
assert_invariant(lhs.padding == 0);
|
||||
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs < *pRhs;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
friend bool operator == (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
|
||||
noexcept {
|
||||
return SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
|
||||
}
|
||||
friend bool operator != (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
|
||||
noexcept {
|
||||
return !SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
|
||||
}
|
||||
friend bool operator < (SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs)
|
||||
noexcept {
|
||||
return SamplerYcbcrConversion::LessThan{}(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(SamplerYcbcrConversion) == 4);
|
||||
|
||||
static_assert(sizeof(SamplerYcbcrConversion) <= sizeof(uint64_t),
|
||||
"SamplerYcbcrConversion must be no more than 64 bits");
|
||||
|
||||
struct ExternalSamplerDatum {
|
||||
ExternalSamplerDatum(SamplerYcbcrConversion ycbcr, SamplerParams spm, uint32_t extFmt)
|
||||
: YcbcrConversion(ycbcr),
|
||||
samplerParams(spm),
|
||||
externalFormat(extFmt) {}
|
||||
bool operator==(ExternalSamplerDatum const& rhs) const {
|
||||
return (YcbcrConversion == rhs.YcbcrConversion && samplerParams == rhs.samplerParams &&
|
||||
externalFormat == rhs.externalFormat);
|
||||
}
|
||||
struct EqualTo {
|
||||
bool operator()(const ExternalSamplerDatum& lhs,
|
||||
const ExternalSamplerDatum& rhs) const noexcept {
|
||||
return (lhs.YcbcrConversion == rhs.YcbcrConversion &&
|
||||
lhs.samplerParams == rhs.samplerParams &&
|
||||
lhs.externalFormat == rhs.externalFormat);
|
||||
}
|
||||
};
|
||||
SamplerYcbcrConversion YcbcrConversion;
|
||||
SamplerParams samplerParams;
|
||||
uint32_t externalFormat;
|
||||
};
|
||||
// No implicit padding allowed due to it being a hash key.
|
||||
static_assert(sizeof(ExternalSamplerDatum) == 12);
|
||||
|
||||
struct DescriptorSetLayout {
|
||||
std::variant<utils::StaticString, utils::CString, std::monostate> label;
|
||||
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
|
||||
|
||||
// TODO: uncomment when needed
|
||||
// utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
|
||||
};
|
||||
|
||||
//! blending equation function
|
||||
|
||||
@@ -29,6 +29,8 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using namespace fvkutils;
|
||||
|
||||
VulkanYcbcrConversionCache::VulkanYcbcrConversionCache(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||
|
||||
#include "utils/Definitions.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/Hash.h>
|
||||
@@ -30,7 +32,7 @@ namespace filament::backend {
|
||||
class VulkanYcbcrConversionCache {
|
||||
public:
|
||||
struct Params {
|
||||
SamplerYcbcrConversion conversion = {}; // 4
|
||||
fvkutils::SamplerYcbcrConversion conversion = {}; // 4
|
||||
VkFormat format; // 4
|
||||
uint64_t externalFormat = 0; // 8
|
||||
};
|
||||
@@ -45,16 +47,15 @@ private:
|
||||
|
||||
struct ConversionEqualTo {
|
||||
bool operator()(Params lhs, Params rhs) const noexcept {
|
||||
SamplerYcbcrConversion::EqualTo equal;
|
||||
fvkutils::SamplerYcbcrConversion::EqualTo equal;
|
||||
return equal(lhs.conversion, rhs.conversion) &&
|
||||
lhs.externalFormat == rhs.externalFormat &&
|
||||
lhs.format == rhs.format;
|
||||
lhs.externalFormat == rhs.externalFormat && lhs.format == rhs.format;
|
||||
}
|
||||
};
|
||||
using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
|
||||
tsl::robin_map<Params, VkSamplerYcbcrConversion, ConversionHashFn, ConversionEqualTo> mCache;
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||
|
||||
@@ -923,7 +923,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
// Keep a copy of context for swapchains.
|
||||
mImpl->mContext = context;
|
||||
|
||||
return VulkanDriver::create(this, mImpl->mContext, driverConfig);
|
||||
return VulkanDriver::create(this, context, driverConfig);
|
||||
}
|
||||
|
||||
// This needs to be explictly written for
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKAN_UTILS_CONVERSION_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_CONVERSION_H
|
||||
|
||||
#include "Definitions.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <private/backend/BackendUtils.h> // for getFormatSize()
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
@@ -379,6 +381,81 @@ static constexpr uint8_t getFragmentStageShift() noexcept {
|
||||
// We have at most 4 descriptor sets. This is to indicate which ones are active.
|
||||
using DescriptorSetMask = utils::bitset8;
|
||||
|
||||
//! this API is copied from (and only applies to) the Vulkan spec.
|
||||
//! These specify YUV to RGB conversions.
|
||||
enum class SamplerYcbcrModelConversion : uint8_t {
|
||||
RGB_IDENTITY = 0,
|
||||
YCBCR_IDENTITY = 1,
|
||||
YCBCR_709 = 2,
|
||||
YCBCR_601 = 3,
|
||||
YCBCR_2020 = 4,
|
||||
};
|
||||
|
||||
enum class SamplerYcbcrRange : uint8_t {
|
||||
ITU_FULL = 0,
|
||||
ITU_NARROW = 1,
|
||||
};
|
||||
|
||||
enum class ChromaLocation : uint8_t {
|
||||
COSITED_EVEN = 0,
|
||||
MIDPOINT = 1,
|
||||
};
|
||||
|
||||
//! Sampler parameters
|
||||
struct SamplerYcbcrConversion { // NOLINT
|
||||
SamplerYcbcrModelConversion ycbcrModel : 4;
|
||||
TextureSwizzle r : 4;
|
||||
TextureSwizzle g : 4;
|
||||
TextureSwizzle b : 4;
|
||||
TextureSwizzle a : 4;
|
||||
SamplerYcbcrRange ycbcrRange : 1;
|
||||
ChromaLocation xChromaOffset : 1;
|
||||
ChromaLocation yChromaOffset : 1;
|
||||
SamplerMagFilter chromaFilter : 1;
|
||||
uint8_t padding;
|
||||
|
||||
struct Hasher {
|
||||
size_t operator()(const SamplerYcbcrConversion p) const noexcept {
|
||||
// we don't use std::hash<> here, so we don't have to include <functional>
|
||||
return *reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&p));
|
||||
}
|
||||
};
|
||||
|
||||
struct EqualTo {
|
||||
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
|
||||
assert_invariant(lhs.padding == 0);
|
||||
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs == *pRhs;
|
||||
}
|
||||
};
|
||||
|
||||
struct LessThan {
|
||||
bool operator()(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) const noexcept {
|
||||
assert_invariant(lhs.padding == 0);
|
||||
auto* pLhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&lhs));
|
||||
auto* pRhs = reinterpret_cast<uint32_t const*>(reinterpret_cast<char const*>(&rhs));
|
||||
return *pLhs < *pRhs;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
friend bool operator==(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
|
||||
return SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
|
||||
}
|
||||
friend bool operator!=(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
|
||||
return !SamplerYcbcrConversion::EqualTo{}(lhs, rhs);
|
||||
}
|
||||
friend bool operator<(SamplerYcbcrConversion lhs, SamplerYcbcrConversion rhs) noexcept {
|
||||
return SamplerYcbcrConversion::LessThan{}(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(SamplerYcbcrConversion) == 4);
|
||||
|
||||
static_assert(sizeof(SamplerYcbcrConversion) <= sizeof(uint64_t),
|
||||
"SamplerYcbcrConversion must be no more than 64 bits");
|
||||
|
||||
} // namespace filament::backend::fvkutils
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKAN_UTILS_DEFINITIONS_H
|
||||
|
||||
5
filament/backend/src/webgpu/SpdMipmapGenerator/Readme.md
Normal file
5
filament/backend/src/webgpu/SpdMipmapGenerator/Readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
WebGPU does not include a trivial way to create mipmaps, and thus we must provide a render pass or compute pass to generate them.
|
||||
|
||||
This directory is the result of porting https://github.com/JolifantoBambla/webgpu-spd to C++, which itself is a WebGPU port of https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/main/docs/samples/single-pass-downsampler.md.
|
||||
|
||||
The first version of our port is primarily from a Gemini "Convert this to C++" request, which worked as an MVP with very few changes. Future commits will likely alter this version significantly, but it is being merged in this early state to facilitate rapid iteration.
|
||||
@@ -0,0 +1,956 @@
|
||||
#include "SpdMipmapGenerator.h"
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
// C++ port of https://github.com/JolifantoBambla/webgpu-spd for early experiments
|
||||
namespace spd {
|
||||
|
||||
// Helper to convert enums to strings for map keys or shader code
|
||||
const char* to_string(wgpu::TextureFormat format) {
|
||||
switch (format) {
|
||||
case wgpu::TextureFormat::RGBA8Unorm:
|
||||
return "rgba8unorm";
|
||||
case wgpu::TextureFormat::BGRA8Unorm:
|
||||
return "bgra8unorm";
|
||||
case wgpu::TextureFormat::R32Float:
|
||||
return "r32float";
|
||||
case wgpu::TextureFormat::RG32Float:
|
||||
return "rg32float";
|
||||
case wgpu::TextureFormat::RGBA32Float:
|
||||
return "rgba32float";
|
||||
case wgpu::TextureFormat::R16Float:
|
||||
return "r16float";
|
||||
case wgpu::TextureFormat::RG16Float:
|
||||
return "rg16float";
|
||||
case wgpu::TextureFormat::RGBA16Float:
|
||||
return "rgba16float";
|
||||
// Add other formats as needed
|
||||
default:
|
||||
return "rgba8unorm";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string MipmapGenerator::GetFilterCode(SPDFilter filter) {
|
||||
switch (filter) {
|
||||
case SPDFilter::Min:
|
||||
return R"(
|
||||
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
|
||||
return min(min(v0, v1), min(v2, v3));
|
||||
}
|
||||
)";
|
||||
case SPDFilter::Max:
|
||||
return R"(
|
||||
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
|
||||
return max(max(v0, v1), max(v2, v3));
|
||||
}
|
||||
)";
|
||||
case SPDFilter::MinMax:
|
||||
return R"(
|
||||
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
|
||||
let max4 = max(max(v0.xy, v1.xy), max(v2.xy, v3.xy));
|
||||
return vec4<SPDScalar>(min(min(v0.x, v1.x), min(v2.x, v3.x)), max(max4.x, max4.y), 0.0, 0.0);
|
||||
}
|
||||
)";
|
||||
case SPDFilter::Average:
|
||||
default:
|
||||
return R"(
|
||||
fn spd_reduce_4(v0: vec4<SPDScalar>, v1: vec4<SPDScalar>, v2: vec4<SPDScalar>, v3: vec4<SPDScalar>) -> vec4<SPDScalar> {
|
||||
return (v0 + v1 + v2 + v3) * 0.25;
|
||||
}
|
||||
)";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MipmapGenerator::MipmapGenerator(const wgpu::Device& device)
|
||||
: m_device(device) {
|
||||
wgpu::BindGroupLayoutEntry bglEntry{};
|
||||
bglEntry.binding = 0;
|
||||
bglEntry.visibility = wgpu::ShaderStage::Compute;
|
||||
bglEntry.buffer.type = wgpu::BufferBindingType::Uniform;
|
||||
bglEntry.buffer.minBindingSize = 16;
|
||||
|
||||
wgpu::BindGroupLayoutDescriptor bglDesc{};
|
||||
bglDesc.entryCount = 1;
|
||||
bglDesc.entries = &bglEntry;
|
||||
m_internalResourcesBindGroupLayout =
|
||||
m_device.CreateBindGroupLayout(&bglDesc); // Basic BGL for <= 6 mips
|
||||
|
||||
wgpu::Limits deviceLimits;
|
||||
m_device.GetLimits(&deviceLimits);
|
||||
// Max number of *output* mips we can generate in a single pass.
|
||||
// This is device.maxStorageTexturesPerShaderStage - 1 (for the source texture binding),
|
||||
// further capped at 11 output mips (which means 12 total texture bindings: 1 source + 11
|
||||
// outputs). WebGPU minspec for maxStorageTexturesPerShaderStage is 4, so
|
||||
// deviceLimits.maxStorageTexturesPerShaderStage >= 4. Thus, m_maxMipsPerPass will be at least
|
||||
// std::min(4u - 1, 11u) = 3.
|
||||
m_maxMipsPerPass =
|
||||
std::min(static_cast<uint32_t>(deviceLimits.maxStorageTexturesPerShaderStage) - 1, 11u);
|
||||
m_maxArrayLayers = deviceLimits.maxStorageTexturesPerShaderStage;
|
||||
|
||||
|
||||
// If we can generate more than 6 mips in a pass, we need an advanced BGL
|
||||
// that includes atomic counter and mid-mip buffer for levels > 5.
|
||||
if (m_maxMipsPerPass > 6) {
|
||||
std::vector<wgpu::BindGroupLayoutEntry> entriesAdvanced(3);
|
||||
// Entry 0: Uniform buffer (DownsamplePassMeta)
|
||||
entriesAdvanced[0].binding = 0;
|
||||
entriesAdvanced[0].visibility = wgpu::ShaderStage::Compute;
|
||||
entriesAdvanced[0].buffer.type = wgpu::BufferBindingType::Uniform;
|
||||
entriesAdvanced[0].buffer.minBindingSize = 16; // sizeof(DownsamplePassMeta) effectively
|
||||
|
||||
// Entry 1: Storage buffer (spd_global_counter)
|
||||
entriesAdvanced[1].binding = 1;
|
||||
entriesAdvanced[1].visibility = wgpu::ShaderStage::Compute;
|
||||
entriesAdvanced[1].buffer.type = wgpu::BufferBindingType::Storage;
|
||||
entriesAdvanced[1].buffer.minBindingSize = 4; // sizeof(atomic<u32>)
|
||||
|
||||
// Entry 2: Storage buffer (mip_dst_6_buffer)
|
||||
entriesAdvanced[2].binding = 2;
|
||||
entriesAdvanced[2].visibility = wgpu::ShaderStage::Compute;
|
||||
entriesAdvanced[2].buffer.type = wgpu::BufferBindingType::Storage;
|
||||
// Size for array<array<array<vec4<f32>, 64>, 64>> which is 16 (bytes for vec4f32) * 64 * 64
|
||||
// * arrayLayers. The shader defines it as [slice][uv.y][uv.x], so 64x64 per slice.
|
||||
// minBindingSize here is for one slice as a baseline. Actual buffer will be larger for
|
||||
// multiple array layers.
|
||||
entriesAdvanced[2].buffer.minBindingSize = 16 * 64 * 64;
|
||||
|
||||
wgpu::BindGroupLayoutDescriptor bglDescAdvanced{};
|
||||
bglDescAdvanced.label = "SPD Internal BGL (Advanced)";
|
||||
bglDescAdvanced.entryCount = static_cast<uint32_t>(entriesAdvanced.size());
|
||||
bglDescAdvanced.entries = entriesAdvanced.data();
|
||||
m_internalResourcesBindGroupLayout_Advanced =
|
||||
m_device.CreateBindGroupLayout(&bglDescAdvanced);
|
||||
}
|
||||
}
|
||||
|
||||
void MipmapGenerator::PreparePipelines(wgpu::TextureFormat format, SPDFilter filter,
|
||||
bool halfPrecision) {
|
||||
SPDScalarType scalarType = SanitizeScalarType(format, halfPrecision);
|
||||
// Prepare for a reasonable number of mips, up to m_maxMipsPerPass
|
||||
// The loop should go from 1 up to and including m_maxMipsPerPass.
|
||||
// If m_maxMipsPerPass is 0 (e.g. maxStorageTexturesPerShaderStage <=1 ), this loop won't run.
|
||||
for (uint32_t i = 1; i <= m_maxMipsPerPass; ++i) {
|
||||
GetOrCreatePipeline({ format, scalarType, filter, i });
|
||||
}
|
||||
}
|
||||
|
||||
SPDScalarType MipmapGenerator::SanitizeScalarType(wgpu::TextureFormat format, bool halfPrecision) {
|
||||
std::string formatStr = to_string(format);
|
||||
std::transform(formatStr.begin(), formatStr.end(), formatStr.begin(), ::tolower);
|
||||
|
||||
SPDScalarType texelType = SPDScalarType::F32; // Default to F32
|
||||
if (formatStr.find("sint") != std::string::npos) {
|
||||
texelType = SPDScalarType::I32;
|
||||
} else if (formatStr.find("uint") != std::string::npos) {
|
||||
texelType = SPDScalarType::U32;
|
||||
}
|
||||
|
||||
if (halfPrecision && texelType == SPDScalarType::F32) {
|
||||
if (m_device.HasFeature(wgpu::FeatureName::ShaderF16)) {
|
||||
return SPDScalarType::F16;
|
||||
} else {
|
||||
// Optional: Log a warning here that halfPrecision was requested for a float format
|
||||
// but the device does not support ShaderF16, so F32 will be used.
|
||||
}
|
||||
} else if (halfPrecision && texelType != SPDScalarType::F32) {
|
||||
// Optional: Log a warning here that halfPrecision was requested for a non-float format.
|
||||
}
|
||||
return texelType;
|
||||
}
|
||||
|
||||
SPDPipeline& MipmapGenerator::GetOrCreatePipeline(const PipelineCacheKey& key) {
|
||||
uint32_t numMipsForPipeline = std::min(key.numMips, m_maxMipsPerPass);
|
||||
if (m_pipelines.find(key) == m_pipelines.end()) {
|
||||
// Create the pipeline
|
||||
SPDPipeline spdPipeline;
|
||||
|
||||
std::vector<wgpu::BindGroupLayoutEntry> mipsBglEntries;
|
||||
// Bindings are 0 for source, 1 to numMipsForPipeline for destinations
|
||||
for (uint32_t i = 0; i <= numMipsForPipeline; ++i) {
|
||||
wgpu::BindGroupLayoutEntry entry{};
|
||||
entry.binding = i;
|
||||
entry.visibility = wgpu::ShaderStage::Compute;
|
||||
if (i == 0) {
|
||||
entry.texture.sampleType = (key.scalarType == SPDScalarType::I32)
|
||||
? wgpu::TextureSampleType::Sint
|
||||
: (key.scalarType == SPDScalarType::U32)
|
||||
? wgpu::TextureSampleType::Uint
|
||||
: wgpu::TextureSampleType::UnfilterableFloat;
|
||||
entry.texture.viewDimension = wgpu::TextureViewDimension::e2DArray;
|
||||
} else {
|
||||
entry.storageTexture.access = wgpu::StorageTextureAccess::WriteOnly;
|
||||
entry.storageTexture.format = key.format;
|
||||
entry.storageTexture.viewDimension = wgpu::TextureViewDimension::e2DArray;
|
||||
}
|
||||
mipsBglEntries.push_back(entry);
|
||||
}
|
||||
|
||||
wgpu::BindGroupLayoutDescriptor mipsBglDesc{};
|
||||
mipsBglDesc.entryCount = mipsBglEntries.size();
|
||||
mipsBglDesc.entries = mipsBglEntries.data();
|
||||
spdPipeline.mipsBindGroupLayout = m_device.CreateBindGroupLayout(&mipsBglDesc);
|
||||
|
||||
std::string shaderCode = MakeShaderCode(key.format, GetFilterCode(key.filter),
|
||||
numMipsForPipeline, key.scalarType);
|
||||
wgpu::ShaderModuleWGSLDescriptor wgslDesc{};
|
||||
wgslDesc.code = shaderCode.c_str();
|
||||
|
||||
wgpu::ShaderModuleDescriptor shaderModuleDesc{};
|
||||
shaderModuleDesc.nextInChain = &wgslDesc;
|
||||
wgpu::ShaderModule shaderModule = m_device.CreateShaderModule(&shaderModuleDesc);
|
||||
|
||||
wgpu::BindGroupLayout chosenInternalBGL =
|
||||
(numMipsForPipeline > 6 && m_internalResourcesBindGroupLayout_Advanced)
|
||||
? m_internalResourcesBindGroupLayout_Advanced
|
||||
: m_internalResourcesBindGroupLayout;
|
||||
|
||||
wgpu::BindGroupLayout bgls[] = { spdPipeline.mipsBindGroupLayout, chosenInternalBGL };
|
||||
wgpu::PipelineLayoutDescriptor layoutDesc{};
|
||||
layoutDesc.bindGroupLayoutCount = 2;
|
||||
layoutDesc.bindGroupLayouts = bgls;
|
||||
|
||||
wgpu::ComputePipelineDescriptor pipelineDesc{};
|
||||
pipelineDesc.layout = m_device.CreatePipelineLayout(&layoutDesc);
|
||||
pipelineDesc.compute.module = shaderModule;
|
||||
pipelineDesc.compute.entryPoint = "downsample";
|
||||
|
||||
spdPipeline.pipeline = m_device.CreateComputePipeline(&pipelineDesc);
|
||||
m_pipelines[key] = std::move(spdPipeline);
|
||||
}
|
||||
return m_pipelines[key];
|
||||
}
|
||||
|
||||
|
||||
void MipmapGenerator::Generate(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
|
||||
const SPDPassConfig& config) {
|
||||
if (config.numMips <= 1 || config.numMips <= config.sourceMipLevel + 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_maxMipsPerPass == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
PreparePipelines(srcTexture.GetFormat(), config.filter, config.halfPrecision);
|
||||
|
||||
uint32_t totalSrcArrayLayers = srcTexture.GetDepthOrArrayLayers();
|
||||
|
||||
// Loop over array layer chunks
|
||||
for (uint32_t currentBaseArrayLayer = 0; currentBaseArrayLayer < totalSrcArrayLayers;
|
||||
currentBaseArrayLayer += m_maxArrayLayers) {
|
||||
|
||||
uint32_t arrayLayersThisPass =
|
||||
std::min(m_maxArrayLayers, totalSrcArrayLayers - currentBaseArrayLayer);
|
||||
|
||||
uint32_t mipsToGenerateCount = config.numMips - (config.sourceMipLevel + 1);
|
||||
uint32_t currentSourceMipLevel = config.sourceMipLevel;
|
||||
|
||||
while (mipsToGenerateCount > 0) {
|
||||
uint32_t mipsThisPass = std::min(mipsToGenerateCount, m_maxMipsPerPass);
|
||||
|
||||
SPDPassConfig passSpecificConfig = {};
|
||||
passSpecificConfig.filter = config.filter;
|
||||
passSpecificConfig.halfPrecision = config.halfPrecision;
|
||||
passSpecificConfig.targetTexture =
|
||||
config.targetTexture ? config.targetTexture : srcTexture;
|
||||
passSpecificConfig.numMips = mipsThisPass;
|
||||
passSpecificConfig.sourceMipLevel = currentSourceMipLevel;
|
||||
|
||||
// Pass baseArrayLayer and numArrayLayers directly to generatePass
|
||||
generatePass(commandEncoder, srcTexture, passSpecificConfig, currentBaseArrayLayer,
|
||||
arrayLayersThisPass);
|
||||
|
||||
mipsToGenerateCount -= mipsThisPass;
|
||||
currentSourceMipLevel += mipsThisPass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MipmapGenerator::generatePass(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
|
||||
const SPDPassConfig& passConfig, uint32_t baseArrayLayer, uint32_t numArrayLayers) {
|
||||
uint32_t width = srcTexture.GetWidth() >> passConfig.sourceMipLevel;
|
||||
uint32_t height = srcTexture.GetHeight() >> passConfig.sourceMipLevel;
|
||||
|
||||
uint32_t arrayLayerCountForThisPass = numArrayLayers;
|
||||
|
||||
wgpu::Texture target = passConfig.targetTexture ? passConfig.targetTexture : srcTexture;
|
||||
uint32_t numMipsThisPass = passConfig.numMips;
|
||||
|
||||
if (numMipsThisPass == 0) return;
|
||||
|
||||
SPDScalarType scalarType = SanitizeScalarType(srcTexture.GetFormat(), passConfig.halfPrecision);
|
||||
SPDPipeline& spdPipeline = GetOrCreatePipeline(
|
||||
{ target.GetFormat(), scalarType, passConfig.filter, numMipsThisPass });
|
||||
|
||||
// --- Create Bind Group 0 (Mips) ---
|
||||
std::vector<wgpu::BindGroupEntry> mipEntries;
|
||||
|
||||
wgpu::TextureViewDescriptor srcViewDesc{};
|
||||
srcViewDesc.dimension = wgpu::TextureViewDimension::e2DArray;
|
||||
srcViewDesc.baseMipLevel = passConfig.sourceMipLevel;
|
||||
srcViewDesc.mipLevelCount = 1;
|
||||
srcViewDesc.baseArrayLayer = baseArrayLayer;
|
||||
srcViewDesc.arrayLayerCount = arrayLayerCountForThisPass;
|
||||
|
||||
wgpu::BindGroupEntry srcEntry{};
|
||||
srcEntry.binding = 0;
|
||||
srcEntry.textureView = srcTexture.CreateView(&srcViewDesc);
|
||||
mipEntries.push_back(srcEntry);
|
||||
|
||||
for (uint32_t i = 0; i < numMipsThisPass; ++i) {
|
||||
wgpu::TextureViewDescriptor dstViewDesc{};
|
||||
dstViewDesc.dimension = wgpu::TextureViewDimension::e2DArray;
|
||||
dstViewDesc.baseMipLevel = passConfig.sourceMipLevel + i + 1;
|
||||
dstViewDesc.mipLevelCount = 1;
|
||||
dstViewDesc.baseArrayLayer = baseArrayLayer;
|
||||
dstViewDesc.arrayLayerCount = arrayLayerCountForThisPass;
|
||||
|
||||
wgpu::BindGroupEntry dstEntry{};
|
||||
dstEntry.binding = i + 1;
|
||||
dstEntry.textureView = target.CreateView(&dstViewDesc);
|
||||
mipEntries.push_back(dstEntry);
|
||||
}
|
||||
|
||||
wgpu::BindGroupDescriptor mipBindGroupDesc{};
|
||||
mipBindGroupDesc.layout = spdPipeline.mipsBindGroupLayout;
|
||||
mipBindGroupDesc.entryCount = mipEntries.size();
|
||||
mipBindGroupDesc.entries = mipEntries.data();
|
||||
wgpu::BindGroup mipsBindGroup = m_device.CreateBindGroup(&mipBindGroupDesc);
|
||||
|
||||
// --- Create Bind Group 1 (Internal Resources) ---
|
||||
uint32_t widthDiv64 = (width + 63) / 64;
|
||||
uint32_t heightDiv64 = (height + 63) / 64;
|
||||
uint32_t numWorkGroups = widthDiv64 * heightDiv64;
|
||||
|
||||
struct DownsamplePassMeta {
|
||||
uint32_t work_group_offset[2] = { 0, 0 };
|
||||
uint32_t num_work_groups;
|
||||
uint32_t mips;
|
||||
uint32_t padding[12];
|
||||
} meta;
|
||||
meta.num_work_groups = numWorkGroups;
|
||||
meta.mips = numMipsThisPass;
|
||||
|
||||
wgpu::BufferDescriptor metaBufferDesc{};
|
||||
metaBufferDesc.size = sizeof(DownsamplePassMeta);
|
||||
metaBufferDesc.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst;
|
||||
wgpu::Buffer metaBuffer = m_device.CreateBuffer(&metaBufferDesc);
|
||||
m_device.GetQueue().WriteBuffer(metaBuffer, 0, &meta, sizeof(meta));
|
||||
|
||||
wgpu::BindGroup internalBindGroup;
|
||||
wgpu::BindGroupLayout chosenInternalBGL =
|
||||
(numMipsThisPass > 6 && m_internalResourcesBindGroupLayout_Advanced)
|
||||
? m_internalResourcesBindGroupLayout_Advanced
|
||||
: m_internalResourcesBindGroupLayout;
|
||||
|
||||
if (numMipsThisPass > 6 && m_internalResourcesBindGroupLayout_Advanced) {
|
||||
wgpu::BufferDescriptor atomicCounterBufferDesc{};
|
||||
atomicCounterBufferDesc.size = sizeof(uint32_t) * arrayLayerCountForThisPass;
|
||||
atomicCounterBufferDesc.usage = wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopyDst;
|
||||
wgpu::Buffer atomicCounterBuffer = m_device.CreateBuffer(&atomicCounterBufferDesc);
|
||||
|
||||
commandEncoder.ClearBuffer(atomicCounterBuffer, 0, WGPU_WHOLE_SIZE);
|
||||
|
||||
wgpu::BufferDescriptor midMipBufferDesc{};
|
||||
midMipBufferDesc.size = 64 * 64 * 16 * arrayLayerCountForThisPass;
|
||||
midMipBufferDesc.usage = wgpu::BufferUsage::Storage;
|
||||
wgpu::Buffer midMipBuffer = m_device.CreateBuffer(&midMipBufferDesc);
|
||||
|
||||
std::vector<wgpu::BindGroupEntry> internalEntries(3);
|
||||
internalEntries[0].binding = 0;
|
||||
internalEntries[0].buffer = metaBuffer;
|
||||
internalEntries[0].size = sizeof(DownsamplePassMeta);
|
||||
|
||||
internalEntries[1].binding = 1;
|
||||
internalEntries[1].buffer = atomicCounterBuffer;
|
||||
internalEntries[1].size = atomicCounterBuffer.GetSize();
|
||||
|
||||
internalEntries[2].binding = 2;
|
||||
internalEntries[2].buffer = midMipBuffer;
|
||||
internalEntries[2].size = midMipBuffer.GetSize();
|
||||
|
||||
wgpu::BindGroupDescriptor internalBindGroupDesc{};
|
||||
internalBindGroupDesc.layout = chosenInternalBGL;
|
||||
internalBindGroupDesc.entryCount = static_cast<uint32_t>(internalEntries.size());
|
||||
internalBindGroupDesc.entries = internalEntries.data();
|
||||
internalBindGroup = m_device.CreateBindGroup(&internalBindGroupDesc);
|
||||
} else {
|
||||
wgpu::BindGroupEntry metaEntry{};
|
||||
metaEntry.binding = 0;
|
||||
metaEntry.buffer = metaBuffer;
|
||||
metaEntry.size = sizeof(DownsamplePassMeta);
|
||||
|
||||
wgpu::BindGroupDescriptor internalBindGroupDesc{};
|
||||
internalBindGroupDesc.layout = chosenInternalBGL;
|
||||
internalBindGroupDesc.entryCount = 1;
|
||||
internalBindGroupDesc.entries = &metaEntry;
|
||||
internalBindGroup = m_device.CreateBindGroup(&internalBindGroupDesc);
|
||||
}
|
||||
|
||||
// --- Dispatch ---
|
||||
wgpu::ComputePassEncoder pass = commandEncoder.BeginComputePass();
|
||||
pass.SetPipeline(spdPipeline.pipeline);
|
||||
pass.SetBindGroup(0, mipsBindGroup);
|
||||
pass.SetBindGroup(1, internalBindGroup);
|
||||
pass.DispatchWorkgroups(widthDiv64, heightDiv64, arrayLayerCountForThisPass);
|
||||
pass.End();
|
||||
}
|
||||
// Main shader generation logic
|
||||
|
||||
// Helper function to check if a string is in a vector of strings
|
||||
bool includes(const std::vector<std::string>& vec, const std::string& str) {
|
||||
for (const auto& s: vec) {
|
||||
if (s == str) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string MakeShaderCode(wgpu::TextureFormat outputFormat, const std::string& filterOp,
|
||||
unsigned int numMips, // Assuming a default value for numMips
|
||||
SPDScalarType scalarType) { // Default scalarType
|
||||
std::stringstream ss;
|
||||
|
||||
std::string texelType;
|
||||
if (scalarType == SPDScalarType::I32) {
|
||||
texelType = "i32";
|
||||
} else if (scalarType == SPDScalarType::U32) {
|
||||
texelType = "u32";
|
||||
} else {
|
||||
texelType = "f32";
|
||||
}
|
||||
|
||||
bool useF16 = (scalarType == SPDScalarType::F16);
|
||||
|
||||
std::string filterCode = filterOp;
|
||||
if (filterOp == SPD_FILTER_AVERAGE && !includes({ "f32", "f16" }, texelType)) {
|
||||
// Replace "* 0.25" with "/ 4"
|
||||
size_t pos = filterCode.find("* 0.25");
|
||||
if (pos != std::string::npos) {
|
||||
filterCode.replace(pos, std::string("* 0.25").length(), "/ 4");
|
||||
}
|
||||
}
|
||||
|
||||
// Generate mipsBindings
|
||||
std::string mipsBindings;
|
||||
for (unsigned int i = 0; i < numMips; ++i) {
|
||||
mipsBindings += "@group(0) @binding(" + std::to_string(i + 1) + ") var dst_mip_" +
|
||||
std::to_string(i + 1) + ": texture_storage_2d_array<" +
|
||||
to_string(outputFormat) + ", write>;\n";
|
||||
}
|
||||
|
||||
// Generate mipsAccessorBody
|
||||
std::string mipsAccessorBody;
|
||||
for (unsigned int i = 0; i < numMips; ++i) {
|
||||
if (i == 5 && numMips > 6) {
|
||||
mipsAccessorBody += " else if mip == 6 {\n";
|
||||
mipsAccessorBody += " textureStore(dst_mip_6, uv, slice, " +
|
||||
(useF16 ? "vec4<" + texelType + ">(value)" : "value") + ");\n";
|
||||
mipsAccessorBody += " mip_dst_6_buffer[slice][uv.y][uv.x] = value;\n";
|
||||
mipsAccessorBody += " }";
|
||||
} else {
|
||||
if (i != 0) {
|
||||
mipsAccessorBody += " else ";
|
||||
}
|
||||
mipsAccessorBody += "if mip == " + std::to_string(i + 1) + " {\n";
|
||||
mipsAccessorBody += " textureStore(dst_mip_" + std::to_string(i + 1) +
|
||||
", uv, slice, " +
|
||||
(useF16 ? "vec4<" + texelType + ">(value)" : "value") + ");\n";
|
||||
mipsAccessorBody += " }";
|
||||
}
|
||||
}
|
||||
|
||||
std::string mipsAccessor =
|
||||
"fn store_dst_mip(value: vec4<SPDScalar>, uv: vec2<u32>, slice: u32, mip: u32) {\n" +
|
||||
mipsAccessorBody + "\n}";
|
||||
std::string midMipAccessor = "return mip_dst_6_buffer[slice][uv.y][uv.x];";
|
||||
|
||||
// Start building the final shader code string
|
||||
ss << R"(
|
||||
// This file is part of the FidelityFX SDK.
|
||||
//
|
||||
// Copyright (C) 2023 Advanced Micro Devices, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files(the “Software”), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions :
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
// AUTHORS COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
|
||||
// Definitions --------------------------------------------------------------------------------------------------------
|
||||
|
||||
)";
|
||||
if (useF16) {
|
||||
ss << "enable f16;\n";
|
||||
}
|
||||
ss << "alias SPDScalar = " << texelType
|
||||
<< ";\n\n"; // Using texelType here, assuming SPDScalar maps to it.
|
||||
|
||||
ss << R"(
|
||||
// Helpers ------------------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* A helper function performing a remap 64x1 to 8x8 remapping which is necessary for 2D wave reductions.
|
||||
* * The 64-wide lane indices to 8x8 remapping is performed as follows:
|
||||
* 00 01 08 09 10 11 18 19
|
||||
* 02 03 0a 0b 12 13 1a 1b
|
||||
* 04 05 0c 0d 14 15 1c 1d
|
||||
* 06 07 0e 0f 16 17 1e 1f
|
||||
* 20 21 28 29 30 31 38 39
|
||||
* 22 23 2a 2b 32 33 3a 3b
|
||||
* 24 25 2c 2d 34 35 3c 3d
|
||||
* 26 27 2e 2f 36 37 3e 3f
|
||||
* * @param a: The input 1D coordinate to remap.
|
||||
*
|
||||
* @returns The remapped 2D coordinates.
|
||||
*/
|
||||
fn remap_for_wave_reduction(a: u32) -> vec2<u32> {
|
||||
return vec2<u32>(
|
||||
insertBits(extractBits(a, 2u, 3u), a, 0u, 1u),
|
||||
insertBits(extractBits(a, 3u, 3u), extractBits(a, 1u, 2u), 0u, 2u)
|
||||
);
|
||||
}
|
||||
|
||||
fn map_to_xy(local_invocation_index: u32) -> vec2<u32> {
|
||||
let sub_xy: vec2<u32> = remap_for_wave_reduction(local_invocation_index % 64);
|
||||
return vec2<u32>(
|
||||
sub_xy.x + 8 * ((local_invocation_index >> 6) % 2),
|
||||
sub_xy.y + 8 * ((local_invocation_index >> 7))
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute a linear value from a SRGB value.
|
||||
* * @param value: The value to convert to linear from SRGB.
|
||||
* * @returns A value in SRGB space.
|
||||
*/
|
||||
/*
|
||||
fn srgb_to_linear(value: SPDScalar) -> SPDScalar {
|
||||
let j = vec3<SPDScalar>(0.0031308 * 12.92, 12.92, 1.0 / 2.4);
|
||||
let k = vec2<SPDScalar>(1.055, -0.055);
|
||||
return clamp(j.x, value * j.y, pow(value, j.z) * k.x + k.y);
|
||||
}
|
||||
*/
|
||||
|
||||
// Resources & Accessors -----------------------------------------------------------------------------------------------
|
||||
struct DownsamplePassMeta {
|
||||
work_group_offset: vec2<u32>,
|
||||
num_work_groups: u32,
|
||||
mips: u32,
|
||||
}
|
||||
|
||||
// In the original version dst_mip_i is an image2Darray [SPD_MAX_MIP_LEVELS+1], i.e., 12+1, but WGSL doesn't support arrays of textures yet
|
||||
// Also these are read_write because for mips 7-13, the workgroup reads from mip level 6 - since most formats don't support read_write access in WGSL yet, we use a single read_write buffer in such cases instead
|
||||
@group(0) @binding(0) var src_mip_0: texture_2d_array<)"
|
||||
<< texelType << R"(>;
|
||||
)" << mipsBindings
|
||||
<< R"(
|
||||
|
||||
@group(1) @binding(0) var<uniform> downsample_pass_meta : DownsamplePassMeta;
|
||||
@group(1) @binding(1) var<storage, read_write> spd_global_counter: array<atomic<u32>>;
|
||||
@group(1) @binding(2) var<storage, read_write> mip_dst_6_buffer: array<array<array<vec4<f32>, 64>, 64>>;
|
||||
|
||||
fn get_mips() -> u32 {
|
||||
return downsample_pass_meta.mips;
|
||||
}
|
||||
|
||||
fn get_num_work_groups() -> u32 {
|
||||
return downsample_pass_meta.num_work_groups;
|
||||
}
|
||||
|
||||
fn get_work_group_offset() -> vec2<u32> {
|
||||
return downsample_pass_meta.work_group_offset;
|
||||
}
|
||||
|
||||
fn load_src_image(uv: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
|
||||
return vec4<SPDScalar>(textureLoad(src_mip_0, uv, slice, 0));
|
||||
}
|
||||
|
||||
fn load_mid_mip_image(uv: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
|
||||
)";
|
||||
if (numMips > 6) {
|
||||
ss << midMipAccessor;
|
||||
} else {
|
||||
ss << "return vec4<SPDScalar>();";
|
||||
}
|
||||
ss << R"(
|
||||
}
|
||||
|
||||
)" << mipsAccessor
|
||||
<< R"(
|
||||
|
||||
// Workgroup -----------------------------------------------------------------------------------------------------------
|
||||
|
||||
var<workgroup> spd_intermediate: array<array<vec4<SPDScalar>, 16>, 16>;
|
||||
var<workgroup> spd_counter: atomic<u32>;
|
||||
|
||||
fn spd_increase_atomic_counter(slice: u32) {
|
||||
atomicStore(&spd_counter, atomicAdd(&spd_global_counter[slice], 1));
|
||||
}
|
||||
|
||||
fn spd_get_atomic_counter() -> u32 {
|
||||
return atomicLoad(&spd_counter);
|
||||
}
|
||||
|
||||
fn spd_reset_atomic_counter(slice: u32) {
|
||||
atomicStore(&spd_global_counter[slice], 0);
|
||||
}
|
||||
|
||||
// Cotnrol flow --------------------------------------------------------------------------------------------------------
|
||||
|
||||
fn spd_barrier() {
|
||||
// in glsl this does: groupMemoryBarrier(); barrier();
|
||||
workgroupBarrier();
|
||||
}
|
||||
|
||||
// Only last active workgroup should proceed
|
||||
fn spd_exit_workgroup(num_work_groups: u32, local_invocation_index: u32, slice: u32) -> bool {
|
||||
// global atomic counter
|
||||
if (local_invocation_index == 0) {
|
||||
spd_increase_atomic_counter(slice);
|
||||
}
|
||||
spd_barrier();
|
||||
return spd_get_atomic_counter() != (num_work_groups - 1);
|
||||
}
|
||||
|
||||
// Pixel access --------------------------------------------------------------------------------------------------------
|
||||
|
||||
)" << filterCode
|
||||
<< R"(
|
||||
|
||||
fn spd_store(pix: vec2<u32>, out_value: vec4<SPDScalar>, mip: u32, slice: u32) {
|
||||
store_dst_mip(out_value, pix, slice, mip + 1);
|
||||
}
|
||||
|
||||
fn spd_load_intermediate(x: u32, y: u32) -> vec4<SPDScalar> {
|
||||
return spd_intermediate[x][y];
|
||||
}
|
||||
|
||||
fn spd_store_intermediate(x: u32, y: u32, value: vec4<SPDScalar>) {
|
||||
spd_intermediate[x][y] = value;
|
||||
}
|
||||
|
||||
fn spd_reduce_intermediate(i0: vec2<u32>, i1: vec2<u32>, i2: vec2<u32>, i3: vec2<u32>) -> vec4<SPDScalar> {
|
||||
let v0 = spd_load_intermediate(i0.x, i0.y);
|
||||
let v1 = spd_load_intermediate(i1.x, i1.y);
|
||||
let v2 = spd_load_intermediate(i2.x, i2.y);
|
||||
let v3 = spd_load_intermediate(i3.x, i3.y);
|
||||
return spd_reduce_4(v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
fn spd_reduce_load_4(base: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
|
||||
let v0 = load_src_image(base + vec2<u32>(0, 0), slice);
|
||||
let v1 = load_src_image(base + vec2<u32>(0, 1), slice);
|
||||
let v2 = load_src_image(base + vec2<u32>(1, 0), slice);
|
||||
let v3 = load_src_image(base + vec2<u32>(1, 1), slice);
|
||||
return spd_reduce_4(v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
fn spd_reduce_load_mid_mip_4(base: vec2<u32>, slice: u32) -> vec4<SPDScalar> {
|
||||
let v0 = load_mid_mip_image(base + vec2<u32>(0, 0), slice);
|
||||
let v1 = load_mid_mip_image(base + vec2<u32>(0, 1), slice);
|
||||
let v2 = load_mid_mip_image(base + vec2<u32>(1, 0), slice);
|
||||
let v3 = load_mid_mip_image(base + vec2<u32>(1, 1), slice);
|
||||
return spd_reduce_4(v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
// Main logic ---------------------------------------------------------------------------------------------------------
|
||||
|
||||
fn spd_downsample_mips_0_1(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
|
||||
var v: array<vec4<SPDScalar>, 4>;
|
||||
|
||||
let workgroup64 = workgroup_id.xy * 64;
|
||||
let workgroup32 = workgroup_id.xy * 32;
|
||||
let workgroup16 = workgroup_id.xy * 16;
|
||||
|
||||
var tex = workgroup64 + vec2<u32>(x * 2, y * 2);
|
||||
var pix = workgroup32 + vec2<u32>(x, y);
|
||||
v[0] = spd_reduce_load_4(tex, slice);
|
||||
spd_store(pix, v[0], 0, slice);
|
||||
|
||||
tex = workgroup64 + vec2<u32>(x * 2 + 32, y * 2);
|
||||
pix = workgroup32 + vec2<u32>(x + 16, y);
|
||||
v[1] = spd_reduce_load_4(tex, slice);
|
||||
spd_store(pix, v[1], 0, slice);
|
||||
|
||||
tex = workgroup64 + vec2<u32>(x * 2, y * 2 + 32);
|
||||
pix = workgroup32 + vec2<u32>(x, y + 16);
|
||||
v[2] = spd_reduce_load_4(tex, slice);
|
||||
spd_store(pix, v[2], 0, slice);
|
||||
|
||||
tex = workgroup64 + vec2<u32>(x * 2 + 32, y * 2 + 32);
|
||||
pix = workgroup32 + vec2<u32>(x + 16, y + 16);
|
||||
v[3] = spd_reduce_load_4(tex, slice);
|
||||
spd_store(pix, v[3], 0, slice);
|
||||
|
||||
if mip <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0u; i < 4u; i++) {
|
||||
spd_store_intermediate(x, y, v[i]);
|
||||
spd_barrier();
|
||||
if local_invocation_index < 64 {
|
||||
v[i] = spd_reduce_intermediate(
|
||||
vec2<u32>(x * 2 + 0, y * 2 + 0),
|
||||
vec2<u32>(x * 2 + 1, y * 2 + 0),
|
||||
vec2<u32>(x * 2 + 0, y * 2 + 1),
|
||||
vec2<u32>(x * 2 + 1, y * 2 + 1)
|
||||
);
|
||||
spd_store(workgroup16 + vec2<u32>(x + (i % 2) * 8, y + (i / 2) * 8), v[i], 1, slice);
|
||||
}
|
||||
spd_barrier();
|
||||
}
|
||||
|
||||
if local_invocation_index < 64 {
|
||||
spd_store_intermediate(x + 0, y + 0, v[0]);
|
||||
spd_store_intermediate(x + 8, y + 0, v[1]);
|
||||
spd_store_intermediate(x + 0, y + 8, v[2]);
|
||||
spd_store_intermediate(x + 8, y + 8, v[3]);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_mip_2(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
|
||||
if local_invocation_index < 64u {
|
||||
let v = spd_reduce_intermediate(
|
||||
vec2<u32>(x * 2 + 0, y * 2 + 0),
|
||||
vec2<u32>(x * 2 + 1, y * 2 + 0),
|
||||
vec2<u32>(x * 2 + 0, y * 2 + 1),
|
||||
vec2<u32>(x * 2 + 1, y * 2 + 1)
|
||||
);
|
||||
spd_store(workgroup_id.xy * 8 + vec2<u32>(x, y), v, mip, slice);
|
||||
// store to LDS, try to reduce bank conflicts
|
||||
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
|
||||
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
// 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 x
|
||||
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
|
||||
// ...
|
||||
// x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0
|
||||
spd_store_intermediate(x * 2 + y % 2, y * 2, v);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_mip_3(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
|
||||
if local_invocation_index < 16u {
|
||||
// x 0 x 0
|
||||
// 0 0 0 0
|
||||
// 0 x 0 x
|
||||
// 0 0 0 0
|
||||
let v = spd_reduce_intermediate(
|
||||
vec2<u32>(x * 4 + 0 + 0, y * 4 + 0),
|
||||
vec2<u32>(x * 4 + 2 + 0, y * 4 + 0),
|
||||
vec2<u32>(x * 4 + 0 + 1, y * 4 + 2),
|
||||
vec2<u32>(x * 4 + 2 + 1, y * 4 + 2)
|
||||
);
|
||||
spd_store(workgroup_id.xy * 4 + vec2<u32>(x, y), v, mip, slice);
|
||||
// store to LDS
|
||||
// x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0
|
||||
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
// 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
// 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 0
|
||||
// ...
|
||||
// 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0
|
||||
// ...
|
||||
// 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x
|
||||
// ...
|
||||
spd_store_intermediate(x * 4 + y, y * 4, v);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_mip_4(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
|
||||
if local_invocation_index < 4u {
|
||||
// x 0 0 0 x 0 0 0
|
||||
// ...
|
||||
// 0 x 0 0 0 x 0 0
|
||||
let v = spd_reduce_intermediate(
|
||||
vec2<u32>(x * 8 + 0 + 0 + y * 2, y * 8 + 0),
|
||||
vec2<u32>(x * 8 + 4 + 0 + y * 2, y * 8 + 0),
|
||||
vec2<u32>(x * 8 + 0 + 1 + y * 2, y * 8 + 4),
|
||||
vec2<u32>(x * 8 + 4 + 1 + y * 2, y * 8 + 4)
|
||||
);
|
||||
spd_store(workgroup_id.xy * 2 + vec2<u32>(x, y), v, mip, slice);
|
||||
// store to LDS
|
||||
// x x x x 0 ...
|
||||
// 0 ...
|
||||
spd_store_intermediate(x + y * 2, 0, v);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_mip_5(workgroup_id: vec2<u32>, local_invocation_index: u32, mip: u32, slice: u32) {
|
||||
if local_invocation_index < 1u {
|
||||
// x x x x 0 ...
|
||||
// 0 ...
|
||||
let v = spd_reduce_intermediate(vec2<u32>(0, 0), vec2<u32>(1, 0), vec2<u32>(2, 0), vec2<u32>(3, 0));
|
||||
spd_store(workgroup_id.xy, v, mip, slice);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_next_four(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32) {
|
||||
if mips <= base_mip {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice);
|
||||
|
||||
if mips <= base_mip + 1 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1, slice);
|
||||
|
||||
if mips <= base_mip + 2 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2, slice);
|
||||
|
||||
if mips <= base_mip + 3 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
spd_downsample_mip_5(workgroup_id, local_invocation_index, base_mip + 3, slice);
|
||||
}
|
||||
|
||||
fn spd_downsample_last_four(x: u32, y: u32, workgroup_id: vec2<u32>, local_invocation_index: u32, base_mip: u32, mips: u32, slice: u32, exit: bool) {
|
||||
if mips <= base_mip {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
if !exit {
|
||||
spd_downsample_mip_2(x, y, workgroup_id, local_invocation_index, base_mip, slice);
|
||||
}
|
||||
|
||||
if mips <= base_mip + 1 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
if !exit {
|
||||
spd_downsample_mip_3(x, y, workgroup_id, local_invocation_index, base_mip + 1, slice);
|
||||
}
|
||||
|
||||
if mips <= base_mip + 2 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
if !exit {
|
||||
spd_downsample_mip_4(x, y, workgroup_id, local_invocation_index, base_mip + 2, slice);
|
||||
}
|
||||
|
||||
if mips <= base_mip + 3 {
|
||||
return;
|
||||
}
|
||||
spd_barrier();
|
||||
if !exit {
|
||||
spd_downsample_mip_5(workgroup_id, local_invocation_index, base_mip + 3, slice);
|
||||
}
|
||||
}
|
||||
|
||||
fn spd_downsample_mips_6_7(x: u32, y: u32, mips: u32, slice: u32) {
|
||||
var tex = vec2<u32>(x * 4 + 0, y * 4 + 0);
|
||||
var pix = vec2<u32>(x * 2 + 0, y * 2 + 0);
|
||||
let v0 = spd_reduce_load_mid_mip_4(tex, slice);
|
||||
spd_store(pix, v0, 6, slice);
|
||||
|
||||
tex = vec2<u32>(x * 4 + 2, y * 4 + 0);
|
||||
pix = vec2<u32>(x * 2 + 1, y * 2 + 0);
|
||||
let v1 = spd_reduce_load_mid_mip_4(tex, slice);
|
||||
spd_store(pix, v1, 6, slice);
|
||||
|
||||
tex = vec2<u32>(x * 4 + 0, y * 4 + 2);
|
||||
pix = vec2<u32>(x * 2 + 0, y * 2 + 1);
|
||||
let v2 = spd_reduce_load_mid_mip_4(tex, slice);
|
||||
spd_store(pix, v2, 6, slice);
|
||||
|
||||
tex = vec2<u32>(x * 4 + 2, y * 4 + 2);
|
||||
pix = vec2<u32>(x * 2 + 1, y * 2 + 1);
|
||||
let v3 = spd_reduce_load_mid_mip_4(tex, slice);
|
||||
spd_store(pix, v3, 6, slice);
|
||||
|
||||
if mips <= 7 {
|
||||
return;
|
||||
}
|
||||
// no barrier needed, working on values only from the same thread
|
||||
|
||||
let v = spd_reduce_4(v0, v1, v2, v3);
|
||||
spd_store(vec2<u32>(x, y), v, 7, slice);
|
||||
spd_store_intermediate(x, y, v);
|
||||
}
|
||||
|
||||
fn spd_downsample_last_6(x: u32, y: u32, local_invocation_index: u32, mips: u32, num_work_groups: u32, slice: u32) {
|
||||
if mips <= 6 {
|
||||
return;
|
||||
}
|
||||
|
||||
// increase the global atomic counter for the given slice and check if it's the last remaining thread group:
|
||||
// terminate if not, continue if yes.
|
||||
let exit = spd_exit_workgroup(num_work_groups, local_invocation_index, slice);
|
||||
|
||||
// can't exit directly because subsequent barrier calls break uniform control flow...
|
||||
if !exit {
|
||||
// reset the global atomic counter back to 0 for the next spd dispatch
|
||||
spd_reset_atomic_counter(slice);
|
||||
|
||||
// After mip 5 there is only a single workgroup left that downsamples the remaining up to 64x64 texels.
|
||||
// compute MIP level 6 and 7
|
||||
spd_downsample_mips_6_7(x, y, mips, slice);
|
||||
}
|
||||
|
||||
// compute MIP level 8, 9, 10, 11
|
||||
spd_downsample_last_four(x, y, vec2<u32>(0, 0), local_invocation_index, 8, mips, slice, exit);
|
||||
}
|
||||
|
||||
/// Downsamples a 64x64 tile based on the work group id.
|
||||
/// If after downsampling it's the last active thread group, computes the remaining MIP levels.
|
||||
///
|
||||
/// @param [in] workGroupID index of the work group / thread group
|
||||
/// @param [in] localInvocationIndex index of the thread within the thread group in 1D
|
||||
/// @param [in] mips the number of total MIP levels to compute for the input texture
|
||||
/// @param [in] numWorkGroups the total number of dispatched work groups / thread groups for this slice
|
||||
/// @param [in] slice the slice of the input texture
|
||||
fn spd_downsample(workgroup_id: vec2<u32>, local_invocation_index: u32, mips: u32, num_work_groups: u32, slice: u32) {
|
||||
let xy = map_to_xy(local_invocation_index);
|
||||
spd_downsample_mips_0_1(xy.x, xy.y, workgroup_id, local_invocation_index, mips, slice);
|
||||
spd_downsample_next_four(xy.x, xy.y, workgroup_id, local_invocation_index, 2, mips, slice);
|
||||
)";
|
||||
if (numMips > 6) {
|
||||
ss << " spd_downsample_last_6(xy.x, xy.y, local_invocation_index, mips, "
|
||||
"num_work_groups, slice);\n";
|
||||
}
|
||||
ss << R"(}
|
||||
|
||||
// Entry points -------------------------------------------------------------------------------------------------------
|
||||
|
||||
@compute
|
||||
@workgroup_size(256, 1, 1)
|
||||
fn downsample(@builtin(local_invocation_index) local_invocation_index: u32, @builtin(workgroup_id) workgroup_id: vec3<u32>) {
|
||||
spd_downsample(
|
||||
workgroup_id.xy + get_work_group_offset(),
|
||||
local_invocation_index,
|
||||
get_mips(),
|
||||
get_num_work_groups(),
|
||||
workgroup_id.z
|
||||
);
|
||||
}
|
||||
)";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
|
||||
} // namespace spd
|
||||
@@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tsl/robin_map.h>
|
||||
#include <vector>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
// C++ port of https://github.com/JolifantoBambla/webgpu-spd for early experiments
|
||||
namespace spd {
|
||||
|
||||
// Enum for selecting the downsampling filter.
|
||||
enum class SPDFilter { Average, Min, Max, MinMax };
|
||||
|
||||
// Enum for shader scalar types.
|
||||
enum class SPDScalarType { F32, F16, I32, U32 };
|
||||
|
||||
// Configuration for a single mipmap generation pass.
|
||||
struct SPDPassConfig {
|
||||
SPDFilter filter = SPDFilter::Average;
|
||||
wgpu::Texture targetTexture = nullptr;
|
||||
uint32_t numMips = 0; // For the public API, this is total desired mips. Internally, mips for
|
||||
// the current pass.
|
||||
bool halfPrecision = false;
|
||||
uint32_t sourceMipLevel = 0;
|
||||
};
|
||||
|
||||
// Holds a pipeline and its corresponding bind group layout.
|
||||
struct SPDPipeline {
|
||||
wgpu::BindGroupLayout mipsBindGroupLayout = nullptr;
|
||||
wgpu::ComputePipeline pipeline = nullptr;
|
||||
};
|
||||
|
||||
// Manages pipeline creation, caching, and execution for mipmap generation.
|
||||
class MipmapGenerator {
|
||||
public:
|
||||
MipmapGenerator(const wgpu::Device& device);
|
||||
|
||||
// Pre-creates pipelines for specified formats and filters.
|
||||
void PreparePipelines(wgpu::TextureFormat format, SPDFilter filter, bool halfPrecision = false);
|
||||
|
||||
// Generates a compute pass for creating mipmaps.
|
||||
void Generate(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
|
||||
const SPDPassConfig& config);
|
||||
|
||||
private:
|
||||
void generatePass(wgpu::CommandEncoder& commandEncoder, wgpu::Texture srcTexture,
|
||||
const SPDPassConfig& passConfig, uint32_t baseArrayLayer,
|
||||
uint32_t numArrayLayers); // Internal pass config might differ slightly if needed
|
||||
|
||||
wgpu::Device m_device;
|
||||
wgpu::BindGroupLayout m_internalResourcesBindGroupLayout;
|
||||
wgpu::BindGroupLayout m_internalResourcesBindGroupLayout_Advanced = nullptr;
|
||||
|
||||
// Maximum number of mips that can be generated in a single pass.
|
||||
uint32_t m_maxMipsPerPass;
|
||||
uint32_t m_maxArrayLayers;
|
||||
|
||||
// Key for the pipeline cache.
|
||||
struct PipelineCacheKey {
|
||||
wgpu::TextureFormat format;
|
||||
SPDScalarType scalarType;
|
||||
SPDFilter filter;
|
||||
uint32_t numMips;
|
||||
|
||||
bool operator==(const PipelineCacheKey& other) const {
|
||||
return format == other.format && scalarType == other.scalarType &&
|
||||
filter == other.filter && numMips == other.numMips;
|
||||
}
|
||||
};
|
||||
|
||||
// Hash function for PipelineCacheKey.
|
||||
struct PipelineCacheKeyHash {
|
||||
std::size_t operator()(const PipelineCacheKey& key) const {
|
||||
std::size_t h1 = std::hash<wgpu::TextureFormat>()(key.format);
|
||||
std::size_t h2 = std::hash<SPDScalarType>()(key.scalarType);
|
||||
std::size_t h3 = std::hash<SPDFilter>()(key.filter);
|
||||
std::size_t h4 = std::hash<uint32_t>()(key.numMips);
|
||||
// A simple way to combine hashes.
|
||||
return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3);
|
||||
}
|
||||
};
|
||||
|
||||
// Cached pipelines.
|
||||
tsl::robin_map<PipelineCacheKey, SPDPipeline, PipelineCacheKeyHash> m_pipelines;
|
||||
|
||||
// Helper methods
|
||||
SPDPipeline& GetOrCreatePipeline(const PipelineCacheKey& key);
|
||||
SPDScalarType SanitizeScalarType(wgpu::TextureFormat format, bool halfPrecision);
|
||||
std::string GetFilterCode(SPDFilter filter);
|
||||
};
|
||||
// Assuming SPD_FILTER_AVERAGE is a string constant
|
||||
const std::string SPD_FILTER_AVERAGE = "value * 0.25"; // Original filter operation
|
||||
|
||||
// Generates the WGSL shader code dynamically.
|
||||
std::string MakeShaderCode(wgpu::TextureFormat outputFormat,
|
||||
const std::string& filterOp = SPD_FILTER_AVERAGE, uint32_t numMips = 0,
|
||||
SPDScalarType scalarType = SPDScalarType::F32);
|
||||
|
||||
} // namespace spd
|
||||
21
filament/backend/src/webgpu/SpdMipmapGenerator/license.txt
Normal file
21
filament/backend/src/webgpu/SpdMipmapGenerator/license.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Lukas Herzberger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "WebGPURenderTarget.h"
|
||||
#include "WebGPUSwapChain.h"
|
||||
#include "WebGPUTexture.h"
|
||||
#include "WebGPUTimerQuery.h"
|
||||
#include "WebGPUVertexBuffer.h"
|
||||
#include "WebGPUVertexBufferInfo.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
@@ -53,6 +54,8 @@
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
@@ -64,13 +67,14 @@ Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverCon
|
||||
|
||||
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
|
||||
const Platform::DriverConfig& driverConfig) noexcept
|
||||
: mPlatform(platform),
|
||||
mHandleAllocator("Handles", driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags) {
|
||||
mAdapter = mPlatform.requestAdapter(nullptr);
|
||||
mDevice = mPlatform.requestDevice(mAdapter);
|
||||
: mPlatform{ platform },
|
||||
mAdapter{ mPlatform.requestAdapter(nullptr) },
|
||||
mDevice{ mPlatform.requestDevice(mAdapter) },
|
||||
mQueue{ mDevice.GetQueue() },
|
||||
mMipMapGenerator{ mDevice },
|
||||
mHandleAllocator{ "Handles", driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags } {
|
||||
mDevice.GetLimits(&mDeviceLimits);
|
||||
mQueue = mDevice.GetQueue();
|
||||
}
|
||||
|
||||
WebGPUDriver::~WebGPUDriver() noexcept = default;
|
||||
@@ -98,8 +102,9 @@ template class ConcreteDispatcher<WebGPUDriver>;
|
||||
void WebGPUDriver::terminate() {
|
||||
}
|
||||
|
||||
void WebGPUDriver::tick(int) {
|
||||
void WebGPUDriver::tick(int /*dummy*/) {
|
||||
mDevice.Tick();
|
||||
mAdapter.GetInstance().ProcessEvents();
|
||||
}
|
||||
|
||||
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
@@ -213,9 +218,6 @@ void WebGPUDriver::destroyStream(Handle<HwStream> sh) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyDescriptorSetLayout(
|
||||
Handle<HwDescriptorSetLayout> descriptorSetLayoutHandle) {
|
||||
if (descriptorSetLayoutHandle) {
|
||||
@@ -265,7 +267,31 @@ Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
|
||||
return Handle<HwTimerQuery>((Handle<HwTimerQuery>::HandleId) mNextFakeHandle++);
|
||||
return allocAndConstructHandle<WebGPUTimerQuery, HwTimerQuery>();
|
||||
}
|
||||
|
||||
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> timerQueryHandle, int /*dummy*/) {
|
||||
// nothing to do, timer query was constructed in createTimerQueryS
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyTimerQuery(Handle<HwTimerQuery> timerQueryHandle) {
|
||||
if (timerQueryHandle) {
|
||||
destructHandle<WebGPUTimerQuery>(timerQueryHandle);
|
||||
}
|
||||
}
|
||||
|
||||
TimerQueryResult WebGPUDriver::getTimerQueryValue(Handle<HwTimerQuery> timerQueryHandle, uint64_t* elapsedTime) {
|
||||
auto* timerQuery = handleCast<WebGPUTimerQuery>(timerQueryHandle);
|
||||
return timerQuery->getQueryResult(elapsedTime) ? TimerQueryResult::AVAILABLE
|
||||
: TimerQueryResult::NOT_READY;
|
||||
}
|
||||
|
||||
void WebGPUDriver::beginTimerQuery(Handle<HwTimerQuery> timerQueryHandle) {
|
||||
mTimerQuery = handleCast<WebGPUTimerQuery>(timerQueryHandle);
|
||||
}
|
||||
|
||||
void WebGPUDriver::endTimerQuery(Handle<HwTimerQuery> timerQueryHandle) {
|
||||
mTimerQuery = handleCast<WebGPUTimerQuery>(timerQueryHandle);
|
||||
}
|
||||
|
||||
Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
|
||||
@@ -459,8 +485,6 @@ void WebGPUDriver::createFenceR(Handle<HwFence> fh, int) {
|
||||
//todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
|
||||
|
||||
void WebGPUDriver::createDescriptorSetLayoutR(
|
||||
Handle<HwDescriptorSetLayout> descriptorSetLayoutHandle,
|
||||
backend::DescriptorSetLayout&& info) {
|
||||
@@ -529,8 +553,8 @@ bool WebGPUDriver::isTextureSwizzleSupported() {
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
|
||||
//todo
|
||||
return true;
|
||||
return WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(
|
||||
WebGPUTexture::fToWGPUTextureFormat(format));
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isRenderTargetFormatSupported(const TextureFormat format) {
|
||||
@@ -605,7 +629,7 @@ uint8_t WebGPUDriver::getMaxDrawBuffers() {
|
||||
}
|
||||
|
||||
size_t WebGPUDriver::getMaxUniformBufferSize() {
|
||||
return 16384u;
|
||||
return mDeviceLimits.maxUniformBufferBindingSize;
|
||||
}
|
||||
|
||||
size_t WebGPUDriver::getMaxTextureSize(const SamplerType target) {
|
||||
@@ -724,10 +748,6 @@ void WebGPUDriver::setupExternalImage(void* image) {
|
||||
//todo
|
||||
}
|
||||
|
||||
TimerQueryResult WebGPUDriver::getTimerQueryValue(Handle<HwTimerQuery> tqh, uint64_t* elapsedTime) {
|
||||
return TimerQueryResult::ERROR;
|
||||
}
|
||||
|
||||
void WebGPUDriver::setupExternalImage2(Platform::ExternalImageHandleRef image) {
|
||||
//todo
|
||||
}
|
||||
@@ -738,59 +758,46 @@ void WebGPUDriver::setExternalStream(Handle<HwTexture> textureHandle,
|
||||
}
|
||||
|
||||
void WebGPUDriver::generateMipmaps(Handle<HwTexture> textureHandle) {
|
||||
if (!mCommandEncoder) {
|
||||
mMipQueue.push_back(textureHandle);
|
||||
return;
|
||||
}
|
||||
auto texture = handleCast<WebGPUTexture>(textureHandle);
|
||||
assert_invariant(texture);
|
||||
wgpu::Texture wgpuTexture = texture->getTexture();
|
||||
assert_invariant(wgpuTexture);
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(wgpuTexture.GetUsage() & wgpu::TextureUsage::CopySrc)
|
||||
<< "Texture intended for mipmap generation (as source) must have CopySrc usage.";
|
||||
FILAMENT_CHECK_PRECONDITION(wgpuTexture.GetUsage() & wgpu::TextureUsage::CopyDst)
|
||||
<< "Texture intended for mipmap generation (as destination) must have CopyDst usage.";
|
||||
const auto usage = wgpuTexture.GetUsage();
|
||||
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::TextureBinding)
|
||||
<< "Texture for mipmap generation must have TextureBinding usage. "
|
||||
"supportsMultipleMipLevels for texture format "
|
||||
<< static_cast<uint32_t>(wgpuTexture.GetFormat()) << " is "
|
||||
<< WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(wgpuTexture.GetFormat());
|
||||
FILAMENT_CHECK_PRECONDITION(usage & wgpu::TextureUsage::StorageBinding)
|
||||
<< "Texture for mipmap generation must have StorageBinding usage. "
|
||||
"supportsMultipleMipLevels for texture format "
|
||||
<< static_cast<uint32_t>(wgpuTexture.GetFormat()) << " is "
|
||||
<< WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(wgpuTexture.GetFormat());
|
||||
|
||||
uint32_t mipLevelCount = wgpuTexture.GetMipLevelCount();
|
||||
if (mipLevelCount <= 1) {
|
||||
const uint32_t totalMipLevels = wgpuTexture.GetMipLevelCount();
|
||||
if (totalMipLevels <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t width = wgpuTexture.GetWidth();
|
||||
uint32_t height = wgpuTexture.GetHeight();
|
||||
// For 3D textures, depth is > 1. For 2D/Cube/Array, effectively 1 for mip-level copies.
|
||||
uint32_t depth =
|
||||
(texture->target == SamplerType::SAMPLER_3D) ? wgpuTexture.GetDepthOrArrayLayers() : 1;
|
||||
// We will record all passes into a single command encoder.
|
||||
wgpu::CommandEncoderDescriptor encoderDesc = {};
|
||||
encoderDesc.label = "Mipmap Command Encoder";
|
||||
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder(&encoderDesc);
|
||||
|
||||
for (uint32_t mipLevel = 0; mipLevel < mipLevelCount - 1; ++mipLevel) {
|
||||
wgpu::TexelCopyTextureInfo sourceCopyInfo{
|
||||
.texture = wgpuTexture,
|
||||
.mipLevel = mipLevel,
|
||||
.aspect = texture->getAspect(),
|
||||
};
|
||||
spd::SPDPassConfig spdConfig = { .filter = spd::SPDFilter::Average,
|
||||
.targetTexture = wgpuTexture,
|
||||
.numMips = totalMipLevels,
|
||||
.halfPrecision = false,
|
||||
.sourceMipLevel = 0 };
|
||||
|
||||
wgpu::TexelCopyTextureInfo destinationCopyInfo{
|
||||
.texture = wgpuTexture,
|
||||
.mipLevel = mipLevel + 1,
|
||||
.aspect = texture->getAspect(),
|
||||
};
|
||||
mMipMapGenerator.Generate(encoder, wgpuTexture, spdConfig);
|
||||
|
||||
uint32_t dstWidth = std::max(1u, width >> 1);
|
||||
uint32_t dstHeight = std::max(1u, height >> 1);
|
||||
uint32_t dstDepth = std::max(1u, depth >> 1);
|
||||
|
||||
wgpu::Extent3D copySize{ .width = dstWidth,
|
||||
.height = dstHeight,
|
||||
.depthOrArrayLayers = (texture->target == SamplerType::SAMPLER_3D)
|
||||
? dstDepth
|
||||
: texture->getArrayLayerCount() };
|
||||
mCommandEncoder.CopyTextureToTexture(&sourceCopyInfo, &destinationCopyInfo, ©Size);
|
||||
|
||||
width = dstWidth;
|
||||
height = dstHeight;
|
||||
depth = dstDepth;
|
||||
}
|
||||
// Finish the encoder and submit all the passes at once.
|
||||
wgpu::CommandBufferDescriptor cmdBufferDesc = {};
|
||||
cmdBufferDesc.label = "Mipmap Command Buffer";
|
||||
wgpu::CommandBuffer commandBuffer = encoder.Finish(&cmdBufferDesc);
|
||||
mQueue.Submit(1, &commandBuffer);
|
||||
}
|
||||
|
||||
void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
@@ -853,7 +860,7 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
const uint32_t depthArrayLayer = depthInfo.layer;
|
||||
customDepthView =
|
||||
depthTexture->getOrMakeTextureView(depthMipLevel, depthArrayLayer);
|
||||
customDepthFormat = depthTexture->getWebGPUFormat();
|
||||
customDepthFormat = depthTexture->getViewFormat();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,7 +877,7 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
|
||||
const uint32_t stencilArrayLayer = stencilInfo.layer;
|
||||
customStencilView =
|
||||
stencilTexture->getOrMakeTextureView(stencilMipLevel, stencilArrayLayer);
|
||||
customStencilFormat = stencilTexture->getWebGPUFormat();
|
||||
customStencilFormat = stencilTexture->getViewFormat();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -924,12 +931,6 @@ void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
|
||||
.label = "command_encoder"
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
if (!mMipQueue.empty()) {
|
||||
for (auto& handle: mMipQueue) {
|
||||
generateMipmaps(handle);
|
||||
}
|
||||
mMipQueue.clear();
|
||||
}
|
||||
assert_invariant(mCommandEncoder);
|
||||
}
|
||||
|
||||
@@ -940,7 +941,34 @@ void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
|
||||
mCommandBuffer = mCommandEncoder.Finish(&commandBufferDescriptor);
|
||||
assert_invariant(mCommandBuffer);
|
||||
mCommandEncoder = nullptr;
|
||||
if (mTimerQuery) {
|
||||
mTimerQuery->beginTimeElapsedQuery();
|
||||
}
|
||||
mQueue.Submit(1, &mCommandBuffer);
|
||||
static bool firstRender = true;
|
||||
// For the first frame rendered, we need to make sure the work is done before presenting or we
|
||||
// get a purple flash
|
||||
if (firstRender) {
|
||||
auto f = mQueue.OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly,
|
||||
[=](wgpu::QueueWorkDoneStatus) {});
|
||||
const wgpu::Instance instance = mAdapter.GetInstance();
|
||||
auto wStatus = instance.WaitAny(f,
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count());
|
||||
if (wStatus != wgpu::WaitStatus::Success) {
|
||||
FWGPU_LOGW << "Waiting for first frame work to finish resulted in an error"
|
||||
<< static_cast<uint32_t>(wStatus);
|
||||
}
|
||||
firstRender = false;
|
||||
} else {
|
||||
mQueue.OnSubmittedWorkDone(wgpu::CallbackMode::AllowSpontaneous,
|
||||
[=](wgpu::QueueWorkDoneStatus status) {
|
||||
if (status == wgpu::QueueWorkDoneStatus::Success) {
|
||||
if (mTimerQuery) {
|
||||
mTimerQuery->endTimeElapsedQuery();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
mCommandBuffer = nullptr;
|
||||
mTextureView = nullptr;
|
||||
assert_invariant(mSwapChain);
|
||||
@@ -1151,12 +1179,6 @@ void WebGPUDriver::scissor(
|
||||
//todo
|
||||
}
|
||||
|
||||
void WebGPUDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::resetState(int) {
|
||||
//todo
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include "SpdMipmapGenerator/SpdMipmapGenerator.h"
|
||||
#include <tsl/robin_map.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -41,6 +43,7 @@
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUSwapChain;
|
||||
class WebGPUTimerQuery;
|
||||
|
||||
/**
|
||||
* WebGPU backend (driver) implementation
|
||||
@@ -70,12 +73,13 @@ private:
|
||||
WebGPUSwapChain* mSwapChain = nullptr;
|
||||
uint64_t mNextFakeHandle = 1;
|
||||
wgpu::CommandEncoder mCommandEncoder = nullptr;
|
||||
std::vector<Handle<HwTexture>> mMipQueue;
|
||||
wgpu::TextureView mTextureView = nullptr;
|
||||
wgpu::RenderPassEncoder mRenderPassEncoder = nullptr;
|
||||
wgpu::CommandBuffer mCommandBuffer = nullptr;
|
||||
WebGPURenderTarget* mDefaultRenderTarget = nullptr;
|
||||
WebGPURenderTarget* mCurrentRenderTarget = nullptr;
|
||||
spd::MipmapGenerator mMipMapGenerator;
|
||||
WebGPUTimerQuery* mTimerQuery = nullptr;
|
||||
|
||||
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineMap;
|
||||
|
||||
@@ -114,6 +118,11 @@ private:
|
||||
return mHandleAllocator.allocate<D>();
|
||||
}
|
||||
|
||||
template<typename D, typename B, typename... ARGS>
|
||||
Handle<B> allocAndConstructHandle(ARGS&&... args) {
|
||||
return mHandleAllocator.allocateAndConstruct<D>(std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
template<typename D, typename B, typename... ARGS>
|
||||
D* constructHandle(Handle<B>& handle, ARGS&&... args) noexcept {
|
||||
return mHandleAllocator.construct<D>(handle, std::forward<ARGS>(args)...);
|
||||
|
||||
@@ -21,50 +21,27 @@
|
||||
|
||||
#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>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* Reusable set of convenience functions for strings -- generally string views, literals, &
|
||||
* streaming -- used in the WebGPU backend
|
||||
* strings -- 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;
|
||||
[[nodiscard]] inline std::string webGPUPrintableToString(const WebGPUPrintable printable) {
|
||||
std::stringstream out;
|
||||
out << printable;
|
||||
return out.str();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -115,46 +92,17 @@ utils::io::ostream& streamInsertWebGPUPrintable(utils::io::ostream& out,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
[[nodiscard]] inline std::string adapterOptionsToString(
|
||||
wgpu::RequestAdapterOptions const& options) {
|
||||
std::stringstream out;
|
||||
out << "power preference " << powerPreferenceToString(options.powerPreference)
|
||||
<< " force fallback adapter " << bool(options.forceFallbackAdapter) << " backend type "
|
||||
<< backendTypeToString(options.backendType);
|
||||
return out;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
[[nodiscard]] std::string adapterInfoToString(wgpu::AdapterInfo const& info) {
|
||||
std::stringstream out;
|
||||
out << "vendor (" << info.vendorID << ") '" << info.vendor
|
||||
<< "' device (" << info.deviceID << ") '" << info.device
|
||||
<< "' adapter " << adapterTypeToString(info.adapterType)
|
||||
@@ -162,7 +110,7 @@ STREAM_TYPE& streamInsertRequestAdapterInfo(STREAM_TYPE& out,
|
||||
<< " architecture '" << info.architecture
|
||||
<< "' subgroupMinSize " << info.subgroupMinSize
|
||||
<< " subgroupMaxSize " << info.subgroupMaxSize;
|
||||
return out;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view deviceLostReasonToString(
|
||||
@@ -175,7 +123,7 @@ STREAM_TYPE& streamInsertRequestAdapterInfo(STREAM_TYPE& out,
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view filamentShaderStageToString(ShaderStage stage) {
|
||||
[[nodiscard]] constexpr std::string_view filamentShaderStageToString(const ShaderStage stage) {
|
||||
switch (stage) {
|
||||
case ShaderStage::VERTEX: return "vertex";
|
||||
case ShaderStage::FRAGMENT: return "fragment";
|
||||
|
||||
@@ -24,43 +24,25 @@
|
||||
#include "backend/DriverEnums.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
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) {
|
||||
FWGPU_LOGI << "WebGPU surface capabilities:";
|
||||
FWGPU_LOGI << " surface usages: " << capabilities.usages;
|
||||
FWGPU_LOGI << " surface usages: " << webGPUPrintableToString(capabilities.usages);
|
||||
FWGPU_LOGI << " surface formats (" << capabilities.formatCount << "):";
|
||||
if (capabilities.formatCount > 0 && capabilities.formats != nullptr) {
|
||||
std::for_each(capabilities.formats, capabilities.formats + capabilities.formatCount,
|
||||
[](wgpu::TextureFormat const format) {
|
||||
FWGPU_LOGI << " " << format;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(format);
|
||||
});
|
||||
}
|
||||
FWGPU_LOGI << " surface present modes (" << capabilities.presentModeCount << "):";
|
||||
@@ -68,7 +50,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
|
||||
std::for_each(capabilities.presentModes,
|
||||
capabilities.presentModes + capabilities.presentModeCount,
|
||||
[](wgpu::PresentMode const presentMode) {
|
||||
FWGPU_LOGI << " " << presentMode;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(presentMode);
|
||||
});
|
||||
}
|
||||
FWGPU_LOGI << " surface alpha modes (" << capabilities.alphaModeCount << "):";
|
||||
@@ -76,7 +58,7 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
|
||||
std::for_each(capabilities.alphaModes,
|
||||
capabilities.alphaModes + capabilities.alphaModeCount,
|
||||
[](wgpu::CompositeAlphaMode const alphaMode) {
|
||||
FWGPU_LOGI << " " << alphaMode;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(alphaMode);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -84,20 +66,20 @@ void printSurfaceCapabilitiesDetails(wgpu::SurfaceCapabilities const& capabiliti
|
||||
void printSurfaceConfiguration(wgpu::SurfaceConfiguration const& config,
|
||||
wgpu::TextureFormat depthFormat) {
|
||||
FWGPU_LOGI << "WebGPU surface configuration:";
|
||||
FWGPU_LOGI << " surface format: " << config.format;
|
||||
FWGPU_LOGI << " surface usage: " << config.usage;
|
||||
FWGPU_LOGI << " surface format: " << webGPUPrintableToString(config.format);
|
||||
FWGPU_LOGI << " surface usage: " << webGPUPrintableToString(config.usage);
|
||||
FWGPU_LOGI << " surface view formats (" << config.viewFormatCount << "):";
|
||||
if (config.viewFormatCount > 0 && config.viewFormats != nullptr) {
|
||||
std::for_each(config.viewFormats, config.viewFormats + config.viewFormatCount,
|
||||
[](wgpu::TextureFormat const viewFormat) {
|
||||
FWGPU_LOGI << " " << viewFormat;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(viewFormat);
|
||||
});
|
||||
}
|
||||
FWGPU_LOGI << " surface alpha mode: " << config.alphaMode;
|
||||
FWGPU_LOGI << " surface alpha mode: " << webGPUPrintableToString(config.alphaMode);
|
||||
FWGPU_LOGI << " surface width: " << config.width;
|
||||
FWGPU_LOGI << " surface height: " << config.height;
|
||||
FWGPU_LOGI << " surface present mode: " << config.presentMode;
|
||||
FWGPU_LOGI << "WebGPU selected depth format: " << depthFormat;
|
||||
FWGPU_LOGI << " surface present mode: " << webGPUPrintableToString(config.presentMode);
|
||||
FWGPU_LOGI << "WebGPU selected depth format: " << webGPUPrintableToString(depthFormat);
|
||||
}
|
||||
#endif// FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
|
||||
@@ -257,8 +239,6 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
|
||||
}// namespace
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
|
||||
wgpu::Adapter const& adapter, wgpu::Device const& device, uint64_t flags)
|
||||
: mDevice(device),
|
||||
|
||||
@@ -25,9 +25,12 @@
|
||||
#include <utils/BitmaskEnum.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
#include <webgpu/webgpu_cpp_print.h>
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
@@ -59,21 +62,108 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param format texture format to potentially be used for storage binding
|
||||
* @return true if the format is compatible with wgpu::TextureUsage::StorageBinding
|
||||
*/
|
||||
[[nodiscard]] constexpr bool isFormatStorageCompatible(const wgpu::TextureFormat format) {
|
||||
switch (format) {
|
||||
// List of formats that support storage binding
|
||||
case wgpu::TextureFormat::R32Float:
|
||||
case wgpu::TextureFormat::R32Sint:
|
||||
case wgpu::TextureFormat::R32Uint:
|
||||
case wgpu::TextureFormat::RG32Float:
|
||||
case wgpu::TextureFormat::RG32Sint:
|
||||
case wgpu::TextureFormat::RG32Uint:
|
||||
case wgpu::TextureFormat::RGBA16Float:
|
||||
case wgpu::TextureFormat::RGBA16Sint:
|
||||
case wgpu::TextureFormat::RGBA16Uint:
|
||||
case wgpu::TextureFormat::RGBA32Float:
|
||||
case wgpu::TextureFormat::RGBA32Sint:
|
||||
case wgpu::TextureFormat::RGBA32Uint:
|
||||
case wgpu::TextureFormat::RGBA8Unorm:
|
||||
case wgpu::TextureFormat::RGBA8Snorm:
|
||||
case wgpu::TextureFormat::RGBA8Uint:
|
||||
case wgpu::TextureFormat::RGBA8Sint:
|
||||
return true;
|
||||
default:
|
||||
// All other formats, including packed floats (RG11B10Ufloat),
|
||||
// depth/stencil, and sRGB formats do not support storage.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param viewFormat potential view format to a texture
|
||||
* @return an underlying texture format that is storage binding compatible for which this view
|
||||
* texture format can be used. If no such format exists, Undefined is returned.
|
||||
* If the view texture is already storage binding compatible then the view texture itself
|
||||
* is returned.
|
||||
*/
|
||||
[[nodiscard]] constexpr wgpu::TextureFormat storageBindingCompatibleFormatForViewFormat(
|
||||
const wgpu::TextureFormat viewFormat) {
|
||||
switch (viewFormat) {
|
||||
case wgpu::TextureFormat::RGBA8UnormSrgb: return wgpu::TextureFormat::RGBA8Unorm;
|
||||
case wgpu::TextureFormat::BGRA8UnormSrgb: return wgpu::TextureFormat::BGRA8Unorm;
|
||||
case wgpu::TextureFormat::BC1RGBAUnormSrgb: return wgpu::TextureFormat::BC1RGBAUnorm;
|
||||
case wgpu::TextureFormat::BC2RGBAUnormSrgb: return wgpu::TextureFormat::BC2RGBAUnorm;
|
||||
case wgpu::TextureFormat::BC3RGBAUnormSrgb: return wgpu::TextureFormat::BC3RGBAUnorm;
|
||||
case wgpu::TextureFormat::BC7RGBAUnormSrgb: return wgpu::TextureFormat::BC7RGBAUnorm;
|
||||
case wgpu::TextureFormat::ETC2RGB8UnormSrgb: return wgpu::TextureFormat::ETC2RGB8Unorm;
|
||||
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb: return wgpu::TextureFormat::ETC2RGB8A1Unorm;
|
||||
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb: return wgpu::TextureFormat::ETC2RGBA8Unorm;
|
||||
case wgpu::TextureFormat::ASTC4x4UnormSrgb: return wgpu::TextureFormat::ASTC4x4Unorm;
|
||||
case wgpu::TextureFormat::ASTC5x4UnormSrgb: return wgpu::TextureFormat::ASTC5x4Unorm;
|
||||
case wgpu::TextureFormat::ASTC5x5UnormSrgb: return wgpu::TextureFormat::ASTC5x5Unorm;
|
||||
case wgpu::TextureFormat::ASTC6x5UnormSrgb: return wgpu::TextureFormat::ASTC6x5Unorm;
|
||||
case wgpu::TextureFormat::ASTC6x6UnormSrgb: return wgpu::TextureFormat::ASTC6x6Unorm;
|
||||
case wgpu::TextureFormat::ASTC8x5UnormSrgb: return wgpu::TextureFormat::ASTC8x5Unorm;
|
||||
case wgpu::TextureFormat::ASTC8x6UnormSrgb: return wgpu::TextureFormat::ASTC8x6Unorm;
|
||||
case wgpu::TextureFormat::ASTC8x8UnormSrgb: return wgpu::TextureFormat::ASTC8x8Unorm;
|
||||
case wgpu::TextureFormat::ASTC10x5UnormSrgb: return wgpu::TextureFormat::ASTC10x5Unorm;
|
||||
case wgpu::TextureFormat::ASTC10x6UnormSrgb: return wgpu::TextureFormat::ASTC10x6Unorm;
|
||||
case wgpu::TextureFormat::ASTC10x8UnormSrgb: return wgpu::TextureFormat::ASTC10x8Unorm;
|
||||
case wgpu::TextureFormat::ASTC10x10UnormSrgb: return wgpu::TextureFormat::ASTC10x10Unorm;
|
||||
case wgpu::TextureFormat::ASTC12x10UnormSrgb: return wgpu::TextureFormat::ASTC12x10Unorm;
|
||||
case wgpu::TextureFormat::ASTC12x12UnormSrgb: return wgpu::TextureFormat::ASTC12x12Unorm;
|
||||
default:
|
||||
if (isFormatStorageCompatible(viewFormat)) {
|
||||
return viewFormat; // view format is itself already storage binding compatible
|
||||
} else {
|
||||
return wgpu::TextureFormat::Undefined; // no valid format in this case
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fUsage Filament's requested texture usage
|
||||
* @param samples How many samples to use for MSAA
|
||||
* @param needsComputeStorageSupport if we need to use this texture as storage binding in something
|
||||
* like a compute shader
|
||||
* @return The appropriate texture usage flags for the underlying texture
|
||||
*/
|
||||
[[nodiscard]] wgpu::TextureUsage fToWGPUTextureUsage(TextureUsage const& fUsage,
|
||||
const uint8_t samples) {
|
||||
const uint8_t samples, const bool needsComputeStorageSupport) {
|
||||
wgpu::TextureUsage retUsage = wgpu::TextureUsage::None;
|
||||
|
||||
// if needsComputeStorageSupport we need to read and write to the texture in a shader and thus
|
||||
// require CopySrc, CopyDst, TextureBinding, & StorageBinding
|
||||
|
||||
// Basing this mapping off of VulkanTexture.cpp's getUsage func and suggestions from Gemini
|
||||
// TODO Validate assumptions, revisit if issues.
|
||||
if (any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
if (needsComputeStorageSupport || any(TextureUsage::BLIT_SRC & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::CopySrc;
|
||||
}
|
||||
if (any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
|
||||
if (needsComputeStorageSupport ||
|
||||
any((TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE) & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::CopyDst;
|
||||
}
|
||||
if (any(TextureUsage::SAMPLEABLE & fUsage)) {
|
||||
if (needsComputeStorageSupport || any(TextureUsage::SAMPLEABLE & fUsage)) {
|
||||
retUsage |= wgpu::TextureUsage::TextureBinding;
|
||||
}
|
||||
if (needsComputeStorageSupport) {
|
||||
retUsage |= wgpu::TextureUsage::StorageBinding;
|
||||
}
|
||||
// WGPU Render attachment covers either color or stencil situation dependant
|
||||
// NOTE: Depth attachment isn't used this way in Vulkan but logically maps to WGPU docs. If
|
||||
// issues, investigate here.
|
||||
@@ -100,7 +190,6 @@ namespace {
|
||||
retUsage |= wgpu::TextureUsage::TransientAttachment;
|
||||
}
|
||||
// NOTE: Unused wgpu flags:
|
||||
// StorageBinding
|
||||
// StorageAttachment
|
||||
|
||||
// NOTE: Unused Filament flags:
|
||||
@@ -205,11 +294,19 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
|
||||
const uint32_t height, const uint32_t depth, const TextureUsage usage,
|
||||
wgpu::Device const& device) noexcept
|
||||
: HwTexture{ samplerType, levels, samples, width, height, depth, format, usage },
|
||||
mWebGPUFormat{ fToWGPUTextureFormat(format) },
|
||||
mViewFormat{ fToWGPUTextureFormat(format) },
|
||||
mSupportsMultipleMipLevels{ levels > 1 &&
|
||||
supportsMultipleMipLevelsViaStorageBinding(mViewFormat) },
|
||||
mWebGPUFormat{ mSupportsMultipleMipLevels
|
||||
? storageBindingCompatibleFormatForViewFormat(mViewFormat)
|
||||
: mViewFormat },
|
||||
mAspect{ fToWGPUTextureViewAspect(usage, format) },
|
||||
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples) },
|
||||
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples, mSupportsMultipleMipLevels) },
|
||||
mViewUsage{ fToWGPUTextureUsage(usage, samples, false) },
|
||||
mBlockWidth{ filament::backend::getBlockWidth(format) },
|
||||
mBlockHeight{ filament::backend::getBlockHeight(format) } {
|
||||
mBlockHeight{ filament::backend::getBlockHeight(format) },
|
||||
mDefaultMipLevel{ 0 },
|
||||
mDefaultBaseArrayLayer{ 0 } {
|
||||
assert_invariant(
|
||||
samples == 1 ||
|
||||
samples == 4 &&
|
||||
@@ -225,10 +322,8 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
|
||||
.format = mWebGPUFormat,
|
||||
.mipLevelCount = levels,
|
||||
.sampleCount = samples,
|
||||
// TODO Is this fine? Could do all-the-things, a naive mapping or get something from
|
||||
// Filament
|
||||
.viewFormatCount = 0,
|
||||
.viewFormats = nullptr,
|
||||
.viewFormatCount = 1,
|
||||
.viewFormats = &mViewFormat,
|
||||
};
|
||||
mArrayLayerCount = toArrayLayerCount(samplerType, textureDescriptor.size.depthOrArrayLayers);
|
||||
assert_invariant(textureDescriptor.format != wgpu::TextureFormat::Undefined &&
|
||||
@@ -240,23 +335,40 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
|
||||
mArrayLayerCount, samplerType);
|
||||
FILAMENT_CHECK_POSTCONDITION(mDefaultTextureView)
|
||||
<< "Failed to create default texture view for " << textureDescriptor.label;
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
if (mViewFormat != mWebGPUFormat) {
|
||||
std::stringstream viewFormatStream;
|
||||
viewFormatStream << mViewFormat;
|
||||
std::stringstream textureFormatStream;
|
||||
textureFormatStream << mWebGPUFormat;
|
||||
FWGPU_LOGD << "Texture '" << textureDescriptor.label << "' has view format "
|
||||
<< viewFormatStream.str() << " and texture format " << textureFormatStream.str();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
WebGPUTexture::WebGPUTexture(WebGPUTexture const* src, const uint8_t baseLevel,
|
||||
const uint8_t levelCount) noexcept
|
||||
: HwTexture{ src->target, levelCount, src->samples, src->width, src->height, src->depth,
|
||||
src->format, src->usage },
|
||||
mTexture{ src->mTexture },
|
||||
mViewFormat{ src->mViewFormat },
|
||||
mSupportsMultipleMipLevels{ src->mSupportsMultipleMipLevels },
|
||||
mWebGPUFormat{ src->mWebGPUFormat },
|
||||
mAspect{ src->mAspect },
|
||||
mWebGPUUsage{ src->mWebGPUUsage },
|
||||
mArrayLayerCount{ src->mArrayLayerCount },
|
||||
mViewUsage{ src->mViewUsage },
|
||||
mBlockWidth{ src->mBlockWidth },
|
||||
mBlockHeight{ src->mBlockHeight },
|
||||
mDefaultTextureView{ makeTextureView(baseLevel, levelCount, 0, src->mArrayLayerCount,
|
||||
src->target) },
|
||||
mArrayLayerCount{ src->mArrayLayerCount },
|
||||
mTexture{ src->mTexture },
|
||||
mDefaultMipLevel{ baseLevel },
|
||||
mDefaultBaseArrayLayer{ src->mArrayLayerCount } {}
|
||||
mDefaultBaseArrayLayer{ src->mArrayLayerCount },
|
||||
mDefaultTextureView{ makeTextureView(mDefaultMipLevel, levelCount, 0, mDefaultBaseArrayLayer,
|
||||
src->target) } {}
|
||||
|
||||
bool WebGPUTexture::supportsMultipleMipLevelsViaStorageBinding(const wgpu::TextureFormat format) {
|
||||
return storageBindingCompatibleFormatForViewFormat(format) != wgpu::TextureFormat::Undefined;
|
||||
}
|
||||
|
||||
wgpu::TextureView WebGPUTexture::getOrMakeTextureView(const uint8_t mipLevel,
|
||||
const uint32_t arrayLayer) {
|
||||
@@ -356,51 +468,43 @@ wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFo
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and discard the alpha and lower precision.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB565 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB9_E5: return wgpu::TextureFormat::RGB9E5Ufloat;
|
||||
case TextureFormat::RGB5_A1:
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and handle the packing/unpacking in shaders.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB5_A1 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGBA4:
|
||||
// No direct mapping in wgpu. Could potentially map to RGBA8Unorm
|
||||
// and handle the packing/unpacking in shaders.
|
||||
FWGPU_LOGW << "Requested Filament texture format RGBA4 but getting "
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::Undefined (no direct mapping in wgpu)";
|
||||
return wgpu::TextureFormat::Undefined;
|
||||
case TextureFormat::RGB8:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8 but getting "
|
||||
"wgpu::TextureFormat::RGBA8Unorm (no direct sRGB equivalent in wgpu "
|
||||
"without alpha)"
|
||||
<< utils::io::endl;
|
||||
"without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Unorm;
|
||||
case TextureFormat::SRGB8:
|
||||
FWGPU_LOGW << "Requested Filament texture format SRGB8 but getting "
|
||||
"wgpu::TextureFormat::RGBA8UnormSrgb (no direct sRGB equivalent in wgpu "
|
||||
"without alpha)"
|
||||
<< utils::io::endl;
|
||||
"without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8UnormSrgb;
|
||||
case TextureFormat::RGB8_SNORM:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB8_SNORM but getting "
|
||||
"wgpu::TextureFormat::RGBA8Snorm (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA8Snorm (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Snorm;
|
||||
case TextureFormat::RGB8UI:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8UI but getting "
|
||||
"wgpu::TextureFormat::RGBA8Uint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA8Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Uint;
|
||||
case TextureFormat::RGB8I:
|
||||
FWGPU_LOGW << "Requested Filament texture format RGB8I but getting "
|
||||
"wgpu::TextureFormat::RGBA8Sint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA8Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA8Sint;
|
||||
case TextureFormat::R11F_G11F_B10F: return wgpu::TextureFormat::RG11B10Ufloat;
|
||||
case TextureFormat::UNUSED: return wgpu::TextureFormat::Undefined;
|
||||
@@ -408,38 +512,32 @@ wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFo
|
||||
case TextureFormat::RGB16F:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16F but getting "
|
||||
"wgpu::TextureFormat::RGBA16Float (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA16Float (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Float;
|
||||
case TextureFormat::RGB16UI:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16UI but getting "
|
||||
"wgpu::TextureFormat::RGBA16Uint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA16Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Uint;
|
||||
case TextureFormat::RGB16I:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB16I but getting "
|
||||
"wgpu::TextureFormat::RGBA16Sint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA16Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA16Sint;
|
||||
case TextureFormat::RGB32F:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32F but getting "
|
||||
"wgpu::TextureFormat::RGBA32Float (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA32Float (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Float;
|
||||
case TextureFormat::RGB32UI:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32UI but getting "
|
||||
"wgpu::TextureFormat::RGBA32Uint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA32Uint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Uint;
|
||||
case TextureFormat::RGB32I:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament texture format RGB32I but getting "
|
||||
"wgpu::TextureFormat::RGBA32Sint (no direct mapping in wgpu without alpha)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::TextureFormat::RGBA32Sint (no direct mapping in wgpu without alpha)";
|
||||
return wgpu::TextureFormat::RGBA32Sint;
|
||||
case TextureFormat::DXT1_RGB: return wgpu::TextureFormat::BC1RGBAUnorm;
|
||||
case TextureFormat::DXT1_RGBA: return wgpu::TextureFormat::BC1RGBAUnorm;
|
||||
@@ -455,16 +553,23 @@ wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFo
|
||||
wgpu::TextureView WebGPUTexture::makeTextureView(const uint8_t& baseLevel,
|
||||
const uint8_t& levelCount, const uint32_t& baseArrayLayer, const uint32_t& arrayLayerCount,
|
||||
const SamplerType samplerType) const noexcept {
|
||||
#if FWGPU_ENABLED(FWGPU_DEBUG_VALIDATION)
|
||||
if (baseLevel > 0 && !mSupportsMultipleMipLevels) {
|
||||
FWGPU_LOGW << "Trying to make a texture view into a level ("
|
||||
<< static_cast<uint32_t>(baseLevel)
|
||||
<< ") for which we cannot generate mip levels.";
|
||||
}
|
||||
#endif
|
||||
const wgpu::TextureViewDescriptor textureViewDescriptor{
|
||||
.label = getUserTextureViewLabel(target),
|
||||
.format = mWebGPUFormat,
|
||||
.format = mViewFormat,
|
||||
.dimension = toWebGPUTextureViewDimension(samplerType),
|
||||
.baseMipLevel = baseLevel,
|
||||
.mipLevelCount = levelCount,
|
||||
.baseArrayLayer = baseArrayLayer,
|
||||
.arrayLayerCount = arrayLayerCount,
|
||||
.aspect = mAspect,
|
||||
.usage = mWebGPUUsage };
|
||||
.usage = mViewUsage };
|
||||
wgpu::TextureView textureView = mTexture.CreateView(&textureViewDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(textureView)
|
||||
<< "Failed to create texture view " << textureViewDescriptor.label;
|
||||
|
||||
@@ -47,29 +47,46 @@ public:
|
||||
|
||||
[[nodiscard]] wgpu::TextureView getOrMakeTextureView(uint8_t mipLevel, uint32_t arrayLayer);
|
||||
|
||||
[[nodiscard]] wgpu::TextureFormat getWebGPUFormat() const { return mWebGPUFormat; }
|
||||
[[nodiscard]] wgpu::TextureFormat getViewFormat() const { return mViewFormat; }
|
||||
|
||||
[[nodiscard]] uint32_t getArrayLayerCount() const { return mArrayLayerCount; }
|
||||
|
||||
[[nodiscard]] bool supportsMultipleMipLevels() const { return mSupportsMultipleMipLevels; }
|
||||
|
||||
[[nodiscard]] static wgpu::TextureFormat fToWGPUTextureFormat(
|
||||
filament::backend::TextureFormat const& fFormat);
|
||||
|
||||
/**
|
||||
* @param format a required texture format (can be a view to a different underlying texture
|
||||
* format)
|
||||
* @return true if multiple mip levels can be generated via a compute shader + storage binding
|
||||
* on the texture, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] static bool supportsMultipleMipLevelsViaStorageBinding(
|
||||
wgpu::TextureFormat format);
|
||||
|
||||
private:
|
||||
// CreateTextureR has info for a texture and sampler. Texture Views are needed for binding,
|
||||
// along with a sampler Current plan: Inherit the sampler and Texture to always exist (It is a
|
||||
// ref counted pointer) when making views. View is optional
|
||||
wgpu::Texture mTexture = nullptr;
|
||||
// the texture view's format, which may differ from the underlying texture's format itself,
|
||||
// an example of when this is needed is when certain underlying shaders don't support
|
||||
// certain formats (e.g. compute shaders), but we need a view to that format elsewhere
|
||||
// (e.g. another shader or render target)
|
||||
wgpu::TextureFormat mViewFormat = wgpu::TextureFormat::Undefined;
|
||||
// this is true IFF levels > 1 AND we can actually generate mipmaps for the texture
|
||||
bool mSupportsMultipleMipLevels = false;
|
||||
// format is inherited from HwTexture. This naming is to distinguish it from Filament's format
|
||||
// this is the underlying texture's format, not necessarily the "view" of it... see mViewFormat
|
||||
wgpu::TextureFormat mWebGPUFormat = wgpu::TextureFormat::Undefined;
|
||||
wgpu::TextureAspect mAspect = wgpu::TextureAspect::Undefined;
|
||||
// usage is inherited from HwTexture. This naming is to distinguish it from Filament's usage
|
||||
wgpu::TextureUsage mWebGPUUsage = wgpu::TextureUsage::None;
|
||||
uint32_t mArrayLayerCount = 1;
|
||||
wgpu::TextureUsage mViewUsage = wgpu::TextureUsage::None;
|
||||
size_t mBlockWidth = 0;
|
||||
size_t mBlockHeight = 0;
|
||||
wgpu::TextureView mDefaultTextureView = nullptr;
|
||||
uint32_t mArrayLayerCount = 1;
|
||||
wgpu::Texture mTexture = nullptr;
|
||||
uint32_t mDefaultMipLevel = 0;
|
||||
uint32_t mDefaultBaseArrayLayer = 0;
|
||||
wgpu::TextureView mDefaultTextureView = nullptr;
|
||||
|
||||
[[nodiscard]] wgpu::TextureView makeTextureView(const uint8_t& baseLevel,
|
||||
const uint8_t& levelCount, const uint32_t& baseArrayLayer,
|
||||
|
||||
60
filament/backend/src/webgpu/WebGPUTimerQuery.cpp
Normal file
60
filament/backend/src/webgpu/WebGPUTimerQuery.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "WebGPUTimerQuery.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
void WebGPUTimerQuery::beginTimeElapsedQuery() {
|
||||
mStatus->elapsedNanoseconds = 0;
|
||||
// Capture the timer query status via a weak_ptr because the WGPUTimerQuery could be destroyed
|
||||
// before the block executes.
|
||||
std::weak_ptr<WebGPUTimerQuery::Status> statusPtr = mStatus;
|
||||
|
||||
if (auto s = statusPtr.lock()) {
|
||||
s->elapsedNanoseconds = std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUTimerQuery::endTimeElapsedQuery() {
|
||||
// Capture the timer query status via a weak_ptr because the WGPUTimerQuery could be destroyed
|
||||
// before the block executes.
|
||||
if (mStatus->elapsedNanoseconds != 0) {
|
||||
std::weak_ptr<WebGPUTimerQuery::Status> statusPtr = mStatus;
|
||||
if (auto s = statusPtr.lock()) {
|
||||
s->previousElapsed = s->elapsedNanoseconds =
|
||||
std::chrono::steady_clock::now().time_since_epoch().count() -
|
||||
s->elapsedNanoseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WebGPUTimerQuery::getQueryResult(uint64_t* outElapsedTime) {
|
||||
if (mStatus->previousElapsed == 0) {
|
||||
return false;
|
||||
}
|
||||
if (outElapsedTime) {
|
||||
*outElapsedTime = mStatus->previousElapsed;
|
||||
mStatus->previousElapsed = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
50
filament/backend/src/webgpu/WebGPUTimerQuery.h
Normal file
50
filament/backend/src/webgpu/WebGPUTimerQuery.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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_WEBGPUTIMERQUERY_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUTIMERQUERY_H
|
||||
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUTimerQuery : public HwTimerQuery {
|
||||
public:
|
||||
WebGPUTimerQuery()
|
||||
: mStatus(std::make_shared<Status>()) {}
|
||||
|
||||
void beginTimeElapsedQuery();
|
||||
void endTimeElapsedQuery();
|
||||
bool getQueryResult(uint64_t* outElapsedTimeNanoseconds);
|
||||
|
||||
private:
|
||||
struct Status {
|
||||
std::atomic<uint64_t> elapsedNanoseconds{ 0 };
|
||||
std::atomic<uint64_t> previousElapsed{ 0 };
|
||||
};
|
||||
|
||||
std::shared_ptr<Status> mStatus;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif //TNT_FILAMENT_BACKEND_WEBGPUTIMERQUERY_H
|
||||
@@ -22,7 +22,6 @@
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
@@ -57,26 +56,22 @@ namespace {
|
||||
case ElementType::BYTE3:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format BYTE3 (normalized) but getting "
|
||||
"wgpu::VertexFormat::Snorm8x4 (no direct mapping in wgpu for x3 byte)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Snorm8x4 (no direct mapping in wgpu for x3 byte)";
|
||||
return VertexFormat::Snorm8x4; // NOT MINSPEC
|
||||
case ElementType::UBYTE3:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format UBYTE3 (normalized) but getting "
|
||||
"wgpu::VertexFormat::Unorm8x4 (no direct mapping in wgpu for x3 byte)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Unorm8x4 (no direct mapping in wgpu for x3 byte)";
|
||||
return VertexFormat::Unorm8x4; // NOT MINSPEC
|
||||
case ElementType::SHORT3:
|
||||
FWGPU_LOGW << "Requested Filament vertex format SHORT3 (normalized) but getting "
|
||||
"wgpu::VertexFormat::Snorm16x4 (no direct mapping in wgpu for x3 "
|
||||
"half/short)"
|
||||
<< utils::io::endl;
|
||||
"half/short)";
|
||||
return VertexFormat::Snorm16x4; // NOT MINSPEC
|
||||
case ElementType::USHORT3:
|
||||
FWGPU_LOGW << "Requested Filament vertex format USHORT3 (normalized) but getting "
|
||||
"wgpu::VertexFormat::Unorm16x4 (no direct mapping in wgpu for x3 "
|
||||
"half/short)"
|
||||
<< utils::io::endl;
|
||||
"half/short)";
|
||||
return VertexFormat::Unorm16x4; // NOT MINSPEC
|
||||
// Four Component Types
|
||||
case ElementType::BYTE4: return VertexFormat::Snorm8x4;
|
||||
@@ -97,26 +92,22 @@ namespace {
|
||||
case ElementType::BYTE:
|
||||
if (integer) return VertexFormat::Sint8;
|
||||
FWGPU_LOGW << "Requested Filament vertex format BYTE (float) but getting "
|
||||
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)";
|
||||
return wgpu::VertexFormat::Float16;
|
||||
case ElementType::UBYTE:
|
||||
if (integer) return VertexFormat::Uint8;
|
||||
FWGPU_LOGW << "Requested Filament vertex format UBYTE (float) but getting "
|
||||
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16 (no direct mapping in wgpu for 8 bit float)";
|
||||
return VertexFormat::Float16;
|
||||
case ElementType::SHORT:
|
||||
if (integer) return VertexFormat::Sint16;
|
||||
FWGPU_LOGW << "Requested Filament vertex format SHORT (float) and getting "
|
||||
"wgpu::VertexFormat::Float16 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16 (potential loss of precision?)";
|
||||
return VertexFormat ::Float16;
|
||||
case ElementType::USHORT:
|
||||
if (integer) return VertexFormat::Uint16;
|
||||
FWGPU_LOGW << "Requested Filament vertex format USHORT (float) and getting "
|
||||
"wgpu::VertexFormat::Float16 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16 (potential loss of precision?)";
|
||||
return VertexFormat::Float16;
|
||||
case ElementType::HALF: return VertexFormat::Float16;
|
||||
case ElementType::INT: return VertexFormat::Sint32;
|
||||
@@ -127,58 +118,49 @@ namespace {
|
||||
if (integer) return VertexFormat::Sint8x2;
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format BYTE2 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)";
|
||||
return VertexFormat::Float16x2;
|
||||
case ElementType::UBYTE2:
|
||||
if (integer) return VertexFormat::Uint8x2;
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format UBYTE2 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x2 (no direct mapping in wgpu for 8 bit float)";
|
||||
return VertexFormat::Float16x2;
|
||||
case ElementType::SHORT2:
|
||||
if (integer) return VertexFormat::Sint16x2;
|
||||
FWGPU_LOGW << "Requested Filament vertex format SHORT2 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)";
|
||||
return VertexFormat::Float16x2;
|
||||
case ElementType::USHORT2:
|
||||
if (integer) return VertexFormat::Uint16x2;
|
||||
FWGPU_LOGW << "Requested Filament vertex format USHORT2 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x2 (potential loss of precision?)";
|
||||
return VertexFormat::Float16x2;
|
||||
case ElementType::HALF2: return VertexFormat::Float16x2;
|
||||
case ElementType::FLOAT2: return VertexFormat::Float32x2;
|
||||
// Three Component Types
|
||||
case ElementType::BYTE3:
|
||||
FWGPU_LOGW << "Requested Filament vertex format BYTE3 but getting "
|
||||
"wgpu::VertexFormat::Sint8x4 (no direct mapping in wgpu for x3 byte)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Sint8x4 (no direct mapping in wgpu for x3 byte)";
|
||||
return VertexFormat::Sint8x4; // NOT MINSPEC
|
||||
case ElementType::UBYTE3:
|
||||
FWGPU_LOGW << "Requested Filament vertex format UBYTE3 but getting "
|
||||
"wgpu::VertexFormat::Uint8x4 (no direct mapping in wgpu for x3 byte)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Uint8x4 (no direct mapping in wgpu for x3 byte)";
|
||||
return VertexFormat::Uint8x4; // NOT MINSPEC
|
||||
case ElementType::SHORT3:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format SHORT3 but getting "
|
||||
"wgpu::VertexFormat::Sint16x4 (no direct mapping in wgpu for x3 half/short)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Sint16x4 (no direct mapping in wgpu for x3 half/short)";
|
||||
return VertexFormat::Sint16x4; // NOT MINSPEC
|
||||
case ElementType::USHORT3:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format USHORT3 but getting "
|
||||
"wgpu::VertexFormat::Uint16x4 (no direct mapping in wgpu for x3 half/short)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Uint16x4 (no direct mapping in wgpu for x3 half/short)";
|
||||
return VertexFormat::Uint16x4; // NOT MINSPEC
|
||||
case ElementType::HALF3:
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format HALF3 but getting "
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for x3 half/short)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for x3 half/short)";
|
||||
return VertexFormat::Float16x4; // NOT MINSPEC
|
||||
case ElementType::FLOAT3: return VertexFormat::Float32x3;
|
||||
// Four Component Types
|
||||
@@ -186,27 +168,23 @@ namespace {
|
||||
if (integer) return VertexFormat::Sint8x4;
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format BYTE4 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)";
|
||||
return VertexFormat::Float16x4;
|
||||
case ElementType::UBYTE4:
|
||||
if (integer) return VertexFormat::Uint8x4;
|
||||
FWGPU_LOGW
|
||||
<< "Requested Filament vertex format UBYTE4 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x4 (no direct mapping in wgpu for 8 bit float)";
|
||||
return VertexFormat::Float16x4;
|
||||
case ElementType::SHORT4:
|
||||
if (integer) return VertexFormat::Sint16x4;
|
||||
FWGPU_LOGW << "Requested Filament vertex format SHORT4 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)";
|
||||
return VertexFormat::Float16x4;
|
||||
case ElementType::USHORT4:
|
||||
if (integer) return VertexFormat::Uint16x4;
|
||||
FWGPU_LOGW << "Requested Filament vertex format USHORT4 (float) but getting "
|
||||
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)"
|
||||
<< utils::io::endl;
|
||||
"wgpu::VertexFormat::Float16x4 (potential loss of precision?)";
|
||||
return VertexFormat::Float16x4;
|
||||
case ElementType::HALF4: return VertexFormat::Float16x4;
|
||||
case ElementType::FLOAT4: return VertexFormat::Float32x4;
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#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>
|
||||
@@ -39,6 +38,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#endif
|
||||
@@ -53,6 +53,9 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// The SPD Algorithm can make use of up to 12 storage texture attachments
|
||||
constexpr uint32_t MAX_MIPMAP_STORAGE_TEXTURES_PER_STAGE = 12u;
|
||||
|
||||
constexpr std::array REQUIRED_FEATURES = { wgpu::FeatureName::TransientAttachments,
|
||||
/*To make filtering assumptions like we want while waiting for Filament to provide that info,
|
||||
float 32 needs to be filterable*/
|
||||
@@ -117,28 +120,6 @@ constexpr void forEachLimitToValidate(std::function<bool(LimitToValidate const)>
|
||||
}
|
||||
}
|
||||
|
||||
#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) {
|
||||
@@ -259,7 +240,7 @@ void printInstanceDetails(wgpu::Instance const& instance) {
|
||||
supportedWGSLLanguageFeatures.features +
|
||||
supportedWGSLLanguageFeatures.featureCount,
|
||||
[](wgpu::WGSLLanguageFeatureName const featureName) {
|
||||
FWGPU_LOGI << " " << featureName;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(featureName);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -299,23 +280,22 @@ void printInstanceDetails(wgpu::Instance const& 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() << ": ";
|
||||
static constexpr std::string_view indent = " ";
|
||||
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);
|
||||
FWGPU_LOGI << indent << name.data() << ": " << 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);
|
||||
FWGPU_LOGI << indent << name.data() << ": " << std::get<uint64_t>(value);
|
||||
}
|
||||
}
|
||||
if (undefined) {
|
||||
FWGPU_LOGI << "UNDEFINED";
|
||||
FWGPU_LOGI << indent << name.data() << ": UNDEFINED";
|
||||
}
|
||||
FWGPU_LOGI << "";
|
||||
}
|
||||
#endif// FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
|
||||
@@ -393,16 +373,16 @@ struct AdapterDetails final {
|
||||
}
|
||||
};
|
||||
|
||||
template<class STREAM_TYPE>
|
||||
STREAM_TYPE& operator<<(STREAM_TYPE& out, AdapterDetails const& details) noexcept {
|
||||
out << details.info
|
||||
[[nodiscard]] std::string toString(AdapterDetails const& details) {
|
||||
std::stringstream out;
|
||||
out << adapterInfoToString(details.info)
|
||||
<< " power preference " << powerPreferenceToString(details.powerPreference);
|
||||
return out;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
void printAdapterDetails(AdapterDetails const& details) {
|
||||
FWGPU_LOGI << "Selected WebGPU adapter info: " << details;
|
||||
FWGPU_LOGI << "Selected WebGPU adapter info: " << toString(details);
|
||||
wgpu::SupportedFeatures supportedFeatures{};
|
||||
details.adapter.GetFeatures(&supportedFeatures);
|
||||
FWGPU_LOGI << "WebGPU adapter supported features (" << supportedFeatures.featureCount
|
||||
@@ -411,7 +391,7 @@ void printAdapterDetails(AdapterDetails const& details) {
|
||||
std::for_each(supportedFeatures.features,
|
||||
supportedFeatures.features + supportedFeatures.featureCount,
|
||||
[](wgpu::FeatureName const featureName) {
|
||||
FWGPU_LOGI << " " << featureName;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(featureName);
|
||||
});
|
||||
}
|
||||
wgpu::Limits supportedLimits{};
|
||||
@@ -444,12 +424,12 @@ struct AdapterDetailsHash final {
|
||||
return details.adapter.HasFeature(featureName);
|
||||
})) {
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
FWGPU_LOGI << "WebGPU adapter " << details
|
||||
FWGPU_LOGI << "WebGPU adapter " << toString(details)
|
||||
<< " does not have the minimum required features.";
|
||||
FWGPU_LOGI << "Missing required feature(s): ";
|
||||
for (wgpu::FeatureName const& requiredFeature: REQUIRED_FEATURES) {
|
||||
if (!details.adapter.HasFeature(requiredFeature)) {
|
||||
FWGPU_LOGI << requiredFeature;
|
||||
FWGPU_LOGI << webGPUPrintableToString(requiredFeature);
|
||||
}
|
||||
}
|
||||
FWGPU_LOGI << "";
|
||||
@@ -458,10 +438,10 @@ struct AdapterDetailsHash final {
|
||||
}
|
||||
wgpu::Limits supportedLimits {};
|
||||
FILAMENT_CHECK_POSTCONDITION(details.adapter.GetLimits(&supportedLimits))
|
||||
<< "Failed to get limits for WebGPU adapter: " << details;
|
||||
<< "Failed to get limits for WebGPU adapter: " << toString(details);
|
||||
if (!satisfiesLimits(supportedLimits)) {
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
FWGPU_LOGI << " (for WebGPU adapter " << details << ")";
|
||||
FWGPU_LOGI << " (for WebGPU adapter " << toString(details) << ")";
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -487,14 +467,15 @@ struct AdapterDetailsHash final {
|
||||
status != wgpu::RequestAdapterStatus::CallbackCancelled)
|
||||
<< "Failed to request a WebGPU adapter due to the request callback "
|
||||
"being cancelled? Options: "
|
||||
<< options << " " << message.data;
|
||||
<< adapterOptionsToString(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;
|
||||
<< adapterOptionsToString(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 << ")";
|
||||
<< "Failed to get info for adapter (options: "
|
||||
<< adapterOptionsToString(options) << ")";
|
||||
const std::lock_guard<std::mutex> lock(adaptersMutex);
|
||||
compatibleAdapters.emplace(std::move(details.info), details.powerPreference,
|
||||
std::move(details.adapter));
|
||||
@@ -509,9 +490,11 @@ struct AdapterDetailsHash final {
|
||||
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;
|
||||
<< "Timed out requesting a WebGPU adapter with options "
|
||||
<< adapterOptionsToString(options);
|
||||
FILAMENT_CHECK_POSTCONDITION(status != wgpu::WaitStatus::Error)
|
||||
<< "Failed to request a WebGPU adapter with options " << options
|
||||
<< "Failed to request a WebGPU adapter with options "
|
||||
<< adapterOptionsToString(options)
|
||||
<< " due to an error (as request was made synchronous)";
|
||||
assert_invariant(status == wgpu::WaitStatus::Success);
|
||||
}
|
||||
@@ -519,7 +502,7 @@ struct AdapterDetailsHash final {
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
FWGPU_LOGI << compatibleAdapters.size() << " WebGPU adapter(s) found:";
|
||||
for (auto& details: compatibleAdapters) {
|
||||
FWGPU_LOGI << " WebGPU adapter: " << details;
|
||||
FWGPU_LOGI << " WebGPU adapter: " << toString(details);
|
||||
}
|
||||
#endif
|
||||
return compatibleAdapters;
|
||||
@@ -586,7 +569,7 @@ void printDeviceDetails(wgpu::Device const& device) {
|
||||
std::for_each(supportedFeatures.features,
|
||||
supportedFeatures.features + supportedFeatures.featureCount,
|
||||
[](wgpu::FeatureName const featureName) {
|
||||
FWGPU_LOGI << " " << featureName;
|
||||
FWGPU_LOGI << " " << webGPUPrintableToString(featureName);
|
||||
});
|
||||
}
|
||||
wgpu::Limits supportedLimits{};
|
||||
@@ -628,7 +611,21 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
||||
deviceDescriptor.defaultQueue.label = "default_queue";
|
||||
deviceDescriptor.requiredFeatureCount = enabledFeatures.size();
|
||||
deviceDescriptor.requiredFeatures = enabledFeatures.data();
|
||||
deviceDescriptor.requiredLimits = &REQUIRED_LIMITS;
|
||||
|
||||
// It is helpful to increase maxStorageTexturesPerShaderStage if the hardware supports it, but
|
||||
// not required. This allows our mipmap generation to perform as well as possible for the given
|
||||
// hardware. If we have more cases of "Nice to increase" limits like this we can generalize it
|
||||
// in the future.
|
||||
wgpu::Limits supportedLimits{};
|
||||
FILAMENT_CHECK_POSTCONDITION(
|
||||
adapter.GetLimits(&supportedLimits).status == wgpu::Status::Success)
|
||||
<< "Failed to get limits for WebGPU adapter";
|
||||
auto limitsToRequest = REQUIRED_LIMITS;
|
||||
limitsToRequest.maxStorageTexturesPerShaderStage =
|
||||
std::min(MAX_MIPMAP_STORAGE_TEXTURES_PER_STAGE,
|
||||
supportedLimits.maxStorageTexturesPerShaderStage);
|
||||
deviceDescriptor.requiredLimits = &limitsToRequest;
|
||||
|
||||
deviceDescriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous,
|
||||
[](wgpu::Device const&, wgpu::DeviceLostReason const& reason,
|
||||
wgpu::StringView message) {
|
||||
@@ -684,7 +681,6 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
||||
"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))
|
||||
|
||||
@@ -40,18 +40,24 @@ using namespace filament::math;
|
||||
|
||||
using namespace image;
|
||||
#endif
|
||||
#include <iostream>
|
||||
|
||||
namespace test {
|
||||
|
||||
Backend BackendTest::sBackend = Backend::NOOP;
|
||||
OperatingSystem BackendTest::sOperatingSystem = OperatingSystem::OTHER;
|
||||
bool BackendTest::sIsMobilePlatform = false;
|
||||
int BackendTest::sArgc = 0;
|
||||
char** BackendTest::sArgv = nullptr;
|
||||
std::vector<std::string> BackendTest::sFailedImages;
|
||||
|
||||
void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform) {
|
||||
void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform,
|
||||
int argc, char** argv) {
|
||||
sBackend = backend;
|
||||
sOperatingSystem = operatingSystem;
|
||||
sIsMobilePlatform = isMobilePlatform;
|
||||
sArgc = argc;
|
||||
sArgv = argv;
|
||||
}
|
||||
|
||||
BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE,
|
||||
@@ -160,6 +166,11 @@ void BackendTest::markImageAsFailure(std::string failedImageName) {
|
||||
sFailedImages.emplace_back(std::move(failedImageName));
|
||||
}
|
||||
|
||||
std::filesystem::path BackendTest::binaryDirectory() {
|
||||
assert(sArgc >= 1);
|
||||
return std::filesystem::path(sArgv[0]).remove_filename().string();
|
||||
}
|
||||
|
||||
void BackendTest::recordFailedImages() {
|
||||
if (!sFailedImages.empty()) {
|
||||
std::string failedImages;
|
||||
@@ -187,8 +198,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]) {
|
||||
BackendTest::init(backend, operatingSystem, isMobile);
|
||||
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc,
|
||||
char* argv[]) {
|
||||
BackendTest::init(backend, operatingSystem, isMobile, argc, argv);
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
::testing::AddGlobalTestEnvironment(new Environment);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include "private/backend/CommandBufferQueue.h"
|
||||
@@ -31,16 +33,20 @@ namespace test {
|
||||
|
||||
class BackendTest : public ::testing::Test {
|
||||
public:
|
||||
|
||||
static void init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform);
|
||||
static void init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform,
|
||||
int argc, char** argv);
|
||||
|
||||
static Backend sBackend;
|
||||
static OperatingSystem sOperatingSystem;
|
||||
static bool sIsMobilePlatform;
|
||||
static int sArgc;
|
||||
static char** sArgv;
|
||||
|
||||
// Takes the name of the image that wasn't correct, without the .png suffix
|
||||
static void markImageAsFailure(std::string failedImageName);
|
||||
|
||||
static std::filesystem::path binaryDirectory();
|
||||
|
||||
protected:
|
||||
|
||||
BackendTest();
|
||||
|
||||
@@ -59,28 +59,28 @@ uint32_t ScreenshotParams::expectedHash() const {
|
||||
return mExpectedPixelHash;
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::actualDirectoryPath() {
|
||||
return "images/actual_images";
|
||||
std::filesystem::path ScreenshotParams::actualDirectoryPath() {
|
||||
return BackendTest::binaryDirectory().append("images/actual_images");
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::actualFileName() const {
|
||||
return absl::StrFormat("%s_actual.png", mFileName);
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::actualFilePath() const {
|
||||
return absl::StrFormat("%s/%s", actualDirectoryPath(), actualFileName());
|
||||
std::filesystem::path ScreenshotParams::actualFilePath() const {
|
||||
return actualDirectoryPath().append(actualFileName());
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::expectedDirectoryPath() {
|
||||
return "images/expected_images";
|
||||
std::filesystem::path ScreenshotParams::expectedDirectoryPath() {
|
||||
return BackendTest::binaryDirectory().append("images/expected_images");
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::expectedFileName() const {
|
||||
return absl::StrFormat("%s.png", mFileName);
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::expectedFilePath() const {
|
||||
return absl::StrFormat("%s/%s", expectedDirectoryPath(), expectedFileName());
|
||||
std::filesystem::path ScreenshotParams::expectedFilePath() const {
|
||||
return expectedDirectoryPath().append(expectedFileName());
|
||||
}
|
||||
|
||||
const std::string ScreenshotParams::filePrefix() const {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef TNT_IMAGE_EXPECTATIONS_H
|
||||
#define TNT_IMAGE_EXPECTATIONS_H
|
||||
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
@@ -51,12 +52,12 @@ public:
|
||||
bool isSrgb() const;
|
||||
uint32_t expectedHash() const;
|
||||
|
||||
static std::string actualDirectoryPath();
|
||||
static std::filesystem::path actualDirectoryPath();
|
||||
std::string actualFileName() const;
|
||||
std::string actualFilePath() const;
|
||||
static std::string expectedDirectoryPath();
|
||||
std::filesystem::path actualFilePath() const;
|
||||
static std::filesystem::path expectedDirectoryPath();
|
||||
std::string expectedFileName() const;
|
||||
std::string expectedFilePath() const;
|
||||
std::filesystem::path expectedFilePath() const;
|
||||
const std::string filePrefix() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -139,6 +139,24 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
return bufferDimension;
|
||||
}
|
||||
|
||||
void exportScreenshot(void* pixelData) const {
|
||||
#ifndef FILAMENT_IOS
|
||||
const size_t width = readRect.width, height = readRect.height;
|
||||
LinearImage image(width, height, 4);
|
||||
if (format == PixelDataFormat::RGBA && type == PixelDataType::UBYTE) {
|
||||
image = toLinearWithAlpha<uint8_t>(width, height, width * 4, (uint8_t*)pixelData);
|
||||
}
|
||||
if (format == PixelDataFormat::RGBA && type == PixelDataType::FLOAT) {
|
||||
memcpy(image.getPixelRef(), pixelData, width * height * sizeof(math::float4));
|
||||
}
|
||||
std::string png = std::string(testName) + ".png";
|
||||
std::filesystem::path path = ScreenshotParams::actualDirectoryPath();
|
||||
path.append(png);
|
||||
std::ofstream outputStream(path.c_str(), std::ios::binary | std::ios::trunc);
|
||||
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "", png);
|
||||
#endif
|
||||
}
|
||||
|
||||
// The format and type for the readPixels call.
|
||||
PixelDataFormat format = PixelDataFormat::RGBA;
|
||||
PixelDataType type = PixelDataType::UBYTE;
|
||||
@@ -311,6 +329,8 @@ TEST_F(ReadPixelsTest, ReadPixels) {
|
||||
const auto* test = (const TestCase*)user;
|
||||
assert_invariant(test);
|
||||
|
||||
test->exportScreenshot(buffer);
|
||||
|
||||
// Hash the contents of the buffer and check that they match.
|
||||
uint32_t hash = utils::hash::murmur3((const uint32_t*)buffer, size / 4, 0);
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ DescriptorSet& DescriptorSet::operator=(DescriptorSet&& rhs) noexcept {
|
||||
mDirty = rhs.mDirty;
|
||||
mValid = rhs.mValid;
|
||||
mSetAfterCommitWarning = rhs.mSetAfterCommitWarning;
|
||||
mSetUndefinedParameterWarning = rhs.mSetUndefinedParameterWarning;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -106,11 +107,12 @@ void DescriptorSet::commitSlow(DescriptorSetLayout const& layout,
|
||||
});
|
||||
|
||||
auto const unsetValidDescriptors = layout.getValidDescriptors() & ~mValid;
|
||||
if (UTILS_VERY_UNLIKELY(!unsetValidDescriptors.empty())) {
|
||||
if (UTILS_VERY_UNLIKELY(!unsetValidDescriptors.empty() && !mSetUndefinedParameterWarning)) {
|
||||
unsetValidDescriptors.forEachSetBit([&](auto i) {
|
||||
LOG(WARNING) << (layout.isSampler(i) ? "Sampler" : "Buffer") << " descriptor " << i
|
||||
<< " of " << mName.c_str() << " is not set. Please report this issue.";
|
||||
});
|
||||
mSetUndefinedParameterWarning = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,6 +110,7 @@ private:
|
||||
mutable utils::bitset64 mValid; // 8
|
||||
backend::DescriptorSetHandle mDescriptorSetHandle; // 4
|
||||
mutable bool mSetAfterCommitWarning = false; // 1
|
||||
mutable bool mSetUndefinedParameterWarning = false; // 1
|
||||
utils::StaticString mName; // 16
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.61.2"
|
||||
spec.version = "1.61.1"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.61.2/filament-v1.61.2-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.61.1/filament-v1.61.1-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <filament/Material.h>
|
||||
#include <filament/MaterialInstance.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <array>
|
||||
@@ -196,6 +197,7 @@ void processShaderString(std::string* shader, const UvMap& uvmap,
|
||||
* Creates a material provider that builds materials on the fly, composing GLSL at run time.
|
||||
*
|
||||
* @param optimizeShaders Optimizes shaders, but at significant cost to construction time.
|
||||
* @param variantFilters Filter out variants that are not required.
|
||||
* @return New material provider that can build materials at run time.
|
||||
*
|
||||
* Requires \c libfilamat to be linked in. Not available in \c libgltfio_core.
|
||||
@@ -203,7 +205,8 @@ void processShaderString(std::string* shader, const UvMap& uvmap,
|
||||
* @see createUbershaderProvider
|
||||
*/
|
||||
UTILS_PUBLIC
|
||||
MaterialProvider* createJitShaderProvider(Engine* engine, bool optimizeShaders = false);
|
||||
MaterialProvider* createJitShaderProvider(Engine* engine, bool optimizeShaders = false,
|
||||
utils::FixedCapacityVector<char const*> const& variantFilters = {});
|
||||
|
||||
/**
|
||||
* Creates a material provider that loads a small set of pre-built materials.
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <gltfio/MaterialProvider.h>
|
||||
|
||||
#include <filament/MaterialEnums.h>
|
||||
#include <filamat/MaterialBuilder.h>
|
||||
|
||||
#include <utils/Hash.h>
|
||||
@@ -23,6 +24,7 @@
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace filamat;
|
||||
using namespace filament;
|
||||
@@ -33,7 +35,8 @@ namespace {
|
||||
|
||||
class JitShaderProvider : public MaterialProvider {
|
||||
public:
|
||||
explicit JitShaderProvider(Engine* engine, bool optimizeShaders);
|
||||
explicit JitShaderProvider(Engine* engine, bool optimizeShaders,
|
||||
utils::FixedCapacityVector<char const*> const& variantFilters);
|
||||
~JitShaderProvider() override;
|
||||
|
||||
MaterialInstance* createMaterialInstance(MaterialKey* config, UvMap* uvmap,
|
||||
@@ -49,15 +52,37 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
using HashFn = hash::MurmurHashFn<MaterialKey>;
|
||||
tsl::robin_map<MaterialKey, Material*, HashFn> mCache;
|
||||
std::vector<Material*> mMaterials;
|
||||
Engine* const mEngine;
|
||||
const bool mOptimizeShaders;
|
||||
filament::UserVariantFilterMask mVariantFilter;
|
||||
};
|
||||
|
||||
JitShaderProvider::JitShaderProvider(Engine* engine, bool optimizeShaders) : mEngine(engine),
|
||||
mOptimizeShaders(optimizeShaders) {
|
||||
JitShaderProvider::JitShaderProvider(Engine* engine, bool optimizeShaders,
|
||||
utils::FixedCapacityVector<char const*> const& variantFilters)
|
||||
: mEngine(engine),
|
||||
mOptimizeShaders(optimizeShaders) {
|
||||
|
||||
// Note that this is the same as the list in tools/matc/src/ParametersProcessor.cpp
|
||||
static const std::unordered_map<std::string, filament::UserVariantFilterBit> strToEnum = [] {
|
||||
std::unordered_map<std::string, filament::UserVariantFilterBit> strToEnum;
|
||||
strToEnum["directionalLighting"] = filament::UserVariantFilterBit::DIRECTIONAL_LIGHTING;
|
||||
strToEnum["dynamicLighting"] = filament::UserVariantFilterBit::DYNAMIC_LIGHTING;
|
||||
strToEnum["shadowReceiver"] = filament::UserVariantFilterBit::SHADOW_RECEIVER;
|
||||
strToEnum["skinning"] = filament::UserVariantFilterBit::SKINNING;
|
||||
strToEnum["vsm"] = filament::UserVariantFilterBit::VSM;
|
||||
strToEnum["fog"] = filament::UserVariantFilterBit::FOG;
|
||||
strToEnum["ssr"] = filament::UserVariantFilterBit::SSR;
|
||||
strToEnum["stereo"] = filament::UserVariantFilterBit::STE;
|
||||
return strToEnum;
|
||||
}();
|
||||
|
||||
for (auto& filterStr : variantFilters) {
|
||||
mVariantFilter |= (uint32_t)strToEnum.at(filterStr);
|
||||
}
|
||||
MaterialBuilder::init();
|
||||
}
|
||||
|
||||
@@ -344,24 +369,25 @@ std::string shaderFromKey(const MaterialKey& config) {
|
||||
}
|
||||
|
||||
Material* createMaterial(Engine* engine, const MaterialKey& config, const UvMap& uvmap,
|
||||
const char* name, bool optimizeShaders) {
|
||||
const char* name, bool optimizeShaders, filament::UserVariantFilterMask variantFilter) {
|
||||
std::string shader = shaderFromKey(config);
|
||||
processShaderString(&shader, uvmap, config);
|
||||
MaterialBuilder builder;
|
||||
builder.name(name)
|
||||
.flipUV(false)
|
||||
.specularAmbientOcclusion(MaterialBuilder::SpecularAmbientOcclusion::SIMPLE)
|
||||
.specularAntiAliasing(true)
|
||||
.clearCoatIorChange(false)
|
||||
.material(shader.c_str())
|
||||
.doubleSided(config.doubleSided)
|
||||
.transparencyMode(config.doubleSided ?
|
||||
MaterialBuilder::TransparencyMode::TWO_PASSES_TWO_SIDES :
|
||||
MaterialBuilder::TransparencyMode::DEFAULT)
|
||||
.reflectionMode(MaterialBuilder::ReflectionMode::SCREEN_SPACE)
|
||||
.targetApi(filamat::targetApiFromBackend(engine->getBackend()))
|
||||
.stereoscopicType(engine->getConfig().stereoscopicType)
|
||||
.stereoscopicEyeCount(engine->getConfig().stereoscopicEyeCount);
|
||||
.flipUV(false)
|
||||
.specularAmbientOcclusion(MaterialBuilder::SpecularAmbientOcclusion::SIMPLE)
|
||||
.specularAntiAliasing(true)
|
||||
.clearCoatIorChange(false)
|
||||
.material(shader.c_str())
|
||||
.doubleSided(config.doubleSided)
|
||||
.transparencyMode(config.doubleSided
|
||||
? MaterialBuilder::TransparencyMode::TWO_PASSES_TWO_SIDES
|
||||
: MaterialBuilder::TransparencyMode::DEFAULT)
|
||||
.reflectionMode(MaterialBuilder::ReflectionMode::SCREEN_SPACE)
|
||||
.targetApi(filamat::targetApiFromBackend(engine->getBackend()))
|
||||
.stereoscopicType(engine->getConfig().stereoscopicType)
|
||||
.stereoscopicEyeCount(engine->getConfig().stereoscopicEyeCount)
|
||||
.variantFilter(variantFilter);
|
||||
|
||||
if (!optimizeShaders) {
|
||||
builder.optimization(MaterialBuilder::Optimization::NONE);
|
||||
@@ -589,13 +615,13 @@ Material* JitShaderProvider::getMaterial(MaterialKey* config, UvMap* uvmap, cons
|
||||
constrainMaterial(config, uvmap);
|
||||
auto iter = mCache.find(*config);
|
||||
if (iter == mCache.end()) {
|
||||
|
||||
bool optimizeShaders = mOptimizeShaders;
|
||||
#ifndef NDEBUG
|
||||
optimizeShaders = false;
|
||||
#endif
|
||||
|
||||
Material* mat = createMaterial(mEngine, *config, *uvmap, label, optimizeShaders);
|
||||
Material* mat =
|
||||
createMaterial(mEngine, *config, *uvmap, label, optimizeShaders, mVariantFilter);
|
||||
mCache.emplace(std::make_pair(*config, mat));
|
||||
mMaterials.push_back(mat);
|
||||
return mat;
|
||||
@@ -612,8 +638,9 @@ MaterialInstance* JitShaderProvider::createMaterialInstance(MaterialKey* config,
|
||||
|
||||
namespace filament::gltfio {
|
||||
|
||||
MaterialProvider* createJitShaderProvider(filament::Engine* engine, bool optimizeShaders) {
|
||||
return new JitShaderProvider(engine, optimizeShaders);
|
||||
MaterialProvider* createJitShaderProvider(Engine* engine, bool optimizeShaders,
|
||||
utils::FixedCapacityVector<char const*> const& variantFilters) {
|
||||
return new JitShaderProvider(engine, optimizeShaders, variantFilters);
|
||||
}
|
||||
|
||||
} // namespace filament::gltfio
|
||||
|
||||
@@ -222,6 +222,8 @@ target_sources(gltf-demo-resources PRIVATE ${RESGEN_SOURCE})
|
||||
|
||||
add_library(samples-common STATIC
|
||||
common/arguments.cpp
|
||||
common/configuration.h
|
||||
common/configuration.cpp
|
||||
)
|
||||
|
||||
target_include_directories(samples-common PRIVATE
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "arguments.h"
|
||||
|
||||
#include <filament/Engine.h>
|
||||
|
||||
#include <utils/Path.h>
|
||||
|
||||
@@ -22,8 +22,10 @@
|
||||
#include <string>
|
||||
|
||||
namespace samples {
|
||||
filament::Engine::Backend parseArgumentsForBackend(int argc, char* argv[]);
|
||||
filament::Engine::Backend parseArgumentsForBackend(const std::string& backend);
|
||||
std::string getBackendAPIArgumentsUsage();
|
||||
}
|
||||
|
||||
filament::Engine::Backend parseArgumentsForBackend(int argc, char* argv[]);
|
||||
filament::Engine::Backend parseArgumentsForBackend(const std::string& backend);
|
||||
std::string getBackendAPIArgumentsUsage();
|
||||
|
||||
} // namespace samples
|
||||
#endif //TNT_SAMPLES_ARGUMENTS_H
|
||||
|
||||
65
samples/common/configuration.cpp
Normal file
65
samples/common/configuration.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#include <filament/Engine.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace samples {
|
||||
|
||||
namespace {
|
||||
|
||||
using FilterVector = utils::FixedCapacityVector<char const*>;
|
||||
std::unordered_map<filament::Engine::Backend, FilterVector> gFilters;
|
||||
|
||||
// TODO: This is not necessary once we can support these variants in webgpu.
|
||||
char const* WEBGPU_VARIANT_FILTERS[] = {
|
||||
"skinning",
|
||||
"stereo",
|
||||
};
|
||||
|
||||
} // namespace anonymous
|
||||
|
||||
FilterVector const& getJitMaterialVariantFilter(filament::Engine::Backend backend) {
|
||||
if (auto itr = gFilters.find(backend); itr != gFilters.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
auto build = [&](char const** filters, size_t filterCount) -> FilterVector {
|
||||
utils::FixedCapacityVector<char const*> filterStr;
|
||||
filterStr.clear();
|
||||
filterStr.reserve(filterCount);
|
||||
for (size_t i = 0; i < filterCount; ++i) {
|
||||
filterStr.push_back(filters[i]);
|
||||
}
|
||||
return filterStr;
|
||||
};
|
||||
|
||||
switch (backend) {
|
||||
case filament::Engine::Backend::WEBGPU:
|
||||
gFilters[backend] = build(WEBGPU_VARIANT_FILTERS,
|
||||
sizeof(WEBGPU_VARIANT_FILTERS) / sizeof(char const*));
|
||||
break;
|
||||
default:
|
||||
gFilters[backend] = build(nullptr, 0);
|
||||
}
|
||||
return gFilters[backend];
|
||||
}
|
||||
|
||||
} // namespace samples
|
||||
31
samples/common/configuration.h
Normal file
31
samples/common/configuration.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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_SAMPLES_CONFIGURATION_H
|
||||
#define TNT_SAMPLES_CONFIGURATION_H
|
||||
|
||||
#include <filament/Engine.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
namespace samples {
|
||||
|
||||
utils::FixedCapacityVector<char const*> const& getJitMaterialVariantFilter(
|
||||
filament::Engine::Backend backend);
|
||||
|
||||
}
|
||||
|
||||
#endif // TNT_SAMPLES_CONFIGURATION_H
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "common/arguments.h"
|
||||
#include "common/configuration.h"
|
||||
|
||||
#include <filamentapp/Config.h>
|
||||
#include <filamentapp/FilamentApp.h>
|
||||
@@ -244,8 +245,11 @@ int main(int argc, char** argv) {
|
||||
app.names = new NameComponentManager(EntityManager::get());
|
||||
app.viewer = new ViewerGui(engine, scene, view);
|
||||
|
||||
app.materials = (app.materialSource == JITSHADER) ? createJitShaderProvider(engine) :
|
||||
createUbershaderProvider(engine, UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE);
|
||||
app.materials = (app.materialSource == JITSHADER)
|
||||
? createJitShaderProvider(engine, false /* optimize */,
|
||||
samples::getJitMaterialVariantFilter(app.config.backend))
|
||||
: createUbershaderProvider(engine, UBERARCHIVE_DEFAULT_DATA,
|
||||
UBERARCHIVE_DEFAULT_SIZE);
|
||||
|
||||
app.loader = AssetLoader::create({engine, app.materials, app.names });
|
||||
if (filename.isEmpty()) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "common/arguments.h"
|
||||
#include "common/configuration.h"
|
||||
|
||||
#include <filamentapp/Config.h>
|
||||
#include <filamentapp/FilamentApp.h>
|
||||
@@ -778,11 +779,13 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
app.materials = (app.materialSource == JITSHADER) ?
|
||||
createJitShaderProvider(engine, OPTIMIZE_MATERIALS) :
|
||||
createUbershaderProvider(engine, UBERARCHIVE_DEFAULT_DATA, UBERARCHIVE_DEFAULT_SIZE);
|
||||
app.materials = (app.materialSource == JITSHADER)
|
||||
? createJitShaderProvider(engine, OPTIMIZE_MATERIALS,
|
||||
samples::getJitMaterialVariantFilter(app.config.backend))
|
||||
: createUbershaderProvider(engine, UBERARCHIVE_DEFAULT_DATA,
|
||||
UBERARCHIVE_DEFAULT_SIZE);
|
||||
|
||||
app.assetLoader = AssetLoader::create({engine, app.materials, app.names });
|
||||
app.assetLoader = AssetLoader::create({ engine, app.materials, app.names });
|
||||
app.mainCamera = &view->getCamera();
|
||||
if (filename.isEmpty()) {
|
||||
app.asset = app.assetLoader->createAsset(
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "common/arguments.h"
|
||||
#include "common/configuration.h"
|
||||
|
||||
#include <filamentapp/Config.h>
|
||||
#include <filamentapp/FilamentApp.h>
|
||||
@@ -72,6 +74,7 @@ struct App {
|
||||
gltfio::TextureProvider* stbDecoder = nullptr;
|
||||
gltfio::TextureProvider* ktxDecoder = nullptr;
|
||||
FilamentInstance* instance;
|
||||
bool enableMSAA = false;
|
||||
};
|
||||
|
||||
static const char* DEFAULT_IBL = "assets/ibl/lightroom_14b";
|
||||
@@ -87,7 +90,9 @@ static void printUsage(char* name) {
|
||||
" Prints this message\n\n"
|
||||
"API_USAGE"
|
||||
" --ibl=<path to cmgen IBL>, -i <path>\n"
|
||||
" Override the built-in IBL\n\n");
|
||||
" Override the built-in IBL\n\n"
|
||||
" --msaa, -m\n"
|
||||
" setMultiSampleAntiAliasingOptions\n\n");
|
||||
const std::string from("SHOWCASE");
|
||||
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
|
||||
usage.replace(pos, from.length(), exec_name);
|
||||
@@ -101,9 +106,11 @@ static void printUsage(char* name) {
|
||||
}
|
||||
|
||||
static int handleCommandLineArguments(int argc, char* argv[], App* app) {
|
||||
static constexpr const char* OPTSTR = "ha:i:un:m:";
|
||||
static constexpr const char* OPTSTR = "hma:i:";
|
||||
static const struct option OPTIONS[] = { { "help", no_argument, nullptr, 'h' },
|
||||
{ "api", required_argument, nullptr, 'a' }, { "ibl", required_argument, nullptr, 'i' },
|
||||
{ "api", required_argument, nullptr, 'a' },
|
||||
{ "ibl", required_argument, nullptr, 'i' },
|
||||
{ "msaa", no_argument, nullptr, 'm' },
|
||||
{ nullptr, 0, nullptr, 0 } };
|
||||
int opt;
|
||||
int option_index = 0;
|
||||
@@ -122,6 +129,9 @@ static int handleCommandLineArguments(int argc, char* argv[], App* app) {
|
||||
case 'i':
|
||||
app->config.iblDirectory = arg;
|
||||
break;
|
||||
case 'm':
|
||||
app->enableMSAA = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return optind;
|
||||
@@ -205,13 +215,19 @@ int main(int argc, char** argv) {
|
||||
};
|
||||
|
||||
auto setup = [&](Engine* engine, View* view, Scene* scene) {
|
||||
view->setMultiSampleAntiAliasingOptions({ .enabled = true});
|
||||
if (app.enableMSAA) {
|
||||
// TODO Investigate why
|
||||
// Enabling MSAA fixes the "transmission" sample but breaks the others
|
||||
view->setMultiSampleAntiAliasingOptions({ .enabled = true});
|
||||
}
|
||||
app.engine = engine;
|
||||
app.names = new NameComponentManager(EntityManager::get());
|
||||
app.viewer = new ViewerGui(engine, scene, view);
|
||||
|
||||
|
||||
app.materials = (app.materialSource == JITSHADER)
|
||||
? createJitShaderProvider(engine)
|
||||
? createJitShaderProvider(engine, false /* optimize */,
|
||||
samples::getJitMaterialVariantFilter(app.config.backend))
|
||||
: createUbershaderProvider(engine, UBERARCHIVE_DEFAULT_DATA,
|
||||
UBERARCHIVE_DEFAULT_SIZE);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "filament",
|
||||
"version": "1.61.2",
|
||||
"version": "1.61.1",
|
||||
"description": "Real-time physically based rendering engine",
|
||||
"main": "filament.js",
|
||||
"module": "filament.js",
|
||||
|
||||
Reference in New Issue
Block a user