Compare commits

..

7 Commits

Author SHA1 Message Date
Juan Caldas
c7da1c0ad5 Update timer Query call back 2025-06-17 12:02:26 -04:00
Juan Caldas
04ff88fab2 Implement TimerQueries 2025-06-17 11:42:18 -04:00
Matthew Hoffman
7d242341f2 Let backend test binary run from anywhere. (#8864) 2025-06-16 20:49:05 +00:00
bridgewaterrobbie
e625c7024c webgpu: fix max uniform buffer size not being set properly. This resolves a validation error seen with TransmissionSuzanne.gltf 2025-06-16 16:15:06 -04:00
bridgewaterrobbie
192a61a06b Wait for work of first frame to be done before any presenting 2025-06-16 11:21:27 -04:00
Powei Feng
6a93e3a765 vk: clean-up ycbcr conversion enums (#8859)
Moved them out of DriverEnums because only the vk backend needs
them.
2025-06-16 05:58:48 +00:00
Powei Feng
e1fb1391f9 ds: emit undefined param warning once per descriptorset (#8862) 2025-06-16 05:42:45 +00:00
38 changed files with 380 additions and 275 deletions

View File

@@ -283,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

View File

@@ -474,12 +474,7 @@ constexpr std::string_view to_string(DescriptorType type) noexcept {
enum class DescriptorFlags : uint8_t {
NONE = 0x00,
// Indicate a UNIFORM_BUFFER will have dynamic offsets.
DYNAMIC_OFFSET = 0x01,
// To indicate a texture/sampler type should be unfiltered.
UNFILTERABLE = 0x02,
DYNAMIC_OFFSET = 0x01
};
using descriptor_set_t = uint8_t;
@@ -500,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;
}
};
@@ -1259,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)
@@ -1347,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

View File

@@ -29,6 +29,8 @@ using namespace bluevk;
namespace filament::backend {
using namespace fvkutils;
VulkanYcbcrConversionCache::VulkanYcbcrConversionCache(VkDevice device)
: mDevice(device) {}

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -15,7 +15,6 @@
*/
#include "WebGPUDescriptorSetLayout.h"
#include "WebGPUConstants.h"
#include <backend/DriverEnums.h>
@@ -118,10 +117,10 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
samplerEntryInfo.binding = samplerEntry.binding;
samplerEntry.visibility = wEntry.visibility;
wEntry.texture.multisampled = isMultiSampledTypeDescriptor(fEntry.type);
// TODO: Set once we have the filtering values
if (isDepthDescriptor(fEntry.type)) {
samplerEntry.sampler.type = wgpu::SamplerBindingType::Comparison;
} else if (isIntDescriptor(fEntry.type) ||
any(fEntry.flags & DescriptorFlags::UNFILTERABLE)) {
} else if (isIntDescriptor(fEntry.type)) {
samplerEntry.sampler.type = wgpu::SamplerBindingType::NonFiltering;
} else {
samplerEntry.sampler.type = wgpu::SamplerBindingType::Filtering;
@@ -152,11 +151,8 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
if (isDepthDescriptor(fEntry.type)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::Depth;
} else if (isFloatDescriptor(fEntry.type)) {
if (any(fEntry.flags & DescriptorFlags::UNFILTERABLE)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::UnfilterableFloat;
} else {
wEntry.texture.sampleType = wgpu::TextureSampleType::Float;
}
// TODO: Set once we have the filtering values
wEntry.texture.sampleType = wgpu::TextureSampleType::Float;
} else if (isIntDescriptor(fEntry.type)) {
wEntry.texture.sampleType = wgpu::TextureSampleType::Sint;
} else if (isUnsignedIntDescriptor(fEntry.type)) {

View File

@@ -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>
@@ -32,7 +33,6 @@
#include "CommandStreamDispatcher.h"
#include "DriverBase.h"
#include "private/backend/Dispatcher.h"
#include "webgpu/WebGPUConstants.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <backend/TargetBufferInfo.h>
@@ -54,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 {
@@ -100,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,
@@ -215,9 +218,6 @@ void WebGPUDriver::destroyStream(Handle<HwStream> sh) {
//TODO
}
void WebGPUDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
}
void WebGPUDriver::destroyDescriptorSetLayout(
Handle<HwDescriptorSetLayout> descriptorSetLayoutHandle) {
if (descriptorSetLayoutHandle) {
@@ -267,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 {
@@ -461,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) {
@@ -607,7 +629,7 @@ uint8_t WebGPUDriver::getMaxDrawBuffers() {
}
size_t WebGPUDriver::getMaxUniformBufferSize() {
return 16384u;
return mDeviceLimits.maxUniformBufferBindingSize;
}
size_t WebGPUDriver::getMaxTextureSize(const SamplerType target) {
@@ -726,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
}
@@ -923,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);
@@ -1134,12 +1179,6 @@ void WebGPUDriver::scissor(
//todo
}
void WebGPUDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
}
void WebGPUDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
}
void WebGPUDriver::resetState(int) {
//todo
}
@@ -1167,7 +1206,6 @@ void WebGPUDriver::updateDescriptorSetBuffer(Handle<HwDescriptorSet> descriptorS
void WebGPUDriver::updateDescriptorSetTexture(Handle<HwDescriptorSet> descriptorSetHandle,
const backend::descriptor_binding_t binding, Handle<HwTexture> textureHandle,
const SamplerParams params) {
auto bindGroup = handleCast<WebGPUDescriptorSet>(descriptorSetHandle);
auto texture = handleCast<WebGPUTexture>(textureHandle);

View File

@@ -43,6 +43,7 @@
namespace filament::backend {
class WebGPUSwapChain;
class WebGPUTimerQuery;
/**
* WebGPU backend (driver) implementation
@@ -78,6 +79,7 @@ private:
WebGPURenderTarget* mDefaultRenderTarget = nullptr;
WebGPURenderTarget* mCurrentRenderTarget = nullptr;
spd::MipmapGenerator mMipMapGenerator;
WebGPUTimerQuery* mTimerQuery = nullptr;
tsl::robin_map<uint32_t, wgpu::RenderPipeline> mPipelineMap;
@@ -116,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)...);

View File

@@ -91,20 +91,16 @@ void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor&
.clearValue = { params.clearColor.r, params.clearColor.g, params.clearColor.b,
params.clearColor.a } });
bool const depthReadonly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0;
if (defaultDepthStencilTextureView) {
mDepthStencilAttachmentDescriptor = {
.view = defaultDepthStencilTextureView,
.depthLoadOp = depthReadonly ? wgpu::LoadOp::Undefined
: WebGPURenderTarget::getLoadOperation(params,
TargetBufferFlags::DEPTH),
.depthStoreOp = depthReadonly ? wgpu::StoreOp::Undefined
: WebGPURenderTarget::getStoreOperation(params,
TargetBufferFlags::DEPTH),
.depthLoadOp =
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH),
.depthStoreOp =
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH),
.depthClearValue = static_cast<float>(params.clearDepth),
.depthReadOnly = depthReadonly,
.depthReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0,
.stencilLoadOp = wgpu::LoadOp::Undefined,
.stencilStoreOp = wgpu::StoreOp::Undefined,
.stencilClearValue = params.clearStencil,
@@ -160,19 +156,14 @@ void WebGPURenderTarget::setUpRenderPassAttachments(wgpu::RenderPassDescriptor&
customDepthTextureView ? customDepthTextureView : customStencilTextureView;
if (hasDepth) {
bool const depthReadonly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0;
mDepthStencilAttachmentDescriptor.depthLoadOp =
depthReadonly ? wgpu::LoadOp::Undefined :
WebGPURenderTarget::getLoadOperation(params, TargetBufferFlags::DEPTH);
mDepthStencilAttachmentDescriptor.depthStoreOp =
depthReadonly ? wgpu::StoreOp::Undefined :
WebGPURenderTarget::getStoreOperation(params, TargetBufferFlags::DEPTH);
mDepthStencilAttachmentDescriptor.depthClearValue =
static_cast<float>(params.clearDepth);
mDepthStencilAttachmentDescriptor.depthReadOnly = depthReadonly;
mDepthStencilAttachmentDescriptor.depthReadOnly =
(params.readOnlyDepthStencil & RenderPassParams::READONLY_DEPTH) > 0;
} else {
mDepthStencilAttachmentDescriptor.depthLoadOp = wgpu::LoadOp::Undefined;
mDepthStencilAttachmentDescriptor.depthStoreOp = wgpu::StoreOp::Undefined;

View 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

View 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

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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:

View File

@@ -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);

View File

@@ -481,7 +481,6 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
uint8_t fieldType = 0;
uint8_t fieldFormat = 0;
uint8_t fieldPrecision = 0;
bool fieldUnfilterable = false;
bool fieldMultisample = false;
if (!unflattener.read(&fieldName)) {
@@ -504,10 +503,6 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
return false;
}
if (!unflattener.read(&fieldUnfilterable)) {
return false;
}
if (!unflattener.read(&fieldMultisample)) {
return false;
}
@@ -517,7 +512,6 @@ bool ChunkSamplerInterfaceBlock::unflatten(Unflattener& unflattener,
SamplerInterfaceBlock::Type(fieldType),
SamplerInterfaceBlock::Format(fieldFormat),
SamplerInterfaceBlock::Precision(fieldPrecision),
fieldUnfilterable,
fieldMultisample);
}

View File

@@ -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;
}
}

View File

@@ -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
};

View File

@@ -8,8 +8,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : float,

View File

@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: medium,
unfilterable: true
precision: medium
},
{
type : float4,

View File

@@ -9,8 +9,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : float4,
@@ -78,3 +77,4 @@ fragment {
postProcess.color = color;
}
}

View File

@@ -9,8 +9,7 @@ material {
{
type : sampler2d,
name : depth,
precision: medium,
unfilterable: true
precision: medium
},
{
type : float2,

View File

@@ -9,8 +9,7 @@ material {
{
type : sampler2d,
name : depth,
precision: medium,
unfilterable: true
precision: medium
},
{
type : float2,

View File

@@ -5,8 +5,7 @@ material {
type : sampler2d,
name : depth,
precision: high,
multisample : true,
unfilterable: true
multisample : true
}
],
outputs : [

View File

@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : mat4,

View File

@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : mat4,

View File

@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
}
],
variables : [

View File

@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : mat4,

View File

@@ -4,8 +4,7 @@ material {
{
type : sampler2d,
name : depth,
precision: high,
unfilterable: true
precision: high
},
{
type : mat4,

View File

@@ -61,7 +61,6 @@ public:
Type type; // type of this sampler
Format format; // format of this sampler
Precision precision; // precision of this sampler
bool unfilterable; // whether the sampling should be unfiltered.
bool multisample; // multisample capable
ShaderStageFlags stages; // stages the sampler can be accessed from
};
@@ -84,7 +83,6 @@ public:
Type type; // type of this sampler
Format format; // format of this sampler
Precision precision; // precision of this sampler
bool unfilterable = false; // whether the sampling should be unfiltered.
bool multisample = false; // multisample capable
ShaderStageFlags stages =
ShaderStageFlags::ALL_SHADER_STAGE_FLAGS; // shader stages using this sampler
@@ -97,8 +95,7 @@ public:
// Add a sampler
Builder& add(std::string_view samplerName, Binding binding, Type type, Format format,
Precision precision = Precision::MEDIUM, bool unfilterable = false,
bool multisample = false,
Precision precision = Precision::MEDIUM, bool multisample = false,
ShaderStageFlags stages = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS) noexcept;
// Add multiple samplers

View File

@@ -50,12 +50,12 @@ SamplerInterfaceBlock::Builder::stageFlags(backend::ShaderStageFlags stageFlags)
}
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(std::string_view samplerName,
Binding binding, Type type, Format format, Precision precision, bool unfilterable,
bool multisample, ShaderStageFlags stages) noexcept {
Binding binding, Type type, Format format, Precision precision, bool multisample,
ShaderStageFlags stages) noexcept {
mEntries.push_back({
{ samplerName.data(), samplerName.size() }, // name
{}, // uniform name
binding, type, format, precision, unfilterable, multisample, stages });
{ samplerName.data(), samplerName.size() }, // name
{ }, // uniform name
binding, type, format, precision, multisample, stages });
return *this;
}
@@ -65,9 +65,8 @@ SamplerInterfaceBlock SamplerInterfaceBlock::Builder::build() {
SamplerInterfaceBlock::Builder& SamplerInterfaceBlock::Builder::add(
std::initializer_list<ListEntry> list) noexcept {
for (auto& e: list) {
add(e.name, e.binding, e.type, e.format, e.precision, e.unfilterable, e.multisample,
e.stages);
for (auto& e : list) {
add(e.name, e.binding, e.type, e.format, e.precision, e.multisample, e.stages);
}
return *this;
}

View File

@@ -323,8 +323,8 @@ public:
*/
MaterialBuilder& parameter(const char* name, SamplerType samplerType,
SamplerFormat format = SamplerFormat::FLOAT,
ParameterPrecision precision = ParameterPrecision::DEFAULT, bool unfilterable = false,
bool multisample = false, const char* transformName = "",
ParameterPrecision precision = ParameterPrecision::DEFAULT, bool multisample = false,
const char* transformName = "",
ShaderStageFlags stages = ShaderStageFlags::ALL_SHADER_STAGE_FLAGS);
MaterialBuilder& buffer(filament::BufferInterfaceBlock bib);
@@ -658,17 +658,9 @@ public:
// Sampler
Parameter(const char* paramName, SamplerType t, SamplerFormat f, ParameterPrecision p,
bool unfilterable, bool ms, const char* tn, ShaderStageFlags s)
: name(paramName),
size(1),
precision(p),
samplerType(t),
format(f),
parameterType(SAMPLER),
unfilterable(unfilterable),
multisample(ms),
transformName(tn),
stages(s) {}
bool ms, const char* tn, ShaderStageFlags s)
: name(paramName), size(1), precision(p), samplerType(t), format(f),
parameterType(SAMPLER), multisample(ms), transformName(tn), stages(s) { }
// Uniform
Parameter(const char* paramName, UniformType t, size_t typeSize, ParameterPrecision p)
@@ -684,9 +676,8 @@ public:
ParameterPrecision precision;
SamplerType samplerType;
SubpassType subpassType;
SamplerFormat format = SamplerFormat::INT; // 0 of the enum
bool unfilterable = false;
bool multisample = false;
SamplerFormat format;
bool multisample;
utils::CString transformName;
ShaderStageFlags stages;
enum {

View File

@@ -300,19 +300,19 @@ MaterialBuilder& MaterialBuilder::parameter(const char* name, UniformType const
return parameter(name, 1, type, precision);
}
MaterialBuilder& MaterialBuilder::parameter(const char* name, SamplerType samplerType,
SamplerFormat format, ParameterPrecision precision, bool unfilterable, bool multisample,
SamplerFormat format, ParameterPrecision precision, bool multisample,
const char* transformName, ShaderStageFlags stages) {
FILAMENT_CHECK_PRECONDITION(
!multisample || (format != SamplerFormat::SHADOW &&
(samplerType == SamplerType::SAMPLER_2D ||
samplerType == SamplerType::SAMPLER_2D_ARRAY)))
FILAMENT_CHECK_PRECONDITION(!multisample ||
(format != SamplerFormat::SHADOW &&
(samplerType == SamplerType::SAMPLER_2D ||
samplerType == SamplerType::SAMPLER_2D_ARRAY)))
<< "multisample samplers only possible with SAMPLER_2D or SAMPLER_2D_ARRAY,"
" as long as type is not SHADOW";
FILAMENT_CHECK_POSTCONDITION(mParameterCount < MAX_PARAMETERS_COUNT) << "Too many parameters";
mParameters[mParameterCount++] = { name, samplerType, format, precision, unfilterable,
multisample, transformName, stages };
mParameters[mParameterCount++] = { name, samplerType, format, precision, multisample, transformName, stages };
return *this;
}
@@ -639,12 +639,10 @@ void MaterialBuilder::prepareToBuild(MaterialInfo& info) noexcept {
assert_invariant(!param.isSubpass());
if (param.isSampler()) {
sbb.add({ param.name.data(), param.name.size() }, binding, param.samplerType,
param.format, param.precision, param.unfilterable, param.multisample,
param.stages);
param.format, param.precision, param.multisample, param.stages);
if (!param.transformName.empty()) {
ibb.add({ { { param.transformName.data(), param.transformName.size() },
uint8_t(binding), 0, UniformType::MAT3, Precision::DEFAULT,
FeatureLevel::FEATURE_LEVEL_0 } });
ibb.add({{{ param.transformName.data(), param.transformName.size() }, uint8_t(binding),
0, UniformType::MAT3, Precision::DEFAULT, FeatureLevel::FEATURE_LEVEL_0 }});
}
binding++;
} else if (param.isUniform()) {

View File

@@ -77,7 +77,6 @@ void MaterialSamplerInterfaceBlockChunk::flatten(Flattener& f) {
f.writeUint8(static_cast<uint8_t>(sInfo.type));
f.writeUint8(static_cast<uint8_t>(sInfo.format));
f.writeUint8(static_cast<uint8_t>(sInfo.precision));
f.writeBool(sInfo.unfilterable);
f.writeBool(sInfo.multisample);
}
}
@@ -231,11 +230,7 @@ void MaterialDescriptorSetLayoutChunk::flatten(Flattener& f) {
f.writeUint8(uint8_t(descriptor_sets::getDescriptorType(entry.type, entry.format)));
f.writeUint8(uint8_t(entry.stages));
f.writeUint8(entry.binding);
if (entry.unfilterable) {
f.writeUint8(uint8_t(DescriptorFlags::UNFILTERABLE));
} else {
f.writeUint8(uint8_t(DescriptorFlags::NONE));
}
f.writeUint8(uint8_t(DescriptorFlags::NONE));
f.writeUint16(0);
}
}

View File

@@ -383,10 +383,8 @@ ViewerGui::ViewerGui(filament::Engine* engine, filament::Scene* scene, filament:
mSettings.view.vsmShadowOptions.anisotropy = 0;
mSettings.view.dithering = Dithering::TEMPORAL;
mSettings.view.antiAliasing = AntiAliasing::FXAA;
// mSettings.view.msaa = { .enabled = true, .sampleCount = 4 };
mSettings.view.msaa = { .enabled = false, .sampleCount = 4 };
// mSettings.view.ssao.enabled = true;
mSettings.view.ssao.enabled = false;
mSettings.view.msaa = { .enabled = true, .sampleCount = 4 };
mSettings.view.ssao.enabled = true;
mSettings.view.bloom.enabled = true;
DebugRegistry& debug = mEngine->getDebugRegistry();

View File

@@ -185,13 +185,6 @@ static bool processParameter(MaterialBuilder& builder, const JsonishObject& json
}
}
const JsonishValue* unfilterableValue = jsonObject.getValue("unfilterable");
if (unfilterableValue) {
if (unfilterableValue->getType() != JsonishValue::BOOL) {
std::cerr << "parameters: unfilterable must be a BOOL." << std::endl;
return false;
}
}
const JsonishValue* multiSampleValue = jsonObject.getValue("multisample");
if (multiSampleValue) {
if (multiSampleValue->getType() != JsonishValue::BOOL) {
@@ -267,7 +260,6 @@ static bool processParameter(MaterialBuilder& builder, const JsonishObject& json
auto precision = precisionValue ? Enums::toEnum<ParameterPrecision>(
precisionValue->toJsonString()->getString()) : ParameterPrecision::DEFAULT;
auto unfilterable = unfilterableValue ? unfilterableValue->toJsonBool()->getBool() : false;
auto multisample = multiSampleValue ? multiSampleValue->toJsonBool()->getBool() : false;
if (stages == ShaderStageFlags::NONE) {
@@ -284,11 +276,10 @@ static bool processParameter(MaterialBuilder& builder, const JsonishObject& json
return false;
}
auto transformName = transformNameValue->toJsonString()->getString();
builder.parameter(nameString.c_str(), type, format, precision, unfilterable,
multisample, transformName.c_str(), stages);
builder.parameter(nameString.c_str(), type, format, precision, multisample,
transformName.c_str(), stages);
} else {
builder.parameter(nameString.c_str(), type, format, precision, unfilterable,
multisample, "", stages);
builder.parameter(nameString.c_str(), type, format, precision, multisample, "", stages);
}
} else {