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
17 changed files with 324 additions and 152 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

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

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

@@ -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 {
@@ -99,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,
@@ -214,9 +218,6 @@ void WebGPUDriver::destroyStream(Handle<HwStream> sh) {
//TODO
}
void WebGPUDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
}
void WebGPUDriver::destroyDescriptorSetLayout(
Handle<HwDescriptorSetLayout> descriptorSetLayoutHandle) {
if (descriptorSetLayoutHandle) {
@@ -266,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 {
@@ -460,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) {
@@ -606,7 +629,7 @@ uint8_t WebGPUDriver::getMaxDrawBuffers() {
}
size_t WebGPUDriver::getMaxUniformBufferSize() {
return 16384u;
return mDeviceLimits.maxUniformBufferBindingSize;
}
size_t WebGPUDriver::getMaxTextureSize(const SamplerType target) {
@@ -725,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
}
@@ -922,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);
@@ -1133,12 +1179,6 @@ void WebGPUDriver::scissor(
//todo
}
void WebGPUDriver::beginTimerQuery(Handle<HwTimerQuery> tqh) {
}
void WebGPUDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
}
void WebGPUDriver::resetState(int) {
//todo
}

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

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

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