Compare commits

..
Author SHA1 Message Date
bridgewaterrobbie 3b6496d352 Implement readpixels initial 2025-07-23 11:34:29 -04:00
bridgewaterrobbie 06ffa35540 f1 2025-07-22 14:00:06 -04:00
bridgewaterrobbie b6ed1313c6 tweak1 2025-07-22 13:56:45 -04:00
67 changed files with 990 additions and 2055 deletions
+10 -23
View File
@@ -130,8 +130,7 @@ jobs:
run: |
bash build/common/get-mesa.sh
pip install tifffile numpy
- name: Render and compare
id: render_compare
- name: Render
run: |
TEST_DIR=test/renderdiff
source ${TEST_DIR}/src/preamble.sh
@@ -141,32 +140,20 @@ jobs:
python3 ${TEST_DIR}/src/golden_manager.py \
--branch=${GOLDEN_BRANCH} \
--output=${GOLDEN_OUTPUT_DIR}
# Note that we need to upload the output even if comparison fails, so we undo `set -ex`
end_
python3 ${TEST_DIR}/src/compare.py \
--src=${GOLDEN_OUTPUT_DIR} \
--dest=${RENDER_OUTPUT_DIR} \
--out=${DIFF_OUTPUT_DIR} 2>&1 | tee compare_output.txt
if grep "Failed" compare_output.txt > /dev/null; then
DELIMITER="EOF_FILE_CONTENT_$(date +%s)" # Using timestamp to make it more unique
echo "err<<$DELIMITER" >> "$GITHUB_OUTPUT"
cat compare_output.txt >> "$GITHUB_OUTPUT"
echo "$DELIMITER" >> "$GITHUB_OUTPUT"
fi
# Note that we need to upload the output even if comparison fails
- uses: actions/upload-artifact@v4
with:
name: presubmit-renderdiff-result
path: ./out/renderdiff
- name: Compare result
- name: Compare output
run: |
ERROR_STR="${{ steps.render_compare.outputs.err }}"
if [ -n "${ERROR_STR}" ]; then
echo "${ERROR_STR}"
exit 1
fi
TEST_DIR=test/renderdiff
source ${TEST_DIR}/src/preamble.sh
python3 ${TEST_DIR}/src/compare.py \
--src=${GOLDEN_OUTPUT_DIR} \
--dest=${RENDER_OUTPUT_DIR} \
--out=${DIFF_OUTPUT_DIR}
end_
validate-wgsl-webgpu:
name: validate-wgsl-webgpu
-2
View File
@@ -134,14 +134,12 @@ set(SRCS
src/details/Texture.cpp
src/details/VertexBuffer.cpp
src/details/View.cpp
src/ds/PerViewDescriptorSetUtils.cpp
src/ds/ColorPassDescriptorSet.cpp
src/ds/DescriptorSet.cpp
src/ds/DescriptorSetLayout.cpp
src/ds/PostProcessDescriptorSet.cpp
src/ds/ShadowMapDescriptorSet.cpp
src/ds/SsrPassDescriptorSet.cpp
src/ds/StructureDescriptorSet.cpp
src/fg/Blackboard.cpp
src/fg/DependencyGraph.cpp
src/fg/FrameGraph.cpp
+1 -3
View File
@@ -290,9 +290,7 @@ if (FILAMENT_SUPPORTS_WEBGPU)
src/webgpu/WebGPUSwapChain.cpp
src/webgpu/WebGPUSwapChain.h
src/webgpu/WebGPUTexture.cpp
src/webgpu/WebGPUTextureHelpers.h
src/webgpu/WebGPUTextureSwizzler.cpp
src/webgpu/WebGPUTextureSwizzler.h
src/webgpu/WebGPUTexture.h
src/webgpu/WebGPUVertexBuffer.cpp
src/webgpu/WebGPUVertexBuffer.h
src/webgpu/WebGPUVertexBufferInfo.cpp
@@ -193,13 +193,6 @@ public:
* - PlatformEGL
*/
GpuContextPriority gpuContextPriority = GpuContextPriority::DEFAULT;
/**
* Bypass the staging buffer because the device is of Unified Memory Architecture.
* This is only supported for:
* - VulkanPlatform
*/
bool vulkanEnableStagingBufferBypass = false;
};
Platform() noexcept;
+2 -4
View File
@@ -88,8 +88,7 @@ public:
// sets the material name and variant for diagnostic purposes only
Program& diagnostics(utils::CString const& name,
utils::Invocable<utils::io::ostream&(utils::CString const& name,
utils::io::ostream& out)>&& logger);
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)>&& logger);
// Sets one of the program's shader (e.g. vertex, fragment)
// string-based shaders are null terminated, consequently the size parameter must include the
@@ -174,8 +173,7 @@ private:
utils::CString mName;
uint64_t mCacheId{};
CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH;
utils::Invocable<utils::io::ostream&(utils::CString const& name, utils::io::ostream& out)>
mLogger;
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)> mLogger;
SpecializationConstantsInfo mSpecializationConstants;
std::array<utils::FixedCapacityVector<PushConstant>, SHADER_TYPE_COUNT> mPushConstants;
DescriptorSetInfo mDescriptorBindings;
+2 -2
View File
@@ -45,7 +45,7 @@ Program& Program::priorityQueue(CompilerPriorityQueue priorityQueue) noexcept {
}
Program& Program::diagnostics(CString const& name,
Invocable<io::ostream&(utils::CString const& name, io::ostream&)>&& logger) {
Invocable<io::ostream&(io::ostream&)>&& logger) {
mName = name;
mLogger = std::move(logger);
return *this;
@@ -103,7 +103,7 @@ Program& Program::multiview(bool multiview) noexcept {
io::ostream& operator<<(io::ostream& out, const Program& builder) {
out << "Program{";
builder.mLogger(builder.mName, out);
builder.mLogger(out);
out << "}";
return out;
}
@@ -113,7 +113,13 @@ bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
case ShaderLanguage::MSL: {
// By default, Metal uses the most recent language version.
MTLCompileOptions* options = [MTLCompileOptions new];
options.fastMathEnabled = YES;
// Disable Fast Math optimizations.
// This ensures that operations adhere to IEEE standards for floating-point
// arithmetic, which is crucial for half precision floats in scenarios where fast
// math optimizations lead to inaccuracies, such as in handling special values like
// NaN or Infinity.
options.fastMathEnabled = NO;
assert_invariant(source[source.size() - 1] == '\0');
// the shader string is null terminated and the length includes the null character
@@ -20,30 +20,33 @@
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanMemory.h"
#include "VulkanHandles.h"
using namespace bluevk;
namespace filament::backend {
VulkanBufferProxy::VulkanBufferProxy(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, VulkanBufferUsage usage,
uint32_t numBytes)
: mStagingBufferBypassEnabled(context.stagingBufferBypassEnabled()),
mAllocator(allocator),
VulkanBufferProxy::VulkanBufferProxy(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, VulkanBufferUsage usage, uint32_t numBytes)
: mAllocator(allocator),
mStagePool(stagePool),
mBufferCache(bufferCache),
mBuffer(mBufferCache.acquire(usage, numBytes)),
mLastReadAge(0) {}
mUpdatedOffset(0),
mUpdatedBytes(0) {}
void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData,
uint32_t byteOffset, uint32_t numBytes) {
// The VulkanBuffer is available if the only object reference is hold by the
// `VulkanBufferProxy`. This means that the buffer is not currently referenced by a
// `VulkanCommandBuffer`.
bool const isAvailable = mBuffer->getCount() == 1;
// This means that we're recording a write into a command buffer without a previous read, so it
// should be safe to
// 1) Do a direct memcpy in UMA mode
// 2) Skip adding a barrier (to protect the write from writing over a read).
bool const isAvailable = commands.age() != mLastReadAge;
if (isAvailable) {
// We are up to date with all the update operations, so no need to synchronize with previous
// updates.
mUpdatedBytes = 0;
mUpdatedOffset = 0;
}
// Keep track of the VulkanBuffer usage
commands.acquire(mBuffer);
@@ -54,7 +57,8 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
// buffer.
bool const isMemcopyable = mBuffer->getGpuBuffer()->allocationInfo.pMappedData != nullptr;
bool const isUniform = getUsage() == VulkanBufferUsage::UNIFORM;
bool const useMemcpy = isUniform && isMemcopyable && isAvailable && mStagingBufferBypassEnabled;
bool const useMemcpy =
isUniform && isMemcopyable && isAvailable && !FVK_FORCE_STAGING_FOR_BUFFER_UPDATES;
if (useMemcpy) {
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
byteOffset;
@@ -62,10 +66,6 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
vmaFlushAllocation(mAllocator, mBuffer->getGpuBuffer()->vmaAllocation, byteOffset,
numBytes);
return;
// TODO: to properly bypass staging buffer, we'd need to be able to swap out a VulkanBuffer,
// which represents a VkBuffer. This means that the corresponding descriptor sets also have
// to be updated.
}
// Note: this should be stored within the command buffer before going out of
@@ -76,9 +76,10 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
memcpy(stage->mapping(), cpuData, numBytes);
vmaFlushAllocation(mAllocator, stage->memory(), stage->offset(), numBytes);
// If there was a previous read, then we need to make sure the following write is properly
// If there was a previous update, then we need to make sure the following write is properly
// synced with the previous read.
if (!isAvailable) {
if (mUpdatedBytes > 0 &&
(byteOffset >= mUpdatedOffset && byteOffset <= (mUpdatedOffset + mUpdatedBytes))) {
VkAccessFlags srcAccess = 0;
VkPipelineStageFlags srcStage = 0;
if (getUsage() == VulkanBufferUsage::UNIFORM) {
@@ -113,6 +114,9 @@ void VulkanBufferProxy::loadFromCpu(VulkanCommandBuffer& commands, const void* c
};
vkCmdCopyBuffer(commands.buffer(), stage->buffer(), getVkBuffer(), 1, &region);
mUpdatedOffset = byteOffset;
mUpdatedBytes = numBytes;
// Firstly, ensure that the copy finishes before the next draw call.
// Secondly, in case the user decides to upload another chunk (without ever using the first one)
// we need to ensure that this upload completes first (hence
@@ -156,9 +160,4 @@ VulkanBufferUsage VulkanBufferProxy::getUsage() const noexcept {
return mBuffer->getGpuBuffer()->usage;
}
void VulkanBufferProxy::referencedBy(VulkanCommandBuffer& commands) {
commands.acquire(mBuffer);
mLastReadAge = commands.age();
}
} // namespace filament::backend
@@ -25,35 +25,28 @@
namespace filament::backend {
struct VulkanDescriptorSet;
struct VulkanCommandBuffer;
// This class acts as a dynamic wrapper for a `VulkanBuffer`. It allows you to modify the
// `VulkanBuffer` it references at runtime, wihtout affecting any external objects.
class VulkanBufferProxy {
public:
VulkanBufferProxy(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, VulkanBufferUsage usage,
uint32_t numBytes);
VulkanBufferProxy(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, VulkanBufferUsage usage, uint32_t numBytes);
void loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData, uint32_t byteOffset,
uint32_t numBytes);
VkBuffer getVkBuffer() const noexcept;
void referencedBy(VulkanCommandBuffer& commands);
private:
VulkanBufferUsage getUsage() const noexcept;
bool const mStagingBufferBypassEnabled;
private:
VmaAllocator mAllocator;
VulkanStagePool& mStagePool;
VulkanBufferCache& mBufferCache;
fvkmemory::resource_ptr<VulkanBuffer> mBuffer;
uint32_t mLastReadAge;
uint32_t mUpdatedOffset = 0;
uint32_t mUpdatedBytes = 0;
};
} // namespace filament::backend
@@ -86,8 +86,6 @@ bool VulkanGroupMarkers::empty() const noexcept {
}
#endif // FVK_DEBUG_GROUP_MARKERS
uint32_t VulkanCommandBuffer::sAgeCounter = 0;
VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext const& context, VkDevice device,
VkQueue queue, VkCommandPool pool, bool isProtected)
: mContext(context),
@@ -96,8 +94,7 @@ VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext const& context, VkDevice
mDevice(device),
mQueue(queue),
mBuffer(createCommandBuffer(device, pool)),
mFenceStatus(std::make_shared<VulkanCmdFence>(VK_INCOMPLETE)),
mAge(++sAgeCounter) {
mFenceStatus(std::make_shared<VulkanCmdFence>(VK_INCOMPLETE)) {
VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
vkCreateSemaphore(mDevice, &sci, VKALLOC, &mSubmission);
@@ -114,7 +111,6 @@ void VulkanCommandBuffer::reset() noexcept {
mMarkerCount = 0;
mResources.clear();
mWaitSemaphores.clear();
mAge = ++sAgeCounter;
// Internally we use the VK_INCOMPLETE status to mean "not yet submitted". When this fence
// gets, gets submitted, its status changes to VK_NOT_READY. Finally, when the GPU actually
@@ -108,13 +108,7 @@ struct VulkanCommandBuffer {
return mBuffer;
}
uint32_t age() const {
return mAge;
}
private:
static uint32_t sAgeCounter;
VulkanContext const& mContext;
uint8_t mMarkerCount;
bool const isProtected;
@@ -126,7 +120,6 @@ private:
VkFence mFence;
std::shared_ptr<VulkanCmdFence> mFenceStatus;
std::vector<fvkmemory::resource_ptr<Resource>> mResources;
uint32_t mAge;
};
struct CommandBufferPool {
@@ -214,4 +214,8 @@ constexpr static const int FVK_MAX_PIPELINE_AGE = FVK_MAX_COMMAND_BUFFERS;
// destroying any unused pipeline object.
static_assert(FVK_MAX_PIPELINE_AGE >= FVK_MAX_COMMAND_BUFFERS);
// Whether the buffer updates always use staging or not. Otherwise its allowed to also use memcpy
// for buffer updates. Default is false
constexpr static const bool FVK_FORCE_STAGING_FOR_BUFFER_UPDATES = false;
#endif
@@ -146,10 +146,6 @@ public:
return mIsUnifiedMemoryArchitecture;
}
inline bool stagingBufferBypassEnabled() const noexcept {
return mStagingBufferBypassEnabled;
}
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
@@ -173,7 +169,6 @@ private:
bool mLazilyAllocatedMemorySupported = false;
bool mProtectedMemorySupported = false;
bool mIsUnifiedMemoryArchitecture = false;
bool mStagingBufferBypassEnabled = false;
fvkutils::VkFormatList mDepthStencilFormats;
fvkutils::VkFormatList mBlittableDepthStencilFormats;
@@ -290,9 +290,7 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
if (mLastBoundInfo.pipelineLayout == pipelineLayout) {
auto& lastBoundSets = mLastBoundInfo.boundSets;
curMask.forEachSetBit([&](size_t index) {
auto& set = updateSets[index];
if (set == lastBoundSets[index] && !useExternalSamplers[index] &&
set->uniqueDynamicUboCount == 0) {
if (updateSets[index] == lastBoundSets[index] && !useExternalSamplers[index]) {
curMask.unset(index);
}
});
@@ -304,24 +302,25 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
VkCommandBuffer const cmdbuffer = commands->buffer();
VkDescriptorSet vkset = useExternalSamplers[index] ? set->getExternalSamplerVkSet() :
set->getVkSet();
commands->acquire(set);
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
1, &vkset, set->uniqueDynamicUboCount, set->getOffsets()->data());
set->referencedBy(*commands);
commands->acquire(set);
});
mStashedSets = {};
mLastBoundInfo = {
pipelineLayout,
setMask,
updateSets,
};
mStashedSets = {};
}
void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
uint8_t binding, fvkmemory::resource_ptr<VulkanBufferObject> bufferObject,
VkDeviceSize offset, VkDeviceSize size) noexcept {
VkDescriptorBufferInfo const info = {
.buffer = bufferObject->getVkBuffer(),
.buffer = bufferObject->buffer.getVkBuffer(),
.offset = offset,
.range = size,
};
+8 -8
View File
@@ -532,8 +532,8 @@ void VulkanDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType ele
uint32_t indexCount, BufferUsage usage) {
FVK_SYSTRACE_SCOPE();
auto elementSize = (uint8_t) getElementTypeSize(elementType);
auto ib = resource_ptr<VulkanIndexBuffer>::make(&mResourceManager, ibh, mContext, mAllocator,
mStagePool, mBufferCache, elementSize, indexCount);
auto ib = resource_ptr<VulkanIndexBuffer>::make(&mResourceManager, ibh, mAllocator, mStagePool,
mBufferCache, elementSize, indexCount);
ib.inc();
}
@@ -549,8 +549,8 @@ void VulkanDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
BufferObjectBinding bindingType, BufferUsage usage) {
FVK_SYSTRACE_SCOPE();
auto bo = resource_ptr<VulkanBufferObject>::make(&mResourceManager, boh, mContext, mAllocator,
mStagePool, mBufferCache, byteCount, bindingType);
auto bo = resource_ptr<VulkanBufferObject>::make(&mResourceManager, boh, mAllocator, mStagePool,
mBufferCache, byteCount, bindingType);
bo.inc();
}
@@ -1239,7 +1239,7 @@ void VulkanDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor
VulkanCommandBuffer& commands = mCommands.get();
auto ib = resource_ptr<VulkanIndexBuffer>::cast(&mResourceManager, ibh);
commands.acquire(ib);
ib->loadFromCpu(commands, p.buffer, byteOffset, p.size);
ib->buffer.loadFromCpu(commands, p.buffer, byteOffset, p.size);
scheduleDestroy(std::move(p));
}
@@ -1255,7 +1255,7 @@ void VulkanDriver::updateBufferObject(Handle<HwBufferObject> boh, BufferDescript
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
commands.acquire(bo);
bo->loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
bo->buffer.loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
scheduleDestroy(std::move(bd));
}
@@ -1266,7 +1266,7 @@ void VulkanDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> boh,
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
commands.acquire(bo);
// TODO: implement unsynchronized version
bo->loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
bo->buffer.loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
scheduleDestroy(std::move(bd));
}
@@ -1915,7 +1915,7 @@ void VulkanDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
// avoid rebinding these if they are already bound, but since we do not (yet) support subranges
// it would be rare for a client to make consecutive draw calls with the same render primitive.
vkCmdBindVertexBuffers(cmdbuffer, 0, bufferCount, buffers, offsets);
vkCmdBindIndexBuffer(cmdbuffer, prim->indexBuffer->getVkBuffer(), 0,
vkCmdBindIndexBuffer(cmdbuffer, prim->indexBuffer->buffer.getVkBuffer(), 0,
prim->indexBuffer->indexType);
}
+13 -31
View File
@@ -39,20 +39,6 @@ namespace filament::backend {
namespace {
inline VulkanBufferUsage getBufferObjectUsage(BufferObjectBinding bindingType) noexcept {
switch (bindingType) {
case BufferObjectBinding::VERTEX:
return VulkanBufferUsage::VERTEX;
case BufferObjectBinding::UNIFORM:
return VulkanBufferUsage::UNIFORM;
case BufferObjectBinding::SHADER_STORAGE:
return VulkanBufferUsage::SHADER_STORAGE;
// when adding more buffer-types here, make sure to update VulkanBuffer::loadFromCpu()
// if necessary.
}
return VulkanBufferUsage::UNKNOWN;
}
void flipVertically(VkViewport* rect, uint32_t framebufferHeight) {
rect->y = framebufferHeight - rect->y - rect->height;
}
@@ -180,6 +166,14 @@ VulkanAttachment createSwapchainAttachment(const fvkmemory::resource_ptr<VulkanT
} // anonymous namespace
void VulkanDescriptorSet::acquire(fvkmemory::resource_ptr<VulkanTexture> texture) {
mResources.push_back(texture);
}
void VulkanDescriptorSet::acquire(fvkmemory::resource_ptr<VulkanBufferObject> obj) {
mResources.push_back(obj);
}
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout&& layout,
VkDescriptorSetLayout vkLayout)
: bitmask(fromBackendLayout(layout)),
@@ -191,17 +185,6 @@ VulkanDescriptorSetLayout::Bitmask VulkanDescriptorSetLayout::Bitmask::fromLayou
return fromBackendLayout(layout);
}
// This method will store an age associated with this command buffer into the VulkanBuffer, which
// will allow us to determine whether a barrier is necessary or not.
void VulkanDescriptorSet::referencedBy(VulkanCommandBuffer& commands) {
mUboMask.forEachSetBit([this, &commands](size_t index) {
auto& res = mResources[index];
fvkmemory::resource_ptr<VulkanBufferObject> bo =
fvkmemory::resource_ptr<VulkanBufferObject>::cast((VulkanBufferObject*) res.get());
bo->referencedBy(commands);
});
}
PushConstantDescription::PushConstantDescription(backend::Program const& program) {
mRangeCount = 0;
for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) {
@@ -605,18 +588,17 @@ void VulkanVertexBuffer::setBuffer(fvkmemory::resource_ptr<VulkanBufferObject> b
int8_t const* const attribToBuffer = vbi->getAttributeToBuffer();
for (uint8_t attribIndex = 0; attribIndex < count; attribIndex++) {
if (attribToBuffer[attribIndex] == static_cast<int8_t>(index)) {
vkbuffers[attribIndex] = bufferObject->getVkBuffer();
vkbuffers[attribIndex] = bufferObject->buffer.getVkBuffer();
}
}
mResources.push_back(bufferObject);
}
VulkanBufferObject::VulkanBufferObject(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, uint32_t byteCount,
BufferObjectBinding bindingType)
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, uint32_t byteCount, BufferObjectBinding bindingType)
: HwBufferObject(byteCount),
bindingType(bindingType),
mBuffer(context, allocator, stagePool, bufferCache, getBufferObjectUsage(bindingType), byteCount) {}
buffer(allocator, stagePool, bufferCache, getBufferObjectUsage(bindingType), byteCount),
bindingType(bindingType) {}
VulkanRenderPrimitive::VulkanRenderPrimitive(PrimitiveType pt,
fvkmemory::resource_ptr<VulkanVertexBuffer> vb,
+28 -49
View File
@@ -26,7 +26,6 @@
#include "VulkanFboCache.h"
#include "VulkanSwapChain.h"
#include "VulkanTexture.h"
#include "vulkan/VulkanCommands.h"
#include "vulkan/memory/Resource.h"
#include "vulkan/utils/Definitions.h"
#include "vulkan/utils/StaticVector.h"
@@ -201,15 +200,8 @@ public:
return &mOffsets;
}
template<typename Resource>
void acquire(fvkmemory::resource_ptr<Resource> res) {
if (res->template isType<VulkanBufferObject>()) {
mUboMask.set(mResources.size());
}
mResources.push_back(res);
}
void referencedBy(VulkanCommandBuffer& commands);
void acquire(fvkmemory::resource_ptr<VulkanTexture> texture);
void acquire(fvkmemory::resource_ptr<VulkanBufferObject> buffer);
fvkutils::UniformBufferBitmask const dynamicUboMask;
uint8_t const uniqueDynamicUboCount;
@@ -222,7 +214,6 @@ private:
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
OnRecycle mOnRecycleFn;
OnRecycle mOnRecycleExternalSamplerFn;
fvkutils::UniformBufferBitmask mUboMask;
};
using PushConstantNameArray = utils::FixedCapacityVector<char const*>;
@@ -428,9 +419,6 @@ struct VulkanVertexBuffer : public HwVertexBuffer, fvkmemory::Resource {
fvkmemory::resource_ptr<VulkanVertexBufferInfo> vbi);
void setBuffer(fvkmemory::resource_ptr<VulkanBufferObject> bufferObject, uint32_t index);
// TODO: because VulkanBufferObject is backed by VulkanBufferProxy, which could switch out the
// backing VkBuffer, we cannot store the VkBuffers for optimization here. We could store a dirty
// bit to indicate if a VkBuffer has changed maybe.
inline VkBuffer const* getVkBuffers() const { return mBuffers.data(); }
inline VkBuffer* getVkBuffers() { return mBuffers.data(); }
fvkmemory::resource_ptr<VulkanVertexBufferInfo> vbi;
@@ -441,47 +429,23 @@ private:
};
struct VulkanIndexBuffer : public HwIndexBuffer, fvkmemory::Resource {
VulkanIndexBuffer(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, uint8_t elementSize,
uint32_t indexCount)
VulkanIndexBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, uint8_t elementSize, uint32_t indexCount)
: HwIndexBuffer(elementSize, indexCount),
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32),
mBuffer(context, allocator, stagePool, bufferCache, VulkanBufferUsage::INDEX,
elementSize * indexCount) {}
buffer(allocator, stagePool, bufferCache, VulkanBufferUsage::INDEX,
elementSize * indexCount),
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32) {}
inline void loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData, uint32_t byteOffset,
uint32_t numBytes) {
mBuffer.loadFromCpu(commands, cpuData, byteOffset, numBytes);
}
inline VkBuffer getVkBuffer() const noexcept { return mBuffer.getVkBuffer(); }
VkIndexType const indexType;
private:
VulkanBufferProxy mBuffer;
VulkanBufferProxy buffer;
const VkIndexType indexType;
};
struct VulkanBufferObject : public HwBufferObject, fvkmemory::Resource {
VulkanBufferObject(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, uint32_t byteCount,
BufferObjectBinding bindingType);
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, uint32_t byteCount, BufferObjectBinding bindingType);
inline void loadFromCpu(VulkanCommandBuffer& commands, const void* cpuData, uint32_t byteOffset,
uint32_t numBytes) {
mBuffer.loadFromCpu(commands, cpuData, byteOffset, numBytes);
}
inline VkBuffer getVkBuffer() const noexcept { return mBuffer.getVkBuffer(); }
inline void referencedBy(VulkanCommandBuffer& commands) {
mBuffer.referencedBy(commands);
}
BufferObjectBinding const bindingType;
private:
VulkanBufferProxy mBuffer;
VulkanBufferProxy buffer;
const BufferObjectBinding bindingType;
};
struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource {
@@ -493,6 +457,21 @@ struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource {
fvkmemory::resource_ptr<VulkanIndexBuffer> indexBuffer;
};
inline constexpr VulkanBufferUsage getBufferObjectUsage(BufferObjectBinding bindingType) noexcept {
switch (bindingType) {
case BufferObjectBinding::VERTEX:
return VulkanBufferUsage::VERTEX;
case BufferObjectBinding::UNIFORM:
return VulkanBufferUsage::UNIFORM;
case BufferObjectBinding::SHADER_STORAGE:
return VulkanBufferUsage::SHADER_STORAGE;
// when adding more buffer-types here, make sure to update VulkanBuffer::loadFromCpu()
// if necessary.
}
return VulkanBufferUsage::UNKNOWN;
}
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_VULKANHANDLES_H
@@ -17,7 +17,7 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
#define TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
#include "vulkan/utils/Definitions.h"
#include "utils/Definitions.h"
#include <backend/DriverEnums.h>
@@ -72,10 +72,7 @@ struct Resource {
restype(ResourceType::UNDEFINED_TYPE),
mHandleConsideredDestroyed(false) {}
template<typename D>
bool isType() const {
return getTypeEnum<D>() == restype;
}
uint32_t getCount() const { return mCount; }
private:
inline void inc() noexcept {
@@ -741,10 +741,6 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
}
VulkanContext& context = mImpl->mContext;
// Pass along relevant driver config (feature flags)
context.mStagingBufferBypassEnabled = driverConfig.vulkanEnableStagingBufferBypass;
ExtensionSet instExts;
// If using a shared context, we do not assume any extensions.
if (!mImpl->mSharedContext) {
+84 -24
View File
@@ -28,7 +28,6 @@
#include "WebGPUStrings.h"
#include "WebGPUSwapChain.h"
#include "WebGPUTexture.h"
#include "WebGPUTextureSwizzler.h"
#include "WebGPUVertexBuffer.h"
#include "WebGPUVertexBufferInfo.h"
#include <backend/platforms/WebGPUPlatform.h>
@@ -78,7 +77,6 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
mPipelineCache{ mDevice },
mRenderPassMipmapGenerator{ mDevice },
mSpdComputePassMipmapGenerator{ mDevice },
mTextureSwizzler{ mDevice },
mHandleAllocator{ "Handles", driverConfig.handleArenaSize,
driverConfig.disableHandleUseAfterFreeCheck, driverConfig.disableHeapHandleTags } {
mDevice.GetLimits(&mDeviceLimits);
@@ -409,24 +407,10 @@ void WebGPUDriver::createTextureViewR(Handle<HwTexture> textureHandle,
}
void WebGPUDriver::createTextureViewSwizzleR(Handle<HwTexture> textureHandle,
Handle<HwTexture> sourceTextureHandle, const backend::TextureSwizzle red,
const backend::TextureSwizzle green, const backend::TextureSwizzle blue,
const backend::TextureSwizzle alpha) {
auto source{ handleCast<WebGPUTexture>(sourceTextureHandle) };
auto destination{ handleCast<WebGPUTexture>(textureHandle) };
// TODO fix this. This does not work. The "destination" texture at this point is "junk", meaning
// we need to create it here. Also, we should NOT do the swizzling at this point either,
// as we should track the swizzle parameters such that we can have swizzles of swizzles
// (they can be stacked), and we should only do the actual swizzle action below when it
// time to use the texture. As a result, we are abandoning this custom implementation
// in favor of using Dawn's texture sizzle view when we can (otherwise tell Filament above
// that we can't swizzle textures).
PANIC_POSTCONDITION("The texture swizzling implementation is not complete. We need to support "
"swizzle stacking and lazy swizzle computation at the appropriate places "
"fot this implementation to be viable. Most likely, we should use Dawn's "
"texture swizzle view or nothing.");
mTextureSwizzler.swizzle(mQueue, source->getTexture(), destination->getTexture(), red, green,
blue, alpha);
Handle<HwTexture> sourceTextureHandle, const backend::TextureSwizzle r,
const backend::TextureSwizzle g, const backend::TextureSwizzle b,
const backend::TextureSwizzle a) {
PANIC_POSTCONDITION("Swizzle WebGPU Texture is not supported");
}
void WebGPUDriver::createTextureExternalImage2R(Handle<HwTexture> textureHandle,
@@ -570,7 +554,7 @@ bool WebGPUDriver::isTextureFormatSupported(const TextureFormat format) {
}
bool WebGPUDriver::isTextureSwizzleSupported() {
return true;
return false;
}
bool WebGPUDriver::isTextureFormatMipmappable(const TextureFormat format) {
@@ -1095,7 +1079,7 @@ void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
mCommandBuffer = nullptr;
mTextureView = nullptr;
assert_invariant(mSwapChain);
mSwapChain->present();
mSwapChain->present(mQueue);
}
void WebGPUDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
@@ -1126,8 +1110,84 @@ void WebGPUDriver::stopCapture(int) {
void WebGPUDriver::readPixels(Handle<HwRenderTarget> sourceRenderTargetHandle, const uint32_t x,
const uint32_t y, const uint32_t width, const uint32_t height,
PixelBufferDescriptor&& pixelBufferDescriptor) {
// todo
scheduleDestroy(std::move(pixelBufferDescriptor));
auto srcTarget = handleCast<WebGPURenderTarget>(sourceRenderTargetHandle);
assert_invariant(srcTarget);
wgpu::Texture srcTexture = nullptr;
if (srcTarget->isDefaultRenderTarget()) {
assert_invariant(mSwapChain);
srcTexture = mSwapChain->getCurrentTexture(mPlatform.getSurfaceExtent(mNativeWindow));
} else {
// TODO: Handle custom render targets
scheduleDestroy(std::move(pixelBufferDescriptor));
return;
}
assert_invariant(srcTexture);
// Create a staging buffer to copy the texture to
const size_t bytesPerPixel = 4;
const size_t unpaddedBytesPerRow = width * bytesPerPixel;
const size_t alignment = 256;
const size_t paddedBytesPerRow = (unpaddedBytesPerRow + alignment - 1) & ~(alignment - 1);
size_t bufferSize = paddedBytesPerRow * height;
wgpu::BufferDescriptor bufferDesc;
bufferDesc.size = bufferSize;
bufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
wgpu::Buffer stagingBuffer = mDevice.CreateBuffer(&bufferDesc);
assert_invariant(stagingBuffer);
wgpu::CommandEncoder encoder = mDevice.CreateCommandEncoder();
wgpu::TexelCopyTextureInfo source {
.texture = srcTexture,
.mipLevel = 0,
.origin = { x, y, 0 }
};
wgpu::TexelCopyBufferInfo destination {
.layout = {
.offset = 0,
.bytesPerRow = static_cast<uint32_t>(paddedBytesPerRow),
.rowsPerImage = height,
},
.buffer = stagingBuffer
};
wgpu::Extent3D copySize {
.width = width,
.height = height,
.depthOrArrayLayers = 1
};
encoder.CopyTextureToBuffer(&source, &destination, &copySize);
wgpu::CommandBuffer commandBuffer = encoder.Finish();
mQueue.Submit(1, &commandBuffer);
// Map the buffer to read the data
struct UserData {
PixelBufferDescriptor pbd;
wgpu::Buffer buffer;
size_t unpaddedBytesPerRow;
size_t paddedBytesPerRow;
uint32_t height;
};
auto userData = std::make_unique<UserData>();
userData->pbd = std::move(pixelBufferDescriptor);
userData->buffer = stagingBuffer;
userData->unpaddedBytesPerRow = unpaddedBytesPerRow;
userData->paddedBytesPerRow = paddedBytesPerRow;
userData->height = height;
stagingBuffer.MapAsync(wgpu::MapMode::Read, 0, bufferSize, wgpu::CallbackMode::AllowSpontaneous, [](wgpu::MapAsyncStatus status, const char* message, UserData* userdata) {
std::unique_ptr<UserData> data(static_cast<UserData*>(userdata));
if (status == wgpu::MapAsyncStatus::Success) {
const char* src = static_cast<const char*>(data->buffer.GetConstMappedRange(0, data->buffer.GetSize()));
char* dst = static_cast<char*>(data->pbd.buffer);
for (uint32_t i = 0; i < data->height; ++i) {
memcpy(dst + i * data->unpaddedBytesPerRow, src + i * data->paddedBytesPerRow, data->unpaddedBytesPerRow);
}
data->buffer.Unmap();
}
// scheduleDestroy(std::move(data->pbd)); // This line is problematic, need to pass scheduleDestroy func
}, userData.release());
}
void WebGPUDriver::readBufferSubData(Handle<HwBufferObject> bufferObjectHandle,
@@ -23,7 +23,6 @@
#include "webgpu/WebGPUPipelineCache.h"
#include "webgpu/WebGPUPipelineLayoutCache.h"
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
#include "webgpu/WebGPUTextureSwizzler.h"
#include <backend/platforms/WebGPUPlatform.h>
#include "DriverBase.h"
@@ -86,7 +85,6 @@ private:
WebGPURenderPassMipmapGenerator mRenderPassMipmapGenerator;
spd::MipmapGenerator mSpdComputePassMipmapGenerator;
WebGPUMsaaTextureResolver mMsaaTextureResolver{};
WebGPUTextureSwizzler mTextureSwizzler;
struct DescriptorSetBindingInfo{
wgpu::BindGroup bindGroup;
@@ -16,7 +16,7 @@
#include "WebGPUMsaaTextureResolver.h"
#include "WebGPUTextureHelpers.h"
#include "WebGPUTexture.h"
#include <utils/Panic.h>
@@ -17,7 +17,7 @@
#include "WebGPUPipelineCache.h"
#include "WebGPUConstants.h"
#include "WebGPUTextureHelpers.h"
#include "WebGPUTexture.h"
#include "WebGPUVertexBufferInfo.h"
#include <backend/DriverEnums.h>
@@ -16,8 +16,6 @@
#include "WebGPURenderPassMipmapGenerator.h"
#include "WebGPUTextureHelpers.h"
#include <utils/Panic.h>
#include <webgpu/webgpu_cpp.h>
@@ -299,6 +297,132 @@ WebGPURenderPassMipmapGenerator::getCompatibilityFor(const wgpu::TextureFormat f
return { .compatible = true, .reason = "" };
}
constexpr WebGPURenderPassMipmapGenerator::ScalarSampleType
WebGPURenderPassMipmapGenerator::getScalarSampleTypeFrom(const wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::R8Uint:
case wgpu::TextureFormat::R16Uint:
case wgpu::TextureFormat::R32Uint:
case wgpu::TextureFormat::Stencil8:
case wgpu::TextureFormat::RG8Uint:
case wgpu::TextureFormat::RG16Uint:
case wgpu::TextureFormat::RGBA8Uint:
case wgpu::TextureFormat::RGB10A2Uint:
case wgpu::TextureFormat::RG32Uint:
case wgpu::TextureFormat::RGBA16Uint:
case wgpu::TextureFormat::RGBA32Uint:
return ScalarSampleType::U32;
case wgpu::TextureFormat::R8Sint:
case wgpu::TextureFormat::R16Sint:
case wgpu::TextureFormat::R32Sint:
case wgpu::TextureFormat::RG8Sint:
case wgpu::TextureFormat::RG16Sint:
case wgpu::TextureFormat::RGBA8Sint:
case wgpu::TextureFormat::RG32Sint:
case wgpu::TextureFormat::RGBA16Sint:
case wgpu::TextureFormat::RGBA32Sint:
return ScalarSampleType::I32;
case wgpu::TextureFormat::R8Snorm:
case wgpu::TextureFormat::R8Unorm:
case wgpu::TextureFormat::R16Float:
case wgpu::TextureFormat::RG16Float:
case wgpu::TextureFormat::RG8Unorm:
case wgpu::TextureFormat::RG8Snorm:
case wgpu::TextureFormat::R32Float:
case wgpu::TextureFormat::RGBA8Unorm:
case wgpu::TextureFormat::RGBA8UnormSrgb:
case wgpu::TextureFormat::RGBA8Snorm:
case wgpu::TextureFormat::BGRA8Unorm:
case wgpu::TextureFormat::BGRA8UnormSrgb:
case wgpu::TextureFormat::RGB10A2Unorm:
case wgpu::TextureFormat::RG11B10Ufloat:
case wgpu::TextureFormat::RGB9E5Ufloat:
case wgpu::TextureFormat::RG32Float:
case wgpu::TextureFormat::RGBA16Float:
case wgpu::TextureFormat::RGBA32Float:
case wgpu::TextureFormat::BC1RGBAUnorm:
case wgpu::TextureFormat::BC1RGBAUnormSrgb:
case wgpu::TextureFormat::BC2RGBAUnorm:
case wgpu::TextureFormat::BC2RGBAUnormSrgb:
case wgpu::TextureFormat::BC3RGBAUnorm:
case wgpu::TextureFormat::BC3RGBAUnormSrgb:
case wgpu::TextureFormat::BC4RUnorm:
case wgpu::TextureFormat::BC4RSnorm:
case wgpu::TextureFormat::BC5RGUnorm:
case wgpu::TextureFormat::BC5RGSnorm:
case wgpu::TextureFormat::BC6HRGBUfloat:
case wgpu::TextureFormat::BC6HRGBFloat:
case wgpu::TextureFormat::BC7RGBAUnorm:
case wgpu::TextureFormat::BC7RGBAUnormSrgb:
case wgpu::TextureFormat::ETC2RGB8Unorm:
case wgpu::TextureFormat::ETC2RGB8UnormSrgb:
case wgpu::TextureFormat::ETC2RGB8A1Unorm:
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb:
case wgpu::TextureFormat::ETC2RGBA8Unorm:
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb:
case wgpu::TextureFormat::EACR11Unorm:
case wgpu::TextureFormat::EACR11Snorm:
case wgpu::TextureFormat::EACRG11Unorm:
case wgpu::TextureFormat::EACRG11Snorm:
case wgpu::TextureFormat::ASTC4x4Unorm:
case wgpu::TextureFormat::ASTC4x4UnormSrgb:
case wgpu::TextureFormat::ASTC5x4Unorm:
case wgpu::TextureFormat::ASTC5x4UnormSrgb:
case wgpu::TextureFormat::ASTC5x5Unorm:
case wgpu::TextureFormat::ASTC5x5UnormSrgb:
case wgpu::TextureFormat::ASTC6x5Unorm:
case wgpu::TextureFormat::ASTC6x5UnormSrgb:
case wgpu::TextureFormat::ASTC6x6Unorm:
case wgpu::TextureFormat::ASTC6x6UnormSrgb:
case wgpu::TextureFormat::ASTC8x5Unorm:
case wgpu::TextureFormat::ASTC8x5UnormSrgb:
case wgpu::TextureFormat::ASTC8x6Unorm:
case wgpu::TextureFormat::ASTC8x6UnormSrgb:
case wgpu::TextureFormat::ASTC8x8Unorm:
case wgpu::TextureFormat::ASTC8x8UnormSrgb:
case wgpu::TextureFormat::ASTC10x5Unorm:
case wgpu::TextureFormat::ASTC10x5UnormSrgb:
case wgpu::TextureFormat::ASTC10x6Unorm:
case wgpu::TextureFormat::ASTC10x6UnormSrgb:
case wgpu::TextureFormat::ASTC10x8Unorm:
case wgpu::TextureFormat::ASTC10x8UnormSrgb:
case wgpu::TextureFormat::ASTC10x10Unorm:
case wgpu::TextureFormat::ASTC10x10UnormSrgb:
case wgpu::TextureFormat::ASTC12x10Unorm:
case wgpu::TextureFormat::ASTC12x10UnormSrgb:
case wgpu::TextureFormat::ASTC12x12Unorm:
case wgpu::TextureFormat::ASTC12x12UnormSrgb:
case wgpu::TextureFormat::R16Unorm:
case wgpu::TextureFormat::RG16Unorm:
case wgpu::TextureFormat::RGBA16Unorm:
case wgpu::TextureFormat::R16Snorm:
case wgpu::TextureFormat::RG16Snorm:
case wgpu::TextureFormat::RGBA16Snorm:
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm:
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
case wgpu::TextureFormat::R8BG8Biplanar422Unorm:
case wgpu::TextureFormat::R8BG8Biplanar444Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
return ScalarSampleType::F32;
case wgpu::TextureFormat::Depth16Unorm:
case wgpu::TextureFormat::Depth24Plus:
case wgpu::TextureFormat::Depth24PlusStencil8:
case wgpu::TextureFormat::Depth32Float:
case wgpu::TextureFormat::Depth32FloatStencil8:
PANIC_POSTCONDITION("A depth texture format requires special sampler treatment and "
"does not generally support linear filtering (needed for mipmap "
"generation). Thus no applicable scalar sample type for %d",
format);
break;
case wgpu::TextureFormat::Undefined:
case wgpu::TextureFormat::External:
PANIC_POSTCONDITION("No scalar sample type for texture format %d", format);
break;
}
}
void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
wgpu::Texture const& texture) {
const uint32_t mipLevelCount{ texture.GetMipLevelCount() };
@@ -312,7 +436,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
const wgpu::CommandEncoder commandEncoder{ mDevice.CreateCommandEncoder(
&commandEncoderDescriptor) };
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
<< "Failed to create command encoder for layer for render pass mipmap generation?";
<< "Failed to create command encoder for layer for render pass mipmap generation?";
const uint32_t layerCount{ texture.GetDepthOrArrayLayers() };
for (uint32_t layer = 0; layer < layerCount; layer++) {
for (uint32_t mipLevel = 1; mipLevel < mipLevelCount; mipLevel++) {
@@ -325,7 +449,7 @@ void WebGPURenderPassMipmapGenerator::generateMipmaps(wgpu::Queue const& queue,
};
const wgpu::CommandBuffer commandBuffer{ commandEncoder.Finish(&commandBufferDescriptor) };
FILAMENT_CHECK_POSTCONDITION(commandBuffer)
<< "Failed to create command buffer for layer for render pass mipmap generation?";
<< "Failed to create command buffer for layer for render pass mipmap generation?";
queue.Submit(1, &commandBuffer);
}
@@ -42,6 +42,15 @@ public:
void generateMipmaps(wgpu::Queue const&, wgpu::Texture const&);
private:
enum class ScalarSampleType : uint8_t {
F32,
I32,
U32,
};
[[nodiscard]] constexpr static ScalarSampleType getScalarSampleTypeFrom(
wgpu::TextureFormat format);
[[nodiscard]] wgpu::RenderPipeline& getOrCreatePipelineFor(wgpu::TextureFormat);
void generateMipmap(wgpu::CommandEncoder const&, wgpu::Texture const&,
@@ -377,8 +377,24 @@ wgpu::TextureView WebGPUSwapChain::getCurrentHeadlessTextureView() {
return mRenderTargetViews[mHeadlessBufferIndex];
}
void WebGPUSwapChain::present() {
wgpu::Texture WebGPUSwapChain::getCurrentTexture(wgpu::Extent2D const& extent) {
if (isHeadless()) {
return mRenderTargetTextures[mHeadlessBufferIndex];
} else {
setExtent(extent);
wgpu::SurfaceTexture surfaceTexture;
mSurface.GetCurrentTexture(&surfaceTexture);
return surfaceTexture.texture;
}
}
void WebGPUSwapChain::present(wgpu::Queue const& queue) {
if (isHeadless()) {
// To ensure the CPU doesn't read the texture before the GPU is done,
// we wait for the queue to be idle.
wgpu::Future future = queue.OnSubmittedWorkDone(wgpu::CallbackMode::WaitAnyOnly, [](wgpu::QueueWorkDoneStatus) {});
wgpu::FutureWaitInfo waitInfo { .future = future };
mDevice.GetAdapter().GetInstance().WaitAny(1, &waitInfo, UINT64_MAX);
mHeadlessBufferIndex = (mHeadlessBufferIndex + 1) % mHeadlessBufferCount;
} else {
mSurface.Present();
@@ -42,12 +42,13 @@ public:
[[nodiscard]] wgpu::TextureView getCurrentTextureView(wgpu::Extent2D const& extent);
[[nodiscard]] wgpu::TextureView getCurrentHeadlessTextureView();
[[nodiscard]] wgpu::Texture getCurrentTexture(wgpu::Extent2D const& extent);
[[nodiscard]] wgpu::TextureView getDepthTextureView() const { return mDepthTextureView; }
[[nodiscard]] bool isHeadless() const { return mType == SwapChainType::HEADLESS; }
void present();
void present(wgpu::Queue const& queue);
private:
+43 -1
View File
@@ -19,7 +19,6 @@
#include "WebGPUConstants.h"
#include "WebGPURenderPassMipmapGenerator.h"
#include "WebGPUStrings.h"
#include "WebGPUTextureHelpers.h"
#include "DriverBase.h"
#include "private/backend/BackendUtils.h"
@@ -65,6 +64,37 @@ 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
@@ -219,6 +249,18 @@ namespace {
return wgpu::TextureAspect::All;
}
[[nodiscard]] constexpr wgpu::TextureViewDimension toWebGPUTextureViewDimension(
const SamplerType samplerType) {
switch (samplerType) {
case SamplerType::SAMPLER_2D: return wgpu::TextureViewDimension::e2D;
case SamplerType::SAMPLER_2D_ARRAY: return wgpu::TextureViewDimension::e2DArray;
case SamplerType::SAMPLER_CUBEMAP: return wgpu::TextureViewDimension::Cube;
case SamplerType::SAMPLER_EXTERNAL: return wgpu::TextureViewDimension::e2D;
case SamplerType::SAMPLER_3D: return wgpu::TextureViewDimension::e3D;
case SamplerType::SAMPLER_CUBEMAP_ARRAY: return wgpu::TextureViewDimension::CubeArray;
}
}
[[nodiscard]] constexpr wgpu::TextureDimension toWebGPUTextureDimension(
const SamplerType samplerType) {
switch (samplerType) {
@@ -26,6 +26,20 @@
namespace filament::backend {
[[nodiscard]] constexpr bool hasStencil(const wgpu::TextureFormat textureFormat) {
return textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8 ||
textureFormat == wgpu::TextureFormat::Stencil8;
}
[[nodiscard]] constexpr bool hasDepth(const wgpu::TextureFormat textureFormat) {
return textureFormat == wgpu::TextureFormat::Depth16Unorm ||
textureFormat == wgpu::TextureFormat::Depth32Float ||
textureFormat == wgpu::TextureFormat::Depth24Plus ||
textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8;
}
class WebGPUTexture : public HwTexture {
public:
enum class MipmapGenerationStrategy : uint8_t {
@@ -1,226 +0,0 @@
/*
* 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_WEBGPUTEXTUREHELPERS_H
#define TNT_FILAMENT_BACKEND_WEBGPUTEXTUREHELPERS_H
#include <backend/DriverEnums.h>
#include <utils/Panic.h>
#include <webgpu/webgpu_cpp.h>
namespace filament::backend {
[[nodiscard]] constexpr bool hasStencil(const wgpu::TextureFormat textureFormat) {
return textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8 ||
textureFormat == wgpu::TextureFormat::Stencil8;
}
[[nodiscard]] constexpr bool hasDepth(const wgpu::TextureFormat textureFormat) {
return textureFormat == wgpu::TextureFormat::Depth16Unorm ||
textureFormat == wgpu::TextureFormat::Depth32Float ||
textureFormat == wgpu::TextureFormat::Depth24Plus ||
textureFormat == wgpu::TextureFormat::Depth24PlusStencil8 ||
textureFormat == wgpu::TextureFormat::Depth32FloatStencil8;
}
/**
* @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;
}
}
[[nodiscard]] constexpr wgpu::TextureViewDimension toWebGPUTextureViewDimension(
const SamplerType samplerType) {
switch (samplerType) {
case SamplerType::SAMPLER_2D: return wgpu::TextureViewDimension::e2D;
case SamplerType::SAMPLER_2D_ARRAY: return wgpu::TextureViewDimension::e2DArray;
case SamplerType::SAMPLER_CUBEMAP: return wgpu::TextureViewDimension::Cube;
case SamplerType::SAMPLER_EXTERNAL: return wgpu::TextureViewDimension::e2D;
case SamplerType::SAMPLER_3D: return wgpu::TextureViewDimension::e3D;
case SamplerType::SAMPLER_CUBEMAP_ARRAY: return wgpu::TextureViewDimension::CubeArray;
}
}
enum class ScalarSampleType : uint8_t {
F32,
I32,
U32,
};
[[nodiscard]] constexpr ScalarSampleType getScalarSampleTypeFrom(const wgpu::TextureFormat format) {
switch (format) {
case wgpu::TextureFormat::R8Uint:
case wgpu::TextureFormat::R16Uint:
case wgpu::TextureFormat::R32Uint:
case wgpu::TextureFormat::Stencil8:
case wgpu::TextureFormat::RG8Uint:
case wgpu::TextureFormat::RG16Uint:
case wgpu::TextureFormat::RGBA8Uint:
case wgpu::TextureFormat::RGB10A2Uint:
case wgpu::TextureFormat::RG32Uint:
case wgpu::TextureFormat::RGBA16Uint:
case wgpu::TextureFormat::RGBA32Uint:
return ScalarSampleType::U32;
case wgpu::TextureFormat::R8Sint:
case wgpu::TextureFormat::R16Sint:
case wgpu::TextureFormat::R32Sint:
case wgpu::TextureFormat::RG8Sint:
case wgpu::TextureFormat::RG16Sint:
case wgpu::TextureFormat::RGBA8Sint:
case wgpu::TextureFormat::RG32Sint:
case wgpu::TextureFormat::RGBA16Sint:
case wgpu::TextureFormat::RGBA32Sint:
return ScalarSampleType::I32;
case wgpu::TextureFormat::R8Snorm:
case wgpu::TextureFormat::R8Unorm:
case wgpu::TextureFormat::R16Float:
case wgpu::TextureFormat::RG16Float:
case wgpu::TextureFormat::RG8Unorm:
case wgpu::TextureFormat::RG8Snorm:
case wgpu::TextureFormat::R32Float:
case wgpu::TextureFormat::RGBA8Unorm:
case wgpu::TextureFormat::RGBA8UnormSrgb:
case wgpu::TextureFormat::RGBA8Snorm:
case wgpu::TextureFormat::BGRA8Unorm:
case wgpu::TextureFormat::BGRA8UnormSrgb:
case wgpu::TextureFormat::RGB10A2Unorm:
case wgpu::TextureFormat::RG11B10Ufloat:
case wgpu::TextureFormat::RGB9E5Ufloat:
case wgpu::TextureFormat::RG32Float:
case wgpu::TextureFormat::RGBA16Float:
case wgpu::TextureFormat::RGBA32Float:
case wgpu::TextureFormat::BC1RGBAUnorm:
case wgpu::TextureFormat::BC1RGBAUnormSrgb:
case wgpu::TextureFormat::BC2RGBAUnorm:
case wgpu::TextureFormat::BC2RGBAUnormSrgb:
case wgpu::TextureFormat::BC3RGBAUnorm:
case wgpu::TextureFormat::BC3RGBAUnormSrgb:
case wgpu::TextureFormat::BC4RUnorm:
case wgpu::TextureFormat::BC4RSnorm:
case wgpu::TextureFormat::BC5RGUnorm:
case wgpu::TextureFormat::BC5RGSnorm:
case wgpu::TextureFormat::BC6HRGBUfloat:
case wgpu::TextureFormat::BC6HRGBFloat:
case wgpu::TextureFormat::BC7RGBAUnorm:
case wgpu::TextureFormat::BC7RGBAUnormSrgb:
case wgpu::TextureFormat::ETC2RGB8Unorm:
case wgpu::TextureFormat::ETC2RGB8UnormSrgb:
case wgpu::TextureFormat::ETC2RGB8A1Unorm:
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb:
case wgpu::TextureFormat::ETC2RGBA8Unorm:
case wgpu::TextureFormat::ETC2RGBA8UnormSrgb:
case wgpu::TextureFormat::EACR11Unorm:
case wgpu::TextureFormat::EACR11Snorm:
case wgpu::TextureFormat::EACRG11Unorm:
case wgpu::TextureFormat::EACRG11Snorm:
case wgpu::TextureFormat::ASTC4x4Unorm:
case wgpu::TextureFormat::ASTC4x4UnormSrgb:
case wgpu::TextureFormat::ASTC5x4Unorm:
case wgpu::TextureFormat::ASTC5x4UnormSrgb:
case wgpu::TextureFormat::ASTC5x5Unorm:
case wgpu::TextureFormat::ASTC5x5UnormSrgb:
case wgpu::TextureFormat::ASTC6x5Unorm:
case wgpu::TextureFormat::ASTC6x5UnormSrgb:
case wgpu::TextureFormat::ASTC6x6Unorm:
case wgpu::TextureFormat::ASTC6x6UnormSrgb:
case wgpu::TextureFormat::ASTC8x5Unorm:
case wgpu::TextureFormat::ASTC8x5UnormSrgb:
case wgpu::TextureFormat::ASTC8x6Unorm:
case wgpu::TextureFormat::ASTC8x6UnormSrgb:
case wgpu::TextureFormat::ASTC8x8Unorm:
case wgpu::TextureFormat::ASTC8x8UnormSrgb:
case wgpu::TextureFormat::ASTC10x5Unorm:
case wgpu::TextureFormat::ASTC10x5UnormSrgb:
case wgpu::TextureFormat::ASTC10x6Unorm:
case wgpu::TextureFormat::ASTC10x6UnormSrgb:
case wgpu::TextureFormat::ASTC10x8Unorm:
case wgpu::TextureFormat::ASTC10x8UnormSrgb:
case wgpu::TextureFormat::ASTC10x10Unorm:
case wgpu::TextureFormat::ASTC10x10UnormSrgb:
case wgpu::TextureFormat::ASTC12x10Unorm:
case wgpu::TextureFormat::ASTC12x10UnormSrgb:
case wgpu::TextureFormat::ASTC12x12Unorm:
case wgpu::TextureFormat::ASTC12x12UnormSrgb:
case wgpu::TextureFormat::R16Unorm:
case wgpu::TextureFormat::RG16Unorm:
case wgpu::TextureFormat::RGBA16Unorm:
case wgpu::TextureFormat::R16Snorm:
case wgpu::TextureFormat::RG16Snorm:
case wgpu::TextureFormat::RGBA16Snorm:
case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar420Unorm:
case wgpu::TextureFormat::R8BG8A8Triplanar420Unorm:
case wgpu::TextureFormat::R8BG8Biplanar422Unorm:
case wgpu::TextureFormat::R8BG8Biplanar444Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar422Unorm:
case wgpu::TextureFormat::R10X6BG10X6Biplanar444Unorm:
return ScalarSampleType::F32;
case wgpu::TextureFormat::Depth16Unorm:
case wgpu::TextureFormat::Depth24Plus:
case wgpu::TextureFormat::Depth24PlusStencil8:
case wgpu::TextureFormat::Depth32Float:
case wgpu::TextureFormat::Depth32FloatStencil8:
PANIC_POSTCONDITION("A depth texture format requires special sampler treatment and "
"does not generally support linear filtering (needed for mipmap "
"generation). Thus no applicable scalar sample type for %d",
format);
break;
case wgpu::TextureFormat::Undefined:
case wgpu::TextureFormat::External:
PANIC_POSTCONDITION("No scalar sample type for texture format %d", format);
break;
}
}
[[nodiscard]] constexpr std::string_view toWGSLString(const ScalarSampleType type) {
switch (type) {
case ScalarSampleType::F32: return "f32";
case ScalarSampleType::I32: return "i32";
case ScalarSampleType::U32: return "u32";
}
}
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUTEXTUREHELPERS_H
@@ -1,537 +0,0 @@
/*
* 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 "WebGPUTextureSwizzler.h"
#include "WebGPUTextureHelpers.h"
#include <backend/DriverEnums.h>
#include <utils/Hash.h>
#include <utils/Panic.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
#include <sstream> // for shader template search and replace
#include <string> // for shader template search and replace
#include <string_view>
namespace filament::backend {
namespace {
// Note: would be nice to generate the constexpr string for the template from the constexpr
// variables below, e.g. TEXTURE_BINDING_INDEX, to avoid them getting out of sync while still having
// the compile/build time optimization
constexpr uint32_t BIND_GROUP_INDEX{ 0 };
constexpr size_t BIND_GROUP_ENTRY_SIZE{ 2 }; // texture and uniform
constexpr uint32_t TEXTURE_BINDING_INDEX{ 0 };
constexpr uint32_t UNIFORM_BINDING_INDEX{ 1 };
constexpr std::string_view VERTEX_SHADER_ENTRY_POINT{ "vertexShaderMain" };
constexpr std::string_view FRAGMENT_SHADER_ENTRY_POINT{ "fragmentShaderMain" };
// note that the placeholders below must start and end with this prefix and suffix:
constexpr std::string_view PLACEHOLDER_PREFIX{ "{{" };
constexpr std::string_view PLACEHOLDER_SUFFIX{ "}}" };
// texture_2d<...> or texture_multisampled_2d<...>
constexpr std::string_view TEXTURE_TYPE_PLACEHOLDER{ "TEXTURE_TYPE" };
// f32, i32, u32
constexpr std::string_view SCALAR_TYPE_PLACEHOLDER{ "SCALAR_TYPE" };
// empty for single-sampled, ", @builtin(sample_index) sampleIndex" for multi-sampled
constexpr std::string_view SAMPLE_INDEX_PARAM_PLACEHOLDER{ "SAMPLE_INDEX_PARAM" };
// "sampleIndex" for multi-sampled, "0" for single-sampled
constexpr std::string_view SAMPLE_INDEX_OR_LEVEL_PLACEHOLDER{ "SAMPLE_INDEX_OR_LEVEL" };
constexpr std::string_view SHADER_SOURCE_TEMPLATE{ R"(
struct TextureSwizzle {
redSource: u32,
greenSource: u32,
blueSource: u32,
alphaSource: u32,
};
@group(0) @binding(0) var sourceTexture: {{TEXTURE_TYPE}};
@group(0) @binding(1) var<uniform> swizzle: TextureSwizzle;
fn getComponent(texel: vec4<{{SCALAR_TYPE}}>, index: u32) -> {{SCALAR_TYPE}} {
switch index {
case 0u: { return 0; }
case 1u: { return 1; }
case 2u: { return texel.r; }
case 3u: { return texel.g; }
case 4u: { return texel.b; }
case 5u: { return texel.a; }
default: { return 0; }
}
}
@vertex
fn vertexShaderMain(@builtin(vertex_index) vertexIndex: u32) -> @builtin(position) vec4<f32> {
let fullScreenTriangleVertices = array<vec2<f32>, 3>(
vec2<f32>(-1.0, -1.0),
vec2<f32>( 3.0, -1.0),
vec2<f32>(-1.0, 3.0)
);
return vec4<f32>(fullScreenTriangleVertices[vertexIndex].xy, 0.0, 1.0);
}
@fragment
fn fragmentShaderMain(@builtin(position) vec4<f32> textureCoordinate{{SAMPLE_INDEX_PARAM}}) -> @location(0) vec4<{{SCALAR_TYPE}}> {
let x = u32(textureCoordinate.x);
let y = u32(textureCoordinate.y);
let originalColor = textureLoad(sourceTexture, vec2<u32>(x, y), {{SAMPLE_INDEX_OR_LEVEL}});
return vec4<{{SCALAR_TYPE}}>(
getComponent(originalColor, swizzle.redSource),
getComponent(originalColor, swizzle.greenSource),
getComponent(originalColor, swizzle.blueSource),
getComponent(originalColor, swizzle.alphaSource));
}
)" };
[[nodiscard]] constexpr uint32_t toSwizzleIndex(const TextureSwizzle swizzle) {
switch (swizzle) {
case TextureSwizzle::SUBSTITUTE_ZERO: return 0;
case TextureSwizzle::SUBSTITUTE_ONE: return 1;
case TextureSwizzle::CHANNEL_0: return 2;
case TextureSwizzle::CHANNEL_1: return 3;
case TextureSwizzle::CHANNEL_2: return 4;
case TextureSwizzle::CHANNEL_3: return 5;
}
}
} // namespace
WebGPUTextureSwizzler::WebGPUTextureSwizzler(wgpu::Device const& device)
: mDevice{ device } {}
void WebGPUTextureSwizzler::swizzle(wgpu::Queue const& queue, wgpu::Texture const& source,
wgpu::Texture const& destination, const TextureSwizzle red, const TextureSwizzle green,
const TextureSwizzle blue, const TextureSwizzle alpha) {
FILAMENT_CHECK_PRECONDITION(source.GetFormat() == destination.GetFormat())
<< "Source and destination formats must match";
FILAMENT_CHECK_PRECONDITION(!hasDepth(source.GetFormat()))
<< "depth texture swizzling not supported";
FILAMENT_CHECK_PRECONDITION(!hasStencil(source.GetFormat()))
<< "stencil texture swizzling not supported";
FILAMENT_CHECK_PRECONDITION(source.GetDimension() == destination.GetDimension())
<< "Source and destination dimensions must match";
FILAMENT_CHECK_PRECONDITION(source.GetDimension() == wgpu::TextureDimension::e2D)
<< "Source and destination dimensions must be 2D";
FILAMENT_CHECK_PRECONDITION(source.GetWidth() == destination.GetWidth())
<< "Source and destination widths must match";
FILAMENT_CHECK_PRECONDITION(source.GetHeight() == destination.GetHeight())
<< "Source and destination heights must match";
FILAMENT_CHECK_PRECONDITION(
source.GetDepthOrArrayLayers() == destination.GetDepthOrArrayLayers())
<< "Source and destination depthOrArrayLayers must match";
FILAMENT_CHECK_PRECONDITION(source.GetMipLevelCount() == destination.GetMipLevelCount())
<< "Source and destination mip level counts must match";
FILAMENT_CHECK_PRECONDITION(source.GetSampleCount() == destination.GetSampleCount())
<< "Source and destination sample counts must match";
const wgpu::CommandEncoderDescriptor commandEncoderDescriptor{
.label = "texture_swizzle_render_pass_cmd_encoder",
};
const wgpu::CommandEncoder commandEncoder{ mDevice.CreateCommandEncoder(
&commandEncoderDescriptor) };
FILAMENT_CHECK_POSTCONDITION(commandEncoder)
<< "Failed to create command encoder for texture swizzle?";
wgpu::BindGroupLayout const& bindGroupLayout{ getOrCreateBindGroupLayout(
getScalarSampleTypeFrom(source.GetFormat()), source.GetSampleCount() > 1) };
wgpu::RenderPipeline const& pipeline{ getOrCreateRenderPipelineFor(source.GetFormat(),
source.GetSampleCount()) };
// Create and upload the uniform buffer with swizzle parameters.
uint32_t swizzleIndices[]{
toSwizzleIndex(red),
toSwizzleIndex(green),
toSwizzleIndex(blue),
toSwizzleIndex(alpha),
};
wgpu::BufferDescriptor bufferDescriptor{
.label = "texture_swizzle_uniform_buffer",
.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst,
.size = sizeof(swizzleIndices),
.mappedAtCreation = false, // explicitly set for consistency
};
const wgpu::Buffer uniformBuffer{ mDevice.CreateBuffer(&bufferDescriptor) };
FILAMENT_CHECK_POSTCONDITION(uniformBuffer)
<< "Failed to create uniform buffer for texture swizzle?";
queue.WriteBuffer(uniformBuffer, 0, swizzleIndices, sizeof(swizzleIndices));
const uint32_t layerCount{ source.GetDepthOrArrayLayers() };
for (uint32_t layer = 0; layer < layerCount; layer++) {
for (uint32_t mipLevel = 1; mipLevel < source.GetMipLevelCount(); mipLevel++) {
swizzle(commandEncoder, pipeline, bindGroupLayout, source, destination, layer, mipLevel,
uniformBuffer);
}
}
// submit the command buffer...
const wgpu::CommandBufferDescriptor commandBufferDescriptor{
.label = "texture_swizzle_render_pass_cmd_buffer",
};
const wgpu::CommandBuffer commandBuffer{ commandEncoder.Finish(&commandBufferDescriptor) };
FILAMENT_CHECK_POSTCONDITION(commandBuffer)
<< "Failed to create command buffer for texture swizzle?";
queue.Submit(1, &commandBuffer);
}
void WebGPUTextureSwizzler::swizzle(wgpu::CommandEncoder const& commandEncoder,
wgpu::RenderPipeline const& renderPipeline, wgpu::BindGroupLayout const& bindGroupLayout,
wgpu::Texture const& source, wgpu::Texture const& destination, const uint32_t layer,
const uint32_t mipLevel, wgpu::Buffer const& uniformBuffer) {
// create texture views for this pass...
const wgpu::TextureFormat format{ source.GetFormat() };
// only 2D textures supported at this time...
const wgpu::TextureViewDimension dimension{ wgpu::TextureViewDimension::e2D };
const wgpu::TextureViewDescriptor sourceViewDescriptor{
.label = "source_of_texture_swizzle_view",
.format = format,
.dimension = dimension,
.baseMipLevel = mipLevel,
.mipLevelCount = 1,
.baseArrayLayer = layer,
.arrayLayerCount = 1,
.aspect = wgpu::TextureAspect::All,
.usage = wgpu::TextureUsage::TextureBinding,
};
const wgpu::TextureView sourceView{ source.CreateView(&sourceViewDescriptor) };
FILAMENT_CHECK_POSTCONDITION(sourceView)
<< "Failed to create source texture view for layer " << layer << " and mip level "
<< mipLevel << " for render pass texture swizzle?";
const wgpu::TextureViewDescriptor destinationViewDescriptor{
.label = "destination_of_texture_swizzle_view",
.format = format,
.dimension = dimension,
.baseMipLevel = mipLevel,
.mipLevelCount = 1,
.baseArrayLayer = layer,
.arrayLayerCount = 1,
.aspect = wgpu::TextureAspect::All,
.usage = wgpu::TextureUsage::RenderAttachment,
};
const wgpu::TextureView destinationView{ destination.CreateView(&destinationViewDescriptor) };
FILAMENT_CHECK_POSTCONDITION(destinationView)
<< "Failed to create destination texture view for layer " << layer << " and mip level "
<< mipLevel << " for render pass texture swizzle?";
// create the render pass...
const wgpu::BindGroupEntry bindGroupEntries[BIND_GROUP_ENTRY_SIZE]{
{
.binding = TEXTURE_BINDING_INDEX,
.textureView = sourceView,
},
{
.binding = UNIFORM_BINDING_INDEX,
.buffer = uniformBuffer,
},
};
const wgpu::BindGroupDescriptor bindGroupDescriptor{
.label = "render_pass_texture_swizzle_bind_group",
.layout = bindGroupLayout,
.entryCount = BIND_GROUP_ENTRY_SIZE,
.entries = bindGroupEntries,
};
const wgpu::BindGroup bindGroup{ mDevice.CreateBindGroup(&bindGroupDescriptor) };
FILAMENT_CHECK_POSTCONDITION(bindGroup)
<< "Failed to create bind group for layer " << layer << " and mip level " << mipLevel
<< " for render pass texture swizzle?";
const wgpu::RenderPassColorAttachment colorAttachment{
.view = destinationView,
.depthSlice = wgpu::kDepthSliceUndefined, // not applicable
.resolveTarget = nullptr, // not applicable
.loadOp = wgpu::LoadOp::Clear,
.storeOp = wgpu::StoreOp::Store,
.clearValue = { .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
};
const wgpu::RenderPassDescriptor renderPassDescriptor{
.label = "texture_swizzle_render_pass",
.colorAttachmentCount = 1,
.colorAttachments = &colorAttachment,
.depthStencilAttachment = nullptr, // not applicable
.occlusionQuerySet = nullptr, // not applicable
.timestampWrites = nullptr, // not applicable
};
const wgpu::RenderPassEncoder renderPassEncoder{ commandEncoder.BeginRenderPass(
&renderPassDescriptor) };
FILAMENT_CHECK_POSTCONDITION(renderPassEncoder)
<< "Failed to create render pass encoder for layer " << layer << " and mip level "
<< mipLevel << " for render pass texture swizzle?";
renderPassEncoder.SetPipeline(renderPipeline);
renderPassEncoder.SetBindGroup(BIND_GROUP_INDEX, bindGroup);
renderPassEncoder.Draw(3); // draw the full-screen triangle
// with hard-coded vertices in the shader
renderPassEncoder.End();
}
wgpu::RenderPipeline const& WebGPUTextureSwizzler::getOrCreateRenderPipelineFor(
const wgpu::TextureFormat format, const uint32_t sampleCount) {
const bool multiSampled{ sampleCount > 1 };
const size_t key{ hashPipelineKey(format, sampleCount) };
if (mPipelines.find(key) == mPipelines.end()) {
const ScalarSampleType scalarTextureFormat{ getScalarSampleTypeFrom(format) };
const wgpu::ShaderModule& shaderModule{ getOrCreateShaderModuleFor(scalarTextureFormat,
multiSampled) };
const wgpu::PipelineLayout& pipelineLayout{ getOrCreatePipelineLayoutFor(
scalarTextureFormat, multiSampled) };
mPipelines[key] = createRenderPipeline(shaderModule, pipelineLayout, format, sampleCount);
}
return mPipelines[key];
}
wgpu::RenderPipeline WebGPUTextureSwizzler::createRenderPipeline(
wgpu::ShaderModule const& shaderModule, wgpu::PipelineLayout const& pipelineLayout,
const wgpu::TextureFormat textureFormat, const uint32_t sampleCount) {
const wgpu::ColorTargetState colorTargetState{ .format = textureFormat };
const wgpu::FragmentState fragmentState{
.module = shaderModule,
.entryPoint = VERTEX_SHADER_ENTRY_POINT,
.constantCount = 0, // should not matter, just being consistently defined
.constants = nullptr, // should not matter, just being consistently defined
.targetCount = 1,
.targets = &colorTargetState,
};
const wgpu::RenderPipelineDescriptor pipelineDescriptor{
.label = "render_pass_texture_swizzle_pipeline",
.layout = pipelineLayout,
.vertex = {
.module = shaderModule,
.entryPoint = FRAGMENT_SHADER_ENTRY_POINT,
.constantCount = 0,
.constants = nullptr,
.bufferCount = 0,
.buffers = nullptr,
},
.primitive = {
.topology = wgpu::PrimitiveTopology::TriangleList,
.stripIndexFormat = wgpu::IndexFormat::Undefined, // should not matter
// (not using a strip topology),
// just being consistently defined
.frontFace = wgpu::FrontFace::Undefined, // should not matter (no cull mode),
// just being consistently defined
.cullMode = wgpu::CullMode::None,
.unclippedDepth = false, // should not matter, just being consistently defined
},
.depthStencil = nullptr, // not applicable, but explicitly set here for
// consistent definition
.multisample = {
.count = sampleCount,
.mask = 0xFFFFFFFF,
.alphaToCoverageEnabled = false,
},
.fragment = &fragmentState,
};
const wgpu::RenderPipeline pipeline{ mDevice.CreateRenderPipeline(&pipelineDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipeline)
<< "Failed to create pipeline for render pass texture swizzle?";
return pipeline;
}
wgpu::PipelineLayout const& WebGPUTextureSwizzler::getOrCreatePipelineLayoutFor(
const ScalarSampleType scalarTextureFormat, const bool multiSampled) {
const size_t key{ hashPipelineLayoutKey(scalarTextureFormat, multiSampled) };
if (mPipelineLayouts.find(key) == mPipelineLayouts.end()) {
mPipelineLayouts[key] = createPipelineLayout(scalarTextureFormat, multiSampled);
}
return mPipelineLayouts[key];
}
wgpu::PipelineLayout WebGPUTextureSwizzler::createPipelineLayout(
const ScalarSampleType scalarTextureFormat, const bool multiSampled) {
wgpu::BindGroupLayout const& bindGroupLayout{ getOrCreateBindGroupLayout(scalarTextureFormat,
multiSampled) };
#ifndef NDEBUG
std::stringstream labelStream{};
labelStream << "texture_swizzle_pipeline_layout_" << toWGSLString(scalarTextureFormat)
<< (multiSampled ? "_multisampled" : "_singlesampled");
const wgpu::StringView label{ labelStream.str() };
#else
const wgpu::StringView label{ "texture_swizzle_pipeline_layout" };
#endif
const wgpu::PipelineLayoutDescriptor pipelineLayoutDescriptor{
.label = label,
.bindGroupLayoutCount = 1,
.bindGroupLayouts = &bindGroupLayout,
};
const wgpu::PipelineLayout pipelineLayout{ mDevice.CreatePipelineLayout(
&pipelineLayoutDescriptor) };
FILAMENT_CHECK_POSTCONDITION(pipelineLayout)
<< "Failed to create pipeline layout for texture swizzling?";
return pipelineLayout;
}
wgpu::BindGroupLayout const& WebGPUTextureSwizzler::getOrCreateBindGroupLayout(
const ScalarSampleType scalarTextureFormat, const bool multiSampled) {
const size_t key{ hashBindGroupLayoutKey(scalarTextureFormat, multiSampled) };
if (mBindGroupLayouts.find(key) == mBindGroupLayouts.end()) {
mBindGroupLayouts[key] = createBindGroupLayout(scalarTextureFormat, multiSampled);
}
return mBindGroupLayouts[key];
}
wgpu::BindGroupLayout WebGPUTextureSwizzler::createBindGroupLayout(
const ScalarSampleType scalarTextureFormat, const bool multiSampled) {
wgpu::TextureSampleType sampleType{ wgpu::TextureSampleType::Undefined };
switch (scalarTextureFormat) {
case ScalarSampleType::F32:
sampleType = wgpu::TextureSampleType::UnfilterableFloat;
break;
case ScalarSampleType::I32:
sampleType = wgpu::TextureSampleType::Sint;
break;
case ScalarSampleType::U32:
sampleType = wgpu::TextureSampleType::Uint;
break;
}
const wgpu::BindGroupLayoutEntry bindGroupLayoutEntries[BIND_GROUP_ENTRY_SIZE] {
{
.binding = TEXTURE_BINDING_INDEX,
.visibility = wgpu::ShaderStage::Fragment,
.texture = {
.sampleType = sampleType,
.viewDimension = wgpu::TextureViewDimension::e2D, // only 2D supported for now
.multisampled = multiSampled,
},
},
{
.binding = UNIFORM_BINDING_INDEX,
.visibility = wgpu::ShaderStage::Fragment,
.buffer = {
.type = wgpu::BufferBindingType::Uniform,
.hasDynamicOffset = false, // set explicitly for consistency
.minBindingSize = 0, // TODO set based on the swizzle data size
},
},
};
const wgpu::BindGroupLayoutDescriptor bindGroupLayoutDescriptor{
.label = "render_pass_texture_swizzle_bind_group_layout",
.entryCount = BIND_GROUP_ENTRY_SIZE,
.entries = bindGroupLayoutEntries,
};
const wgpu::BindGroupLayout bindGroupLayout{ mDevice.CreateBindGroupLayout(
&bindGroupLayoutDescriptor) };
FILAMENT_CHECK_POSTCONDITION(bindGroupLayout)
<< "Failed to create texture bind group layout for render pass texture swizzle?";
return bindGroupLayout;
}
wgpu::ShaderModule const& WebGPUTextureSwizzler::getOrCreateShaderModuleFor(
const ScalarSampleType scalarTextureFormat, const bool multiSampled) {
const size_t key{ hashShaderModuleKey(scalarTextureFormat, multiSampled) };
if (mShaderModules.find(key) == mShaderModules.end()) {
const wgpu::ShaderModule shaderModule{ createShaderModule(scalarTextureFormat,
multiSampled) };
mShaderModules[key] = shaderModule;
}
return mShaderModules[key];
}
wgpu::ShaderModule WebGPUTextureSwizzler::createShaderModule(
const ScalarSampleType scalarTextureFormat, const bool multiSampled) {
// build the proper source from the template...
const std::string_view scalarType{ toWGSLString(scalarTextureFormat) };
std::stringstream textureTypeStream{};
textureTypeStream << (multiSampled ? "texture_multisampled_2d" : "texture_2d") << '<'
<< scalarType << '>';
const std::string textureType{ textureTypeStream.str() };
const std::string_view sampleIndexParam{ multiSampled ? ", @builtin(sample_index) sampleIndex"
: "" };
const std::string_view sampleIndexOrLevel{ multiSampled ? "sampleIndex" : "0" };
const char* const sourceData = SHADER_SOURCE_TEMPLATE.data();
std::stringstream processedShaderSource{};
size_t positionCursorInTemplateString{ 0 };
while (positionCursorInTemplateString < SHADER_SOURCE_TEMPLATE.size()) {
const size_t positionOfNextPlaceholder{ SHADER_SOURCE_TEMPLATE.find(PLACEHOLDER_PREFIX,
positionCursorInTemplateString) };
if (positionOfNextPlaceholder == std::string::npos) {
// no more placeholders, so just stream the rest of the source code string
processedShaderSource << std::string_view(sourceData + positionCursorInTemplateString,
SHADER_SOURCE_TEMPLATE.size() - positionCursorInTemplateString);
break;
}
const size_t positionOfPlaceholder{ positionOfNextPlaceholder + PLACEHOLDER_PREFIX.size() };
// stream up to the placeholder...
processedShaderSource << std::string_view(sourceData + positionCursorInTemplateString,
positionOfPlaceholder);
const size_t positionAfterPlaceholder{ SHADER_SOURCE_TEMPLATE.find(
PLACEHOLDER_SUFFIX, positionOfPlaceholder) };
assert_invariant(positionAfterPlaceholder != std::string::npos &&
"Malformed shader with missing suffix to placeholder");
const std::string_view placeholder{ std::string_view(sourceData + positionOfPlaceholder,
positionAfterPlaceholder - positionOfPlaceholder) };
// stream the value in place of the placeholder...
if (placeholder == TEXTURE_TYPE_PLACEHOLDER) {
processedShaderSource << textureType;
} else if (placeholder == SCALAR_TYPE_PLACEHOLDER) {
processedShaderSource << scalarType;
} else if (placeholder == SAMPLE_INDEX_PARAM_PLACEHOLDER) {
processedShaderSource << sampleIndexParam;
} else if (placeholder == SAMPLE_INDEX_OR_LEVEL_PLACEHOLDER) {
processedShaderSource << sampleIndexOrLevel;
} else {
PANIC_POSTCONDITION(
"Unknown/unhandled placeholder %s when processing texture swizzler shader",
placeholder.data());
}
// update the cursor for after the placeholder...
positionCursorInTemplateString = positionAfterPlaceholder + PLACEHOLDER_SUFFIX.size();
}
const std::string shaderSource{ processedShaderSource.str() };
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
wgslDescriptor.code = shaderSource.data();
#ifndef NDEBUG
std::stringstream labelStream{};
labelStream << "texture_swizzle_shaders_" << scalarType
<< (multiSampled ? "_multisampled" : "_singlesampled");
const wgpu::StringView label{ labelStream.str() };
#else
const wgpu::StringView label{ "texture_swizzle_shaders" };
#endif
const wgpu::ShaderModuleDescriptor shaderModuleDescriptor{
.nextInChain = &wgslDescriptor,
.label = label,
};
const wgpu::ShaderModule shaderModule{ mDevice.CreateShaderModule(&shaderModuleDescriptor) };
FILAMENT_CHECK_POSTCONDITION(shaderModule)
<< "Failed to create shader module for texture swizzling? scalarType " << scalarType
<< " multiSampled " << multiSampled;
return shaderModule;
}
size_t WebGPUTextureSwizzler::hashBindGroupLayoutKey(const ScalarSampleType textureScalarFormat,
const bool multiSampled) {
size_t seed{ std::hash<uint32_t>{}(static_cast<uint8_t>(textureScalarFormat)) };
utils::hash::combine(seed, multiSampled);
return seed;
}
size_t WebGPUTextureSwizzler::hashPipelineLayoutKey(const ScalarSampleType textureScalarFormat,
const bool multiSampled) {
size_t seed{ std::hash<uint32_t>{}(static_cast<uint8_t>(textureScalarFormat)) };
utils::hash::combine(seed, multiSampled);
return seed;
}
size_t WebGPUTextureSwizzler::hashShaderModuleKey(const ScalarSampleType textureScalarFormat,
const bool multiSampled) {
size_t seed{ std::hash<uint32_t>{}(static_cast<uint8_t>(textureScalarFormat)) };
utils::hash::combine(seed, multiSampled);
return seed;
}
size_t WebGPUTextureSwizzler::hashPipelineKey(const wgpu::TextureFormat format,
const uint32_t sampleCount) {
size_t seed{ std::hash<uint32_t>{}(static_cast<uint32_t>(format)) };
utils::hash::combine(seed, sampleCount);
return seed;
}
} // namespace filament::backend
@@ -1,86 +0,0 @@
/*
* 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_WEBGPUTEXTURESWIZZLER_H
#define TNT_FILAMENT_BACKEND_WEBGPUTEXTURESWIZZLER_H
#include "WebGPUTextureHelpers.h"
#include <backend/DriverEnums.h>
#include <tsl/robin_map.h>
#include <webgpu/webgpu_cpp.h>
#include <cstdint>
namespace filament::backend {
class WebGPUTextureSwizzler final {
public:
explicit WebGPUTextureSwizzler(wgpu::Device const&);
void swizzle(wgpu::Queue const&, wgpu::Texture const& source, wgpu::Texture const& destination,
TextureSwizzle red, TextureSwizzle green, TextureSwizzle blue, TextureSwizzle alpha);
private:
void swizzle(wgpu::CommandEncoder const&, wgpu::RenderPipeline const&,
wgpu::BindGroupLayout const&, wgpu::Texture const& source,
wgpu::Texture const& destination, uint32_t layer, uint32_t mipLevel,
wgpu::Buffer const& uniformBuffer);
[[nodiscard]] wgpu::RenderPipeline const& getOrCreateRenderPipelineFor(wgpu::TextureFormat,
uint32_t sampleCount);
[[nodiscard]] wgpu::RenderPipeline createRenderPipeline(wgpu::ShaderModule const&,
wgpu::PipelineLayout const&, wgpu::TextureFormat, uint32_t sampleCount);
[[nodiscard]] wgpu::PipelineLayout const& getOrCreatePipelineLayoutFor(ScalarSampleType,
bool multiSampled);
[[nodiscard]] wgpu::PipelineLayout createPipelineLayout(ScalarSampleType, bool multiSampled);
[[nodiscard]] wgpu::BindGroupLayout const& getOrCreateBindGroupLayout(
ScalarSampleType scalarTextureFormat, bool multiSampled);
[[nodiscard]] wgpu::BindGroupLayout createBindGroupLayout(ScalarSampleType scalarTextureFormat,
bool multiSampled);
[[nodiscard]] wgpu::ShaderModule const& getOrCreateShaderModuleFor(ScalarSampleType,
bool multiSampled);
[[nodiscard]] wgpu::ShaderModule createShaderModule(ScalarSampleType, bool multiSampled);
[[nodiscard]] static size_t hashBindGroupLayoutKey(ScalarSampleType textureScalarFormat,
bool multiSampled);
[[nodiscard]] static size_t hashPipelineLayoutKey(ScalarSampleType textureScalarFormat,
bool multiSampled);
[[nodiscard]] static size_t hashShaderModuleKey(ScalarSampleType textureScalarFormat,
bool multiSampled);
[[nodiscard]] static size_t hashPipelineKey(wgpu::TextureFormat, uint32_t sampleCount);
wgpu::Device const& mDevice;
tsl::robin_map<size_t, wgpu::BindGroupLayout> mBindGroupLayouts{};
tsl::robin_map<size_t, wgpu::PipelineLayout> mPipelineLayouts{};
tsl::robin_map<size_t, wgpu::ShaderModule> mShaderModules{};
tsl::robin_map<size_t, wgpu::RenderPipeline> mPipelines{};
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_WEBGPUTEXTURESWIZZLER_H
+2 -63
View File
@@ -329,7 +329,6 @@ void PostProcessManager::init() noexcept {
mSsrPassDescriptorSet.init(engine);
mPostProcessDescriptorSet.init(engine);
mStructureDescriptorSet.init(engine);
mWorkaroundSplitEasu =
driver.isWorkaroundNeeded(Workaround::SPLIT_EASU);
@@ -406,7 +405,6 @@ void PostProcessManager::terminate(DriverApi& driver) noexcept {
mPostProcessDescriptorSet.terminate(engine.getDescriptorSetLayoutFactory(), driver);
mSsrPassDescriptorSet.terminate(driver);
mStructureDescriptorSet.terminate(driver);
}
Handle<HwTexture> PostProcessManager::getOneTexture() const {
@@ -548,8 +546,7 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
Variant structureVariant(Variant::DEPTH_VARIANT);
structureVariant.setPicking(config.picking);
// bind the per-view descriptorSet that is used for the structure pass
getStructureDescriptorSet().bind(driver);
bindPostProcessDescriptorSet(driver);
passBuilder.renderFlags(structureRenderFlags);
passBuilder.variant(structureVariant);
@@ -612,63 +609,6 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
return { depth, structurePass->picking };
}
FrameGraphId<FrameGraphTexture> PostProcessManager::transparentPicking(FrameGraph& fg,
RenderPassBuilder const& passBuilder, uint8_t const structureRenderFlags,
uint32_t width, uint32_t height, float const scale) noexcept {
struct PickingRenderPassData {
FrameGraphId<FrameGraphTexture> depth;
FrameGraphId<FrameGraphTexture> picking;
};
auto const& pickingRenderPass = fg.addPass<PickingRenderPassData>("Picking Render Pass",
[&](FrameGraph::Builder& builder, auto& data) {
bool const isFL0 = mEngine.getDriverApi().getFeatureLevel() ==
FeatureLevel::FEATURE_LEVEL_0;
// TODO: Specify the precision for picking pass
width = std::max(32u, uint32_t(std::ceil(float(width) * scale)));
height = std::max(32u, uint32_t(std::ceil(float(height) * scale)));
data.depth = builder.createTexture("Depth Buffer", {
.width = width, .height = height,
.format = isFL0 ? TextureFormat::DEPTH24 : TextureFormat::DEPTH32F });
data.depth = builder.write(data.depth,
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
data.picking = builder.createTexture("Picking Buffer", {
.width = width, .height = height,
.format = isFL0 ? TextureFormat::RGBA8 : TextureFormat::RG32F });
data.picking = builder.write(data.picking,
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
builder.declareRenderPass("Picking Render Target", {
.attachments = {.color = { data.picking }, .depth = data.depth },
.clearFlags = TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH
});
},
[=, passBuilder = passBuilder](FrameGraphResources const& resources,
auto const&, DriverApi& driver) mutable {
Variant pickingVariant(Variant::DEPTH_VARIANT);
pickingVariant.setPicking(true);
// bind the per-view descriptorSet that is used for the structure pass
getStructureDescriptorSet().bind(driver);
auto [target, params] = resources.getRenderPassInfo();
passBuilder.renderFlags(structureRenderFlags);
passBuilder.variant(pickingVariant);
passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::DEPTH);
RenderPass const pass{ passBuilder.build(mEngine, driver) };
driver.beginRenderPass(target, params);
pass.getExecutor().execute(mEngine, driver);
driver.endRenderPass();
});
return pickingRenderPass->picking;
}
// ------------------------------------------------------------------------------------------------
FrameGraphId<FrameGraphTexture> PostProcessManager::ssr(FrameGraph& fg,
@@ -918,8 +858,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
});
},
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
// bind the per-view descriptorSet that is used for the structure pass
getStructureDescriptorSet().bind(driver);
bindPostProcessDescriptorSet(driver);
auto depth = resources.getTexture(data.depth);
auto ssao = resources.getRenderPassInfo();
+1 -9
View File
@@ -23,7 +23,6 @@
#include "ds/PostProcessDescriptorSet.h"
#include "ds/SsrPassDescriptorSet.h"
#include "ds/StructureDescriptorSet.h"
#include "ds/TypedUniformBuffer.h"
#include "materials/StaticMaterialInfo.h"
@@ -105,10 +104,6 @@ public:
RenderPassBuilder const& passBuilder, uint8_t structureRenderFlags,
uint32_t width, uint32_t height, StructurePassConfig const& config) noexcept;
FrameGraphId<FrameGraphTexture> transparentPicking(FrameGraph& fg,
RenderPassBuilder const& passBuilder, uint8_t structureRenderFlags,
uint32_t width, uint32_t height, float scale) noexcept;
// reflections pass
FrameGraphId<FrameGraphTexture> ssr(FrameGraph& fg,
RenderPassBuilder const& passBuilder,
@@ -396,9 +391,7 @@ public:
FMaterialInstance* configureColorGradingMaterial(
PostProcessMaterial& material, FColorGrading const* colorGrading,
ColorGradingConfig const& colorGradingConfig, VignetteOptions const& vignetteOptions,
uint32_t width, uint32_t height) noexcept;
StructureDescriptorSet& getStructureDescriptorSet() const noexcept { return mStructureDescriptorSet; }
uint32_t const width, uint32_t const height) noexcept;
private:
backend::RenderPrimitiveHandle mFullScreenQuadRph;
@@ -409,7 +402,6 @@ private:
mutable SsrPassDescriptorSet mSsrPassDescriptorSet;
mutable PostProcessDescriptorSet mPostProcessDescriptorSet;
mutable StructureDescriptorSet mStructureDescriptorSet;
struct BilateralPassConfig {
uint8_t kernelSize = 11;
+3 -3
View File
@@ -521,9 +521,9 @@ public:
}
// Specifies camera information (e.g. used for sorting commands)
RenderPassBuilder& camera(math::float3 position, math::float3 forward) noexcept {
mCameraPosition = position;
mCameraForwardVector = forward;
RenderPassBuilder& camera(const CameraInfo& camera) noexcept {
mCameraPosition = camera.getPosition();
mCameraForwardVector = camera.getForwardVector();
return *this;
}
-1
View File
@@ -39,7 +39,6 @@ void FRenderPrimitive::init(HwRenderPrimitiveFactory& factory, backend::DriverAp
mMaterialInstance = downcast(entry.materialInstance);
mBlendOrder = entry.blendOrder;
mGlobalBlendOrderEnabled = entry.globalBlendOrderEnabled;
if (entry.indices && entry.vertices) {
FVertexBuffer const* vertexBuffer = downcast(entry.vertices);
+2 -7
View File
@@ -1366,8 +1366,8 @@ float4 ShadowMap::getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const
// ------------------------------------------------------------------------------------------------
void ShadowMap::prepareCamera(Transaction const& transaction,
FEngine const& engine, const CameraInfo& cameraInfo) noexcept {
ShadowMapDescriptorSet::prepareCamera(transaction, engine, cameraInfo);
DriverApi& driver, const CameraInfo& cameraInfo) noexcept {
ShadowMapDescriptorSet::prepareCamera(transaction, driver, cameraInfo);
ShadowMapDescriptorSet::prepareLodBias(transaction, 0.0f);
}
@@ -1381,11 +1381,6 @@ void ShadowMap::prepareTime(Transaction const& transaction,
ShadowMapDescriptorSet::prepareTime(transaction, engine, userTime);
}
void ShadowMap::prepareMaterialGlobals(Transaction const& transaction,
std::array<float4, 4> const& materialGlobals) noexcept {
ShadowMapDescriptorSet::prepareMaterialGlobals(transaction, materialGlobals);
}
void ShadowMap::prepareShadowMapping(Transaction const& transaction,
bool const highPrecision) noexcept {
ShadowMapDescriptorSet::prepareShadowMapping(transaction, highPrecision);
+6 -5
View File
@@ -33,6 +33,9 @@
#include <utils/compiler.h>
#include <math/mathfwd.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <math/mat4.h>
#include <array>
@@ -191,13 +194,11 @@ public:
using Transaction = ShadowMapDescriptorSet::Transaction;
static void prepareCamera(Transaction const& transaction,
FEngine const& engine, const CameraInfo& cameraInfo) noexcept;
backend::DriverApi& driver, const CameraInfo& cameraInfo) noexcept;
static void prepareViewport(Transaction const& transaction,
backend::Viewport const& viewport) noexcept;
static void prepareTime(Transaction const& transaction,
FEngine const& engine, math::float4 const& userTime) noexcept;
static void prepareMaterialGlobals(Transaction const& transaction,
std::array<math::float4, 4> const& materialGlobals) noexcept;
static void prepareShadowMapping(Transaction const& transaction,
bool highPrecision) noexcept;
static ShadowMapDescriptorSet::Transaction open(backend::DriverApi& driver) noexcept;
@@ -325,12 +326,12 @@ private:
static float texelSizeWorldSpace(const math::mat4f& W, const math::mat4f& MbMtF,
uint16_t shadowDimension) noexcept;
static constexpr Segment sBoxSegments[12] = {
static constexpr const Segment sBoxSegments[12] = {
{ 0, 1 }, { 1, 3 }, { 3, 2 }, { 2, 0 },
{ 4, 5 }, { 5, 7 }, { 7, 6 }, { 6, 4 },
{ 0, 4 }, { 1, 5 }, { 3, 7 }, { 2, 6 },
};
static constexpr Quad sBoxQuads[6] = {
static constexpr const Quad sBoxQuads[6] = {
{ 2, 0, 1, 3 }, // far
{ 6, 4, 5, 7 }, // near
{ 2, 0, 4, 6 }, // left
+2 -3
View File
@@ -385,10 +385,9 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
const CameraInfo cameraInfo{ shadowMap.getCamera(), mainCameraInfo };
auto transaction = ShadowMap::open(driver);
ShadowMap::prepareCamera(transaction, engine, cameraInfo);
ShadowMap::prepareCamera(transaction, driver, cameraInfo);
ShadowMap::prepareViewport(transaction, shadowMap.getViewport());
ShadowMap::prepareTime(transaction, engine, userTime);
ShadowMap::prepareMaterialGlobals(transaction, view.getMaterialGlobals());
ShadowMap::prepareShadowMapping(transaction,
vsmShadowOptions.highPrecision);
shadowMap.commit(transaction, engine, driver);
@@ -413,7 +412,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
RenderPass const pass = passBuilder
.renderFlags(RenderPass::HAS_DEPTH_CLAMP, renderPassFlags)
.camera(cameraInfo.getPosition(), cameraInfo.getForwardVector())
.camera(cameraInfo)
.visibilityMask(entry.visibilityMask)
.geometry(scene->getRenderableData(), entry.range)
.commandTypeFlags(RenderPass::CommandTypeFlags::SHADOW)
+26 -26
View File
@@ -94,30 +94,6 @@ namespace filament {
using namespace backend;
using namespace filaflat;
namespace {
backend::Platform::DriverConfig getDriverConfig(FEngine* instance) {
return {
.handleArenaSize = instance->getRequestedDriverHandleArenaSize(),
.metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes,
.disableParallelShaderCompile = instance->features.backend.disable_parallel_shader_compile,
.disableHandleUseAfterFreeCheck =
instance->features.backend.disable_handle_use_after_free_check,
.disableHeapHandleTags = instance->features.backend.disable_heap_handle_tags,
.forceGLES2Context = instance->getConfig().forceGLES2Context,
.stereoscopicType = instance->getConfig().stereoscopicType,
.assertNativeWindowIsValid =
instance->features.backend.opengl.assert_native_window_is_valid,
.metalDisablePanicOnDrawableFailure =
instance->getConfig().metalDisablePanicOnDrawableFailure,
.gpuContextPriority = instance->getConfig().gpuContextPriority,
.vulkanEnableStagingBufferBypass =
instance->features.backend.vulkan.enable_staging_buffer_bypass,
};
}
} // anonymous
struct Engine::BuilderDetails {
Backend mBackend = Backend::DEFAULT;
Platform* mPlatform = nullptr;
@@ -155,7 +131,19 @@ Engine* FEngine::create(Builder const& builder) {
delete instance;
return nullptr;
}
instance->mDriver = platform->createDriver(sharedContext, getDriverConfig(instance));
DriverConfig const driverConfig{
.handleArenaSize = instance->getRequestedDriverHandleArenaSize(),
.metalUploadBufferSizeBytes = instance->getConfig().metalUploadBufferSizeBytes,
.disableParallelShaderCompile = instance->features.backend.disable_parallel_shader_compile,
.disableHandleUseAfterFreeCheck = instance->features.backend.disable_handle_use_after_free_check,
.disableHeapHandleTags = instance->features.backend.disable_heap_handle_tags,
.forceGLES2Context = instance->getConfig().forceGLES2Context,
.stereoscopicType = instance->getConfig().stereoscopicType,
.assertNativeWindowIsValid = instance->features.backend.opengl.assert_native_window_is_valid,
.metalDisablePanicOnDrawableFailure = instance->getConfig().metalDisablePanicOnDrawableFailure,
.gpuContextPriority = instance->getConfig().gpuContextPriority,
};
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
} else {
// start the driver thread
@@ -764,7 +752,19 @@ int FEngine::loop() {
JobSystem::setThreadName("FEngine::loop");
JobSystem::setThreadPriority(JobSystem::Priority::DISPLAY);
mDriver = mPlatform->createDriver(mSharedGLContext, getDriverConfig(this));
DriverConfig const driverConfig {
.handleArenaSize = getRequestedDriverHandleArenaSize(),
.metalUploadBufferSizeBytes = mConfig.metalUploadBufferSizeBytes,
.disableParallelShaderCompile = features.backend.disable_parallel_shader_compile,
.disableHandleUseAfterFreeCheck = features.backend.disable_handle_use_after_free_check,
.disableHeapHandleTags = features.backend.disable_heap_handle_tags,
.forceGLES2Context = mConfig.forceGLES2Context,
.stereoscopicType = mConfig.stereoscopicType,
.assertNativeWindowIsValid = features.backend.opengl.assert_native_window_is_valid,
.metalDisablePanicOnDrawableFailure = mConfig.metalDisablePanicOnDrawableFailure,
.gpuContextPriority = mConfig.gpuContextPriority,
};
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
mDriverBarrier.latch();
if (UTILS_UNLIKELY(!mDriver)) {
+4 -13
View File
@@ -737,12 +737,6 @@ public:
struct {
bool assert_native_window_is_valid = false;
} opengl;
struct {
// On Unified Memory Architecture device, it is possible to bypass using the staging
// buffer. This is an experimental feature that still needs to be implemented fully
// before it can be fully enabled.
bool enable_staging_buffer_bypass = false;
} vulkan;
bool disable_parallel_shader_compile = false;
bool disable_handle_use_after_free_check = false;
bool disable_heap_handle_tags = true; // FIXME: this should be false
@@ -768,21 +762,18 @@ public:
{ "engine.shadows.use_shadow_atlas",
"Uses an array of atlases to store shadow maps.",
&features.engine.shadows.use_shadow_atlas, false },
{ "engine.debug.assert_material_instance_in_use",
{ "features.engine.debug.assert_material_instance_in_use",
"Assert when a MaterialInstance is destroyed while it is in use by RenderableManager.",
&features.engine.debug.assert_material_instance_in_use, false },
{ "engine.debug.assert_destroy_material_before_material_instance",
{ "features.engine.debug.assert_destroy_material_before_material_instance",
"Assert when a Material is destroyed but its instances are still alive.",
&features.engine.debug.assert_destroy_material_before_material_instance, false },
{ "engine.debug.assert_vertex_buffer_count_exceeds_8",
{ "features.engine.debug.assert_vertex_buffer_count_exceeds_8",
"Assert when a client's number of buffers for a VertexBuffer exceeds 8.",
&features.engine.debug.assert_vertex_buffer_count_exceeds_8, false },
{ "engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4",
{ "features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4",
"Assert that the attribute stride of a vertex buffer is a multiple of 4.",
&features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4, false },
{ "backend.vulkan.enable_staging_buffer_bypass",
"vulkan: enable a staging bypass logic for unified memory architecture",
&features.backend.vulkan.enable_staging_buffer_bypass, false },
}};
utils::Slice<const FeatureFlag> getFeatureFlags() const noexcept {
+3 -3
View File
@@ -619,10 +619,10 @@ Program FMaterial::getProgramWithVariants(
.shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size())
.shaderLanguage(mMaterialParser->getShaderLanguage())
.diagnostics(mName,
[variant, vertexVariant, fragmentVariant](utils::CString const& name,
[this, variant, vertexVariant, fragmentVariant](
io::ostream& out) -> io::ostream& {
return out << name.c_str_safe() << ", variant=(" << io::hex << +variant.key
<< io::dec << "), vertexVariant=(" << io::hex
return out << mName.c_str_safe() << ", variant=(" << io::hex
<< +variant.key << io::dec << "), vertexVariant=(" << io::hex
<< +vertexVariant.key << io::dec << "), fragmentVariant=("
<< io::hex << +fragmentVariant.key << io::dec << ")";
});
+1 -3
View File
@@ -284,13 +284,11 @@ void FMaterialInstance::setParameterImpl(std::string_view const name,
auto const& descriptorSetLayout = mMaterial->getDescriptorSetLayout();
DescriptorType const descriptorType = descriptorSetLayout.getDescriptorType(binding);
TextureType const textureType = texture->getTextureType();
SamplerType const samplerType = texture->getTarget();
FILAMENT_CHECK_PRECONDITION(
DescriptorSet::isTextureCompatibleWithDescriptor(textureType, samplerType, descriptorType))
DescriptorSet::isTextureCompatibleWithDescriptor(textureType, descriptorType))
<< "Texture format " << int(texture->getFormat())
<< " of type " << to_string(textureType)
<< " with sampler type " << to_string(samplerType)
<< " is not compatible with material \"" << getMaterial()->getName().c_str() << "\""
<< " parameter \"" << name << "\""
<< " of type " << to_string(descriptorType);
+125 -81
View File
@@ -28,19 +28,9 @@
#include "details/Fence.h"
#include "details/Scene.h"
#include "details/SwapChain.h"
#include "details/Texture.h"
#include "details/View.h"
#include "ds/StructureDescriptorSet.h"
#include "fg/FrameGraph.h"
#include "fg/FrameGraphId.h"
#include "fg/FrameGraphResources.h"
#include "fg/FrameGraphTexture.h"
#include <private/filament/Variant.h>
#include <private/utils/Tracing.h>
#include <filament/Camera.h>
#include <filament/Fence.h>
#include <filament/Options.h>
@@ -51,17 +41,23 @@
#include <backend/Handle.h>
#include <backend/PixelBufferDescriptor.h>
#include "fg/FrameGraph.h"
#include "fg/FrameGraphId.h"
#include "fg/FrameGraphResources.h"
#include "fg/FrameGraphTexture.h"
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/mat4.h>
#include <utils/architecture.h>
#include <utils/Allocator.h>
#include <private/utils/Tracing.h>
#include <utils/JobSystem.h>
#include <utils/Logger.h>
#include <utils/Panic.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/ostream.h>
#include <chrono>
#include <limits>
@@ -202,7 +198,6 @@ TextureFormat FRenderer::getHdrFormat(const FView& view, bool const translucent)
return mHdrQualityHigh;
}
}
return mHdrQualityMedium;
}
TextureFormat FRenderer::getLdrFormat(bool const translucent) const noexcept {
@@ -235,7 +230,7 @@ void FRenderer::initializeClearFlags() noexcept {
mClearFlags = getClearFlags();
}
void FRenderer::setPresentationTime(int64_t const monotonic_clock_ns) const {
void FRenderer::setPresentationTime(int64_t const monotonic_clock_ns) {
FEngine::DriverApi& driver = mEngine.getDriverApi();
driver.setPresentationTime(monotonic_clock_ns);
}
@@ -244,23 +239,6 @@ void FRenderer::setVsyncTime(uint64_t const steadyClockTimeNano) noexcept {
mVsyncSteadyClockTimeNano = steadyClockTimeNano;
}
std::pair<float, float2> FRenderer::prepareUpscaler(float2 const scale,
TemporalAntiAliasingOptions const& taaOptions,
DynamicResolutionOptions const& dsrOptions) {
float bias = 0.0f;
float2 derivativesScale{ 1.0f };
if (dsrOptions.enabled && dsrOptions.quality >= QualityLevel::HIGH) {
bias = std::log2(std::min(scale.x, scale.y));
}
if (taaOptions.enabled) {
bias += taaOptions.lodBias;
if (taaOptions.upscaling) {
derivativesScale = 0.5f;
}
}
return { bias, derivativesScale };
}
void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
@@ -323,7 +301,7 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
using namespace std::chrono;
const steady_clock::time_point now{ steady_clock::now() };
const steady_clock::time_point userVsync{ steady_clock::duration(vsyncSteadyClockTimeNano) };
const time_point appVsync(vsyncSteadyClockTimeNano ? userVsync : now);
const time_point<steady_clock> appVsync(vsyncSteadyClockTimeNano ? userVsync : now);
mFrameId++;
mViewRenderedCount = 0;
@@ -363,7 +341,7 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
* to ignore the return value and render the frame anyway -- which is perfectly fine.
* The remaining work will be done when the first render() call is made.
*/
auto beginFrameInternal = [this, appVsync, swapChain] {
auto beginFrameInternal = [this, appVsync, swapChain]() {
FEngine& engine = mEngine;
FEngine::DriverApi& driver = engine.getDriverApi();
@@ -420,7 +398,7 @@ void FRenderer::endFrame() {
FEngine& engine = mEngine;
FEngine::DriverApi& driver = engine.getDriverApi();
if constexpr (UTILS_HAS_THREADING) {
if (UTILS_HAS_THREADING) {
// on debug builds this helps to catch cases where we're writing to
// the buffer form another thread, which is currently not allowed.
driver.debugThreading();
@@ -463,9 +441,7 @@ void FRenderer::endFrame() {
js.waitAndRelease(job);
}
void FRenderer::readPixels(
uint32_t const xoffset, uint32_t const yoffset,
uint32_t const width, uint32_t const height,
void FRenderer::readPixels(uint32_t const xoffset, uint32_t const yoffset, uint32_t const width, uint32_t const height,
PixelBufferDescriptor&& buffer) {
const bool withinFrame = mSwapChain != nullptr;
@@ -477,9 +453,8 @@ void FRenderer::readPixels(
xoffset, yoffset, width, height, std::move(buffer));
}
void FRenderer::readPixels(FRenderTarget const* renderTarget,
uint32_t const xoffset, uint32_t const yoffset,
uint32_t const width, uint32_t const height,
void FRenderer::readPixels(FRenderTarget* renderTarget,
uint32_t const xoffset, uint32_t const yoffset, uint32_t const width, uint32_t const height,
PixelBufferDescriptor&& buffer) {
// TODO: change the following to an assert when client call sites have addressed the issue.
@@ -816,29 +791,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
xvp.bottom = int32_t(guardBand);
}
auto [bias, derivativeScale] = prepareUpscaler(scale, taaOptions, dsrOptions);
view.prepare(engine, driver, rootArenaScope, svp, cameraInfo, getShaderUserTime(), needsAlphaChannel);
view.prepareLodBias(bias, derivativeScale);
// Set the PER_VIEW UBO for the passes that use the user materials, but are not the color pass
// (e.g. structure, ssao). The PER_VIEW UBO may be different because these passes don't run
// at the same resolution. We set only the values that are relevant to both user-materials
// and the concerned passes. The PER_VIEW UBO contains data that is needed by the user
// materials public APIs.
StructureDescriptorSet& descriptorSet = ppm.getStructureDescriptorSet();
descriptorSet.prepareCamera(engine, cameraInfo);
descriptorSet.prepareTime(engine, getShaderUserTime());
descriptorSet.prepareViewport(svp, {
int32_t(float(xvp.left) * aoOptions.resolution),
int32_t(float(xvp.bottom) * aoOptions.resolution),
uint32_t(float(xvp.width) * aoOptions.resolution),
uint32_t(float(xvp.height) * aoOptions.resolution) });
descriptorSet.prepareLodBias(bias, derivativeScale);
descriptorSet.prepareMaterialGlobals(view.getMaterialGlobals());
descriptorSet.commit(driver);
view.prepareUpscaler(scale, taaOptions, dsrOptions);
/*
* Allocate command buffer
@@ -980,9 +935,49 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
FView::updatePrimitivesLod(scene.getRenderableData(),
engine, cameraInfo, view.getVisibleRenderables());
passBuilder.camera(cameraInfo.getPosition(), cameraInfo.getForwardVector());
passBuilder.camera(cameraInfo);
passBuilder.geometry(scene.getRenderableData(), view.getVisibleRenderables());
// view set-ups that need to happen before rendering
fg.addTrivialSideEffectPass("Prepare View Uniforms",
[=, &view, &engine](DriverApi& driver) {
view.prepareCamera(engine, cameraInfo);
// The code here is a little fragile. In theory, we need to call prepareViewport()
// for each render pass, because the viewport parameters depend on the resolution.
// However, in practice, we only have two resolutions: the color pass resolution,
// and the structure pass which is governed by aoOptions.resolution (this could
// change in the future).
// So here we set the parameters for the structure pass and SSAO passes which
// are always done first. The SSR pass will also use these parameters which
// is wrong if it doesn't run at the same resolution as SSAO.
// prepareViewport() is called again during the color pass, which resets the
// values correctly for the Color pass, however, this will be again wrong
// for passes that come after the Color pass, such as DoF.
//
// The solution is to call prepareViewport() for each pass, really (so we should
// move it to its own UBO).
//
// The reason why this bug is acceptable is that the viewport parameters are
// currently only used for generating noise, so it's not too bad.
// note: aoOptions.resolution is either 1.0 or 0.5, and the result is then
// guaranteed to be an integer (because xvp is a multiple of 16).
view.prepareViewport(svp,
filament::Viewport{
int32_t(float(xvp.left ) * aoOptions.resolution),
int32_t(float(xvp.bottom) * aoOptions.resolution),
uint32_t(float(xvp.width ) * aoOptions.resolution),
uint32_t(float(xvp.height) * aoOptions.resolution)});
// this needs to reset the sampler that are only set in RendererUtils::colorPass(), because
// this descriptor-set is also used for ssr/picking/structure and these could be stale
// it would be better to use a separate desriptor-set for those two cases so that we don't
// have to do this
view.unbindSamplers(engine);
view.commitUniformsAndSamplers(driver);
});
// --------------------------------------------------------------------------------------------
// structure pass -- automatically culled if not used
// Currently it consists of a simple depth pass.
@@ -1003,20 +998,68 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
if (view.hasPicking()) {
if (view.isTransparentPickingEnabled()) {
picking = ppm.transparentPicking(fg,
passBuilder, renderFlags, svp.width, svp.height, aoOptions.resolution);
struct PickingRenderPassData {
FrameGraphId<FrameGraphTexture> depth;
FrameGraphId<FrameGraphTexture> picking;
};
auto& pickingRenderPass = fg.addPass<PickingRenderPassData>("Picking Render Pass",
[&](FrameGraph::Builder& builder, auto& data) {
bool const isFL0 = mEngine.getDriverApi().getFeatureLevel() ==
FeatureLevel::FEATURE_LEVEL_0;
// TODO: Specify the precision for picking pass
uint32_t const width = std::max(32u,
(uint32_t)std::ceil(float(svp.width) * aoOptions.resolution));
uint32_t const height = std::max(32u,
(uint32_t)std::ceil(float(svp.height) * aoOptions.resolution));
data.depth = builder.createTexture("Depth Buffer", {
.width = width, .height = height,
.format = isFL0 ? TextureFormat::DEPTH24 : TextureFormat::DEPTH32F });
data.depth = builder.write(data.depth,
FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
data.picking = builder.createTexture("Picking Buffer", {
.width = width, .height = height,
.format = isFL0 ? TextureFormat::RGBA8 : TextureFormat::RG32F });
data.picking = builder.write(data.picking,
FrameGraphTexture::Usage::COLOR_ATTACHMENT);
builder.declareRenderPass("Picking Render Target", {
.attachments = {.color = { data.picking }, .depth = data.depth },
.clearFlags = TargetBufferFlags::COLOR0 | TargetBufferFlags::DEPTH
});
},
[=, passBuilder = passBuilder](FrameGraphResources const& resources,
auto const& data, DriverApi& driver) mutable {
Variant pickingVariant(Variant::DEPTH_VARIANT);
pickingVariant.setPicking(true);
auto out = resources.getRenderPassInfo();
passBuilder.renderFlags(renderFlags);
passBuilder.variant(pickingVariant);
passBuilder.commandTypeFlags(RenderPass::CommandTypeFlags::DEPTH);
RenderPass const pass{ passBuilder.build(mEngine, driver) };
driver.beginRenderPass(out.target, out.params);
pass.getExecutor().execute(mEngine, driver);
driver.endRenderPass();
});
picking = pickingRenderPass->picking;
}
struct PickingResolvePassData {
FrameGraphId<FrameGraphTexture> picking;
};
fg.addPass<PickingResolvePassData>("Picking Resolve Pass",
fg.addPass<PickingResolvePassData>(
"Picking Resolve Pass",
[&](FrameGraph::Builder& builder, auto& data) {
// Note that BLIT_SRC is needed because this texture will be read later (via
// readPixels()).
data.picking = builder.read(picking,
FrameGraphTexture::Usage::COLOR_ATTACHMENT |
FrameGraphTexture::Usage::BLIT_SRC);
data.picking =
builder.read(picking, FrameGraphTexture::Usage::COLOR_ATTACHMENT |
FrameGraphTexture::Usage::BLIT_SRC);
builder.declareRenderPass("Picking Resolve Target", {
.attachments = { .color = { data.picking }}
});
@@ -1024,21 +1067,23 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
},
[=, &view](FrameGraphResources const& resources,
auto const&, DriverApi& driver) mutable {
auto [target, params] = resources.getRenderPassInfo();
view.executePickingQueries(driver, target, scale * aoOptions.resolution);
auto out = resources.getRenderPassInfo();
view.executePickingQueries(driver, out.target, scale * aoOptions.resolution);
});
}
// Store this frame's camera projection in the frame history.
// TODO: We do this after we're configured the structure and ssao passes;
// but I'm not 100% sure this should be done before or after.
if (UTILS_UNLIKELY(taaOptions.enabled)) {
// Apply the TAA jitter to everything after the structure pass, starting with the color pass.
ppm.TaaJitterCamera(svp, taaOptions, view.getFrameHistory(),
&FrameHistoryEntry::taa, &cameraInfo);
// this just re-set the color pass UBO content
view.prepareCamera(engine, cameraInfo);
fg.addTrivialSideEffectPass("Jitter Camera",
[&engine, &cameraInfo, &descriptorSet = view.getColorPassDescriptorSet()]
(DriverApi& driver) {
descriptorSet.prepareCamera(engine, cameraInfo);
descriptorSet.commit(driver);
});
}
// --------------------------------------------------------------------------------------------
@@ -1084,9 +1129,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// (i.e. it won't be culled, unless everything is culled), so no need to complexify things.
passBuilder.variant(variant);
// We need to specify the ColorPassDescriptorSet (which is in fact a collection of descriptor
// sets) because the layout can change based on the material used, and that's only known
// at execution time.
// This is optional, if not set, the per-view descriptor-set must be set before calling execute()
passBuilder.colorPassDescriptorSet(&view.getColorPassDescriptorSet());
// color-grading as subpass is done either by the color pass or the TAA pass if any
@@ -1098,7 +1141,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
passBuilder.customCommand(3,
RenderPass::Pass::BLENDED,
RenderPass::CustomCommand::EPILOG,
0, [&ppm, &driver, colorGradingConfigForColor] {
0, [&ppm, &driver, colorGradingConfigForColor]() {
ppm.colorGradingSubpass(driver, colorGradingConfigForColor);
});
} else if (colorGradingConfig.customResolve) {
@@ -1106,7 +1149,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
passBuilder.customCommand(3,
RenderPass::Pass::BLENDED,
RenderPass::CustomCommand::EPILOG,
0, [&ppm, &driver] {
0, [&ppm, &driver]() {
ppm.customResolveSubpass(driver);
});
}
@@ -1177,7 +1220,8 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
// We use a framegraph pass to wait for froxelization to finish (so it can be done
// in parallel with .compile()
if (auto sync = view.getFroxelizerSync()) {
auto sync = view.getFroxelizerSync();
if (sync) {
js.waitAndRelease(sync);
view.commitFroxels(driver);
}
+2 -6
View File
@@ -87,7 +87,7 @@ public:
void renderStandaloneView(FView const* view);
void setPresentationTime(int64_t monotonic_clock_ns) const;
void setPresentationTime(int64_t monotonic_clock_ns);
void setVsyncTime(uint64_t steadyClockTimeNano) noexcept;
@@ -108,7 +108,7 @@ public:
backend::PixelBufferDescriptor&& buffer);
// read pixel from a rendertarget. must be called between beginFrame/enfFrame.
void readPixels(FRenderTarget const* renderTarget,
void readPixels(FRenderTarget* renderTarget,
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
backend::PixelBufferDescriptor&& buffer);
@@ -189,10 +189,6 @@ private:
void renderInternal(FView const* view);
void renderJob(RootArenaScope& rootArenaScope, FView& view);
std::pair<float, math::float2> prepareUpscaler(math::float2 scale,
TemporalAntiAliasingOptions const& taaOptions,
DynamicResolutionOptions const& dsrOptions);
// keep a reference to our engine
FEngine& mEngine;
FrameSkipper mFrameSkipper;
+42 -48
View File
@@ -16,15 +16,11 @@
#include "details/View.h"
#include "Allocators.h"
#include "Culler.h"
#include "DebugRegistry.h"
#include "FrameHistory.h"
#include "FrameInfo.h"
#include "Froxelizer.h"
#include "RenderPrimitive.h"
#include "ResourceAllocator.h"
#include "ShadowMap.h"
#include "ShadowMapManager.h"
#include "details/Engine.h"
@@ -34,15 +30,9 @@
#include "details/Scene.h"
#include "details/Skybox.h"
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <fg/FrameGraphTexture.h>
#include <fg/FrameGraphId.h>
#include <filament/Exposure.h>
#include <filament/Frustum.h>
#include <filament/DebugRegistry.h>
#include <filament/TextureSampler.h>
#include <filament/View.h>
#include <private/filament/UibStructs.h>
@@ -50,29 +40,18 @@
#include <private/utils/Tracing.h>
#include <utils/architecture.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Panic.h>
#include <utils/Profiler.h>
#include <utils/Slice.h>
#include <utils/Zip2Iterator.h>
#include <math/mat3.h>
#include <math/mat4.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <math/scalar.h>
#include <assert.h>
#include <math/fast.h>
#include <array>
#include <algorithm>
#include <cmath>
#include <chrono>
#include <functional>
#include <memory>
#include <ratio>
#include <utility>
#include <tuple>
using namespace utils;
@@ -135,7 +114,7 @@ FView::FView(FEngine& engine)
#ifndef NDEBUG
// This can fail if another view has already registered this data source
mDebugState->owner = debugRegistry.registerDataSource("d.view.frame_info",
[weak = std::weak_ptr(mDebugState)]() -> DebugRegistry::DataSource {
[weak = std::weak_ptr<DebugState>(mDebugState)]() -> DebugRegistry::DataSource {
// the View could have been destroyed by the time we do this
auto const state = weak.lock();
if (!state) {
@@ -221,8 +200,8 @@ void FView::terminate(FEngine& engine) {
void FView::setViewport(filament::Viewport const& viewport) noexcept {
// catch the cases were user had an underflow and didn't catch it.
assert(int32_t(viewport.width) > 0);
assert(int32_t(viewport.height) > 0);
assert((int32_t)viewport.width > 0);
assert((int32_t)viewport.height > 0);
mViewport = viewport;
}
@@ -295,7 +274,7 @@ float2 FView::updateScale(FEngine& engine,
const float dt = 1.0f; // we don't really need dt here, setting it to 1, means our parameters are in "frames"
const float target = (1000.0f * float(frameRateOptions.interval)) / displayInfo.refreshRate;
const float targetWithHeadroom = target * (1.0f - frameRateOptions.headRoomRatio);
float const measured = duration{ info.denoisedFrameTime }.count();
float const measured = duration<float, std::milli>{ info.denoisedFrameTime }.count();
float const out = mPidController.update(measured / targetWithHeadroom, 1.0f, dt);
// maps pid command to a scale (absolute or relative, see below)
@@ -454,7 +433,7 @@ void FView::prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableD
if (builder.hasShadowMaps()) {
ShadowMapManager::createIfNeeded(engine, mShadowMapManager);
auto const shadowTechnique = mShadowMapManager->update(builder, engine, *this,
auto shadowTechnique = mShadowMapManager->update(builder, engine, *this,
cameraInfo, renderableData, lightData);
mHasShadowing = any(shadowTechnique);
@@ -514,7 +493,7 @@ void FView::prepareLighting(FEngine& engine, CameraInfo const& cameraInfo) noexc
getColorPassDescriptorSet().prepareDirectionalLight(engine, exposure, sceneSpaceDirection, directionalLight);
}
CameraInfo FView::computeCameraInfo(FEngine const& engine) const noexcept {
CameraInfo FView::computeCameraInfo(FEngine& engine) const noexcept {
FScene const* const scene = getScene();
/*
@@ -567,14 +546,15 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren
// In the common case when we don't have a viewing camera, cameraInfo.view is
// already the culling view matrix
return Frustum{ mat4f{ highPrecisionMultiply(cameraInfo.cullingProjection, cameraInfo.view) }};
} else {
// Otherwise, we need to recalculate it from the culling camera.
// Note: it is correct to always do the math from mCullingCamera, but it hides the
// intent of the code, which is that we should only depend on CameraInfo here.
// This is an extremely uncommon case.
const mat4 projection = mCullingCamera->getCullingProjectionMatrix();
const mat4 view = inverse(cameraInfo.worldTransform * mCullingCamera->getModelMatrix());
return Frustum{ mat4f{ projection * view }};
}
// Otherwise, we need to recalculate it from the culling camera.
// Note: it is correct to always do the math from mCullingCamera, but it hides the
// intent of the code, which is that we should only depend on CameraInfo here.
// This is an extremely uncommon case.
const mat4 projection = mCullingCamera->getCullingProjectionMatrix();
const mat4 view = inverse(cameraInfo.worldTransform * mCullingCamera->getModelMatrix());
return Frustum{ mat4f{ projection * view }};
};
const Frustum cullingFrustum = getFrustum();
@@ -619,9 +599,9 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren
setFroxelizerSync(froxelizeLightsJob);
Range merged;
FScene::RenderableSoa& renderableData = scene->getRenderableData();
{ // all the operations in this scope must happen sequentially
FScene::RenderableSoa& renderableData = scene->getRenderableData();
Slice<Culler::result_type> cullingMask = renderableData.slice<FScene::VISIBLE_MASK>();
std::uninitialized_fill(cullingMask.begin(), cullingMask.end(), 0);
@@ -666,7 +646,7 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren
}
// We need to pass viewMatrix by value here because it extends the scope of this
// function.
std::function froxelizerWork =
std::function<void(JobSystem&, JobSystem::Job*)> froxelizerWork =
[&froxelizer = mFroxelizer, &engine, viewMatrix = cameraInfo.view, &lightData]
(JobSystem&, JobSystem::Job*) {
froxelizer.froxelizeLights(engine, viewMatrix, lightData);
@@ -874,7 +854,6 @@ void FView::prepare(FEngine& engine, DriverApi& driver, RootArenaScope& rootAren
auto const fogTransform = tcm.getWorldTransformAccurate(tcm.getInstance(mFogEntity));
auto& colorPassDescriptorSet = getColorPassDescriptorSet();
colorPassDescriptorSet.prepareCamera(engine, cameraInfo);
colorPassDescriptorSet.prepareTime(engine, userTime);
colorPassDescriptorSet.prepareFog(engine, cameraInfo, fogTransform, mFogOptions,
scene->getIndirectLight());
@@ -926,15 +905,29 @@ UTILS_NOINLINE
});
}
void FView::prepareUpscaler(float2 const scale,
TemporalAntiAliasingOptions const& taaOptions,
DynamicResolutionOptions const& dsrOptions) const noexcept {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
float bias = 0.0f;
float2 derivativesScale{ 1.0f };
if (dsrOptions.enabled && dsrOptions.quality >= QualityLevel::HIGH) {
bias = std::log2(std::min(scale.x, scale.y));
}
if (taaOptions.enabled) {
bias += taaOptions.lodBias;
if (taaOptions.upscaling) {
derivativesScale = 0.5f;
}
}
getColorPassDescriptorSet().prepareLodBias(bias, derivativesScale);
}
void FView::prepareCamera(FEngine& engine, const CameraInfo& cameraInfo) const noexcept {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
getColorPassDescriptorSet().prepareCamera(engine, cameraInfo);
}
void FView::prepareLodBias(float const bias, float2 const derivativesScale) const noexcept {
getColorPassDescriptorSet().prepareLodBias(bias, derivativesScale);
}
void FView::prepareViewport(
const filament::Viewport& physicalViewport,
const filament::Viewport& logicalViewport) const noexcept {
@@ -1127,7 +1120,7 @@ void FView::prepareVisibleLights(FLightManager const& lcm,
computeLightCameraDistances(distances, viewMatrix, spheres, visibleLightCount);
// skip directional light
Zip2Iterator b = { lightData.begin(), distances };
Zip2Iterator<FScene::LightSoa::iterator, float*> b = { lightData.begin(), distances };
std::sort(b + FScene::DIRECTIONAL_LIGHTS_COUNT, b + visibleLightCount,
[](auto const& lhs, auto const& rhs) { return lhs.second < rhs.second; });
}
@@ -1297,8 +1290,8 @@ void FView::setAmbientOcclusionOptions(AmbientOcclusionOptions options) noexcept
options.ssct.lightDirection = normalize(options.ssct.lightDirection);
options.ssct.depthBias = std::max(0.0f, options.ssct.depthBias);
options.ssct.depthSlopeBias = std::max(0.0f, options.ssct.depthSlopeBias);
options.ssct.sampleCount = clamp(unsigned(options.ssct.sampleCount), 1u, 255u);
options.ssct.rayCount = clamp(unsigned(options.ssct.rayCount), 1u, 255u);
options.ssct.sampleCount = clamp((unsigned)options.ssct.sampleCount, 1u, 255u);
options.ssct.rayCount = clamp((unsigned)options.ssct.rayCount, 1u, 255u);
mAmbientOcclusionOptions = options;
}
void FView::setVsmShadowOptions(VsmShadowOptions options) noexcept {
@@ -1326,6 +1319,7 @@ void FView::setFogOptions(FogOptions options) noexcept {
options.maximumOpacity = clamp(options.maximumOpacity, 0.0f, 1.0f);
options.density = std::max(0.0f, options.density);
options.heightFalloff = std::max(0.0f, options.heightFalloff);
options.inScatteringSize = options.inScatteringSize;
options.inScatteringStart = std::max(0.0f, options.inScatteringStart);
mFogOptions = options;
}
+11 -13
View File
@@ -102,7 +102,6 @@ class FScene;
class FView : public View {
public:
using MaterialGlobals = std::array<math::float4, 4>;
using Range = utils::Range<uint32_t>;
explicit FView(FEngine& engine);
@@ -110,7 +109,7 @@ public:
void terminate(FEngine& engine);
CameraInfo computeCameraInfo(FEngine const& engine) const noexcept;
CameraInfo computeCameraInfo(FEngine& engine) const noexcept;
// note: viewport/cameraInfo are passed by value to make it clear that prepare cannot
// keep references on them that would outlive the scope of prepare() (e.g. with JobSystem).
@@ -160,10 +159,11 @@ public:
return mName.c_str_safe();
}
void prepareUpscaler(math::float2 scale,
TemporalAntiAliasingOptions const& taaOptions,
DynamicResolutionOptions const& dsrOptions) const noexcept;
void prepareCamera(FEngine& engine, const CameraInfo& cameraInfo) const noexcept;
void prepareLodBias(float bias, math::float2 derivativesScale) const noexcept;
void prepareViewport(
const Viewport& physicalViewport,
const Viewport& logicalViewport) const noexcept;
@@ -483,8 +483,6 @@ public:
return mFrameGraphViewerViewHandle;
}
MaterialGlobals getMaterialGlobals() const { return mMaterialGlobals; }
private:
struct FPickingQuery : public PickingQuery {
private:
@@ -611,14 +609,14 @@ private:
std::unique_ptr<ShadowMapManager> mShadowMapManager;
MaterialGlobals mMaterialGlobals = {{
{ 0, 0, 0, 1 },
{ 0, 0, 0, 1 },
{ 0, 0, 0, 1 },
{ 0, 0, 0, 1 },
}};
std::array<math::float4, 4> mMaterialGlobals = {{
{ 0, 0, 0, 1 },
{ 0, 0, 0, 1 },
{ 0, 0, 0, 1 },
{ 0, 0, 0, 1 },
}};
fgviewer::ViewHandle mFrameGraphViewerViewHandle{};
fgviewer::ViewHandle mFrameGraphViewerViewHandle;
#ifndef NDEBUG
struct DebugState {
+71 -7
View File
@@ -16,9 +16,7 @@
#include "ColorPassDescriptorSet.h"
#include "Froxelizer.h"
#include "PerViewDescriptorSetUtils.h"
#include "HwDescriptorSetLayoutFactory.h"
#include "ShadowMapManager.h"
#include "TypedUniformBuffer.h"
@@ -146,11 +144,44 @@ void ColorPassDescriptorSet::terminate(HwDescriptorSetLayoutFactory& factory, Dr
}
void ColorPassDescriptorSet::prepareCamera(FEngine& engine, const CameraInfo& camera) noexcept {
PerViewDescriptorSetUtils::prepareCamera(mUniforms.edit(), engine, camera);
mat4f const& viewFromWorld = camera.view;
mat4f const& worldFromView = camera.model;
mat4f const& clipFromView = camera.projection;
const mat4f viewFromClip{ inverse((mat4)camera.projection) };
const mat4f worldFromClip{ highPrecisionMultiply(worldFromView, viewFromClip) };
auto& s = mUniforms.edit();
s.viewFromWorldMatrix = viewFromWorld; // view
s.worldFromViewMatrix = worldFromView; // model
s.clipFromViewMatrix = clipFromView; // projection
s.viewFromClipMatrix = viewFromClip; // 1/projection
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldTransform));
s.clipTransform = camera.clipTransform;
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
mat4f const& headFromWorld = camera.view;
Engine::Config const& config = engine.getConfig();
for (int i = 0; i < config.stereoscopicEyeCount; i++) {
mat4f const& eyeFromHead = camera.eyeFromView[i]; // identity for monoscopic rendering
mat4f const& clipFromEye = camera.eyeProjection[i];
// clipFromEye * eyeFromHead * headFromWorld
s.clipFromWorldMatrix[i] = highPrecisionMultiply(
clipFromEye, highPrecisionMultiply(eyeFromHead, headFromWorld));
}
// with a clip-space of [-w, w] ==> z' = -z
// with a clip-space of [0, w] ==> z' = (w - z)/2
s.clipControl = engine.getDriverApi().getClipSpaceParams();
}
void ColorPassDescriptorSet::prepareLodBias(float const bias, float2 const derivativesScale) noexcept {
PerViewDescriptorSetUtils::prepareLodBias(mUniforms.edit(), bias, derivativesScale);
auto& s = mUniforms.edit();
s.lodBias = bias;
s.derivativesScale = derivativesScale;
}
void ColorPassDescriptorSet::prepareExposure(float const ev100) noexcept {
@@ -163,11 +194,22 @@ void ColorPassDescriptorSet::prepareExposure(float const ev100) noexcept {
void ColorPassDescriptorSet::prepareViewport(
const filament::Viewport& physicalViewport,
const filament::Viewport& logicalViewport) noexcept {
PerViewDescriptorSetUtils::prepareViewport(mUniforms.edit(), physicalViewport, logicalViewport);
float4 const physical{ physicalViewport.left, physicalViewport.bottom,
physicalViewport.width, physicalViewport.height };
float4 const logical{ logicalViewport.left, logicalViewport.bottom,
logicalViewport.width, logicalViewport.height };
auto& s = mUniforms.edit();
s.resolution = { physical.zw, 1.0f / physical.zw };
s.logicalViewportScale = physical.zw / logical.zw;
s.logicalViewportOffset = -logical.xy / logical.zw;
}
void ColorPassDescriptorSet::prepareTime(FEngine& engine, float4 const& userTime) noexcept {
PerViewDescriptorSetUtils::prepareTime(mUniforms.edit(), engine, userTime);
auto& s = mUniforms.edit();
const uint64_t oneSecondRemainder = engine.getEngineTime().count() % 1000000000;
const float fraction = float(double(oneSecondRemainder) / 1000000000.0);
s.time = fraction;
s.userTime = userTime;
}
void ColorPassDescriptorSet::prepareTemporalNoise(FEngine& engine,
@@ -282,7 +324,10 @@ void ColorPassDescriptorSet::prepareBlending(bool const needsAlphaChannel) noexc
void ColorPassDescriptorSet::prepareMaterialGlobals(
std::array<float4, 4> const& materialGlobals) noexcept {
PerViewDescriptorSetUtils::prepareMaterialGlobals(mUniforms.edit(), materialGlobals);
mUniforms.edit().custom[0] = materialGlobals[0];
mUniforms.edit().custom[1] = materialGlobals[1];
mUniforms.edit().custom[2] = materialGlobals[2];
mUniforms.edit().custom[3] = materialGlobals[3];
}
void ColorPassDescriptorSet::prepareSSR(Handle<HwTexture> ssr,
@@ -300,6 +345,25 @@ void ColorPassDescriptorSet::prepareSSR(Handle<HwTexture> ssr,
s.ssrDistance = (ssrOptions.enabled && !disableSSR) ? ssrOptions.maxDistance : 0.0f;
}
void ColorPassDescriptorSet::prepareHistorySSR(Handle<HwTexture> ssr,
mat4f const& historyProjection,
mat4f const& uvFromViewMatrix,
ScreenSpaceReflectionsOptions const& ssrOptions) noexcept {
setSampler(+PerViewBindingPoints::SSR, ssr, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR
});
auto& s = mUniforms.edit();
s.ssrReprojection = historyProjection;
s.ssrUvFromViewMatrix = uvFromViewMatrix;
s.ssrThickness = ssrOptions.thickness;
s.ssrBias = ssrOptions.bias;
s.ssrDistance = ssrOptions.enabled ? ssrOptions.maxDistance : 0.0f;
s.ssrStride = ssrOptions.stride;
}
void ColorPassDescriptorSet::prepareStructure(Handle<HwTexture> structure) noexcept {
// sampler must be NEAREST
setSampler(+PerViewBindingPoints::STRUCTURE, structure, {});
+5
View File
@@ -117,6 +117,11 @@ public:
float refractionLodOffset,
ScreenSpaceReflectionsOptions const& ssrOptions) noexcept;
void prepareHistorySSR(TextureHandle ssr,
math::mat4f const& historyProjection,
math::mat4f const& uvFromViewMatrix,
ScreenSpaceReflectionsOptions const& ssrOptions) noexcept;
void prepareShadowMapping(backend::BufferObjectHandle shadowUniforms, bool highPrecision) noexcept;
void prepareDirectionalLight(FEngine& engine, float exposure,
+3 -33
View File
@@ -73,7 +73,8 @@ DescriptorSet& DescriptorSet::operator=(DescriptorSet&& rhs) noexcept {
void DescriptorSet::terminate(FEngine::DriverApi& driver) noexcept {
if (mDescriptorSetHandle) {
driver.destroyDescriptorSet(std::move(mDescriptorSetHandle));
driver.destroyDescriptorSet(mDescriptorSetHandle);
mDescriptorSetHandle.clear();
}
}
@@ -197,40 +198,9 @@ DescriptorSet DescriptorSet::duplicate(
return set;
}
bool DescriptorSet::isTextureCompatibleWithDescriptor(
backend::TextureType t, backend::SamplerType s, backend::DescriptorType d) noexcept {
backend::TextureType t, backend::DescriptorType d) noexcept {
using namespace backend;
switch (s) {
case SamplerType::SAMPLER_2D:
if (!is2dTypeDescriptor(d)) {
return false;
}
break;
case SamplerType::SAMPLER_2D_ARRAY:
if (!is2dArrayTypeDescriptor(d)) {
return false;
}
break;
case SamplerType::SAMPLER_CUBEMAP:
if (!isCubeTypeDescriptor(d)) {
return false;
}
break;
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
if (!isCubeArrayTypeDescriptor(d)) {
return false;
}
break;
case SamplerType::SAMPLER_3D:
if (!is3dTypeDescriptor(d)) {
return false;
}
break;
case SamplerType::SAMPLER_EXTERNAL:
break;
}
// check that the descriptor type is compatible with the texture format type
switch (d) {
case DescriptorType::SAMPLER_2D_FLOAT:
case DescriptorType::SAMPLER_2D_ARRAY_FLOAT:
+1 -2
View File
@@ -86,8 +86,7 @@ public:
}
static bool isTextureCompatibleWithDescriptor(
backend::TextureType t, backend::SamplerType s,
backend::DescriptorType d) noexcept;
backend::TextureType t, backend::DescriptorType d) noexcept;
private:
struct Desc {
@@ -1,110 +0,0 @@
/*
* 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 "PerViewDescriptorSetUtils.h"
#include "details/Camera.h"
#include "details/Engine.h"
#include <private/filament/UibStructs.h>
#include <filament/Engine.h>
#include <filament/Viewport.h>
#include <backend/DriverEnums.h>
#include <math/mat4.h>
#include <math/vec4.h>
#include <array>
#include <stdint.h>
namespace filament {
using namespace backend;
using namespace math;
void PerViewDescriptorSetUtils::prepareCamera(PerViewUib& s,
FEngine const& engine, const CameraInfo& camera) noexcept {
mat4f const& viewFromWorld = camera.view;
mat4f const& worldFromView = camera.model;
mat4f const& clipFromView = camera.projection;
const mat4f viewFromClip{ inverse((mat4)camera.projection) };
const mat4f worldFromClip{ highPrecisionMultiply(worldFromView, viewFromClip) };
s.viewFromWorldMatrix = viewFromWorld; // view
s.worldFromViewMatrix = worldFromView; // model
s.clipFromViewMatrix = clipFromView; // projection
s.viewFromClipMatrix = viewFromClip; // 1/projection
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldTransform));
s.clipTransform = camera.clipTransform;
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
mat4f const& headFromWorld = camera.view;
Engine::Config const& config = engine.getConfig();
for (int i = 0; i < config.stereoscopicEyeCount; i++) {
mat4f const& eyeFromHead = camera.eyeFromView[i]; // identity for monoscopic rendering
mat4f const& clipFromEye = camera.eyeProjection[i];
// clipFromEye * eyeFromHead * headFromWorld
s.clipFromWorldMatrix[i] = highPrecisionMultiply(
clipFromEye, highPrecisionMultiply(eyeFromHead, headFromWorld));
}
// with a clip-space of [-w, w] ==> z' = -z
// with a clip-space of [0, w] ==> z' = (w - z)/2
s.clipControl = const_cast<FEngine&>(engine).getDriverApi().getClipSpaceParams();
}
void PerViewDescriptorSetUtils::prepareLodBias(PerViewUib& s, float bias,
float2 derivativesScale) noexcept {
s.lodBias = bias;
s.derivativesScale = derivativesScale;
}
void PerViewDescriptorSetUtils::prepareViewport(PerViewUib& s,
backend::Viewport const& physicalViewport,
backend::Viewport const& logicalViewport) noexcept {
float4 const physical{ physicalViewport.left, physicalViewport.bottom,
physicalViewport.width, physicalViewport.height };
float4 const logical{ logicalViewport.left, logicalViewport.bottom,
logicalViewport.width, logicalViewport.height };
s.resolution = { physical.zw, 1.0f / physical.zw };
s.logicalViewportScale = physical.zw / logical.zw;
s.logicalViewportOffset = -logical.xy / logical.zw;
}
void PerViewDescriptorSetUtils::prepareTime(PerViewUib& s,
FEngine const& engine, float4 const& userTime) noexcept {
const uint64_t oneSecondRemainder = engine.getEngineTime().count() % 1000000000;
const float fraction = float(double(oneSecondRemainder) / 1000000000.0);
s.time = fraction;
s.userTime = userTime;
}
void PerViewDescriptorSetUtils::prepareMaterialGlobals(PerViewUib& s,
std::array<float4, 4> const& materialGlobals) noexcept {
s.custom[0] = materialGlobals[0];
s.custom[1] = materialGlobals[1];
s.custom[2] = materialGlobals[2];
s.custom[3] = materialGlobals[3];
}
} // namespace filament
@@ -1,53 +0,0 @@
/*
* 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.
*/
#pragma once
#include <private/filament/UibStructs.h>
#include <math/vec2.h>
#include <math/vec4.h>
#include <array>
namespace filament {
namespace backend {
struct Viewport;
} // namespace backend
class FEngine;
struct CameraInfo;
class PerViewDescriptorSetUtils {
public:
static void prepareCamera(PerViewUib& uniforms,
FEngine const& engine, const CameraInfo& camera) noexcept;
static void prepareLodBias(PerViewUib& uniforms,
float bias, math::float2 derivativesScale) noexcept;
static void prepareViewport(PerViewUib& uniforms,
backend::Viewport const& physicalViewport,
backend::Viewport const& logicalViewport) noexcept;
static void prepareTime(PerViewUib& uniforms,
FEngine const& engine, math::float4 const& userTime) noexcept;
static void prepareMaterialGlobals(PerViewUib& uniforms,
std::array<math::float4, 4> const& materialGlobals) noexcept;
};
} //namespace filament
+40 -17
View File
@@ -16,21 +16,20 @@
#include "ShadowMapDescriptorSet.h"
#include "PerViewDescriptorSetUtils.h"
#include "details/Camera.h"
#include "details/Engine.h"
#include <private/filament/EngineEnums.h>
#include <private/filament/DescriptorSets.h>
#include <private/filament/UibStructs.h>
#include <backend/DriverEnums.h>
#include <utils/debug.h>
#include <math/vec4.h>
#include <math/mat4.h>
#include <array>
#include <stdint.h>
namespace filament {
@@ -63,30 +62,54 @@ PerViewUib& ShadowMapDescriptorSet::edit(Transaction const& transaction) noexcep
}
void ShadowMapDescriptorSet::prepareCamera(Transaction const& transaction,
FEngine const& engine, const CameraInfo& camera) noexcept {
PerViewDescriptorSetUtils::prepareCamera(edit(transaction), engine, camera);
// TODO: stereo values didn't used to be set
DriverApi& driver, const CameraInfo& camera) noexcept {
mat4f const& viewFromWorld = camera.view;
mat4f const& worldFromView = camera.model;
mat4f const& clipFromView = camera.projection;
const mat4f viewFromClip{ inverse((mat4)camera.projection) };
const mat4f clipFromWorld{ highPrecisionMultiply(clipFromView, viewFromWorld) };
const mat4f worldFromClip{ highPrecisionMultiply(worldFromView, viewFromClip) };
auto& s = edit(transaction);
s.viewFromWorldMatrix = viewFromWorld; // view
s.worldFromViewMatrix = worldFromView; // model
s.clipFromViewMatrix = clipFromView; // projection
s.viewFromClipMatrix = viewFromClip; // 1/projection
s.clipFromWorldMatrix[0] = clipFromWorld; // projection * view
s.worldFromClipMatrix = worldFromClip; // 1/(projection * view)
s.userWorldFromWorldMatrix = mat4f(inverse(camera.worldTransform));
s.clipTransform = camera.clipTransform;
s.cameraFar = camera.zf;
s.oneOverFarMinusNear = 1.0f / (camera.zf - camera.zn);
s.nearOverFarMinusNear = camera.zn / (camera.zf - camera.zn);
// with a clip-space of [-w, w] ==> z' = -z
// with a clip-space of [0, w] ==> z' = (w - z)/2
s.clipControl = driver.getClipSpaceParams();
}
void ShadowMapDescriptorSet::prepareLodBias(Transaction const& transaction, float const bias) noexcept {
PerViewDescriptorSetUtils::prepareLodBias(edit(transaction), bias, 0);
// TODO: check why derivativesScale was missing
auto& s = edit(transaction);
s.lodBias = bias;
}
void ShadowMapDescriptorSet::prepareViewport(Transaction const& transaction,
backend::Viewport const& viewport) noexcept {
PerViewDescriptorSetUtils::prepareViewport(edit(transaction), viewport, viewport);
// TODO: offset calculation is now different
float2 const dimensions{ viewport.width, viewport.height };
auto& s = edit(transaction);
s.resolution = { dimensions, 1.0f / dimensions };
s.logicalViewportScale = 1.0f;
s.logicalViewportOffset = 0.0f;
}
void ShadowMapDescriptorSet::prepareTime(Transaction const& transaction,
FEngine const& engine, float4 const& userTime) noexcept {
PerViewDescriptorSetUtils::prepareTime(edit(transaction), engine, userTime);
}
void ShadowMapDescriptorSet::prepareMaterialGlobals(Transaction const& transaction,
std::array<float4, 4> const& materialGlobals) noexcept {
PerViewDescriptorSetUtils::prepareMaterialGlobals(edit(transaction), materialGlobals);
auto& s = edit(transaction);
const uint64_t oneSecondRemainder = engine.getEngineTime().count() % 1'000'000'000;
const float fraction = float(double(oneSecondRemainder) / 1'000'000'000.0);
s.time = fraction;
s.userTime = userTime;
}
void ShadowMapDescriptorSet::prepareShadowMapping(Transaction const& transaction,
+1 -8
View File
@@ -29,8 +29,6 @@
#include <math/vec4.h>
#include <array>
namespace filament {
struct CameraInfo;
@@ -57,10 +55,8 @@ public:
void terminate(backend::DriverApi& driver);
// All UBO values that can affect user code must be set here
static void prepareCamera(Transaction const& transaction,
FEngine const& engine, const CameraInfo& camera) noexcept;
backend::DriverApi& driver, const CameraInfo& camera) noexcept;
static void prepareLodBias(Transaction const& transaction,
float bias) noexcept;
@@ -71,9 +67,6 @@ public:
static void prepareTime(Transaction const& transaction,
FEngine const& engine, math::float4 const& userTime) noexcept;
static void prepareMaterialGlobals(Transaction const& transaction,
std::array<math::float4, 4> const& materialGlobals) noexcept;
static void prepareShadowMapping(Transaction const& transaction,
bool highPrecision) noexcept;
+1 -1
View File
@@ -76,7 +76,7 @@ void SsrPassDescriptorSet::prepareHistorySSR(FEngine const& engine, Handle<HwTex
ScreenSpaceReflectionsOptions const& ssrOptions) noexcept {
mDescriptorSet.setSampler(engine.getPerViewDescriptorSetLayoutSsrVariant(),
+PerViewBindingPoints::SSR_HISTORY, ssr, {
+PerViewBindingPoints::SSR, ssr, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR
});
-101
View File
@@ -1,101 +0,0 @@
/*
* 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 "StructureDescriptorSet.h"
#include "PerViewDescriptorSetUtils.h"
#include "details/Camera.h"
#include "details/Engine.h"
#include <private/filament/EngineEnums.h>
#include <private/filament/UibStructs.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <math/vec2.h>
#include <math/vec4.h>
#include <array>
namespace filament {
using namespace backend;
using namespace math;
StructureDescriptorSet::StructureDescriptorSet() noexcept = default;
StructureDescriptorSet::~StructureDescriptorSet() noexcept {
assert_invariant(!mDescriptorSet.getHandle());
}
void StructureDescriptorSet::init(FEngine& engine) noexcept {
mUniforms.init(engine.getDriverApi());
mDescriptorSetLayout = &engine.getPerViewDescriptorSetLayoutDepthVariant();
// create the descriptor-set from the layout
mDescriptorSet = DescriptorSet{
"StructureDescriptorSet", *mDescriptorSetLayout };
// initialize the descriptor-set
mDescriptorSet.setBuffer(*mDescriptorSetLayout, +PerViewBindingPoints::FRAME_UNIFORMS,
mUniforms.getUboHandle(), 0, sizeof(PerViewUib));
}
void StructureDescriptorSet::terminate(DriverApi& driver) {
mDescriptorSet.terminate(driver);
mUniforms.terminate(driver);
}
void StructureDescriptorSet::commit(DriverApi& driver) noexcept {
assert_invariant(mDescriptorSetLayout);
driver.updateBufferObject(mUniforms.getUboHandle(),
mUniforms.toBufferDescriptor(driver), 0);
mDescriptorSet.commit(*mDescriptorSetLayout, driver);
}
void StructureDescriptorSet::bind(DriverApi& driver) const noexcept {
mDescriptorSet.bind(driver, DescriptorSetBindingPoints::PER_VIEW);
}
void StructureDescriptorSet::prepareViewport(
backend::Viewport const& physicalViewport,
backend::Viewport const& logicalViewport) noexcept {
PerViewDescriptorSetUtils::prepareViewport(mUniforms.edit(), physicalViewport, logicalViewport);
}
void StructureDescriptorSet::prepareCamera(FEngine const& engine,
CameraInfo const& camera) noexcept {
PerViewDescriptorSetUtils::prepareCamera(mUniforms.edit(), engine, camera);
}
void StructureDescriptorSet::prepareLodBias(float bias, float2 const derivativesScale) noexcept {
PerViewDescriptorSetUtils::prepareLodBias(mUniforms.edit(), bias, derivativesScale);
}
void StructureDescriptorSet::prepareTime(FEngine const& engine, float4 const& userTime) noexcept {
PerViewDescriptorSetUtils::prepareTime(mUniforms.edit(), engine, userTime);
}
void StructureDescriptorSet::prepareMaterialGlobals(
std::array<float4, 4> const& materialGlobals) noexcept {
PerViewDescriptorSetUtils::prepareMaterialGlobals(mUniforms.edit(), materialGlobals);
}
} // namespace filament
-72
View File
@@ -1,72 +0,0 @@
/*
* 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.
*/
#pragma once
#include "DescriptorSet.h"
#include "TypedUniformBuffer.h"
#include <private/filament/UibStructs.h>
#include <math/vec2.h>
#include <math/vec4.h>
#include <array>
namespace filament {
namespace backend {
struct Viewport;
} // namespace backend
class FEngine;
class DescriptorSetLayout;
struct CameraInfo;
class StructureDescriptorSet {
public:
StructureDescriptorSet() noexcept;
~StructureDescriptorSet() noexcept;
void init(FEngine& engine) noexcept;
void terminate(backend::DriverApi& driver);
void commit(backend::DriverApi& driver) noexcept;
void bind(backend::DriverApi& driver) const noexcept;
// All UBO values that can affect user code must be set here
void prepareCamera(FEngine const& engine, const CameraInfo& camera) noexcept;
void prepareLodBias(float bias, math::float2 derivativesScale) noexcept;
void prepareViewport(const backend::Viewport& physicalViewport,
const backend::Viewport& logicalViewport) noexcept;
void prepareTime(FEngine const& engine, math::float4 const& userTime) noexcept;
void prepareMaterialGlobals(std::array<math::float4, 4> const& materialGlobals) noexcept;
private:
DescriptorSetLayout const* mDescriptorSetLayout = nullptr;
DescriptorSet mDescriptorSet;
TypedUniformBuffer<PerViewUib> mUniforms;
};
} // namespace filament
+1 -17
View File
@@ -21,11 +21,7 @@
#include <backend/BufferDescriptor.h>
#include <backend/DriverApiForward.h>
#include <backend/DriverEnums.h>
#include <backend/Handle.h>
#include <utils/debug.h>
#include <utility>
#include <stddef.h>
@@ -35,14 +31,7 @@ template<typename T, size_t N = 1>
class TypedUniformBuffer {
public:
TypedUniformBuffer() noexcept = default;
explicit TypedUniformBuffer(backend::DriverApi& driver) noexcept {
init(driver);
}
void init(backend::DriverApi& driver) noexcept {
assert_invariant(!mUboHandle);
mUboHandle = driver.createBufferObject(
mTypedBuffer.getSize(),
backend::BufferObjectBinding::UNIFORM,
@@ -50,12 +39,7 @@ public:
}
void terminate(backend::DriverApi& driver) noexcept {
assert_invariant(mUboHandle);
driver.destroyBufferObject(std::move(mUboHandle));
}
~TypedUniformBuffer() noexcept {
assert_invariant(!mUboHandle);
driver.destroyBufferObject(mUboHandle);
}
TypedBuffer<T,N>& getTypedBuffer() noexcept {
@@ -54,8 +54,7 @@ enum class PerViewBindingPoints : uint8_t {
IBL_DFG_LUT = 7, // user defined (128x128), RGB16F
IBL_SPECULAR = 8, // user defined, user defined, CUBEMAP
SSAO = 9, // variable, RGB8 {AO, [depth]}
SSR = 10, // variable, 2d array, RGB_11_11_10, mipmapped
SSR_HISTORY = 10, // variable, 2d texture, RGB_11_11_10
SSR = 10, // variable, RGB_11_11_10, mipmapped
FOG = 11 // variable, user defined, CUBEMAP
};
+3 -3
View File
@@ -58,8 +58,8 @@ static constexpr std::initializer_list<DescriptorSetLayoutBinding> depthVariantD
static constexpr std::initializer_list<DescriptorSetLayoutBinding> ssrVariantDescriptorSetLayoutList = {
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FRAME_UNIFORMS },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::VERTEX | ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOWS },
{ DescriptorType::SAMPLER_2D_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE, DescriptorFlags::UNFILTERABLE },
{ DescriptorType::SAMPLER_2D_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR_HISTORY },
{ DescriptorType::SAMPLER_2D_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE },
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR },
};
// Used for generating the color pass (i.e. the main pass). This is in fact a template that gets
@@ -83,7 +83,7 @@ static constexpr std::initializer_list<DescriptorSetLayoutBinding> perViewDescri
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::LIGHTS },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::RECORD_BUFFER },
{ DescriptorType::UNIFORM_BUFFER, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::FROXEL_BUFFER },
{ DescriptorType::SAMPLER_2D_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE, DescriptorFlags::UNFILTERABLE },
{ DescriptorType::SAMPLER_2D_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE },
{ DescriptorType::SAMPLER_2D_ARRAY_DEPTH, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SHADOW_MAP },
{ DescriptorType::SAMPLER_2D_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::IBL_DFG_LUT },
{ DescriptorType::SAMPLER_CUBE_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::IBL_SPECULAR },
+9 -13
View File
@@ -3,26 +3,22 @@
//------------------------------------------------------------------------------
/** @public-api */
#define PI 3.14159265359
#define PI 3.14159265359
/** @public-api */
#define HALF_PI 1.570796327
#define HALF_PI 1.570796327
#define MEDIUMP_FLT_MAX 65504.0
#define MEDIUMP_FLT_MIN 0.00006103515625
#define MEDIUMP_FLT_MAX 65504.0
#define MEDIUMP_FLT_MIN 0.00006103515625
#ifdef TARGET_MOBILE
#define FLT_EPS MEDIUMP_FLT_MIN
#define FLT_EPS MEDIUMP_FLT_MIN
#define saturateMediump(x) min(x, MEDIUMP_FLT_MAX)
#else
#define FLT_EPS 1e-5
#define FLT_EPS 1e-5
#define saturateMediump(x) x
#endif
#define saturate(x) clamp(x, 0.0, 1.0)
#ifdef TARGET_MOBILE
#define PREVENT_DIV0(n, d, magic) ((n) / max(d, magic))
#else
#define PREVENT_DIV0(n, d, magic) ((n) / (d))
#endif
#define saturate(x) clamp(x, 0.0, 1.0)
//------------------------------------------------------------------------------
// Scalar operations
+12 -16
View File
@@ -73,9 +73,9 @@ float D_GGX(float roughness, float NoH, const vec3 h) {
#endif
float a = NoH * roughness;
float k = min(roughness / (oneMinusNoHSquared + a * a), 453.5); // 453.5 prevents fp16 overflow
float d = k * (k * (1.0 / PI));
return d;
float k = roughness / (oneMinusNoHSquared + a * a);
float d = k * k * (1.0 / PI);
return saturateMediump(d);
}
float D_GGX_Anisotropic(float at, float ab, float ToH, float BoH, float NoH) {
@@ -105,18 +105,17 @@ float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) {
// TODO: lambdaV can be pre-computed for all the lights, it should be moved out of this function
float lambdaV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2);
float lambdaL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2);
// 0.0000077 = nextafter(0.5 / MEDIUMP_FLT_MAX, 1.0) in fp16, so we don't overflow
float v = PREVENT_DIV0(0.5, lambdaV + lambdaL, 0.0000077);
float v = 0.5 / (lambdaV + lambdaL);
// a2=0 => v = 1 / 4*NoL*NoV => min=1/4, max=+inf
// a2=1 => v = 1 / 2*(NoL+NoV) => min=1/4, max=+inf
return v;
// clamp to the maximum value representable in mediump
return saturateMediump(v);
}
float V_SmithGGXCorrelated_Fast(float roughness, float NoV, float NoL) {
// Hammon 2017, "PBR Diffuse Lighting for GGX+Smith Microsurfaces"
// 0.0000077 = nextafter(0.5 / MEDIUMP_FLT_MAX, 1.0) in fp16, so we don't overflow
float v = PREVENT_DIV0(0.5, mix(2.0 * NoL * NoV, NoL + NoV, roughness), 0.0000077);
return v;
float v = 0.5 / mix(2.0 * NoL * NoV, NoL + NoV, roughness);
return saturateMediump(v);
}
float V_SmithGGXCorrelated_Anisotropic(float at, float ab, float ToV, float BoV,
@@ -125,21 +124,18 @@ float V_SmithGGXCorrelated_Anisotropic(float at, float ab, float ToV, float BoV,
// TODO: lambdaV can be pre-computed for all the lights, it should be moved out of this function
float lambdaV = NoL * length(vec3(at * ToV, ab * BoV, NoV));
float lambdaL = NoV * length(vec3(at * ToL, ab * BoL, NoL));
// 0.0000077 = nextafter(0.5 / MEDIUMP_FLT_MAX, 1.0) in fp16, so we don't overflow
float v = PREVENT_DIV0(0.5, lambdaV + lambdaL, 0.0000077);
return v;
float v = 0.5 / (lambdaV + lambdaL);
return saturateMediump(v);
}
float V_Kelemen(float LoH) {
// Kelemen 2001, "A Microfacet Based Coupled Specular-Matte BRDF Model with Importance Sampling"
// 0.0000039 = nextafter(0.25 / MEDIUMP_FLT_MAX, 1.0) in fp16, so we don't overflow
return PREVENT_DIV0(0.25, LoH * LoH, 0.0000039);
return saturateMediump(0.25 / (LoH * LoH));
}
float V_Neubelt(float NoV, float NoL) {
// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
// 0.00001532 = nextafter(1.0 / MEDIUMP_FLT_MAX, 1.0) in fp16, so we don't overflow
return PREVENT_DIV0(1.0, 4.0 * (NoL + NoV - NoL * NoV), 0.00001532);
return saturateMediump(1.0 / (4.0 * (NoL + NoV - NoL * NoV)));
}
vec3 F_Schlick(const vec3 f0, float f90, float VoH) {
+2 -2
View File
@@ -45,8 +45,8 @@ def _compare_goldens(base_dir, comparison_dir, out_dir=None):
output_fname = os.path.join(output_test_dir, "compare_results.json")
results_meta = {
'results': results,
'base_dir': os.path.relpath(base_test_dir, output_test_dir),
'comparison_dir': os.path.relpath(comp_test_dir, output_test_dir)
'base_dir': os.path.relpath(output_test_dir, base_test_dir),
'comparison_dir': os.path.relpath(output_test_dir, comp_test_dir),
}
with open(output_fname, 'w') as f:
f.write(json.dumps(results_meta, indent=2))
+2 -6
View File
@@ -95,8 +95,8 @@ def _download_github_artifacts(pr_number, github_token, output_dir= ".") -> None
print("Consider checking GitHub Actions runs manually for this PR's branch on GitHub.")
return None
# Do not filter for runs that completed successfully
successful_runs = sorted(workflow_runs, key=lambda run: run['id'], reverse=True)
# Filter for runs that completed successfully
successful_runs = [run for run in workflow_runs if run.get("conclusion") == "success" and run.get("status") == "completed"]
if not successful_runs:
print(f"No *successful and completed* workflow runs found for PR #{pr_number} with commit SHA {commit_sha}. Exiting.")
return None
@@ -161,10 +161,6 @@ def _download_github_artifacts(pr_number, github_token, output_dir= ".") -> None
print(f" Error: Downloaded file for '{artifact_name}' is not a valid zip file. Skipping extraction.")
except Exception as e:
print(f" An error occurred during extraction of '{artifact_name}': {e}")
# Once we find the lastest run with artifacts, we just quit
if len(artifacts) > 0:
break
except requests.exceptions.HTTPError as e:
print(f" An HTTP error occurred while fetching artifacts for run {run_id}: {e}")
if e.response.status_code == 403:
+197 -225
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2025 The Android Open Source Project
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,37 +19,28 @@
#include <getopt/getopt.h>
#include <algorithm>
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
#include <string.h>
using namespace std;
using namespace utils;
// Holds all command-line configuration for the resource generator.
struct AppConfig {
const char* packageName = "resources";
const char* deployDir = ".";
bool keepExtension = false;
bool appendNull = false;
bool generateC = false;
bool quietMode = false;
bool embedJson = false;
std::vector<Path> inputPaths;
};
// A special marker used to identify where to insert the JSON summary blob.
static const char* g_jsonMagicString = "__RESGEN__";
static const char* g_packageName = "resources";
static const char* g_deployDir = ".";
static bool g_keepExtension = false;
static bool g_appendNull = false;
static bool g_generateC = false;
static bool g_quietMode = false;
static bool g_embedJson = false;
// Defines the help message text displayed to the user.
static const char* USAGE = R"TXT(
RESGEN aggregates a sequence of binary blobs, each of which becomes a "resource" whose id
is the basename of the input file. It produces the following set of files:
@@ -91,7 +82,6 @@ Examples:
TEXTURES_BEACH_DATA, TEXTURES_BEACH_SIZE
)TXT";
// Template for the Apple-specific assembly file.
static const char* APPLE_ASM_TEMPLATE = R"ASM(
.global _{RESOURCES}PACKAGE
.section __TEXT,__const
@@ -99,7 +89,6 @@ _{RESOURCES}PACKAGE:
.incbin "{resources}.bin"
)ASM";
// Template for the standard assembly file.
static const char* ASM_TEMPLATE = R"ASM(
.global {RESOURCES}PACKAGE
.section .rodata
@@ -107,304 +96,287 @@ static const char* ASM_TEMPLATE = R"ASM(
.incbin "{resources}.bin"
)ASM";
// Prints the usage string, replacing the placeholder with the actual executable name.
static void printUsage(const char* name) {
std::string const execName(Path(name).getName());
std::string execName(Path(name).getName());
const std::string from("RESGEN");
std::string usage(USAGE);
std::size_t pos = 0;
while ((pos = usage.find(from, pos)) != std::string::npos) {
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
usage.replace(pos, from.length(), execName);
pos += execName.length();
}
std::cout << usage;
puts(usage.c_str());
}
// Prints the license text to the console.
static void license() {
static const char *license[] = {
#include "licenses/licenses.inc"
nullptr
};
for (const char **p = &license[0]; *p; ++p) {
std::cout << *p << std::endl;
}
const char **p = &license[0];
while (*p)
std::cout << *p++ << std::endl;
}
// A helper function to replace all occurrences of a substring within a string.
static std::string& replaceAll(std::string& context, const std::string& from, const std::string& to) {
std::size_t pos = 0;
while ((pos = context.find(from, pos)) != std::string::npos) {
context.replace(pos, from.length(), to);
pos += to.length();
}
return context;
}
// Parses command-line arguments using getopt and populates the AppConfig struct.
static AppConfig handleArguments(int const argc, char* argv[]) {
static int handleArguments(int argc, char* argv[]) {
static constexpr const char* OPTSTR = "hLp:x:ktcqj";
static const option OPTIONS[] = {
{ "help", no_argument, nullptr, 'h' },
{ "license", no_argument, nullptr, 'L' },
{ "package", required_argument, nullptr, 'p' },
{ "deploy", required_argument, nullptr, 'x' },
{ "keep", no_argument, nullptr, 'k' },
{ "text", no_argument, nullptr, 't' },
{ "cfile", no_argument, nullptr, 'c' },
{ "quiet", no_argument, nullptr, 'q' },
{ "json", no_argument, nullptr, 'j' },
{ nullptr, 0, nullptr, 0 }
static const struct option OPTIONS[] = {
{ "help", no_argument, 0, 'h' },
{ "license", no_argument, 0, 'L' },
{ "package", required_argument, 0, 'p' },
{ "deploy", required_argument, 0, 'x' },
{ "keep", no_argument, 0, 'k' },
{ "text", no_argument, 0, 't' },
{ "cfile", no_argument, 0, 'c' },
{ "quiet", no_argument, 0, 'q' },
{ "json", no_argument, 0, 'j' },
{ 0, 0, 0, 0 } // termination of the option list
};
AppConfig config;
int opt;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, nullptr)) >= 0) {
int optionIndex = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
std::string arg(optarg ? optarg : "");
switch (opt) {
default:
case 'h':
printUsage(argv[0]);
std::exit(0);
exit(0);
case 'L':
license();
std::exit(0);
exit(0);
case 'p':
config.packageName = optarg;
g_packageName = optarg;
break;
case 'x':
config.deployDir = optarg;
g_deployDir = optarg;
break;
case 'k':
config.keepExtension = true;
g_keepExtension = true;
break;
case 't':
config.appendNull = true;
g_appendNull = true;
break;
case 'c':
config.generateC = true;
g_generateC = true;
break;
case 'q':
config.quietMode = true;
g_quietMode = true;
break;
case 'j':
config.embedJson = true;
g_embedJson = true;
break;
default:
printUsage(argv[0]);
std::exit(1);
}
}
// Treat remaining arguments as input file paths.
for (int i = optind; i < argc; ++i) {
config.inputPaths.emplace_back(argv[i]);
}
return config;
}
// Opens a file for writing, printing an error and exiting on failure.
static std::ofstream openOutputFile(const Path& path, std::ios_base::openmode const mode = std::ios_base::out) {
std::ofstream stream(path.getPath(), mode);
if (!stream) {
std::cerr << "Unable to open " << path << std::endl;
std::exit(1);
}
return stream;
}
// Reads an entire binary file into a vector of bytes.
static std::vector<std::uint8_t> readFile(const Path& path) {
std::ifstream inStream(path.getPath(), std::ios::binary);
if (!inStream) {
std::cerr << "Unable to open " << path << std::endl;
std::exit(1);
}
return std::vector<std::uint8_t>((std::istreambuf_iterator<char>(inStream)),
(std::istreambuf_iterator<char>()));
}
// Writes the generated header file, but only if its content has changed.
// This helps avoid unnecessary recompilation in build systems.
static void writeHeaderIfChanged(const Path& path, const std::string& newContents) {
std::ifstream headerInStream(path.getPath(), std::ifstream::ate);
if (headerInStream) {
long const fileSize = headerInStream.tellg();
if (fileSize == newContents.size()) {
std::vector<char> previous(fileSize);
headerInStream.seekg(0);
headerInStream.read(previous.data(), fileSize);
if (0 == memcmp(previous.data(), newContents.c_str(), fileSize)) {
return;
}
}
}
std::ofstream headerOutStream = openOutputFile(path);
headerOutStream << newContents;
}
// Writes a resource's data to a C-style char array (similar to 'xxd -i').
static void writeXxdEntry(std::ofstream& xxdStream, const std::string& resourceName,
const std::vector<std::uint8_t>& content) {
xxdStream << "// " << resourceName << "\n";
xxdStream << std::setfill('0') << std::hex;
for (std::size_t i = 0; i < content.size(); i++) {
if (i > 0 && i % 20 == 0) {
xxdStream << "\n";
}
xxdStream << "0x" << std::setw(2) << int(content[i]) << ", ";
}
if (!content.empty() && content.size() % 20 != 0) {
xxdStream << "\n";
}
xxdStream << "\n";
return optind;
}
int main(int argc, char* argv[]) {
// Parse arguments and check for input files.
const AppConfig config = handleArguments(argc, argv);
if (config.inputPaths.empty()) {
const int optionIndex = handleArguments(argc, argv);
const int numArgs = argc - optionIndex;
if (numArgs < 1) {
printUsage(argv[0]);
return 1;
}
// If JSON embedding is enabled, add a placeholder to the input list.
std::vector<Path> inputPaths = config.inputPaths;
if (config.embedJson) {
inputPaths.emplace_back(g_jsonMagicString);
vector<Path> inputPaths;
for (int argIndex = optionIndex; argIndex < argc; ++argIndex) {
inputPaths.emplace_back(argv[argIndex]);
}
if (g_embedJson) {
inputPaths.push_back(g_jsonMagicString);
}
// Generate names and symbols based on the package name.
const std::string packageFile = config.packageName;
std::string packagePrefix = std::string(config.packageName) + "_";
std::transform(packagePrefix.begin(), packagePrefix.end(), packagePrefix.begin(), [](unsigned char c) { return std::toupper(c); });
const std::string packageSymbol = packagePrefix + "PACKAGE";
std::string packageFile = g_packageName;
std::string packagePrefix = std::string(g_packageName) + "_";
transform(packagePrefix.begin(), packagePrefix.end(), packagePrefix.begin(), ::toupper);
std::string package = packagePrefix + "PACKAGE";
// Create the deployment directory if it doesn't exist.
const Path deployDir(config.deployDir);
const Path deployDir(g_deployDir);
if (!deployDir.exists()) {
deployDir.mkdirRecursive();
}
// Define all output file paths.
const Path appleAsmPath(deployDir + (packageFile + ".apple.S"));
const Path asmPath(deployDir + (packageFile + ".S"));
const Path binPath(deployDir + (packageFile + ".bin"));
const Path headerPath(deployDir + (packageFile + ".h"));
const Path xxdPath(deployDir + (packageFile + ".c"));
// Prepare assembly file content by replacing placeholders.
// In the assembly language templates, replace {RESOURCES} with packagePrefix and replace
// {resources} with packageFile.
const std::string k1("{RESOURCES}");
const std::string k2("{resources}");
std::string aasmstr(APPLE_ASM_TEMPLATE);
replaceAll(aasmstr, "{RESOURCES}", packagePrefix);
replaceAll(aasmstr, "{resources}", packageFile);
std::string asmstr(ASM_TEMPLATE);
replaceAll(asmstr, "{RESOURCES}", packagePrefix);
replaceAll(asmstr, "{resources}", packageFile);
for (size_t pos = aasmstr.find(k1); pos != std::string::npos; pos = aasmstr.find(k1, pos))
aasmstr.replace(pos, k1.length(), packagePrefix);
for (size_t pos = aasmstr.find(k2); pos != std::string::npos; pos = aasmstr.find(k2, pos))
aasmstr.replace(pos, k2.length(), packageFile);
for (size_t pos = asmstr.find(k1); pos != std::string::npos; pos = asmstr.find(k1, pos))
asmstr.replace(pos, k1.length(), packagePrefix);
for (size_t pos = asmstr.find(k2); pos != std::string::npos; pos = asmstr.find(k2, pos))
asmstr.replace(pos, k2.length(), packageFile);
// Open all output file streams.
auto appleAsmStream = openOutputFile(appleAsmPath);
auto asmStream = openOutputFile(asmPath);
auto binStream = openOutputFile(binPath, std::ios::binary);
// Begin constructing the C header file content.
std::ostringstream headerStream;
headerStream << "#ifndef " << packagePrefix << "H_\n"
<< "#define " << packagePrefix << "H_\n\n"
<< "#include <stdint.h>\n\n"
<< "extern \"C\" {\n"
<< " extern const uint8_t " << packageSymbol << "[];\n";
// String streams to accumulate header macros and the JSON summary.
std::ostringstream headerMacros;
std::ostringstream jsonStream;
// If generating a C file, open the stream and write the initial boilerplate.
std::ofstream xxdStream;
if (config.generateC) {
xxdStream = openOutputFile(xxdPath);
xxdStream << "#include <stdint.h>\n"
<< "const uint8_t " << packageSymbol << "[] = {\n";
// Open the Apple-friendly assembly language file.
ofstream appleAsmStream(appleAsmPath.getPath());
if (!appleAsmStream) {
cerr << "Unable to open " << appleAsmPath << endl;
exit(1);
}
// Process each input file to build the resource collection.
// Open the non-Apple assembly language file.
ofstream asmStream(asmPath.getPath());
if (!asmStream) {
cerr << "Unable to open " << asmPath << endl;
exit(1);
}
// Open the bin file for writing.
ofstream binStream(binPath.getPath(), ios::binary);
if (!binStream) {
cerr << "Unable to open " << binPath << endl;
exit(1);
}
// Open the header file stream for writing.
ostringstream headerStream;
headerStream << "#ifndef " << packagePrefix << "H_" << endl
<< "#define " << packagePrefix << "H_" << endl << endl
<< "#include <stdint.h>\n" << endl
<< "extern \"C\" {" << endl
<< " extern const uint8_t " << package << "[];" << endl;
ostringstream headerMacros;
ostringstream appleDataAsmStream;
ostringstream dataAsmStream;
ostringstream jsonStream;
// Open the generated C file for writing.
ofstream xxdStream;
if (g_generateC) {
xxdStream = ofstream(xxdPath.getPath());
if (!xxdStream) {
cerr << "Unable to open " << xxdPath << endl;
exit(1);
}
xxdStream << "#include <stdint.h>\n"
<< "const uint8_t " << package << "[] = {\n";
}
// Consume each input file and write it back out into the various output streams.
jsonStream << "{";
std::size_t offset = 0;
size_t offset = 0;
for (const auto& inPath : inputPaths) {
std::vector<std::uint8_t> content;
vector<uint8_t> content;
if (inPath != g_jsonMagicString) {
// For a regular file, read its binary content.
content = readFile(inPath);
ifstream inStream(inPath.getPath(), ios::binary);
if (!inStream) {
cerr << "Unable to open " << inPath << endl;
exit(1);
}
content = vector<uint8_t>((istreambuf_iterator<char>(inStream)), {});
} else {
// For the JSON placeholder, finalize and embed the JSON summary blob.
// The blob is formatted as: __RESGEN__\0<size>\0{...json...}
// To finalize the JSON string, we replace the trailing comma with an end bracket and
// prefix it with the magic identifier and string size.
std::string jsonString = jsonStream.str();
jsonString[jsonString.size() - 1] = '}';
std::ostringstream jsonBlob;
jsonBlob << g_jsonMagicString << '\0';
jsonBlob << jsonString.size() << '\0';
jsonString[jsonString.size()-1] = '}';
ostringstream jsonBlob;
jsonBlob << g_jsonMagicString << "\0";
jsonBlob << jsonString.size() << "\0";
jsonBlob << jsonString;
jsonString = jsonBlob.str();
const auto* jsonPtr = reinterpret_cast<const std::uint8_t*>(jsonString.c_str());
content.assign(jsonPtr, jsonPtr + jsonBlob.str().size());
uint8_t const* jsonPtr = (uint8_t const*) jsonString.c_str();
content = vector<uint8_t>(jsonPtr, jsonPtr + jsonBlob.str().size());
}
// Optionally append a null terminator, useful for text resources.
if (config.appendNull) {
if (g_appendNull) {
content.push_back(0);
}
// Generate the resource's symbol name from its file name.
std::string rname = config.keepExtension ? inPath.getName() : inPath.getNameWithoutExtension();
replaceAll(rname, ".", "_");
std::transform(rname.begin(), rname.end(), rname.begin(), [](unsigned char c) { return std::toupper(c); });
// Formulate the resource name and the prefixed resource name.
std::string rname = g_keepExtension ? inPath.getName() : inPath.getNameWithoutExtension();
replace(rname.begin(), rname.end(), '.', '_');
transform(rname.begin(), rname.end(), rname.begin(), ::toupper);
const std::string prname = packagePrefix + rname;
// Write the resource content to the aggregate binary file.
binStream.write(reinterpret_cast<const char*>(content.data()), content.size());
// Write the binary blob into the bin file.
binStream.write((const char*) content.data(), content.size());
// Generate C preprocessor macros for the resource's offset, size, and data pointer.
headerMacros << "#define " << prname << "_OFFSET " << offset << "\n"
<< "#define " << prname << "_SIZE " << content.size() << "\n"
<< "#define " << prname << "_DATA (" << packageSymbol << " + " << prname << "_OFFSET)\n\n";
// Write the offsets and sizes.
headerMacros
<< "#define " << prname << "_OFFSET " << offset << "\n"
<< "#define " << prname << "_SIZE " << content.size() << "\n"
<< "#define " << prname << "_DATA (" << package << " + " << prname << "_OFFSET)\n\n";
// If enabled, write the content to the C source file.
if (config.generateC) {
writeXxdEntry(xxdStream, rname, content);
// Write the xxd-style ASCII array, followed by a blank line.
if (g_generateC) {
xxdStream << "// " << rname << "\n";
xxdStream << setfill('0') << hex;
size_t i = 0;
for (; i < content.size(); i++) {
if (i > 0 && i % 20 == 0) {
xxdStream << "\n";
}
xxdStream << "0x" << setw(2) << (int) content[i] << ", ";
}
if (i % 20 != 0) xxdStream << "\n";
xxdStream << "\n";
}
// Add an entry to the JSON summary and update the running offset.
jsonStream << "\"" << rname << "\":" << content.size() << ",";
offset += content.size();
}
// Finalize the header file content.
headerStream << "}\n\n";
headerStream << headerMacros.str();
headerStream << "#endif\n";
// Write the header and assembly files.
writeHeaderIfChanged(headerPath, headerStream.str());
asmStream << asmstr << std::endl;
appleAsmStream << aasmstr << std::endl;
// Report generated files to the console unless in quiet mode.
if (!config.quietMode) {
std::cout << "Generated files: " << headerPath << " " << asmPath << " " << appleAsmPath << " "
<< binPath;
}
// Finalize the C file and report it.
if (config.generateC) {
xxdStream << "};\n\n";
if (!config.quietMode) {
std::cout << " " << xxdPath;
// To optimize builds, avoid overwriting the header file if nothing has changed.
bool headerIsDirty = true;
ifstream headerInStream(headerPath.getPath(), std::ifstream::ate);
string headerContents = headerStream.str();
if (headerInStream) {
long fileSize = static_cast<long>(headerInStream.tellg());
if (fileSize == headerContents.size()) {
vector<char> previous(fileSize);
headerInStream.seekg(0);
headerInStream.read(previous.data(), fileSize);
headerIsDirty = 0 != memcmp(previous.data(), headerContents.c_str(), fileSize);
}
}
if (!config.quietMode) {
std::cout << std::endl;
if (headerIsDirty) {
ofstream headerOutStream(headerPath.getPath());
if (!headerOutStream) {
cerr << "Unable to open " << headerPath << endl;
exit(1);
}
headerOutStream << headerContents;
}
return 0;
}
asmStream << asmstr << dataAsmStream.str() << endl;
asmStream.close();
appleAsmStream << aasmstr << appleDataAsmStream.str() << endl;
appleAsmStream.close();
if (!g_quietMode) {
cout << "Generated files: "
<< headerPath << " "
<< asmPath << " "
<< appleAsmPath << " "
<< binPath;
}
if (g_generateC) {
xxdStream << "};\n\n";
if (!g_quietMode) {
cout << " " << xxdPath;
}
}
if (!g_quietMode) {
cout << endl;
}
}