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