Compare commits

..

14 Commits

Author SHA1 Message Date
Juan Caldas
d06afc9a57 set texture view 2025-07-24 12:06:59 -04:00
Doris Wu
04f7837439 Set the blend enabled field (#8983) 2025-07-23 04:57:42 +00:00
Mathias Agopian
85d00831bc minor cleanup and missing includes 2025-07-22 16:53:42 -07:00
Mathias Agopian
4d8f92f087 per-material globals weren't set when generating shadow maps 2025-07-22 16:53:42 -07:00
Mathias Agopian
5850a7eee4 use a separate UBO for the postfx user passes
some of the postfx passes (picking, ssao, structure) are user passes
because they call user materials; these now use their own ubo, which 
should prevent us from having to update a UBO mid frame.
2025-07-22 16:53:42 -07:00
Mathias Agopian
1b2620cdf3 cleanup-up how we update the per-view UBO
The per-view UBO is used in several different contexts, for instance:
- during the color pass
- during the shadow pass
- during the structure pass
- during app postfx passes

For shadows and other passes, its content is actually different because
the camera is very different. In that case, we use a different buffer.

However, the code that was used to set the values into the UBO was
duplicated and did slightly different things. In addition some
UBO values were (and are still) probably missing (e.g. the per-material
data).

To improve this situation we new use a utility class that does the
UBO set up of those "shared" values.

It turns out that the structure, ssr and ssao passes also need a 
slightly different content (because they run at half res) and currently
are sharing the color pass UBO, which causes mid-frame updates.

We introduce a new StructureDescriptorSet which is managing a new UBO,
it too is now implemented in terms of the new utility class. 
StructureDescriptorSet Is not used yet.
2025-07-22 16:53:42 -07:00
Mathias Agopian
ddca795cc9 cleanup resgen code
A test for how Gemini can clean-up code. Changes:

- Configuration Struct: Global variables have been grouped into an 
AppConfig struct. This makes the program's configuration explicit and 
avoids polluting the global namespace.

- Clearer Argument Parsing: The handleArguments function now returns an 
AppConfig object, encapsulating all parsing logic and making the main 
function cleaner.

- Helper Functions: Repetitive tasks like string replacement, file I/O, 
and writing file entries have been extracted into small, well-defined 
helper functions (replaceAll, openOutputFile, readFile, etc.).

- Simplified main Function: The main function is now a high-level 
coordinator, delegating work to helper functions. This makes the overall
 program flow much easier to follow.

- Modern C++ Idioms: The code now consistently uses C++ streams 
(std::cout, std::ofstream) and std::string objects, which is more 
idiomatic than a mix of C and C++ styles.

- added short comments
2025-07-22 16:52:11 -07:00
Powei Feng
3a5f558874 vk: VulkanBuffer cannot skip staging buffer after a read (#8946)
The previous introduction of VulkanBufferProxy uses the refcount of
the backing VulkanBuffer as an indication whether there's a write
in progress or not. But we also want to make sure there is no
read in progress (i.e. not referenced in a descriptor set). This
commit fixes the bookkeeping while trying to clean up the relevant
structs.

We add logic to track whether a UBO is actively been read (i.e.
bound to a descriptor set that is itself bound to a command buffer).
2025-07-22 21:47:45 +00:00
Powei Feng
13629ed93a vk: add feature flag for staging buffer bypass (#8958)
- Add a feature flag for bypassing staging buffer for buffer
   updates.  Note that this was assumed on for UMA devices, but
   can cause issue with post-processing.  So now we default the
   path to off and clients can enable it via feature flag.
 - Remove FVK_FORCE_STAGING_FOR_BUFFER_UPDATES since it's intended
   use is subsumed by vulkan_enable_staging_buffer_bypass.
 - Small refactor of FEngine for creating
   backend::Platform::DriverConfig.
2025-07-22 19:33:08 +00:00
Powei Feng
b129e5df4a renderdiff: fix upload issue second try (#8972)
- Move upload step to later so that comparison result is also
    included in the upload
 - Fix the wrong relative paths in the comparison_result.json
 - Fix viewer logic to pick only the latest run for a PR.
2025-07-22 18:47:18 +00:00
Mathias Agopian
2abacaa030 the ssr-history texture is not an array
the descriptor used for SSR is different depending on the pass. In
the color pass, it's used as the source of the SSRs buffers and is an
array (refraction & reflection). In the SSR pass it's the history
buffer and is just a 2D texture.
2025-07-22 10:08:22 -07:00
Powei Feng
9a88537e08 Fix dangling material reference after desctruction (#8977)
The invocable sent to Program::diagnostic contains a reference
to Material, which could be destroyed by the time the invocable
is called. We make sure that invocable does not have a reference.
2025-07-22 05:34:52 +00:00
Powei Feng
9640f2656f Make sure structure textures are unfilterable 2025-07-21 21:22:47 -07:00
Powei Feng
2811b064a9 vk: fix include for Definitions (#8954)
be more specific in the `utils` include.
2025-07-21 20:51:56 +00:00
55 changed files with 1209 additions and 763 deletions

View File

@@ -130,7 +130,8 @@ jobs:
run: |
bash build/common/get-mesa.sh
pip install tifffile numpy
- name: Render
- name: Render and compare
id: render_compare
run: |
TEST_DIR=test/renderdiff
source ${TEST_DIR}/src/preamble.sh
@@ -140,20 +141,32 @@ 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
# 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
- uses: actions/upload-artifact@v4
with:
name: presubmit-renderdiff-result
path: ./out/renderdiff
- name: Compare output
- name: Compare result
run: |
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_
ERROR_STR="${{ steps.render_compare.outputs.err }}"
if [ -n "${ERROR_STR}" ]; then
echo "${ERROR_STR}"
exit 1
fi
validate-wgsl-webgpu:
name: validate-wgsl-webgpu

View File

@@ -134,12 +134,14 @@ 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

View File

@@ -193,6 +193,13 @@ 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;

View File

@@ -88,7 +88,8 @@ public:
// sets the material name and variant for diagnostic purposes only
Program& diagnostics(utils::CString const& name,
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)>&& logger);
utils::Invocable<utils::io::ostream&(utils::CString const& name,
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
@@ -173,7 +174,8 @@ private:
utils::CString mName;
uint64_t mCacheId{};
CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH;
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)> mLogger;
utils::Invocable<utils::io::ostream&(utils::CString const& name, utils::io::ostream& out)>
mLogger;
SpecializationConstantsInfo mSpecializationConstants;
std::array<utils::FixedCapacityVector<PushConstant>, SHADER_TYPE_COUNT> mPushConstants;
DescriptorSetInfo mDescriptorBindings;

View File

@@ -45,7 +45,7 @@ Program& Program::priorityQueue(CompilerPriorityQueue priorityQueue) noexcept {
}
Program& Program::diagnostics(CString const& name,
Invocable<io::ostream&(io::ostream&)>&& logger) {
Invocable<io::ostream&(utils::CString const& name, 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(out);
builder.mLogger(builder.mName, out);
out << "}";
return out;
}

View File

@@ -20,33 +20,30 @@
#include "VulkanCommands.h"
#include "VulkanContext.h"
#include "VulkanMemory.h"
#include "VulkanHandles.h"
using namespace bluevk;
namespace filament::backend {
VulkanBufferProxy::VulkanBufferProxy(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, VulkanBufferUsage usage, uint32_t numBytes)
: mAllocator(allocator),
VulkanBufferProxy::VulkanBufferProxy(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, VulkanBufferUsage usage,
uint32_t numBytes)
: mStagingBufferBypassEnabled(context.stagingBufferBypassEnabled()),
mAllocator(allocator),
mStagePool(stagePool),
mBufferCache(bufferCache),
mBuffer(mBufferCache.acquire(usage, numBytes)),
mUpdatedOffset(0),
mUpdatedBytes(0) {}
mLastReadAge(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;
if (isAvailable) {
// We are up to date with all the update operations, so no need to synchronize with previous
// updates.
mUpdatedBytes = 0;
mUpdatedOffset = 0;
}
// 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;
// Keep track of the VulkanBuffer usage
commands.acquire(mBuffer);
@@ -57,8 +54,7 @@ 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 && !FVK_FORCE_STAGING_FOR_BUFFER_UPDATES;
bool const useMemcpy = isUniform && isMemcopyable && isAvailable && mStagingBufferBypassEnabled;
if (useMemcpy) {
char* dest = static_cast<char*>(mBuffer->getGpuBuffer()->allocationInfo.pMappedData) +
byteOffset;
@@ -66,6 +62,10 @@ 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,10 +76,9 @@ 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 update, then we need to make sure the following write is properly
// If there was a previous read, then we need to make sure the following write is properly
// synced with the previous read.
if (mUpdatedBytes > 0 &&
(byteOffset >= mUpdatedOffset && byteOffset <= (mUpdatedOffset + mUpdatedBytes))) {
if (!isAvailable) {
VkAccessFlags srcAccess = 0;
VkPipelineStageFlags srcStage = 0;
if (getUsage() == VulkanBufferUsage::UNIFORM) {
@@ -114,9 +113,6 @@ 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
@@ -160,4 +156,9 @@ VulkanBufferUsage VulkanBufferProxy::getUsage() const noexcept {
return mBuffer->getGpuBuffer()->usage;
}
void VulkanBufferProxy::referencedBy(VulkanCommandBuffer& commands) {
commands.acquire(mBuffer);
mLastReadAge = commands.age();
}
} // namespace filament::backend

View File

@@ -25,28 +25,35 @@
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(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, VulkanBufferUsage usage, uint32_t numBytes);
VulkanBufferProxy(VulkanContext const& context, 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;
VulkanBufferUsage getUsage() const noexcept;
void referencedBy(VulkanCommandBuffer& commands);
private:
VulkanBufferUsage getUsage() const noexcept;
bool const mStagingBufferBypassEnabled;
VmaAllocator mAllocator;
VulkanStagePool& mStagePool;
VulkanBufferCache& mBufferCache;
fvkmemory::resource_ptr<VulkanBuffer> mBuffer;
uint32_t mUpdatedOffset = 0;
uint32_t mUpdatedBytes = 0;
uint32_t mLastReadAge;
};
} // namespace filament::backend

View File

@@ -86,6 +86,8 @@ 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),
@@ -94,7 +96,8 @@ VulkanCommandBuffer::VulkanCommandBuffer(VulkanContext const& context, VkDevice
mDevice(device),
mQueue(queue),
mBuffer(createCommandBuffer(device, pool)),
mFenceStatus(std::make_shared<VulkanCmdFence>(VK_INCOMPLETE)) {
mFenceStatus(std::make_shared<VulkanCmdFence>(VK_INCOMPLETE)),
mAge(++sAgeCounter) {
VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
vkCreateSemaphore(mDevice, &sci, VKALLOC, &mSubmission);
@@ -111,6 +114,7 @@ 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

View File

@@ -108,7 +108,13 @@ struct VulkanCommandBuffer {
return mBuffer;
}
uint32_t age() const {
return mAge;
}
private:
static uint32_t sAgeCounter;
VulkanContext const& mContext;
uint8_t mMarkerCount;
bool const isProtected;
@@ -120,6 +126,7 @@ private:
VkFence mFence;
std::shared_ptr<VulkanCmdFence> mFenceStatus;
std::vector<fvkmemory::resource_ptr<Resource>> mResources;
uint32_t mAge;
};
struct CommandBufferPool {

View File

@@ -214,8 +214,4 @@ 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

View File

@@ -146,6 +146,10 @@ public:
return mIsUnifiedMemoryArchitecture;
}
inline bool stagingBufferBypassEnabled() const noexcept {
return mStagingBufferBypassEnabled;
}
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
@@ -169,6 +173,7 @@ private:
bool mLazilyAllocatedMemorySupported = false;
bool mProtectedMemorySupported = false;
bool mIsUnifiedMemoryArchitecture = false;
bool mStagingBufferBypassEnabled = false;
fvkutils::VkFormatList mDepthStencilFormats;
fvkutils::VkFormatList mBlittableDepthStencilFormats;

View File

@@ -290,7 +290,9 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
if (mLastBoundInfo.pipelineLayout == pipelineLayout) {
auto& lastBoundSets = mLastBoundInfo.boundSets;
curMask.forEachSetBit([&](size_t index) {
if (updateSets[index] == lastBoundSets[index] && !useExternalSamplers[index]) {
auto& set = updateSets[index];
if (set == lastBoundSets[index] && !useExternalSamplers[index] &&
set->uniqueDynamicUboCount == 0) {
curMask.unset(index);
}
});
@@ -302,25 +304,24 @@ 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());
commands->acquire(set);
set->referencedBy(*commands);
});
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->buffer.getVkBuffer(),
.buffer = bufferObject->getVkBuffer(),
.offset = offset,
.range = size,
};

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, mAllocator, mStagePool,
mBufferCache, elementSize, indexCount);
auto ib = resource_ptr<VulkanIndexBuffer>::make(&mResourceManager, ibh, mContext, 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, mAllocator, mStagePool,
mBufferCache, byteCount, bindingType);
auto bo = resource_ptr<VulkanBufferObject>::make(&mResourceManager, boh, mContext, 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->buffer.loadFromCpu(commands, p.buffer, byteOffset, p.size);
ib->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->buffer.loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
bo->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->buffer.loadFromCpu(commands, bd.buffer, byteOffset, bd.size);
bo->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->buffer.getVkBuffer(), 0,
vkCmdBindIndexBuffer(cmdbuffer, prim->indexBuffer->getVkBuffer(), 0,
prim->indexBuffer->indexType);
}

View File

@@ -39,6 +39,20 @@ 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;
}
@@ -166,14 +180,6 @@ 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)),
@@ -185,6 +191,17 @@ 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 }) {
@@ -588,17 +605,18 @@ 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->buffer.getVkBuffer();
vkbuffers[attribIndex] = bufferObject->getVkBuffer();
}
}
mResources.push_back(bufferObject);
}
VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, uint32_t byteCount, BufferObjectBinding bindingType)
VulkanBufferObject::VulkanBufferObject(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, uint32_t byteCount,
BufferObjectBinding bindingType)
: HwBufferObject(byteCount),
buffer(allocator, stagePool, bufferCache, getBufferObjectUsage(bindingType), byteCount),
bindingType(bindingType) {}
bindingType(bindingType),
mBuffer(context, allocator, stagePool, bufferCache, getBufferObjectUsage(bindingType), byteCount) {}
VulkanRenderPrimitive::VulkanRenderPrimitive(PrimitiveType pt,
fvkmemory::resource_ptr<VulkanVertexBuffer> vb,

View File

@@ -26,6 +26,7 @@
#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"
@@ -200,8 +201,15 @@ public:
return &mOffsets;
}
void acquire(fvkmemory::resource_ptr<VulkanTexture> texture);
void acquire(fvkmemory::resource_ptr<VulkanBufferObject> buffer);
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);
fvkutils::UniformBufferBitmask const dynamicUboMask;
uint8_t const uniqueDynamicUboCount;
@@ -214,6 +222,7 @@ private:
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
OnRecycle mOnRecycleFn;
OnRecycle mOnRecycleExternalSamplerFn;
fvkutils::UniformBufferBitmask mUboMask;
};
using PushConstantNameArray = utils::FixedCapacityVector<char const*>;
@@ -419,6 +428,9 @@ 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;
@@ -429,23 +441,47 @@ private:
};
struct VulkanIndexBuffer : public HwIndexBuffer, fvkmemory::Resource {
VulkanIndexBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, uint8_t elementSize, uint32_t indexCount)
VulkanIndexBuffer(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, uint8_t elementSize,
uint32_t indexCount)
: HwIndexBuffer(elementSize, indexCount),
buffer(allocator, stagePool, bufferCache, VulkanBufferUsage::INDEX,
elementSize * indexCount),
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32) {}
indexType(elementSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32),
mBuffer(context, allocator, stagePool, bufferCache, VulkanBufferUsage::INDEX,
elementSize * indexCount) {}
VulkanBufferProxy buffer;
const VkIndexType indexType;
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;
};
struct VulkanBufferObject : public HwBufferObject, fvkmemory::Resource {
VulkanBufferObject(VmaAllocator allocator, VulkanStagePool& stagePool,
VulkanBufferCache& bufferCache, uint32_t byteCount, BufferObjectBinding bindingType);
VulkanBufferObject(VulkanContext const& context, VmaAllocator allocator,
VulkanStagePool& stagePool, VulkanBufferCache& bufferCache, uint32_t byteCount,
BufferObjectBinding bindingType);
VulkanBufferProxy buffer;
const 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;
};
struct VulkanRenderPrimitive : public HwRenderPrimitive, fvkmemory::Resource {
@@ -457,21 +493,6 @@ 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

View File

@@ -17,7 +17,7 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
#define TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
#include "utils/Definitions.h"
#include "vulkan/utils/Definitions.h"
#include <backend/DriverEnums.h>

View File

@@ -72,7 +72,10 @@ struct Resource {
restype(ResourceType::UNDEFINED_TYPE),
mHandleConsideredDestroyed(false) {}
uint32_t getCount() const { return mCount; }
template<typename D>
bool isType() const {
return getTypeEnum<D>() == restype;
}
private:
inline void inc() noexcept {

View File

@@ -741,6 +741,10 @@ 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) {

View File

@@ -410,7 +410,7 @@ void WebGPUDriver::createTextureViewSwizzleR(Handle<HwTexture> textureHandle,
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");
// PANIC_POSTCONDITION("Swizzle WebGPU Texture is not supported");
}
void WebGPUDriver::createTextureExternalImage2R(Handle<HwTexture> textureHandle,
@@ -964,15 +964,15 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> renderTargetHandle,
!(hasStencil(customDepthStencilFormat))) {
FILAMENT_CHECK_POSTCONDITION(false)
<< "Custom render target requested stencil, but the provided texture"
"format number"
<< (uint32_t) customDepthStencilFormat
"format number "
<< webGPUTextureFormatToString(customDepthStencilFormat)
<< " does not have a stencil aspect.";
}
if (any(renderTarget->getTargetFlags() & TargetBufferFlags::DEPTH) &&
!(hasDepth(customDepthStencilFormat))) {
FILAMENT_CHECK_POSTCONDITION(false) << "Custom render target requested depth, "
"but the provided texture format number"
<< (uint32_t) customDepthStencilFormat
<< webGPUTextureFormatToString(customDepthStencilFormat)
<< " does not have a depth aspect.";
}
}
@@ -1079,7 +1079,7 @@ void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
mCommandBuffer = nullptr;
mTextureView = nullptr;
assert_invariant(mSwapChain);
mSwapChain->present(mQueue);
mSwapChain->present();
}
void WebGPUDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
@@ -1110,84 +1110,8 @@ 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) {
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());
// todo
scheduleDestroy(std::move(pixelBufferDescriptor));
}
void WebGPUDriver::readBufferSubData(Handle<HwBufferObject> bufferObjectHandle,

View File

@@ -377,24 +377,8 @@ wgpu::TextureView WebGPUSwapChain::getCurrentHeadlessTextureView() {
return mRenderTargetViews[mHeadlessBufferIndex];
}
wgpu::Texture WebGPUSwapChain::getCurrentTexture(wgpu::Extent2D const& extent) {
void WebGPUSwapChain::present() {
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();

View File

@@ -42,13 +42,12 @@ 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(wgpu::Queue const& queue);
void present();
private:

View File

@@ -109,7 +109,7 @@ namespace {
case wgpu::TextureFormat::BGRA8UnormSrgb: return wgpu::TextureFormat::BGRA8Unorm;
case wgpu::TextureFormat::BC1RGBAUnormSrgb: return wgpu::TextureFormat::BC1RGBAUnorm;
case wgpu::TextureFormat::BC2RGBAUnormSrgb: return wgpu::TextureFormat::BC2RGBAUnorm;
case wgpu::TextureFormat::BC3RGBAUnormSrgb: return wgpu::TextureFormat::BC3RGBAUnorm;
case wgpu::TextureFormat::BC3RGBAUnormSrgb: return wgpu::TextureFormat::BC3RGBAUnorm;
case wgpu::TextureFormat::BC7RGBAUnormSrgb: return wgpu::TextureFormat::BC7RGBAUnorm;
case wgpu::TextureFormat::ETC2RGB8UnormSrgb: return wgpu::TextureFormat::ETC2RGB8Unorm;
case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb: return wgpu::TextureFormat::ETC2RGB8A1Unorm;
@@ -329,12 +329,12 @@ WebGPUTexture::WebGPUTexture(const SamplerType samplerType, const uint8_t levels
const uint32_t height, const uint32_t depth, const TextureUsage usage,
wgpu::Device const& device) noexcept
: HwTexture{ samplerType, levels, samples, width, height, depth, format, usage },
mViewFormat{ fToWGPUTextureFormat(format) },
mViewFormat{ fToWGPUTextureFormat(format, usage) },
mMipmapGenerationStrategy{ determineMipmapGenerationStrategy(mViewFormat, samplerType,
samples, levels) },
mWebGPUFormat{ mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS
? storageBindingCompatibleFormatForViewFormat(mViewFormat)
: mViewFormat },
: fToWGPUTextureFormat(format, usage) },
mAspect{ fToWGPUTextureViewAspect(usage, format) },
mWebGPUUsage{ fToWGPUTextureUsage(usage, samples,
mMipmapGenerationStrategy == MipmapGenerationStrategy::SPD_COMPUTE_PASS,
@@ -495,6 +495,66 @@ wgpu::TextureView WebGPUTexture::makeMsaaSidecarTextureViewIfTextureSidecarExist
return textureView;
}
wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFormat,
TextureUsage const& fUsage) {
const bool isDepth{ any(fUsage & TextureUsage::DEPTH_ATTACHMENT) };
const bool isStencil{ any(fUsage & TextureUsage::STENCIL_ATTACHMENT) };
const bool isColor{ any(fUsage & TextureUsage::COLOR_ATTACHMENT) };
const bool isBlitSrc{ any(fUsage & TextureUsage::BLIT_SRC) };
const bool isBlitDst{ any(fUsage & TextureUsage::BLIT_DST) };
const bool isUploadable{ any(fUsage & TextureUsage::UPLOADABLE) };
const bool isSampleable{ any(fUsage & TextureUsage::SAMPLEABLE) };
const bool isSubpassInput{ any(fUsage & TextureUsage::SUBPASS_INPUT) };
const bool isProtected{ any(fUsage & TextureUsage::PROTECTED) };
FWGPU_LOGD << ""
<< " isDepth: "
<< isDepth
<< " isStencil: "
<< isStencil
<< " isColor: "
<< isColor
<< " isBlitSrc: "
<< isBlitSrc
<< " isBlitDst: "
<< isBlitDst
<< " isUploadable: "
<< isUploadable
<< " isSampleable: "
<< isSampleable
<< " isSubpassInput: "
<< isSubpassInput
<< " isProtected: "
<< isProtected;
const bool depthOnly{ isDepth && !isColor && !isStencil };
const bool stencilOnly = { isStencil && !isColor && !isDepth };
if (depthOnly || stencilOnly) {
switch (fFormat) {
case TextureFormat::DEPTH24_STENCIL8:
if (depthOnly && stencilOnly) {
return wgpu::TextureFormat::Depth24PlusStencil8;
} else if (depthOnly) {
return wgpu::TextureFormat::Depth24Plus;
} else {
return wgpu::TextureFormat::Stencil8;
}
case TextureFormat::DEPTH32F_STENCIL8:
if (depthOnly && stencilOnly) {
return wgpu::TextureFormat::Depth32FloatStencil8;
} else if (depthOnly) {
return wgpu::TextureFormat::Depth32Float;
} else {
return wgpu::TextureFormat::Stencil8;
}
default:
break;
}
}
return fToWGPUTextureFormat(fFormat);
}
wgpu::TextureFormat WebGPUTexture::fToWGPUTextureFormat(TextureFormat const& fFormat) {
switch (fFormat) {
case TextureFormat::R8: return wgpu::TextureFormat::R8Unorm;

View File

@@ -104,10 +104,13 @@ public:
/**
* @return nullptr if a MSAA sidecar texture is not appliable, otherwise a view to one
*/
[[nodiscard]] wgpu::TextureView makeMsaaSidecarTextureView(wgpu::Texture const&, uint8_t mipLevel, uint32_t arrayLayer) const;
[[nodiscard]] wgpu::TextureView makeMsaaSidecarTextureView(wgpu::Texture const&,
uint8_t mipLevel, uint32_t arrayLayer) const;
[[nodiscard]] static wgpu::TextureFormat fToWGPUTextureFormat(
filament::backend::TextureFormat const& fFormat);
[[nodiscard]] static wgpu::TextureFormat fToWGPUTextureFormat(
filament::backend::TextureFormat const& fFormat, TextureUsage const& fUsage);
/**
* @param format a required texture format (can be a view to a different underlying texture

View File

@@ -329,6 +329,7 @@ void PostProcessManager::init() noexcept {
mSsrPassDescriptorSet.init(engine);
mPostProcessDescriptorSet.init(engine);
mStructureDescriptorSet.init(engine);
mWorkaroundSplitEasu =
driver.isWorkaroundNeeded(Workaround::SPLIT_EASU);
@@ -405,6 +406,7 @@ void PostProcessManager::terminate(DriverApi& driver) noexcept {
mPostProcessDescriptorSet.terminate(engine.getDescriptorSetLayoutFactory(), driver);
mSsrPassDescriptorSet.terminate(driver);
mStructureDescriptorSet.terminate(driver);
}
Handle<HwTexture> PostProcessManager::getOneTexture() const {
@@ -546,7 +548,8 @@ PostProcessManager::StructurePassOutput PostProcessManager::structure(FrameGraph
Variant structureVariant(Variant::DEPTH_VARIANT);
structureVariant.setPicking(config.picking);
bindPostProcessDescriptorSet(driver);
// bind the per-view descriptorSet that is used for the structure pass
getStructureDescriptorSet().bind(driver);
passBuilder.renderFlags(structureRenderFlags);
passBuilder.variant(structureVariant);
@@ -609,6 +612,63 @@ 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,
@@ -858,7 +918,8 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::screenSpaceAmbientOcclusion(
});
},
[=](FrameGraphResources const& resources, auto const& data, DriverApi& driver) {
bindPostProcessDescriptorSet(driver);
// bind the per-view descriptorSet that is used for the structure pass
getStructureDescriptorSet().bind(driver);
auto depth = resources.getTexture(data.depth);
auto ssao = resources.getRenderPassInfo();

View File

@@ -23,6 +23,7 @@
#include "ds/PostProcessDescriptorSet.h"
#include "ds/SsrPassDescriptorSet.h"
#include "ds/StructureDescriptorSet.h"
#include "ds/TypedUniformBuffer.h"
#include "materials/StaticMaterialInfo.h"
@@ -104,6 +105,10 @@ 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,
@@ -391,7 +396,9 @@ public:
FMaterialInstance* configureColorGradingMaterial(
PostProcessMaterial& material, FColorGrading const* colorGrading,
ColorGradingConfig const& colorGradingConfig, VignetteOptions const& vignetteOptions,
uint32_t const width, uint32_t const height) noexcept;
uint32_t width, uint32_t height) noexcept;
StructureDescriptorSet& getStructureDescriptorSet() const noexcept { return mStructureDescriptorSet; }
private:
backend::RenderPrimitiveHandle mFullScreenQuadRph;
@@ -402,6 +409,7 @@ private:
mutable SsrPassDescriptorSet mSsrPassDescriptorSet;
mutable PostProcessDescriptorSet mPostProcessDescriptorSet;
mutable StructureDescriptorSet mStructureDescriptorSet;
struct BilateralPassConfig {
uint8_t kernelSize = 11;

View File

@@ -521,9 +521,9 @@ public:
}
// Specifies camera information (e.g. used for sorting commands)
RenderPassBuilder& camera(const CameraInfo& camera) noexcept {
mCameraPosition = camera.getPosition();
mCameraForwardVector = camera.getForwardVector();
RenderPassBuilder& camera(math::float3 position, math::float3 forward) noexcept {
mCameraPosition = position;
mCameraForwardVector = forward;
return *this;
}

View File

@@ -39,6 +39,7 @@ 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);

View File

@@ -1366,8 +1366,8 @@ float4 ShadowMap::getClampToEdgeCoords(ShadowMapInfo const& shadowMapInfo) const
// ------------------------------------------------------------------------------------------------
void ShadowMap::prepareCamera(Transaction const& transaction,
DriverApi& driver, const CameraInfo& cameraInfo) noexcept {
ShadowMapDescriptorSet::prepareCamera(transaction, driver, cameraInfo);
FEngine const& engine, const CameraInfo& cameraInfo) noexcept {
ShadowMapDescriptorSet::prepareCamera(transaction, engine, cameraInfo);
ShadowMapDescriptorSet::prepareLodBias(transaction, 0.0f);
}
@@ -1381,6 +1381,11 @@ 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);

View File

@@ -33,9 +33,6 @@
#include <utils/compiler.h>
#include <math/mathfwd.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <math/mat4.h>
#include <array>
@@ -194,11 +191,13 @@ public:
using Transaction = ShadowMapDescriptorSet::Transaction;
static void prepareCamera(Transaction const& transaction,
backend::DriverApi& driver, const CameraInfo& cameraInfo) noexcept;
FEngine const& engine, 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;
@@ -326,12 +325,12 @@ private:
static float texelSizeWorldSpace(const math::mat4f& W, const math::mat4f& MbMtF,
uint16_t shadowDimension) noexcept;
static constexpr const Segment sBoxSegments[12] = {
static constexpr 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 const Quad sBoxQuads[6] = {
static constexpr Quad sBoxQuads[6] = {
{ 2, 0, 1, 3 }, // far
{ 6, 4, 5, 7 }, // near
{ 2, 0, 4, 6 }, // left

View File

@@ -385,9 +385,10 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
const CameraInfo cameraInfo{ shadowMap.getCamera(), mainCameraInfo };
auto transaction = ShadowMap::open(driver);
ShadowMap::prepareCamera(transaction, driver, cameraInfo);
ShadowMap::prepareCamera(transaction, engine, 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);
@@ -412,7 +413,7 @@ FrameGraphId<FrameGraphTexture> ShadowMapManager::render(FEngine& engine, FrameG
RenderPass const pass = passBuilder
.renderFlags(RenderPass::HAS_DEPTH_CLAMP, renderPassFlags)
.camera(cameraInfo)
.camera(cameraInfo.getPosition(), cameraInfo.getForwardVector())
.visibilityMask(entry.visibilityMask)
.geometry(scene->getRenderableData(), entry.range)
.commandTypeFlags(RenderPass::CommandTypeFlags::SHADOW)

View File

@@ -94,6 +94,30 @@ 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;
@@ -131,19 +155,7 @@ Engine* FEngine::create(Builder const& builder) {
delete instance;
return nullptr;
}
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);
instance->mDriver = platform->createDriver(sharedContext, getDriverConfig(instance));
} else {
// start the driver thread
@@ -752,19 +764,7 @@ int FEngine::loop() {
JobSystem::setThreadName("FEngine::loop");
JobSystem::setThreadPriority(JobSystem::Priority::DISPLAY);
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);
mDriver = mPlatform->createDriver(mSharedGLContext, getDriverConfig(this));
mDriverBarrier.latch();
if (UTILS_UNLIKELY(!mDriver)) {

View File

@@ -737,6 +737,12 @@ 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
@@ -762,18 +768,21 @@ public:
{ "engine.shadows.use_shadow_atlas",
"Uses an array of atlases to store shadow maps.",
&features.engine.shadows.use_shadow_atlas, false },
{ "features.engine.debug.assert_material_instance_in_use",
{ "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 },
{ "features.engine.debug.assert_destroy_material_before_material_instance",
{ "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 },
{ "features.engine.debug.assert_vertex_buffer_count_exceeds_8",
{ "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 },
{ "features.engine.debug.assert_vertex_buffer_attribute_stride_mult_of_4",
{ "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 {

View File

@@ -619,10 +619,10 @@ Program FMaterial::getProgramWithVariants(
.shader(ShaderStage::FRAGMENT, fsBuilder.data(), fsBuilder.size())
.shaderLanguage(mMaterialParser->getShaderLanguage())
.diagnostics(mName,
[this, variant, vertexVariant, fragmentVariant](
[variant, vertexVariant, fragmentVariant](utils::CString const& name,
io::ostream& out) -> io::ostream& {
return out << mName.c_str_safe() << ", variant=(" << io::hex
<< +variant.key << io::dec << "), vertexVariant=(" << io::hex
return out << name.c_str_safe() << ", variant=(" << io::hex << +variant.key
<< io::dec << "), vertexVariant=(" << io::hex
<< +vertexVariant.key << io::dec << "), fragmentVariant=("
<< io::hex << +fragmentVariant.key << io::dec << ")";
});

View File

@@ -284,11 +284,13 @@ 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, descriptorType))
DescriptorSet::isTextureCompatibleWithDescriptor(textureType, samplerType, 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);

View File

@@ -28,9 +28,19 @@
#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>
@@ -41,23 +51,17 @@
#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 <private/utils/Tracing.h>
#include <utils/architecture.h>
#include <utils/Allocator.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>
@@ -198,6 +202,7 @@ TextureFormat FRenderer::getHdrFormat(const FView& view, bool const translucent)
return mHdrQualityHigh;
}
}
return mHdrQualityMedium;
}
TextureFormat FRenderer::getLdrFormat(bool const translucent) const noexcept {
@@ -230,7 +235,7 @@ void FRenderer::initializeClearFlags() noexcept {
mClearFlags = getClearFlags();
}
void FRenderer::setPresentationTime(int64_t const monotonic_clock_ns) {
void FRenderer::setPresentationTime(int64_t const monotonic_clock_ns) const {
FEngine::DriverApi& driver = mEngine.getDriverApi();
driver.setPresentationTime(monotonic_clock_ns);
}
@@ -239,6 +244,23 @@ 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);
@@ -301,7 +323,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<steady_clock> appVsync(vsyncSteadyClockTimeNano ? userVsync : now);
const time_point appVsync(vsyncSteadyClockTimeNano ? userVsync : now);
mFrameId++;
mViewRenderedCount = 0;
@@ -341,7 +363,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();
@@ -398,7 +420,7 @@ void FRenderer::endFrame() {
FEngine& engine = mEngine;
FEngine::DriverApi& driver = engine.getDriverApi();
if (UTILS_HAS_THREADING) {
if constexpr (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();
@@ -441,7 +463,9 @@ 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;
@@ -453,8 +477,9 @@ void FRenderer::readPixels(uint32_t const xoffset, uint32_t const yoffset, uint3
xoffset, yoffset, width, height, std::move(buffer));
}
void FRenderer::readPixels(FRenderTarget* renderTarget,
uint32_t const xoffset, uint32_t const yoffset, uint32_t const width, uint32_t const height,
void FRenderer::readPixels(FRenderTarget const* 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.
@@ -791,9 +816,29 @@ 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.prepareUpscaler(scale, taaOptions, dsrOptions);
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);
/*
* Allocate command buffer
@@ -935,49 +980,9 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
FView::updatePrimitivesLod(scene.getRenderableData(),
engine, cameraInfo, view.getVisibleRenderables());
passBuilder.camera(cameraInfo);
passBuilder.camera(cameraInfo.getPosition(), cameraInfo.getForwardVector());
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.
@@ -998,68 +1003,20 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
if (view.hasPicking()) {
if (view.isTransparentPickingEnabled()) {
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;
picking = ppm.transparentPicking(fg,
passBuilder, renderFlags, svp.width, svp.height, aoOptions.resolution);
}
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 }}
});
@@ -1067,23 +1024,21 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
},
[=, &view](FrameGraphResources const& resources,
auto const&, DriverApi& driver) mutable {
auto out = resources.getRenderPassInfo();
view.executePickingQueries(driver, out.target, scale * aoOptions.resolution);
auto [target, params] = resources.getRenderPassInfo();
view.executePickingQueries(driver, 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);
fg.addTrivialSideEffectPass("Jitter Camera",
[&engine, &cameraInfo, &descriptorSet = view.getColorPassDescriptorSet()]
(DriverApi& driver) {
descriptorSet.prepareCamera(engine, cameraInfo);
descriptorSet.commit(driver);
});
// this just re-set the color pass UBO content
view.prepareCamera(engine, cameraInfo);
}
// --------------------------------------------------------------------------------------------
@@ -1129,7 +1084,9 @@ 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);
// This is optional, if not set, the per-view descriptor-set must be set before calling execute()
// 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.
passBuilder.colorPassDescriptorSet(&view.getColorPassDescriptorSet());
// color-grading as subpass is done either by the color pass or the TAA pass if any
@@ -1141,7 +1098,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) {
@@ -1149,7 +1106,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);
});
}
@@ -1220,8 +1177,7 @@ 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()
auto sync = view.getFroxelizerSync();
if (sync) {
if (auto sync = view.getFroxelizerSync()) {
js.waitAndRelease(sync);
view.commitFroxels(driver);
}

View File

@@ -87,7 +87,7 @@ public:
void renderStandaloneView(FView const* view);
void setPresentationTime(int64_t monotonic_clock_ns);
void setPresentationTime(int64_t monotonic_clock_ns) const;
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* renderTarget,
void readPixels(FRenderTarget const* renderTarget,
uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height,
backend::PixelBufferDescriptor&& buffer);
@@ -189,6 +189,10 @@ 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;

View File

@@ -16,11 +16,15 @@
#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"
@@ -30,9 +34,15 @@
#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>
@@ -40,18 +50,29 @@
#include <private/utils/Tracing.h>
#include <utils/architecture.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/Profiler.h>
#include <utils/Panic.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 <math/fast.h>
#include <assert.h>
#include <array>
#include <algorithm>
#include <cmath>
#include <chrono>
#include <functional>
#include <memory>
#include <tuple>
#include <ratio>
#include <utility>
using namespace utils;
@@ -114,7 +135,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<DebugState>(mDebugState)]() -> DebugRegistry::DataSource {
[weak = std::weak_ptr(mDebugState)]() -> DebugRegistry::DataSource {
// the View could have been destroyed by the time we do this
auto const state = weak.lock();
if (!state) {
@@ -200,8 +221,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;
}
@@ -274,7 +295,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<float, std::milli>{ info.denoisedFrameTime }.count();
float const measured = duration{ info.denoisedFrameTime }.count();
float const out = mPidController.update(measured / targetWithHeadroom, 1.0f, dt);
// maps pid command to a scale (absolute or relative, see below)
@@ -433,7 +454,7 @@ void FView::prepareShadowing(FEngine& engine, FScene::RenderableSoa& renderableD
if (builder.hasShadowMaps()) {
ShadowMapManager::createIfNeeded(engine, mShadowMapManager);
auto shadowTechnique = mShadowMapManager->update(builder, engine, *this,
auto const shadowTechnique = mShadowMapManager->update(builder, engine, *this,
cameraInfo, renderableData, lightData);
mHasShadowing = any(shadowTechnique);
@@ -493,7 +514,7 @@ void FView::prepareLighting(FEngine& engine, CameraInfo const& cameraInfo) noexc
getColorPassDescriptorSet().prepareDirectionalLight(engine, exposure, sceneSpaceDirection, directionalLight);
}
CameraInfo FView::computeCameraInfo(FEngine& engine) const noexcept {
CameraInfo FView::computeCameraInfo(FEngine const& engine) const noexcept {
FScene const* const scene = getScene();
/*
@@ -546,15 +567,14 @@ 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();
@@ -599,9 +619,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);
@@ -646,7 +666,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<void(JobSystem&, JobSystem::Job*)> froxelizerWork =
std::function froxelizerWork =
[&froxelizer = mFroxelizer, &engine, viewMatrix = cameraInfo.view, &lightData]
(JobSystem&, JobSystem::Job*) {
froxelizer.froxelizeLights(engine, viewMatrix, lightData);
@@ -854,6 +874,7 @@ 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());
@@ -905,29 +926,15 @@ 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 {
@@ -1120,7 +1127,7 @@ void FView::prepareVisibleLights(FLightManager const& lcm,
computeLightCameraDistances(distances, viewMatrix, spheres, visibleLightCount);
// skip directional light
Zip2Iterator<FScene::LightSoa::iterator, float*> b = { lightData.begin(), distances };
Zip2Iterator b = { lightData.begin(), distances };
std::sort(b + FScene::DIRECTIONAL_LIGHTS_COUNT, b + visibleLightCount,
[](auto const& lhs, auto const& rhs) { return lhs.second < rhs.second; });
}
@@ -1290,8 +1297,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 {
@@ -1319,7 +1326,6 @@ 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;
}

View File

@@ -102,6 +102,7 @@ class FScene;
class FView : public View {
public:
using MaterialGlobals = std::array<math::float4, 4>;
using Range = utils::Range<uint32_t>;
explicit FView(FEngine& engine);
@@ -109,7 +110,7 @@ public:
void terminate(FEngine& engine);
CameraInfo computeCameraInfo(FEngine& engine) const noexcept;
CameraInfo computeCameraInfo(FEngine const& 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).
@@ -159,11 +160,10 @@ 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,6 +483,8 @@ public:
return mFrameGraphViewerViewHandle;
}
MaterialGlobals getMaterialGlobals() const { return mMaterialGlobals; }
private:
struct FPickingQuery : public PickingQuery {
private:
@@ -609,14 +611,14 @@ private:
std::unique_ptr<ShadowMapManager> mShadowMapManager;
std::array<math::float4, 4> mMaterialGlobals = {{
{ 0, 0, 0, 1 },
{ 0, 0, 0, 1 },
{ 0, 0, 0, 1 },
{ 0, 0, 0, 1 },
}};
MaterialGlobals 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 {

View File

@@ -16,7 +16,9 @@
#include "ColorPassDescriptorSet.h"
#include "Froxelizer.h"
#include "PerViewDescriptorSetUtils.h"
#include "HwDescriptorSetLayoutFactory.h"
#include "ShadowMapManager.h"
#include "TypedUniformBuffer.h"
@@ -144,44 +146,11 @@ void ColorPassDescriptorSet::terminate(HwDescriptorSetLayoutFactory& factory, Dr
}
void ColorPassDescriptorSet::prepareCamera(FEngine& 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) };
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();
PerViewDescriptorSetUtils::prepareCamera(mUniforms.edit(), engine, camera);
}
void ColorPassDescriptorSet::prepareLodBias(float const bias, float2 const derivativesScale) noexcept {
auto& s = mUniforms.edit();
s.lodBias = bias;
s.derivativesScale = derivativesScale;
PerViewDescriptorSetUtils::prepareLodBias(mUniforms.edit(), bias, derivativesScale);
}
void ColorPassDescriptorSet::prepareExposure(float const ev100) noexcept {
@@ -194,22 +163,11 @@ void ColorPassDescriptorSet::prepareExposure(float const ev100) noexcept {
void ColorPassDescriptorSet::prepareViewport(
const filament::Viewport& physicalViewport,
const filament::Viewport& logicalViewport) noexcept {
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;
PerViewDescriptorSetUtils::prepareViewport(mUniforms.edit(), physicalViewport, logicalViewport);
}
void ColorPassDescriptorSet::prepareTime(FEngine& engine, float4 const& userTime) noexcept {
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;
PerViewDescriptorSetUtils::prepareTime(mUniforms.edit(), engine, userTime);
}
void ColorPassDescriptorSet::prepareTemporalNoise(FEngine& engine,
@@ -324,10 +282,7 @@ void ColorPassDescriptorSet::prepareBlending(bool const needsAlphaChannel) noexc
void ColorPassDescriptorSet::prepareMaterialGlobals(
std::array<float4, 4> const& materialGlobals) noexcept {
mUniforms.edit().custom[0] = materialGlobals[0];
mUniforms.edit().custom[1] = materialGlobals[1];
mUniforms.edit().custom[2] = materialGlobals[2];
mUniforms.edit().custom[3] = materialGlobals[3];
PerViewDescriptorSetUtils::prepareMaterialGlobals(mUniforms.edit(), materialGlobals);
}
void ColorPassDescriptorSet::prepareSSR(Handle<HwTexture> ssr,
@@ -345,25 +300,6 @@ 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, {});

View File

@@ -117,11 +117,6 @@ 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,

View File

@@ -73,8 +73,7 @@ DescriptorSet& DescriptorSet::operator=(DescriptorSet&& rhs) noexcept {
void DescriptorSet::terminate(FEngine::DriverApi& driver) noexcept {
if (mDescriptorSetHandle) {
driver.destroyDescriptorSet(mDescriptorSetHandle);
mDescriptorSetHandle.clear();
driver.destroyDescriptorSet(std::move(mDescriptorSetHandle));
}
}
@@ -198,9 +197,40 @@ DescriptorSet DescriptorSet::duplicate(
return set;
}
bool DescriptorSet::isTextureCompatibleWithDescriptor(
backend::TextureType t, backend::DescriptorType d) noexcept {
backend::TextureType t, backend::SamplerType s, 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:

View File

@@ -86,7 +86,8 @@ public:
}
static bool isTextureCompatibleWithDescriptor(
backend::TextureType t, backend::DescriptorType d) noexcept;
backend::TextureType t, backend::SamplerType s,
backend::DescriptorType d) noexcept;
private:
struct Desc {

View File

@@ -0,0 +1,110 @@
/*
* 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

View File

@@ -0,0 +1,53 @@
/*
* 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

View File

@@ -16,20 +16,21 @@
#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/mat4.h>
#include <math/vec4.h>
#include <stdint.h>
#include <array>
namespace filament {
@@ -62,54 +63,30 @@ PerViewUib& ShadowMapDescriptorSet::edit(Transaction const& transaction) noexcep
}
void ShadowMapDescriptorSet::prepareCamera(Transaction const& transaction,
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();
FEngine const& engine, const CameraInfo& camera) noexcept {
PerViewDescriptorSetUtils::prepareCamera(edit(transaction), engine, camera);
// TODO: stereo values didn't used to be set
}
void ShadowMapDescriptorSet::prepareLodBias(Transaction const& transaction, float const bias) noexcept {
auto& s = edit(transaction);
s.lodBias = bias;
PerViewDescriptorSetUtils::prepareLodBias(edit(transaction), bias, 0);
// TODO: check why derivativesScale was missing
}
void ShadowMapDescriptorSet::prepareViewport(Transaction const& transaction,
backend::Viewport const& viewport) noexcept {
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;
PerViewDescriptorSetUtils::prepareViewport(edit(transaction), viewport, viewport);
// TODO: offset calculation is now different
}
void ShadowMapDescriptorSet::prepareTime(Transaction const& transaction,
FEngine const& engine, float4 const& userTime) noexcept {
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;
PerViewDescriptorSetUtils::prepareTime(edit(transaction), engine, userTime);
}
void ShadowMapDescriptorSet::prepareMaterialGlobals(Transaction const& transaction,
std::array<float4, 4> const& materialGlobals) noexcept {
PerViewDescriptorSetUtils::prepareMaterialGlobals(edit(transaction), materialGlobals);
}
void ShadowMapDescriptorSet::prepareShadowMapping(Transaction const& transaction,

View File

@@ -29,6 +29,8 @@
#include <math/vec4.h>
#include <array>
namespace filament {
struct CameraInfo;
@@ -55,8 +57,10 @@ public:
void terminate(backend::DriverApi& driver);
// All UBO values that can affect user code must be set here
static void prepareCamera(Transaction const& transaction,
backend::DriverApi& driver, const CameraInfo& camera) noexcept;
FEngine const& engine, const CameraInfo& camera) noexcept;
static void prepareLodBias(Transaction const& transaction,
float bias) noexcept;
@@ -67,6 +71,9 @@ 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;

View File

@@ -76,7 +76,7 @@ void SsrPassDescriptorSet::prepareHistorySSR(FEngine const& engine, Handle<HwTex
ScreenSpaceReflectionsOptions const& ssrOptions) noexcept {
mDescriptorSet.setSampler(engine.getPerViewDescriptorSetLayoutSsrVariant(),
+PerViewBindingPoints::SSR, ssr, {
+PerViewBindingPoints::SSR_HISTORY, ssr, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR
});

View File

@@ -0,0 +1,101 @@
/*
* 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

View File

@@ -0,0 +1,72 @@
/*
* 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

View File

@@ -21,7 +21,11 @@
#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>
@@ -31,7 +35,14 @@ 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,
@@ -39,7 +50,12 @@ public:
}
void terminate(backend::DriverApi& driver) noexcept {
driver.destroyBufferObject(mUboHandle);
assert_invariant(mUboHandle);
driver.destroyBufferObject(std::move(mUboHandle));
}
~TypedUniformBuffer() noexcept {
assert_invariant(!mUboHandle);
}
TypedBuffer<T,N>& getTypedBuffer() noexcept {

View File

@@ -54,7 +54,8 @@ 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, RGB_11_11_10, mipmapped
SSR = 10, // variable, 2d array, RGB_11_11_10, mipmapped
SSR_HISTORY = 10, // variable, 2d texture, RGB_11_11_10
FOG = 11 // variable, user defined, CUBEMAP
};

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 },
{ DescriptorType::SAMPLER_2D_ARRAY_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR },
{ DescriptorType::SAMPLER_2D_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE, DescriptorFlags::UNFILTERABLE },
{ DescriptorType::SAMPLER_2D_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::SSR_HISTORY },
};
// 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 },
{ DescriptorType::SAMPLER_2D_FLOAT, ShaderStageFlags::FRAGMENT, +PerViewBindingPoints::STRUCTURE, DescriptorFlags::UNFILTERABLE },
{ 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 },

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(output_test_dir, base_test_dir),
'comparison_dir': os.path.relpath(output_test_dir, comp_test_dir),
'base_dir': os.path.relpath(base_test_dir, output_test_dir),
'comparison_dir': os.path.relpath(comp_test_dir, output_test_dir)
}
with open(output_fname, 'w') as f:
f.write(json.dumps(results_meta, indent=2))

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
# Filter for runs that completed successfully
successful_runs = [run for run in workflow_runs if run.get("conclusion") == "success" and run.get("status") == "completed"]
# Do not filter for runs that completed successfully
successful_runs = sorted(workflow_runs, key=lambda run: run['id'], reverse=True)
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,6 +161,10 @@ 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:

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* 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.
@@ -19,28 +19,37 @@
#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;
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;
// 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__";
// 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:
@@ -82,6 +91,7 @@ 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
@@ -89,6 +99,7 @@ _{RESOURCES}PACKAGE:
.incbin "{resources}.bin"
)ASM";
// Template for the standard assembly file.
static const char* ASM_TEMPLATE = R"ASM(
.global {RESOURCES}PACKAGE
.section .rodata
@@ -96,287 +107,304 @@ 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 execName(Path(name).getName());
std::string const execName(Path(name).getName());
const std::string from("RESGEN");
std::string usage(USAGE);
for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) {
std::size_t pos = 0;
while ((pos = usage.find(from, pos)) != std::string::npos) {
usage.replace(pos, from.length(), execName);
pos += execName.length();
}
puts(usage.c_str());
std::cout << usage;
}
// Prints the license text to the console.
static void license() {
static const char *license[] = {
#include "licenses/licenses.inc"
nullptr
};
const char **p = &license[0];
while (*p)
std::cout << *p++ << std::endl;
for (const char **p = &license[0]; *p; ++p) {
std::cout << *p << std::endl;
}
}
static int handleArguments(int argc, char* argv[]) {
// 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 constexpr const char* OPTSTR = "hLp:x:ktcqj";
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
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 }
};
AppConfig config;
int opt;
int optionIndex = 0;
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &optionIndex)) >= 0) {
std::string arg(optarg ? optarg : "");
while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, nullptr)) >= 0) {
switch (opt) {
default:
case 'h':
printUsage(argv[0]);
exit(0);
std::exit(0);
case 'L':
license();
exit(0);
std::exit(0);
case 'p':
g_packageName = optarg;
config.packageName = optarg;
break;
case 'x':
g_deployDir = optarg;
config.deployDir = optarg;
break;
case 'k':
g_keepExtension = true;
config.keepExtension = true;
break;
case 't':
g_appendNull = true;
config.appendNull = true;
break;
case 'c':
g_generateC = true;
config.generateC = true;
break;
case 'q':
g_quietMode = true;
config.quietMode = true;
break;
case 'j':
g_embedJson = true;
config.embedJson = true;
break;
default:
printUsage(argv[0]);
std::exit(1);
}
}
return optind;
// 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";
}
int main(int argc, char* argv[]) {
const int optionIndex = handleArguments(argc, argv);
const int numArgs = argc - optionIndex;
if (numArgs < 1) {
// Parse arguments and check for input files.
const AppConfig config = handleArguments(argc, argv);
if (config.inputPaths.empty()) {
printUsage(argv[0]);
return 1;
}
vector<Path> inputPaths;
for (int argIndex = optionIndex; argIndex < argc; ++argIndex) {
inputPaths.emplace_back(argv[argIndex]);
}
if (g_embedJson) {
inputPaths.push_back(g_jsonMagicString);
// 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);
}
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";
// 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";
const Path deployDir(g_deployDir);
// Create the deployment directory if it doesn't exist.
const Path deployDir(config.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"));
// In the assembly language templates, replace {RESOURCES} with packagePrefix and replace
// {resources} with packageFile.
const std::string k1("{RESOURCES}");
const std::string k2("{resources}");
// Prepare assembly file content by replacing placeholders.
std::string aasmstr(APPLE_ASM_TEMPLATE);
replaceAll(aasmstr, "{RESOURCES}", packagePrefix);
replaceAll(aasmstr, "{resources}", packageFile);
std::string asmstr(ASM_TEMPLATE);
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);
replaceAll(asmstr, "{RESOURCES}", packagePrefix);
replaceAll(asmstr, "{resources}", packageFile);
// Open the Apple-friendly assembly language file.
ofstream appleAsmStream(appleAsmPath.getPath());
if (!appleAsmStream) {
cerr << "Unable to open " << appleAsmPath << endl;
exit(1);
}
// Open all output file streams.
auto appleAsmStream = openOutputFile(appleAsmPath);
auto asmStream = openOutputFile(asmPath);
auto binStream = openOutputFile(binPath, std::ios::binary);
// Open the non-Apple assembly language file.
ofstream asmStream(asmPath.getPath());
if (!asmStream) {
cerr << "Unable to open " << asmPath << endl;
exit(1);
}
// 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";
// Open the bin file for writing.
ofstream binStream(binPath.getPath(), ios::binary);
if (!binStream) {
cerr << "Unable to open " << binPath << endl;
exit(1);
}
// String streams to accumulate header macros and the JSON summary.
std::ostringstream headerMacros;
std::ostringstream jsonStream;
// 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);
}
// 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 " << package << "[] = {\n";
<< "const uint8_t " << packageSymbol << "[] = {\n";
}
// Consume each input file and write it back out into the various output streams.
// Process each input file to build the resource collection.
jsonStream << "{";
size_t offset = 0;
std::size_t offset = 0;
for (const auto& inPath : inputPaths) {
vector<uint8_t> content;
std::vector<std::uint8_t> content;
if (inPath != g_jsonMagicString) {
ifstream inStream(inPath.getPath(), ios::binary);
if (!inStream) {
cerr << "Unable to open " << inPath << endl;
exit(1);
}
content = vector<uint8_t>((istreambuf_iterator<char>(inStream)), {});
// For a regular file, read its binary content.
content = readFile(inPath);
} else {
// To finalize the JSON string, we replace the trailing comma with an end bracket and
// prefix it with the magic identifier and string size.
// For the JSON placeholder, finalize and embed the JSON summary blob.
// The blob is formatted as: __RESGEN__\0<size>\0{...json...}
std::string jsonString = jsonStream.str();
jsonString[jsonString.size()-1] = '}';
ostringstream jsonBlob;
jsonBlob << g_jsonMagicString << "\0";
jsonBlob << jsonString.size() << "\0";
jsonString[jsonString.size() - 1] = '}';
std::ostringstream jsonBlob;
jsonBlob << g_jsonMagicString << '\0';
jsonBlob << jsonString.size() << '\0';
jsonBlob << jsonString;
jsonString = jsonBlob.str();
uint8_t const* jsonPtr = (uint8_t const*) jsonString.c_str();
content = vector<uint8_t>(jsonPtr, jsonPtr + jsonBlob.str().size());
const auto* jsonPtr = reinterpret_cast<const std::uint8_t*>(jsonString.c_str());
content.assign(jsonPtr, jsonPtr + jsonBlob.str().size());
}
if (g_appendNull) {
// Optionally append a null terminator, useful for text resources.
if (config.appendNull) {
content.push_back(0);
}
// 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);
// 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); });
const std::string prname = packagePrefix + rname;
// Write the binary blob into the bin file.
binStream.write((const char*) content.data(), content.size());
// Write the resource content to the aggregate binary file.
binStream.write(reinterpret_cast<const char*>(content.data()), content.size());
// 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";
// 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 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";
// If enabled, write the content to the C source file.
if (config.generateC) {
writeXxdEntry(xxdStream, rname, content);
}
// 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";
// 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);
}
// 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;
}
if (headerIsDirty) {
ofstream headerOutStream(headerPath.getPath());
if (!headerOutStream) {
cerr << "Unable to open " << headerPath << endl;
exit(1);
}
headerOutStream << headerContents;
}
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) {
// Finalize the C file and report it.
if (config.generateC) {
xxdStream << "};\n\n";
if (!g_quietMode) {
cout << " " << xxdPath;
if (!config.quietMode) {
std::cout << " " << xxdPath;
}
}
if (!g_quietMode) {
cout << endl;
if (!config.quietMode) {
std::cout << std::endl;
}
}
return 0;
}