Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edfdc7fe44 | ||
|
|
eb483501f0 | ||
|
|
c54fe53936 | ||
|
|
b915e2f35f | ||
|
|
c24e5089c4 | ||
|
|
87dbd59275 | ||
|
|
3c02eb9a63 |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.58.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.59.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.58.2'
|
||||
pod 'Filament', '~> 1.59.1'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,6 +7,9 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.59.1
|
||||
|
||||
|
||||
## v1.59.0
|
||||
|
||||
- materials: five custom variables (varyings) are now available on the condition that the `color` attribute is not requested (b/404930099). [⚠️ **New Material Version**]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.58.2
|
||||
VERSION_NAME=1.59.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -177,29 +177,6 @@ endif()
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatform.h
|
||||
src/vulkan/VulkanDescriptorSetCache.cpp
|
||||
src/vulkan/VulkanDescriptorSetCache.h
|
||||
src/vulkan/VulkanDescriptorSetLayoutCache.cpp
|
||||
src/vulkan/VulkanDescriptorSetLayoutCache.h
|
||||
src/vulkan/VulkanPipelineLayoutCache.cpp
|
||||
src/vulkan/VulkanPipelineLayoutCache.h
|
||||
src/vulkan/memory/ResourceManager.cpp
|
||||
src/vulkan/memory/ResourceManager.h
|
||||
src/vulkan/memory/ResourcePointer.h
|
||||
src/vulkan/memory/Resource.cpp
|
||||
src/vulkan/memory/Resource.h
|
||||
src/vulkan/platform/VulkanPlatform.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
|
||||
src/vulkan/utils/Conversion.cpp
|
||||
src/vulkan/utils/Conversion.h
|
||||
src/vulkan/utils/Definitions.h
|
||||
src/vulkan/utils/Helper.h
|
||||
src/vulkan/utils/Image.h
|
||||
src/vulkan/utils/Image.cpp
|
||||
src/vulkan/utils/Spirv.h
|
||||
src/vulkan/utils/Spirv.cpp
|
||||
src/vulkan/utils/StaticVector.h
|
||||
src/vulkan/VulkanAsyncHandles.h
|
||||
src/vulkan/VulkanBlitter.cpp
|
||||
src/vulkan/VulkanBlitter.h
|
||||
@@ -210,29 +187,56 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/VulkanConstants.h
|
||||
src/vulkan/VulkanContext.cpp
|
||||
src/vulkan/VulkanContext.h
|
||||
src/vulkan/VulkanDescriptorSetCache.cpp
|
||||
src/vulkan/VulkanDescriptorSetCache.h
|
||||
src/vulkan/VulkanDescriptorSetLayoutCache.cpp
|
||||
src/vulkan/VulkanDescriptorSetLayoutCache.h
|
||||
src/vulkan/VulkanDriver.cpp
|
||||
src/vulkan/VulkanDriver.h
|
||||
src/vulkan/VulkanDriverFactory.h
|
||||
src/vulkan/VulkanExternalImageManager.cpp
|
||||
src/vulkan/VulkanExternalImageManager.h
|
||||
src/vulkan/VulkanFboCache.cpp
|
||||
src/vulkan/VulkanFboCache.h
|
||||
src/vulkan/VulkanHandles.cpp
|
||||
src/vulkan/VulkanHandles.h
|
||||
src/vulkan/VulkanMemory.h
|
||||
src/vulkan/VulkanMemory.cpp
|
||||
src/vulkan/VulkanMemory.h
|
||||
src/vulkan/VulkanPipelineCache.cpp
|
||||
src/vulkan/VulkanPipelineCache.h
|
||||
src/vulkan/VulkanPipelineLayoutCache.cpp
|
||||
src/vulkan/VulkanPipelineLayoutCache.h
|
||||
src/vulkan/VulkanQueryManager.cpp
|
||||
src/vulkan/VulkanQueryManager.h
|
||||
src/vulkan/VulkanReadPixels.cpp
|
||||
src/vulkan/VulkanReadPixels.h
|
||||
src/vulkan/VulkanSamplerCache.cpp
|
||||
src/vulkan/VulkanSamplerCache.h
|
||||
src/vulkan/VulkanStagePool.cpp
|
||||
src/vulkan/VulkanStagePool.h
|
||||
src/vulkan/VulkanSwapChain.cpp
|
||||
src/vulkan/VulkanSwapChain.h
|
||||
src/vulkan/VulkanReadPixels.cpp
|
||||
src/vulkan/VulkanReadPixels.h
|
||||
src/vulkan/VulkanTexture.cpp
|
||||
src/vulkan/VulkanTexture.h
|
||||
src/vulkan/VulkanYcbcrConversionCache.cpp
|
||||
src/vulkan/VulkanYcbcrConversionCache.h
|
||||
src/vulkan/memory/Resource.cpp
|
||||
src/vulkan/memory/Resource.h
|
||||
src/vulkan/memory/ResourceManager.cpp
|
||||
src/vulkan/memory/ResourceManager.h
|
||||
src/vulkan/memory/ResourcePointer.h
|
||||
src/vulkan/platform/VulkanPlatform.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
|
||||
src/vulkan/utils/Conversion.cpp
|
||||
src/vulkan/utils/Conversion.h
|
||||
src/vulkan/utils/Definitions.h
|
||||
src/vulkan/utils/Helper.h
|
||||
src/vulkan/utils/Image.cpp
|
||||
src/vulkan/utils/Image.h
|
||||
src/vulkan/utils/Spirv.cpp
|
||||
src/vulkan/utils/Spirv.h
|
||||
src/vulkan/utils/StaticVector.h
|
||||
)
|
||||
if (LINUX OR WIN32)
|
||||
list(APPEND SRCS src/vulkan/platform/VulkanPlatformLinuxWindows.cpp)
|
||||
|
||||
@@ -298,6 +298,16 @@ public:
|
||||
VkQueue getProtectedGraphicsQueue() const noexcept;
|
||||
|
||||
struct ExternalImageMetadata {
|
||||
/**
|
||||
* The Filament texture format.
|
||||
*/
|
||||
TextureFormat filamentFormat;
|
||||
|
||||
/**
|
||||
* The Filament texture usage.
|
||||
*/
|
||||
TextureUsage filamentUsage;
|
||||
|
||||
/**
|
||||
* The width of the external image
|
||||
*/
|
||||
@@ -308,11 +318,6 @@ public:
|
||||
*/
|
||||
uint32_t height;
|
||||
|
||||
/**
|
||||
* The layerCount of the external image
|
||||
*/
|
||||
uint32_t layerCount;
|
||||
|
||||
/**
|
||||
* The layer count of the external image
|
||||
*/
|
||||
@@ -328,11 +333,6 @@ public:
|
||||
*/
|
||||
VkFormat format;
|
||||
|
||||
/**
|
||||
* An external buffer can be protected. This tells you if it is.
|
||||
*/
|
||||
bool isProtected;
|
||||
|
||||
/**
|
||||
* The type of external format (opaque int) if used.
|
||||
*/
|
||||
@@ -352,21 +352,44 @@ public:
|
||||
* Heap information
|
||||
*/
|
||||
uint32_t memoryTypeBits;
|
||||
|
||||
/**
|
||||
* Ycbcr conversion components
|
||||
*/
|
||||
VkComponentMapping ycbcrConversionComponents;
|
||||
|
||||
/**
|
||||
* Ycbcr model
|
||||
*/
|
||||
VkSamplerYcbcrModelConversion ycbcrModel;
|
||||
|
||||
/**
|
||||
* Ycbcr range
|
||||
*/
|
||||
VkSamplerYcbcrRange ycbcrRange;
|
||||
|
||||
/**
|
||||
* Ycbcr x chroma offset
|
||||
*/
|
||||
VkChromaLocation xChromaOffset;
|
||||
|
||||
/**
|
||||
* Ycbcr y chroma offset
|
||||
*/
|
||||
VkChromaLocation yChromaOffset;
|
||||
};
|
||||
virtual ExternalImageMetadata getExternalImageMetadata(ExternalImageHandleRef externalImage);
|
||||
|
||||
|
||||
// Note that the image metadata might change per-frame, hence we need a method for extracting
|
||||
// it.
|
||||
virtual ExternalImageMetadata extractExternalImageMetadata(ExternalImageHandleRef image) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
using ImageData = std::pair<VkImage, VkDeviceMemory>;
|
||||
virtual ImageData createExternalImageData(ExternalImageHandleRef externalImage,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
||||
VkImageUsageFlags usage);
|
||||
|
||||
virtual VkSampler createExternalSampler(SamplerYcbcrConversion chroma,
|
||||
SamplerParams sampler,
|
||||
uint32_t internalFormat);
|
||||
|
||||
virtual VkImageView createExternalImageView(SamplerYcbcrConversion chroma,
|
||||
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
|
||||
VkImageViewType viewType, VkComponentMapping swizzle);
|
||||
virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const {
|
||||
return { VK_NULL_HANDLE, VK_NULL_HANDLE };
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
@@ -379,20 +402,6 @@ private:
|
||||
// Platform dependent helper methods
|
||||
static ExtensionSet getSwapchainInstanceExtensionsImpl();
|
||||
|
||||
static ExternalImageMetadata getExternalImageMetadataImpl(ExternalImageHandleRef externalImage,
|
||||
VkDevice device);
|
||||
|
||||
static ImageData createExternalImageDataImpl(ExternalImageHandleRef externalImage,
|
||||
VkDevice device, const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
||||
VkImageUsageFlags usage);
|
||||
static VkSampler createExternalSamplerImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma, SamplerParams sampler,
|
||||
uint32_t internalFormat);
|
||||
static VkImageView createExternalImageViewImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
|
||||
VkImageSubresourceRange range, VkImageViewType viewType,
|
||||
VkComponentMapping swizzle);
|
||||
|
||||
// Platform dependent helper methods
|
||||
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) noexcept;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace filament::backend {
|
||||
|
||||
class VulkanPlatformAndroid : public VulkanPlatform {
|
||||
public:
|
||||
Platform::ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer,
|
||||
ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept;
|
||||
|
||||
struct UTILS_PUBLIC ExternalImageDescAndroid {
|
||||
@@ -39,31 +39,26 @@ public:
|
||||
ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(
|
||||
ExternalImageHandleRef externalImage) const noexcept;
|
||||
|
||||
virtual ExternalImageMetadata extractExternalImageMetadata(
|
||||
ExternalImageHandleRef image) const override;
|
||||
|
||||
virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const override;
|
||||
|
||||
protected:
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
|
||||
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
|
||||
private:
|
||||
struct ExternalImageVulkanAndroid : public Platform::ExternalImage {
|
||||
AHardwareBuffer* aHardwareBuffer = nullptr;
|
||||
bool sRGB = false;
|
||||
unsigned int width; // Texture width
|
||||
unsigned int height; // Texture height
|
||||
TextureFormat format;// Texture format
|
||||
TextureUsage usage; // Texture usage flags
|
||||
|
||||
protected:
|
||||
~ExternalImageVulkanAndroid() override;
|
||||
};
|
||||
|
||||
virtual ExternalImageMetadata getExternalImageMetadata(ExternalImageHandleRef externalImage);
|
||||
|
||||
using ImageData = VulkanPlatform::ImageData;
|
||||
virtual ImageData createExternalImageData(ExternalImageHandleRef externalImage,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
||||
VkImageUsageFlags usage);
|
||||
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
|
||||
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept;
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -40,9 +40,11 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define HandleAllocatorGL HandleAllocator<32, 96, 184> // ~4520 / pool / MiB
|
||||
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
|
||||
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
|
||||
#define HandleAllocatorGL HandleAllocator<32, 96, 184> // ~4520 / pool / MiB
|
||||
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
|
||||
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
|
||||
// TODO WebGPU examine right size of handles
|
||||
#define HandleAllocatorWGPU HandleAllocator<64, 160, 552> // ~1820 / pool / MiB
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -211,4 +211,8 @@ template class HandleAllocatorVK;
|
||||
template class HandleAllocatorMTL;
|
||||
#endif
|
||||
|
||||
#if defined (FILAMENT_SUPPORTS_WEBGPU)
|
||||
template class HandleAllocatorWGPU;
|
||||
#endif
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
#include "VulkanBlitter.h"
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanFboCache.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "vulkan/utils/Image.h"
|
||||
|
||||
|
||||
@@ -24,13 +24,6 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanBuffer;
|
||||
class VulkanFboCache;
|
||||
class VulkanPipelineCache;
|
||||
class VulkanSamplerCache;
|
||||
|
||||
struct VulkanProgram;
|
||||
|
||||
class VulkanBlitter {
|
||||
public:
|
||||
VulkanBlitter(VkPhysicalDevice physicalDevice, VulkanCommands* commands) noexcept;
|
||||
|
||||
@@ -56,9 +56,9 @@ VkImageView VulkanAttachment::getImageView() {
|
||||
assert_invariant(texture);
|
||||
VkImageSubresourceRange range = getSubresourceRange();
|
||||
if (range.layerCount > 1) {
|
||||
return texture->getMultiviewAttachmentView(range);
|
||||
return texture->getAttachmentView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY);
|
||||
}
|
||||
return texture->getAttachmentView(range);
|
||||
return texture->getAttachmentView(range, VK_IMAGE_VIEW_TYPE_2D);
|
||||
}
|
||||
|
||||
bool VulkanAttachment::isDepth() const {
|
||||
@@ -68,11 +68,11 @@ bool VulkanAttachment::isDepth() const {
|
||||
VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
|
||||
assert_invariant(texture);
|
||||
return {
|
||||
.aspectMask = texture->getImageAspect(),
|
||||
.baseMipLevel = uint32_t(level),
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = uint32_t(layer),
|
||||
.layerCount = layerCount,
|
||||
.aspectMask = texture->getImageAspect(),
|
||||
.baseMipLevel = uint32_t(level),
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = uint32_t(layer),
|
||||
.layerCount = layerCount,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "vulkan/utils/Image.h"
|
||||
#include "vulkan/utils/Definitions.h"
|
||||
|
||||
@@ -30,8 +29,6 @@
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
VK_DEFINE_HANDLE(VmaAllocator)
|
||||
VK_DEFINE_HANDLE(VmaPool)
|
||||
|
||||
@@ -73,13 +70,11 @@ struct VulkanRenderPass {
|
||||
// context are stored in VulkanPlatform.
|
||||
struct VulkanContext {
|
||||
public:
|
||||
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
|
||||
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
|
||||
assert_invariant(isProtectedMemorySupported() == true);
|
||||
}
|
||||
static uint32_t selectMemoryType(VkPhysicalDeviceMemoryProperties const& memoryProperties,
|
||||
uint32_t flags, VkFlags reqs) {
|
||||
for (uint32_t i = 0; i < VK_MAX_MEMORY_TYPES; i++) {
|
||||
if (flags & 1) {
|
||||
if ((mMemoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
|
||||
if ((memoryProperties.memoryTypes[i].propertyFlags & reqs) == reqs) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -88,6 +83,13 @@ public:
|
||||
return (uint32_t) VK_MAX_MEMORY_TYPES;
|
||||
}
|
||||
|
||||
inline uint32_t selectMemoryType(uint32_t flags, VkFlags reqs) const {
|
||||
if ((reqs & VK_MEMORY_PROPERTY_PROTECTED_BIT) != 0) {
|
||||
assert_invariant(isProtectedMemorySupported());
|
||||
}
|
||||
return selectMemoryType(mMemoryProperties, flags, reqs);
|
||||
}
|
||||
|
||||
inline fvkutils::VkFormatList const& getAttachmentDepthStencilFormats() const {
|
||||
return mDepthStencilFormats;
|
||||
}
|
||||
@@ -121,7 +123,7 @@ public:
|
||||
}
|
||||
|
||||
inline bool isMultiviewEnabled() const noexcept {
|
||||
return mMultiviewEnabled;
|
||||
return mPhysicalDeviceVk11Features.multiview == VK_TRUE;
|
||||
}
|
||||
|
||||
inline bool isClipDistanceSupported() const noexcept {
|
||||
@@ -145,6 +147,9 @@ private:
|
||||
VkPhysicalDeviceProperties2 mPhysicalDeviceProperties = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
|
||||
};
|
||||
VkPhysicalDeviceVulkan11Features mPhysicalDeviceVk11Features = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
|
||||
};
|
||||
VkPhysicalDeviceFeatures2 mPhysicalDeviceFeatures = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||
};
|
||||
@@ -157,7 +162,6 @@ private:
|
||||
};
|
||||
bool mDebugMarkersSupported = false;
|
||||
bool mDebugUtilsSupported = false;
|
||||
bool mMultiviewEnabled = false;
|
||||
bool mLazilyAllocatedMemorySupported = false;
|
||||
bool mProtectedMemorySupported = false;
|
||||
|
||||
|
||||
@@ -260,7 +260,6 @@ VulkanDescriptorSetCache::~VulkanDescriptorSetCache() = default;
|
||||
|
||||
void VulkanDescriptorSetCache::terminate() noexcept{
|
||||
mDescriptorPool.reset();
|
||||
clearHistory();
|
||||
}
|
||||
|
||||
// bind() is not really binding the set but just stashing until we have all the info
|
||||
@@ -283,18 +282,25 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
||||
fvkutils::DescriptorSetMask curMask = setMask;
|
||||
|
||||
auto& updateSets = mStashedSets;
|
||||
auto& lastBoundSets = mLastBoundInfo.boundSets;
|
||||
bool const pipelineLayoutIsSame = mLastBoundInfo.pipelineLayout == pipelineLayout;
|
||||
|
||||
setMask.forEachSetBit([&](size_t index) {
|
||||
if (!updateSets[index] || updateSets[index] == lastBoundSets[index]) {
|
||||
curMask.unset(index);
|
||||
if (pipelineLayoutIsSame) {
|
||||
auto& lastBoundSets = mLastBoundInfo.boundSets;
|
||||
setMask.forEachSetBit([&](size_t index) {
|
||||
if (!updateSets[index] || updateSets[index] == lastBoundSets[index]) {
|
||||
curMask.unset(index);
|
||||
}
|
||||
});
|
||||
if (curMask.none() &&
|
||||
mLastBoundInfo.setMask == setMask && mLastBoundInfo.boundSets == updateSets) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (curMask.none() &&
|
||||
(mLastBoundInfo.pipelineLayout == pipelineLayout && mLastBoundInfo.setMask == setMask &&
|
||||
mLastBoundInfo.boundSets == updateSets)) {
|
||||
return;
|
||||
} else {
|
||||
setMask.forEachSetBit([&](size_t index) {
|
||||
if (!updateSets[index]) {
|
||||
curMask.unset(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) {
|
||||
@@ -302,7 +308,7 @@ void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
||||
auto set = updateSets[index];
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
|
||||
1, &set->vkSet, set->uniqueDynamicUboCount, set->getOffsets()->data());
|
||||
1, &set->getVkSet(), set->uniqueDynamicUboCount, set->getOffsets()->data());
|
||||
commands->acquire(set);
|
||||
});
|
||||
|
||||
@@ -330,8 +336,7 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
||||
}
|
||||
VkWriteDescriptorSet const descriptorWrite = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set->vkSet,
|
||||
.dstSet = set->getVkSet(),
|
||||
.dstBinding = binding,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = type,
|
||||
@@ -344,26 +349,25 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
||||
VkSampler sampler) noexcept {
|
||||
VkDescriptorImageInfo info{
|
||||
.sampler = sampler,
|
||||
};
|
||||
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
|
||||
VkImageSubresourceRange range = texture->getPrimaryViewRange();
|
||||
VkImageViewType const expectedType = texture->getViewType();
|
||||
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
|
||||
expectedType == VK_IMAGE_VIEW_TYPE_2D) {
|
||||
// If the sampler is part of a mipmapped depth texture, where one of the level *can* be
|
||||
// an attachment, then the sampler for this texture has the same view properties as a
|
||||
// view for an attachment. Therefore, we can use getAttachmentView to get a
|
||||
// corresponding VkImageView.
|
||||
info.imageView = texture->getAttachmentView(range);
|
||||
} else {
|
||||
info.imageView = texture->getViewForType(range, expectedType);
|
||||
// an attachment, then the range for this view has exactly one level and one layer.
|
||||
range.levelCount = 1;
|
||||
range.layerCount = 1;
|
||||
}
|
||||
info.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout());
|
||||
VkDescriptorImageInfo info{
|
||||
.sampler = sampler,
|
||||
.imageView = texture->getView(range),
|
||||
.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout()),
|
||||
};
|
||||
|
||||
VkWriteDescriptorSet const descriptorWrite = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set->vkSet,
|
||||
.dstSet = set->getVkSet(),
|
||||
.dstBinding = binding,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
@@ -381,10 +385,10 @@ void VulkanDescriptorSetCache::updateInputAttachment(
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet(
|
||||
Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
auto const vkSet = mDescriptorPool->obtainSet(layout);
|
||||
auto const vkSet = getVkSet(layout);
|
||||
auto const& count = layout->count;
|
||||
auto const vklayout = layout->getVkLayout();
|
||||
return fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle, vkSet,
|
||||
auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle,
|
||||
layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
|
||||
[vkSet, count, vklayout, this](VulkanDescriptorSet*) {
|
||||
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts
|
||||
@@ -393,10 +397,20 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet
|
||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||
}
|
||||
});
|
||||
set->setVkSet(vkSet);
|
||||
return set;
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::clearHistory() {
|
||||
mStashedSets = {};
|
||||
VkDescriptorSet VulkanDescriptorSetCache::getVkSet(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
return mDescriptorPool->obtainSet(layout);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::manualRecyle(VulkanDescriptorSetLayout::Count const& count,
|
||||
VkDescriptorSetLayout vklayout, VkDescriptorSet vkSet) {
|
||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -41,6 +41,8 @@ public:
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
|
||||
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||
using DescriptorSetArray =
|
||||
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
VulkanDescriptorSetCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
||||
~VulkanDescriptorSetCache();
|
||||
@@ -68,14 +70,21 @@ public:
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
void clearHistory();
|
||||
// This method is only meant to be used with external samplers (or internally within this
|
||||
// class).
|
||||
VkDescriptorSet getVkSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
// This method is only meant to be used with external samplers.
|
||||
void manualRecyle(VulkanDescriptorSetLayout::Count const& count, VkDescriptorSetLayout vklayout,
|
||||
VkDescriptorSet vkSet);
|
||||
|
||||
DescriptorSetArray const& getBoundSets() const { return mStashedSets; }
|
||||
|
||||
void gc();
|
||||
|
||||
private:
|
||||
class DescriptorInfinitePool;
|
||||
|
||||
using DescriptorSetArray =
|
||||
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
VkDevice mDevice;
|
||||
fvkmemory::ResourceManager* mResourceManager;
|
||||
std::unique_ptr<DescriptorInfinitePool> mDescriptorPool;
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
@@ -58,6 +60,56 @@ uint32_t appendBindings(VkDescriptorSetLayoutBinding* toBind, VkDescriptorType t
|
||||
return count;
|
||||
}
|
||||
|
||||
uint32_t appendSamplerBindings(VkDescriptorSetLayoutBinding* toBind,
|
||||
fvkutils::SamplerBitmask const& mask, fvkutils::SamplerBitmask const& external,
|
||||
utils::FixedCapacityVector<VkSampler> const& immutableSamplers) {
|
||||
using Bitmask = fvkutils::SamplerBitmask;
|
||||
uint32_t count = 0;
|
||||
Bitmask alreadySeen;
|
||||
uint8_t immutableIndex = 0;
|
||||
size_t const immutableSamplerCount = immutableSamplers.size();
|
||||
mask.forEachSetBit([&](size_t index) {
|
||||
VkShaderStageFlags stages = 0;
|
||||
uint32_t binding = 0;
|
||||
if (index < fvkutils::getFragmentStageShift<Bitmask>()) {
|
||||
binding = (uint32_t) index;
|
||||
stages |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||
auto fragIndex = index + fvkutils::getFragmentStageShift<Bitmask>();
|
||||
if (mask.test(fragIndex)) {
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
alreadySeen.set(fragIndex);
|
||||
}
|
||||
} else if (!alreadySeen.test(index)) {
|
||||
// We are in fragment stage bits
|
||||
binding = (uint32_t) (index - fvkutils::getFragmentStageShift<Bitmask>());
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
}
|
||||
|
||||
if (stages) {
|
||||
toBind[count++] = {
|
||||
.binding = binding,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = stages,
|
||||
.pImmutableSamplers = external[index] && immutableSamplerCount > immutableIndex
|
||||
? &immutableSamplers[immutableIndex++]
|
||||
: nullptr,
|
||||
};
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
uint64_t computeImmutableSamplerHash(utils::FixedCapacityVector<VkSampler> const& samplers) {
|
||||
size_t const size = samplers.size();
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
} else if (size == 1) {
|
||||
return (uint64_t) samplers[0];
|
||||
}
|
||||
return utils::hash::murmur3((uint32_t*) samplers.data(), samplers.size() * 2, 0);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanDescriptorSetLayoutCache::VulkanDescriptorSetLayoutCache(VkDevice device,
|
||||
@@ -73,37 +125,44 @@ void VulkanDescriptorSetLayoutCache::terminate() noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
|
||||
VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
||||
utils::FixedCapacityVector<VkSampler> immutableSamplers) {
|
||||
LayoutKey key = {
|
||||
.bitmask = bitmasks,
|
||||
.immutableSamplerHash = computeImmutableSamplerHash(immutableSamplers),
|
||||
};
|
||||
if (auto itr = mVkLayouts.find(key); itr != mVkLayouts.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
|
||||
uint32_t count = 0;
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
||||
bitmasks.dynamicUbo);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
|
||||
count += appendSamplerBindings(&toBind[count], bitmasks.sampler, bitmasks.externalSampler,
|
||||
immutableSamplers);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
|
||||
bitmasks.inputAttachment);
|
||||
|
||||
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
|
||||
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.bindingCount = count,
|
||||
.pBindings = toBind,
|
||||
};
|
||||
VkDescriptorSetLayout vklayout;
|
||||
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
|
||||
mVkLayouts[key] = vklayout;
|
||||
return vklayout;
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> VulkanDescriptorSetLayoutCache::createLayout(
|
||||
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info) {
|
||||
auto layout = fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::make(mResourceManager, handle,
|
||||
info);
|
||||
VkDescriptorSetLayout vklayout = VK_NULL_HANDLE;
|
||||
auto const& bitmasks = layout->bitmask;
|
||||
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
|
||||
vklayout = itr->second;
|
||||
} else {
|
||||
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
|
||||
uint32_t count = 0;
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
||||
bitmasks.dynamicUbo);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
bitmasks.sampler);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
|
||||
bitmasks.inputAttachment);
|
||||
|
||||
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
|
||||
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.bindingCount = count,
|
||||
.pBindings = toBind,
|
||||
};
|
||||
|
||||
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
|
||||
mVkLayouts[bitmasks] = vklayout;
|
||||
}
|
||||
layout->setVkLayout(vklayout);
|
||||
layout->setVkLayout(getVkLayout(layout->bitmask));
|
||||
return layout;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,11 @@
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanDescriptorSetLayoutCache {
|
||||
@@ -41,21 +40,34 @@ public:
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
// Just a wrapper around getVkLayout()
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> createLayout(
|
||||
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info);
|
||||
|
||||
// This method is meant to be used with external samplers
|
||||
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
||||
utils::FixedCapacityVector<VkSampler> immutableSamplers = {});
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
fvkmemory::ResourceManager* mResourceManager;
|
||||
|
||||
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
|
||||
using BitmaskGroupHashFn = utils::hash::MurmurHashFn<BitmaskGroup>;
|
||||
struct BitmaskGroupEqual {
|
||||
bool operator()(BitmaskGroup const& k1, BitmaskGroup const& k2) const { return k1 == k2; }
|
||||
struct LayoutKey {
|
||||
// this describes the layout using bitset.
|
||||
VulkanDescriptorSetLayout::Bitmask bitmask = {};
|
||||
// number of immutable samplers can be arbitrary; so we hash them into 64-bit.
|
||||
uint64_t immutableSamplerHash = 0;
|
||||
};
|
||||
static_assert(sizeof(LayoutKey) == 48);
|
||||
|
||||
using LayoutKeyHashFn = utils::hash::MurmurHashFn<LayoutKey>;
|
||||
struct LayoutKeyEqual {
|
||||
bool operator()(LayoutKey const& k1, LayoutKey const& k2) const {
|
||||
return k1.bitmask == k2.bitmask && k1.immutableSamplerHash == k2.immutableSamplerHash;
|
||||
}
|
||||
};
|
||||
|
||||
tsl::robin_map<BitmaskGroup, VkDescriptorSetLayout, BitmaskGroupHashFn, BitmaskGroupEqual>
|
||||
mVkLayouts;
|
||||
tsl::robin_map<LayoutKey, VkDescriptorSetLayout, LayoutKeyHashFn, LayoutKeyEqual> mVkLayouts;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -25,10 +25,12 @@
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "vulkan/VulkanSamplerCache.h"
|
||||
#include "vulkan/memory/ResourceManager.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
#include "vulkan/utils/Definitions.h"
|
||||
#include "vulkan/vulkan_core.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
@@ -198,9 +200,7 @@ Dispatcher VulkanDriver::getDispatcher() const noexcept {
|
||||
VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig) noexcept
|
||||
: mPlatform(platform),
|
||||
mResourceManager(
|
||||
driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck,
|
||||
mResourceManager(driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck,
|
||||
driverConfig.disableHeapHandleTags),
|
||||
mAllocator(createAllocator(mPlatform->getInstance(), mPlatform->getPhysicalDevice(),
|
||||
mPlatform->getDevice())),
|
||||
@@ -212,12 +212,15 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mPipelineCache(mPlatform->getDevice()),
|
||||
mStagePool(mAllocator, &mCommands),
|
||||
mFramebufferCache(mPlatform->getDevice()),
|
||||
mYcbcrConversionCache(mPlatform->getDevice()),
|
||||
mSamplerCache(mPlatform->getDevice()),
|
||||
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
|
||||
mReadPixels(mPlatform->getDevice()),
|
||||
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mQueryManager(mPlatform->getDevice()),
|
||||
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
|
||||
&mDescriptorSetLayoutCache),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
||||
mStereoscopicType(driverConfig.stereoscopicType) {
|
||||
|
||||
@@ -310,7 +313,7 @@ void VulkanDriver::terminate() {
|
||||
|
||||
mCurrentSwapChain = {};
|
||||
mDefaultRenderTarget = {};
|
||||
mBoundPipeline = {};
|
||||
mPipelineState = {};
|
||||
|
||||
mQueryManager.terminate();
|
||||
|
||||
@@ -322,6 +325,10 @@ void VulkanDriver::terminate() {
|
||||
|
||||
mCommands.terminate();
|
||||
|
||||
// Must come before samplerCache, ycbcrConversionCache, descriptorSetCache,
|
||||
// descriptorSetLayoutCache
|
||||
mExternalImageManager.terminate();
|
||||
|
||||
mStagePool.terminate();
|
||||
mPipelineCache.terminate();
|
||||
mFramebufferCache.reset();
|
||||
@@ -363,7 +370,7 @@ void VulkanDriver::collectGarbage() {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
// Command buffers need to be submitted and completed before other resources can be gc'd.
|
||||
mCommands.gc();
|
||||
mDescriptorSetCache.clearHistory();
|
||||
mDescriptorSetCache.gc();
|
||||
mStagePool.gc();
|
||||
mFramebufferCache.gc();
|
||||
mPipelineCache.gc();
|
||||
@@ -378,6 +385,10 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs, uint32_t frameId) {
|
||||
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
|
||||
// Do nothing.
|
||||
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
mExternalImageManager.onBeginFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
|
||||
@@ -418,8 +429,16 @@ void VulkanDriver::updateDescriptorSetTexture(
|
||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
||||
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(params);
|
||||
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
||||
if (mExternalImageManager.isExternallySampledTexture(texture)) {
|
||||
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
|
||||
mAppState.hasBoundExternalImages = true;
|
||||
} else {
|
||||
VulkanSamplerCache::Params cacheParams = {
|
||||
.sampler = params,
|
||||
};
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
|
||||
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::flush(int) {
|
||||
@@ -556,39 +575,35 @@ void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::S
|
||||
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
|
||||
Platform::ExternalImageHandleRef externalImage) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
const auto& metadata = mPlatform->getExternalImageMetadata(externalImage);
|
||||
if (metadata.isProtected) {
|
||||
usage |= backend::TextureUsage::PROTECTED;
|
||||
auto const& metadata = mPlatform->extractExternalImageMetadata(externalImage);
|
||||
|
||||
// In theory the following are reasonable expectations, but in practice it's hard for client's
|
||||
// to match up the dimensions of the texture with that of the AHB.
|
||||
// assert_invariant(width == metadata.width);
|
||||
// assert_invariant(height == metadata.height);
|
||||
// assert_invariant(format == metadata.filamentFormat);
|
||||
// assert_invariant(fvkutils::getVkFormat(format) == metadata.format);
|
||||
|
||||
VkImage vkimg;
|
||||
VkDeviceMemory deviceMemory;
|
||||
std::tie(vkimg, deviceMemory) = mPlatform->createVkImageFromExternal(externalImage);
|
||||
|
||||
VkSamplerYcbcrConversion conversion =
|
||||
mExternalImageManager.getVkSamplerYcbcrConversion(metadata);
|
||||
|
||||
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mContext,
|
||||
mPlatform->getDevice(), mAllocator, &mResourceManager, &mCommands, vkimg, deviceMemory,
|
||||
metadata.format, conversion, metadata.samples, metadata.width, metadata.height,
|
||||
metadata.layers, usage, mStagePool);
|
||||
|
||||
if (conversion != VK_NULL_HANDLE) {
|
||||
mExternalImageManager.addExternallySampledTexture(texture, externalImage);
|
||||
}
|
||||
|
||||
VkImageUsageFlags vkUsage = metadata.usage;
|
||||
if (any(usage & TextureUsage::BLIT_SRC)) {
|
||||
vkUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
|
||||
if (any(usage & (TextureUsage::BLIT_DST & TextureUsage::UPLOADABLE))) {
|
||||
vkUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
|
||||
assert_invariant(width == metadata.width);
|
||||
assert_invariant(height == metadata.height);
|
||||
assert_invariant(fvkutils::getVkFormat(format) == metadata.format);
|
||||
|
||||
VkMemoryPropertyFlags const requiredMemoryFlags = any(usage & TextureUsage::UPLOADABLE)
|
||||
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
||||
: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
uint32_t const memoryTypeIndex =
|
||||
mContext.selectMemoryType(metadata.memoryTypeBits, requiredMemoryFlags);
|
||||
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex != VK_MAX_MEMORY_TYPES)
|
||||
<< "failed to find a valid memory type for external image memory.";
|
||||
|
||||
const auto& data =
|
||||
mPlatform->createExternalImageData(externalImage, metadata, memoryTypeIndex, vkUsage);
|
||||
|
||||
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mPlatform->getDevice(),
|
||||
mAllocator, &mResourceManager, &mCommands, data.first, data.second, metadata.format,
|
||||
metadata.samples, metadata.width, metadata.height, metadata.layerCount, usage,
|
||||
mStagePool);
|
||||
// Unlike uploaded textures or swapchains, we need to explicit transition this
|
||||
// texture into the read layout.
|
||||
auto& commands = mCommands.get();
|
||||
texture->transitionLayout(&commands, texture->getPrimaryViewRange(), VulkanLayout::READ_ONLY);
|
||||
|
||||
texture.inc();
|
||||
}
|
||||
@@ -597,13 +612,14 @@ void VulkanDriver::createTextureExternalImageR(Handle<HwTexture> th, backend::Sa
|
||||
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
|
||||
void* externalImage) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
|
||||
assert_invariant(false && "Not supported in Vulkan backend");
|
||||
// not supported in this backend
|
||||
}
|
||||
|
||||
void VulkanDriver::createTextureExternalImagePlaneR(Handle<HwTexture> th,
|
||||
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
|
||||
void* image, uint32_t plane) {
|
||||
assert_invariant(false && "Not supported in Vulkan backend");
|
||||
}
|
||||
|
||||
void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
||||
@@ -611,6 +627,7 @@ void VulkanDriver::importTextureR(Handle<HwTexture> th, intptr_t id,
|
||||
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
||||
TextureUsage usage) {
|
||||
// not supported in this backend
|
||||
assert_invariant(false && "Not supported in Vulkan backend");
|
||||
}
|
||||
|
||||
void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
@@ -619,6 +636,8 @@ void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
}
|
||||
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
||||
texture.dec();
|
||||
|
||||
mExternalImageManager.removeExternallySampledTexture(texture);
|
||||
}
|
||||
|
||||
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
@@ -787,6 +806,11 @@ void VulkanDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
|
||||
auto set = mDescriptorSetCache.createSet(dsh, layout);
|
||||
set.inc();
|
||||
|
||||
if (layout->hasExternalSamplers()) {
|
||||
mAppState.hasExternalSamplerLayouts = true;
|
||||
mExternalImageManager.addDescriptorSet(layout, set);
|
||||
}
|
||||
}
|
||||
|
||||
Handle<HwVertexBufferInfo> VulkanDriver::createVertexBufferInfoS() noexcept {
|
||||
@@ -906,11 +930,19 @@ void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
void VulkanDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh) {
|
||||
auto layout = resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
|
||||
layout.dec();
|
||||
|
||||
if (layout->hasExternalSamplers()) {
|
||||
mExternalImageManager.removeDescriptorSetLayout(layout);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
|
||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||
set.dec();
|
||||
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
mExternalImageManager.removeDescriptorSet(set);
|
||||
}
|
||||
}
|
||||
|
||||
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
|
||||
@@ -1471,7 +1503,6 @@ void VulkanDriver::endRenderPass(int) {
|
||||
// pipeline barrier between framebuffer writes and shader reads.
|
||||
rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer);
|
||||
|
||||
mRenderPassFboInfo = {};
|
||||
mCurrentRenderPass.renderTarget = {};
|
||||
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
||||
|
||||
@@ -1531,10 +1562,10 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
||||
|
||||
void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants");
|
||||
assert_invariant(mPipelineState.program && "Expect a program when writing to push constants");
|
||||
assert_invariant(mCurrentRenderPass.commandBuffer && "Should be called within a renderpass");
|
||||
mBoundPipeline.program->writePushConstant(mCurrentRenderPass.commandBuffer->buffer(),
|
||||
mBoundPipeline.pipelineLayout, stage, index, value);
|
||||
mPipelineState.program->writePushConstant(mCurrentRenderPass.commandBuffer->buffer(),
|
||||
mPipelineState.pipelineLayout, stage, index, value);
|
||||
}
|
||||
|
||||
void VulkanDriver::insertEventMarker(char const* string) {
|
||||
@@ -1713,6 +1744,27 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
}
|
||||
|
||||
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
// We need to determine whether to delay bindning until draw().
|
||||
mPipelineState.bindInDraw.first = false;
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
auto& layouts = pipelineState.pipelineLayout.setLayout;
|
||||
auto haveExternalSamplers = [&](auto hwHandle) {
|
||||
if (!hwHandle) {
|
||||
return false;
|
||||
}
|
||||
auto layout =
|
||||
resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, hwHandle);
|
||||
return layout->hasExternalSamplers();
|
||||
};
|
||||
if (std::any_of(layouts.begin(), layouts.end(), haveExternalSamplers)) {
|
||||
mPipelineState.bindInDraw = { true, pipelineState };
|
||||
return;
|
||||
}
|
||||
}
|
||||
bindPipelineImpl(pipelineState);
|
||||
}
|
||||
|
||||
void VulkanDriver::bindPipelineImpl(PipelineState const& pipelineState) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto commands = mCurrentRenderPass.commandBuffer;
|
||||
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager,
|
||||
@@ -1781,10 +1833,11 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
|
||||
constexpr uint8_t descriptorSetMaskTable[4] = {0x1, 0x3, 0x7, 0xF};
|
||||
|
||||
mBoundPipeline = {
|
||||
mPipelineState = {
|
||||
.program = program,
|
||||
.pipelineLayout = pipelineLayout,
|
||||
.descriptorSetMask = fvkutils::DescriptorSetMask(descriptorSetMaskTable[layoutCount]),
|
||||
.bindInDraw = {false, {}},
|
||||
};
|
||||
|
||||
mPipelineCache.bindLayout(pipelineLayout);
|
||||
@@ -1831,14 +1884,24 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
VkCommandBuffer cmdbuffer = mCurrentRenderPass.commandBuffer->buffer();
|
||||
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
auto const& [bindInDraw, pipelineSt] = mPipelineState.bindInDraw;
|
||||
bool const hasUpdated =
|
||||
mExternalImageManager.prepareBindSets(mDescriptorSetCache.getBoundSets());
|
||||
if (bindInDraw || hasUpdated) {
|
||||
bindPipelineImpl(pipelineSt);
|
||||
}
|
||||
mPipelineState.bindInDraw.first = false;
|
||||
}
|
||||
|
||||
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer,
|
||||
mBoundPipeline.pipelineLayout,
|
||||
mBoundPipeline.descriptorSetMask);
|
||||
mPipelineState.pipelineLayout,
|
||||
mPipelineState.descriptorSetMask);
|
||||
|
||||
// Finally, make the actual draw call. TODO: support subranges
|
||||
const uint32_t firstIndex = indexOffset;
|
||||
const int32_t vertexOffset = 0;
|
||||
const uint32_t firstInstId = 0;
|
||||
uint32_t const firstIndex = indexOffset;
|
||||
constexpr int32_t vertexOffset = 0;
|
||||
constexpr uint32_t firstInstId = 0;
|
||||
|
||||
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanStagePool.h"
|
||||
#include "VulkanQueryManager.h"
|
||||
#include "VulkanYcbcrConversionCache.h"
|
||||
#include "vulkan/VulkanDescriptorSetCache.h"
|
||||
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
|
||||
#include "vulkan/VulkanExternalImageManager.h"
|
||||
#include "vulkan/VulkanPipelineLayoutCache.h"
|
||||
#include "vulkan/memory/ResourceManager.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
@@ -118,6 +120,7 @@ private:
|
||||
|
||||
private:
|
||||
void collectGarbage();
|
||||
void bindPipelineImpl(PipelineState const& pipelineState);
|
||||
|
||||
VulkanPlatform* mPlatform = nullptr;
|
||||
fvkmemory::ResourceManager mResourceManager;
|
||||
@@ -135,27 +138,36 @@ private:
|
||||
VulkanPipelineCache mPipelineCache;
|
||||
VulkanStagePool mStagePool;
|
||||
VulkanFboCache mFramebufferCache;
|
||||
VulkanYcbcrConversionCache mYcbcrConversionCache;
|
||||
VulkanSamplerCache mSamplerCache;
|
||||
VulkanBlitter mBlitter;
|
||||
VulkanReadPixels mReadPixels;
|
||||
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
|
||||
VulkanDescriptorSetCache mDescriptorSetCache;
|
||||
VulkanQueryManager mQueryManager;
|
||||
VulkanExternalImageManager mExternalImageManager;
|
||||
|
||||
// This is necessary for us to write to push constants after binding a pipeline.
|
||||
struct {
|
||||
// For push constant
|
||||
resource_ptr<VulkanProgram> program;
|
||||
// For push commiting dynamic ubos in draw()
|
||||
VkPipelineLayout pipelineLayout;
|
||||
fvkutils::DescriptorSetMask descriptorSetMask;
|
||||
} mBoundPipeline = {};
|
||||
|
||||
// We need to store information about a render pass to enable better barriers at the end of a
|
||||
// renderpass.
|
||||
std::pair<bool, PipelineState> bindInDraw = {false, {}};
|
||||
} mPipelineState = {};
|
||||
|
||||
struct {
|
||||
using AttachmentArray =
|
||||
fvkutils::StaticVector<VulkanAttachment, MAX_RENDERTARGET_ATTACHMENT_TEXTURES>;
|
||||
AttachmentArray attachments;
|
||||
} mRenderPassFboInfo = {};
|
||||
// This tracks whether the app has seen external samplers bound to a the descriptor set.
|
||||
// This will force bindPipeline to take a slow path.
|
||||
bool hasExternalSamplerLayouts = false;
|
||||
bool hasBoundExternalImages = false;
|
||||
|
||||
bool hasExternalSamplers() const noexcept {
|
||||
return hasExternalSamplerLayouts && hasBoundExternalImages;
|
||||
}
|
||||
} mAppState;
|
||||
|
||||
bool const mIsSRGBSwapChainSupported;
|
||||
backend::StereoscopicType const mStereoscopicType;
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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 "VulkanExternalImageManager.h"
|
||||
|
||||
#include "VulkanDescriptorSetCache.h"
|
||||
#include "VulkanDescriptorSetLayoutCache.h"
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanYcbcrConversionCache.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename T>
|
||||
void erasep(std::vector<T>& v, std::function<bool(T const&)> f) {
|
||||
auto newEnd = std::remove_if(v.begin(), v.end(), f);
|
||||
v.erase(newEnd, v.end());
|
||||
}
|
||||
|
||||
} // anonymous
|
||||
|
||||
VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform,
|
||||
VulkanSamplerCache* samplerCache, VulkanYcbcrConversionCache* ycbcrConversionCache,
|
||||
VulkanDescriptorSetCache* setCache, VulkanDescriptorSetLayoutCache* layoutCache)
|
||||
: mPlatform(platform),
|
||||
mSamplerCache(samplerCache),
|
||||
mYcbcrConversionCache(ycbcrConversionCache),
|
||||
mDescriptorSetCache(setCache),
|
||||
mDescriptorSetLayoutCache(layoutCache) {
|
||||
}
|
||||
|
||||
VulkanExternalImageManager::~VulkanExternalImageManager() = default;
|
||||
|
||||
void VulkanExternalImageManager::terminate() {
|
||||
mSetAndLayouts.clear();
|
||||
mSetBindings.clear();
|
||||
mImages.clear();
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::onBeginFrame() {
|
||||
std::for_each(mImages.begin(), mImages.end(), [](ImageData& image) {
|
||||
image.hasBeenValidated = false;
|
||||
});
|
||||
}
|
||||
|
||||
bool VulkanExternalImageManager::prepareBindSets(SetArray const& sets) {
|
||||
bool hasUpdated = false;
|
||||
for (auto set: sets) {
|
||||
if (!set) {
|
||||
continue;
|
||||
}
|
||||
if (auto itr = std::find_if(mSetAndLayouts.begin(), mSetAndLayouts.end(),
|
||||
[&](auto const& setAndLayout) { return setAndLayout.first == set; });
|
||||
itr != mSetAndLayouts.end()) {
|
||||
hasUpdated = updateSetAndLayout(itr->first, itr->second) || hasUpdated;
|
||||
}
|
||||
}
|
||||
return hasUpdated;
|
||||
}
|
||||
|
||||
bool VulkanExternalImageManager::updateSetAndLayout(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
auto findImage = [&](fvkmemory::resource_ptr<VulkanTexture> texture) -> ImageData* {
|
||||
auto itr = std::find_if(mImages.begin(), mImages.end(), [&](ImageData const& data) {
|
||||
return data.ptr == texture;
|
||||
});
|
||||
assert_invariant(itr != mImages.end());
|
||||
return &(*itr);
|
||||
};
|
||||
|
||||
//std::vector<std::pair<uint8_t, ImageData*>> externalImages;
|
||||
utils::FixedCapacityVector<std::pair<uint8_t, VkSampler>> samplerAndBindings;
|
||||
samplerAndBindings.reserve(MAX_SAMPLER_COUNT);
|
||||
|
||||
bool hasImageUpdates = false;
|
||||
for (auto& bindingInfo : mSetBindings) {
|
||||
if (bindingInfo.set != set) {
|
||||
continue;
|
||||
}
|
||||
auto imageData = findImage(bindingInfo.image);
|
||||
hasImageUpdates = updateImage(imageData) || hasImageUpdates;
|
||||
|
||||
auto samplerParams = bindingInfo.samplerParams;
|
||||
// according to spec, these must match chromaFilter
|
||||
// https://registry.khronos.org/vulkan/specs/latest/man/html/VkSamplerCreateInfo.html#VUID-VkSamplerCreateInfo-minFilter-01645
|
||||
samplerParams.filterMag = SamplerMagFilter::NEAREST;
|
||||
samplerParams.filterMin = SamplerMinFilter::NEAREST;
|
||||
|
||||
auto sampler = mSamplerCache->getSampler({
|
||||
.sampler = samplerParams,
|
||||
.conversion = imageData->conversion,
|
||||
});
|
||||
samplerAndBindings.push_back({ bindingInfo.binding, sampler });
|
||||
}
|
||||
|
||||
// We need to sort by binding number
|
||||
std::sort(samplerAndBindings.begin(), samplerAndBindings.end());
|
||||
|
||||
utils::FixedCapacityVector<VkSampler> outSamplers;
|
||||
outSamplers.reserve(MAX_SAMPLER_COUNT);
|
||||
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
|
||||
[&](auto const& b) { outSamplers.push_back(b.second); });
|
||||
|
||||
VkDescriptorSetLayout const oldLayout = layout->getVkLayout();
|
||||
VkDescriptorSetLayout const newLayout =
|
||||
mDescriptorSetLayoutCache->getVkLayout(layout->bitmask, outSamplers);
|
||||
bool const hasLayoutUpdate = oldLayout != newLayout;
|
||||
layout->setVkLayout(newLayout);
|
||||
|
||||
assert_invariant(
|
||||
(!hasImageUpdates && !hasLayoutUpdate) ||
|
||||
(hasImageUpdates && hasLayoutUpdate));
|
||||
|
||||
if (!hasLayoutUpdate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto foldBitsInHalf = [](auto bitset) {
|
||||
constexpr size_t BITMASK_LOWER_BITS_LEN = sizeof(bitset) * 4;
|
||||
decltype(bitset) outBitset;
|
||||
bitset.forEachSetBit([&](size_t index) { outBitset.set(index % BITMASK_LOWER_BITS_LEN); });
|
||||
return outBitset;
|
||||
};
|
||||
// We need to build a new descriptor set from the new layout
|
||||
VkDescriptorSet oldSet = set->getVkSet();
|
||||
VkDescriptorSet newSet = mDescriptorSetCache->getVkSet(layout);
|
||||
|
||||
using Bitmask = fvkutils::UniformBufferBitmask;
|
||||
static_assert(sizeof(Bitmask) * 8 == fvkutils::MAX_DESCRIPTOR_SET_BITMASK_BITS);
|
||||
|
||||
auto const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
||||
auto const samplers = layout->bitmask.sampler & (~layout->bitmask.externalSampler);
|
||||
|
||||
// each bitmask denotes a binding index, and separated into two stages - vertex and buffer
|
||||
// We fold the two stages into just the lower half of the bits to denote a combined set of
|
||||
// bindings.
|
||||
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
|
||||
|
||||
// TODO: fix the size for better memory
|
||||
std::vector<VkCopyDescriptorSet> copies;
|
||||
copyBindings.forEachSetBit([&](size_t index) {
|
||||
copies.push_back({
|
||||
.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
|
||||
.srcSet = oldSet,
|
||||
.srcBinding = (uint32_t) index,
|
||||
.dstSet = newSet,
|
||||
.dstBinding = (uint32_t) index,
|
||||
.descriptorCount = 1,
|
||||
});
|
||||
});
|
||||
vkUpdateDescriptorSets(mPlatform->getDevice(), 0, nullptr, copies.size(), copies.data());
|
||||
|
||||
set->setVkSet(newSet);
|
||||
|
||||
// We need to release the vkset, which is no longer used, back into the pool.
|
||||
mDescriptorSetCache->manualRecyle(layout->count, oldLayout, oldSet);
|
||||
|
||||
// We need to update the external samplers in the set
|
||||
for (auto& bindingInfo: mSetBindings) {
|
||||
if (bindingInfo.set != set) {
|
||||
continue;
|
||||
}
|
||||
mDescriptorSetCache->updateSampler(set, bindingInfo.binding, bindingInfo.image,
|
||||
VK_NULL_HANDLE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
VkSamplerYcbcrConversion VulkanExternalImageManager::getVkSamplerYcbcrConversion(
|
||||
VulkanPlatform::ExternalImageMetadata const& metadata) {
|
||||
// This external image does not require external sampler (YUV conversion).
|
||||
if (metadata.externalFormat == 0) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
VulkanYcbcrConversionCache::Params ycbcrParams = {
|
||||
.conversion = {
|
||||
.ycbcrModel = fvkutils::getYcbcrModelConversionFilament(metadata.ycbcrModel),
|
||||
.r = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.r, 0),
|
||||
.g = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.g, 1),
|
||||
.b = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.b, 2),
|
||||
.a = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.a, 3),
|
||||
.ycbcrRange = fvkutils::getYcbcrRangeFilament(metadata.ycbcrRange),
|
||||
.xChromaOffset = fvkutils::getChromaLocationFilament(metadata.xChromaOffset),
|
||||
.yChromaOffset = fvkutils::getChromaLocationFilament(metadata.yChromaOffset),
|
||||
|
||||
// Unclear where to get the chromaFilter, we just assume it's nearest.
|
||||
.chromaFilter = SamplerMagFilter::NEAREST,
|
||||
},
|
||||
.format = metadata.filamentFormat,
|
||||
.externalFormat = metadata.externalFormat,
|
||||
};
|
||||
return mYcbcrConversionCache->getConversion(ycbcrParams);
|
||||
}
|
||||
|
||||
bool VulkanExternalImageManager::updateImage(ImageData* image) {
|
||||
if (image->hasBeenValidated) {
|
||||
return false;
|
||||
}
|
||||
image->hasBeenValidated = true;
|
||||
|
||||
auto metadata = mPlatform->extractExternalImageMetadata(image->platformHandle);
|
||||
auto vkYcbcr = getVkSamplerYcbcrConversion(metadata);
|
||||
if (vkYcbcr == image->conversion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
image->ptr->setYcbcrConversion(vkYcbcr, metadata.externalFormat != 0);
|
||||
image->conversion = vkYcbcr;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::addDescriptorSet(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set) {
|
||||
mSetAndLayouts.push_back({set, layout});
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::removeDescriptorSet(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> inSet) {
|
||||
erasep<SetAndLayout>(mSetAndLayouts,
|
||||
[&](auto const& setLayout) { return (setLayout.first == inSet); });
|
||||
erasep<SetBindingInfo>(mSetBindings,
|
||||
[&](auto const& bindingInfo) { return (bindingInfo.set == inSet); });
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::removeDescriptorSetLayout(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> inLayout) {
|
||||
erasep<SetAndLayout>(mSetAndLayouts,
|
||||
[&](auto const& setLayout) { return (setLayout.second == inLayout); });
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::bindExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t bindingPoint,
|
||||
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
|
||||
// Should we do duplicate validation here?
|
||||
mSetBindings.push_back({ bindingPoint, image, set, samplerParams });
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::addExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
Platform::ExternalImageHandleRef platformHandleRef) {
|
||||
mImages.push_back({ image, platformHandleRef, false });
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::removeExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanTexture> image) {
|
||||
erasep<SetBindingInfo>(mSetBindings,
|
||||
[&](auto const& bindingInfo) { return (bindingInfo.image == image); });
|
||||
erasep<ImageData>(mImages, [&](auto const& imageData) { return imageData.ptr == image; });
|
||||
}
|
||||
|
||||
bool VulkanExternalImageManager::isExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanTexture> image) const {
|
||||
return std::find_if(mImages.begin(), mImages.end(),
|
||||
[&](auto const& imageData) { return imageData.ptr == image; }) != mImages.end();
|
||||
}
|
||||
|
||||
|
||||
} // namesapce filament::backend
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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_CACHING_VULKANEXTERNALIMAGEMANAGER_H
|
||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANEXTERNALIMAGEMANAGER_H
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanYcbcrConversionCache;
|
||||
class VulkanSamplerCache;
|
||||
class VulkanDescriptorSetLayoutCache;
|
||||
class VulkanDescriptorSetCache;
|
||||
|
||||
// Manages the logic of external images and their quirks wrt Vulikan.
|
||||
class VulkanExternalImageManager {
|
||||
public:
|
||||
|
||||
VulkanExternalImageManager(
|
||||
VulkanPlatform* platform,
|
||||
VulkanSamplerCache* samplerCache,
|
||||
VulkanYcbcrConversionCache* ycbcrConversionCache,
|
||||
VulkanDescriptorSetCache* setCache,
|
||||
VulkanDescriptorSetLayoutCache* layoutCache);
|
||||
|
||||
~VulkanExternalImageManager();
|
||||
|
||||
void terminate();
|
||||
|
||||
void onBeginFrame();
|
||||
|
||||
using SetArray = std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
// This sets the currently bound layouts objects for the pipeline
|
||||
bool prepareBindSets(SetArray const& layouts);
|
||||
|
||||
void addDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||
|
||||
void removeDescriptorSetLayout(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
void removeDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||
|
||||
void bindExternallySampledTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t bindingPoint, fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
SamplerParams samplerParams);
|
||||
|
||||
void addExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
Platform::ExternalImageHandleRef platformHandleRef);
|
||||
|
||||
void removeExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image);
|
||||
|
||||
bool isExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image) const;
|
||||
|
||||
VkSamplerYcbcrConversion getVkSamplerYcbcrConversion(
|
||||
VulkanPlatform::ExternalImageMetadata const& metadata);
|
||||
|
||||
private:
|
||||
|
||||
struct ImageData {
|
||||
fvkmemory::resource_ptr<VulkanTexture> ptr;
|
||||
Platform::ExternalImageHandle platformHandle;
|
||||
bool hasBeenValidated = false; // indicates whether the image has been validated *this frame*
|
||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
bool updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
bool updateImage(ImageData* imageData);
|
||||
|
||||
VulkanPlatform* mPlatform;
|
||||
VulkanSamplerCache* mSamplerCache;
|
||||
VulkanYcbcrConversionCache* mYcbcrConversionCache;
|
||||
VulkanDescriptorSetCache* mDescriptorSetCache;
|
||||
VulkanDescriptorSetLayoutCache* mDescriptorSetLayoutCache;
|
||||
|
||||
using SetAndLayout = std::pair<fvkmemory::resource_ptr<VulkanDescriptorSet>,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>>;
|
||||
|
||||
struct SetBindingInfo {
|
||||
uint8_t binding = 0;
|
||||
fvkmemory::resource_ptr<VulkanTexture> image;
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set;
|
||||
SamplerParams samplerParams;
|
||||
};
|
||||
|
||||
// Use vectors instead of hash maps because we only expect small number of entries.
|
||||
std::vector<SetAndLayout> mSetAndLayouts;
|
||||
std::vector<SetBindingInfo> mSetBindings;
|
||||
std::vector<ImageData> mImages;
|
||||
};
|
||||
|
||||
} // filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_CACHING_VULKANEXTERNALIMAGEMANAGER_H
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <utils/compiler.h> // UTILS_FALLTHROUGH
|
||||
#include <utils/Panic.h> // ASSERT_POSTCONDITION
|
||||
|
||||
using namespace bluevk;
|
||||
@@ -89,8 +90,9 @@ BitmaskGroup fromBackendLayout(DescriptorSetLayout const& layout) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
// TODO: properly handle external sampler
|
||||
case DescriptorType::SAMPLER_EXTERNAL:
|
||||
fromStageFlags(binding.stageFlags, binding.binding, mask.externalSampler);
|
||||
UTILS_FALLTHROUGH;
|
||||
case DescriptorType::SAMPLER: {
|
||||
fromStageFlags(binding.stageFlags, binding.binding, mask.sampler);
|
||||
break;
|
||||
|
||||
@@ -66,18 +66,21 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
||||
|
||||
// The bitmask representation of a set layout.
|
||||
struct Bitmask {
|
||||
// TODO: better utiltize the space below and use bitset instead.
|
||||
fvkutils::UniformBufferBitmask ubo; // 8 bytes
|
||||
fvkutils::UniformBufferBitmask dynamicUbo; // 8 bytes
|
||||
fvkutils::SamplerBitmask sampler; // 8 bytes
|
||||
fvkutils::InputAttachmentBitmask inputAttachment; // 8 bytes
|
||||
|
||||
// This is a subset of the bitmask.sampler field.
|
||||
fvkutils::SamplerBitmask externalSampler; // 8 bytes
|
||||
|
||||
bool operator==(Bitmask const& right) const {
|
||||
return ubo == right.ubo && dynamicUbo == right.dynamicUbo && sampler == right.sampler &&
|
||||
inputAttachment == right.inputAttachment;
|
||||
inputAttachment == right.inputAttachment &&
|
||||
externalSampler == right.externalSampler;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Bitmask) == 32);
|
||||
static_assert(sizeof(Bitmask) == 40);
|
||||
|
||||
// This is a convenience struct to quickly check layout compatibility in terms of descriptor set
|
||||
// pools.
|
||||
@@ -119,10 +122,16 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
||||
|
||||
VulkanDescriptorSetLayout(DescriptorSetLayout const& layout);
|
||||
|
||||
// Note that we don't destroy the vklayout. This is done by the layout cache.
|
||||
~VulkanDescriptorSetLayout() = default;
|
||||
|
||||
VkDescriptorSetLayout getVkLayout() const { return mVkLayout; }
|
||||
void setVkLayout(VkDescriptorSetLayout vklayout) { mVkLayout = vklayout; }
|
||||
VkDescriptorSetLayout const& getVkLayout() const noexcept { return mVkLayout; }
|
||||
|
||||
// It is possible to have the layout switch out due to AHardwarebuffer (external image) format
|
||||
// changes.
|
||||
void setVkLayout(VkDescriptorSetLayout vklayout) noexcept { mVkLayout = vklayout; }
|
||||
|
||||
bool hasExternalSamplers() const noexcept { return bitmask.externalSampler.count() > 0; }
|
||||
|
||||
Bitmask const bitmask;
|
||||
Count const count;
|
||||
@@ -137,12 +146,11 @@ public:
|
||||
// can use to repackage the vk handle.
|
||||
using OnRecycle = std::function<void(VulkanDescriptorSet*)>;
|
||||
|
||||
VulkanDescriptorSet(VkDescriptorSet rawSet,
|
||||
VulkanDescriptorSet(
|
||||
fvkutils::UniformBufferBitmask const& dynamicUboMask,
|
||||
uint8_t uniqueDynamicUboCount,
|
||||
OnRecycle&& onRecycleFn)
|
||||
: vkSet(rawSet),
|
||||
dynamicUboMask(dynamicUboMask),
|
||||
: dynamicUboMask(dynamicUboMask),
|
||||
uniqueDynamicUboCount(uniqueDynamicUboCount),
|
||||
mOnRecycleFn(std::move(onRecycleFn)) {}
|
||||
|
||||
@@ -152,6 +160,13 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
VkDescriptorSet const& getVkSet() const noexcept {
|
||||
return mVkSet;
|
||||
}
|
||||
|
||||
// Note that the only case where you'd set it more than once is with external images/samplers.
|
||||
void setVkSet(VkDescriptorSet vkset) noexcept { mVkSet = vkset; }
|
||||
|
||||
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
|
||||
mOffsets = std::move(offsets);
|
||||
}
|
||||
@@ -163,11 +178,11 @@ public:
|
||||
void acquire(fvkmemory::resource_ptr<VulkanTexture> texture);
|
||||
void acquire(fvkmemory::resource_ptr<VulkanBufferObject> buffer);
|
||||
|
||||
VkDescriptorSet const vkSet;
|
||||
fvkutils::UniformBufferBitmask const dynamicUboMask;
|
||||
uint8_t const uniqueDynamicUboCount;
|
||||
|
||||
private:
|
||||
VkDescriptorSet mVkSet = VK_NULL_HANDLE;
|
||||
backend::DescriptorSetOffsetArray mOffsets;
|
||||
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
|
||||
OnRecycle mOnRecycleFn;
|
||||
|
||||
@@ -18,24 +18,18 @@
|
||||
#define TNT_FILAMENT_BACKEND_VULKANPIPELINECACHE_H
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanMemory.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include "backend/Program.h"
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <list>
|
||||
#include <tsl/robin_map.h>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -14,8 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "vulkan/VulkanSamplerCache.h"
|
||||
#include "VulkanSamplerCache.h"
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
#include "vulkan/vulkan_core.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
@@ -26,41 +29,48 @@ namespace filament::backend {
|
||||
VulkanSamplerCache::VulkanSamplerCache(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VkSampler VulkanSamplerCache::getSampler(SamplerParams params) noexcept {
|
||||
VkSampler VulkanSamplerCache::getSampler(Params params) noexcept {
|
||||
auto iter = mCache.find(params);
|
||||
if (UTILS_LIKELY(iter != mCache.end())) {
|
||||
return iter->second;
|
||||
}
|
||||
VkSamplerCreateInfo samplerInfo {
|
||||
VkSamplerYcbcrConversionInfo ycbcrConversion = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
|
||||
.conversion = params.conversion,
|
||||
};
|
||||
|
||||
auto const& samplerParams = params.sampler;
|
||||
VkSamplerCreateInfo samplerInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.magFilter = fvkutils::getFilter(params.filterMag),
|
||||
.minFilter = fvkutils::getFilter(params.filterMin),
|
||||
.mipmapMode = fvkutils::getMipmapMode(params.filterMin),
|
||||
.addressModeU = fvkutils::getWrapMode(params.wrapS),
|
||||
.addressModeV = fvkutils::getWrapMode(params.wrapT),
|
||||
.addressModeW = fvkutils::getWrapMode(params.wrapR),
|
||||
.anisotropyEnable = params.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
|
||||
.maxAnisotropy = (float)(1u << params.anisotropyLog2),
|
||||
.compareEnable = fvkutils::getCompareEnable(params.compareMode),
|
||||
.compareOp = fvkutils::getCompareOp(params.compareFunc),
|
||||
.pNext = params.conversion != VK_NULL_HANDLE ? &ycbcrConversion : VK_NULL_HANDLE,
|
||||
.magFilter = fvkutils::getFilter(samplerParams.filterMag),
|
||||
.minFilter = fvkutils::getFilter(samplerParams.filterMin),
|
||||
.mipmapMode = fvkutils::getMipmapMode(samplerParams.filterMin),
|
||||
.addressModeU = fvkutils::getWrapMode(samplerParams.wrapS),
|
||||
.addressModeV = fvkutils::getWrapMode(samplerParams.wrapT),
|
||||
.addressModeW = fvkutils::getWrapMode(samplerParams.wrapR),
|
||||
.anisotropyEnable = samplerParams.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
|
||||
.maxAnisotropy = (float) (1u << samplerParams.anisotropyLog2),
|
||||
.compareEnable = fvkutils::getCompareEnable(samplerParams.compareMode),
|
||||
.compareOp = fvkutils::getCompareOp(samplerParams.compareFunc),
|
||||
.minLod = 0.0f,
|
||||
.maxLod = fvkutils::getMaxLod(params.filterMin),
|
||||
.maxLod = fvkutils::getMaxLod(samplerParams.filterMin),
|
||||
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
|
||||
.unnormalizedCoordinates = VK_FALSE
|
||||
.unnormalizedCoordinates = VK_FALSE,
|
||||
};
|
||||
VkSampler sampler;
|
||||
VkResult result = vkCreateSampler(mDevice, &samplerInfo, VKALLOC, &sampler);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create sampler."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
mCache.insert({params, sampler});
|
||||
mCache.insert({ params, sampler });
|
||||
return sampler;
|
||||
}
|
||||
|
||||
void VulkanSamplerCache::terminate() noexcept {
|
||||
for (auto pair : mCache) {
|
||||
for (auto pair: mCache) {
|
||||
vkDestroySampler(mDevice, pair.second, VKALLOC);
|
||||
}
|
||||
mCache.clear();
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANSAMPLERCACHE_H
|
||||
|
||||
#include "VulkanContext.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
namespace filament::backend {
|
||||
@@ -26,12 +29,28 @@ namespace filament::backend {
|
||||
// Simple manager for VkSampler objects.
|
||||
class VulkanSamplerCache {
|
||||
public:
|
||||
struct Params {
|
||||
SamplerParams sampler = {};
|
||||
uint32_t padding = 0;
|
||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Params) == 16);
|
||||
|
||||
explicit VulkanSamplerCache(VkDevice device);
|
||||
VkSampler getSampler(SamplerParams params) noexcept;
|
||||
VkSampler getSampler(Params params) noexcept;
|
||||
void terminate() noexcept;
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
tsl::robin_map<SamplerParams, VkSampler, SamplerParams::Hasher, SamplerParams::EqualTo> mCache;
|
||||
|
||||
struct SamplerEqualTo {
|
||||
bool operator()(Params lhs, Params rhs) const noexcept {
|
||||
SamplerParams::EqualTo equal;
|
||||
return equal(lhs.sampler, rhs.sampler) && lhs.conversion == rhs.conversion;
|
||||
}
|
||||
};
|
||||
using SamplerHashFn = utils::hash::MurmurHashFn<Params>;
|
||||
tsl::robin_map<Params, VkSampler, SamplerHashFn, SamplerEqualTo> mCache;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -32,6 +32,7 @@ VulkanSwapChain::VulkanSwapChain(VulkanPlatform* platform, VulkanContext const&
|
||||
VulkanCommands* commands, VulkanStagePool& stagePool, void* nativeWindow, uint64_t flags,
|
||||
VkExtent2D extent)
|
||||
: mPlatform(platform),
|
||||
mContext(context),
|
||||
mResourceManager(resourceManager),
|
||||
mCommands(commands),
|
||||
mAllocator(allocator),
|
||||
@@ -77,16 +78,16 @@ void VulkanSwapChain::update() {
|
||||
}
|
||||
for (auto const color: bundle.colors) {
|
||||
auto colorTexture = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager,
|
||||
device, mAllocator, mResourceManager, mCommands, color, VK_NULL_HANDLE,
|
||||
bundle.colorFormat, 1, bundle.extent.width, bundle.extent.height, bundle.layerCount, colorUsage,
|
||||
mStagePool);
|
||||
mContext, device, mAllocator, mResourceManager, mCommands, color, VK_NULL_HANDLE,
|
||||
bundle.colorFormat, VK_NULL_HANDLE /*ycrcb */, 1, bundle.extent.width,
|
||||
bundle.extent.height, bundle.layerCount, colorUsage, mStagePool);
|
||||
mColors.push_back(colorTexture);
|
||||
}
|
||||
|
||||
mDepth = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager, device,
|
||||
mAllocator, mResourceManager, mCommands, bundle.depth, VK_NULL_HANDLE,
|
||||
bundle.depthFormat, 1, bundle.extent.width, bundle.extent.height, bundle.layerCount, depthUsage,
|
||||
mStagePool);
|
||||
mDepth = fvkmemory::resource_ptr<VulkanTexture>::construct(mResourceManager, mContext, device,
|
||||
mAllocator, mResourceManager, mCommands, bundle.depth, VK_NULL_HANDLE,
|
||||
bundle.depthFormat, VK_NULL_HANDLE /*ycrcb */, 1, bundle.extent.width,
|
||||
bundle.extent.height, bundle.layerCount, depthUsage, mStagePool);
|
||||
|
||||
mExtent = bundle.extent;
|
||||
mLayerCount = bundle.layerCount;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "vulkan/memory/Resource.h"
|
||||
@@ -28,7 +29,6 @@
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -83,6 +83,7 @@ private:
|
||||
void update();
|
||||
|
||||
VulkanPlatform* mPlatform;
|
||||
VulkanContext const& mContext;
|
||||
fvkmemory::ResourceManager* mResourceManager;
|
||||
VulkanCommands* mCommands;
|
||||
VmaAllocator mAllocator;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanContext.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
@@ -54,7 +55,6 @@ VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMap
|
||||
VK_COMPONENT_SWIZZLE_B,
|
||||
VK_COMPONENT_SWIZZLE_A,
|
||||
};
|
||||
|
||||
auto const compose = [](VkComponentMapping const& prev,
|
||||
VkComponentMapping const& next) -> VkComponentMapping {
|
||||
VkComponentSwizzle vals[4] = { next.r, next.g, next.b, next.a };
|
||||
@@ -151,35 +151,152 @@ uint8_t getLayerCountFromDepth(uint32_t const depth) {
|
||||
return getLayerCount(getSamplerTypeFromDepth(depth), depth);
|
||||
}
|
||||
|
||||
VkImageUsageFlags getUsage(VulkanContext const& context, uint8_t samples,
|
||||
VkPhysicalDevice physicalDevice, VkFormat vkFormat, TextureUsage tusage) {
|
||||
VkImageUsageFlags usage = {};
|
||||
if (any(tusage & TextureUsage::BLIT_SRC)) {
|
||||
usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
if (any(tusage & TextureUsage::BLIT_DST)) {
|
||||
usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
|
||||
// Determine if we can use the transient usage flag combined with lazily allocated memory.
|
||||
const bool useTransientAttachment =
|
||||
// Lazily allocated memory is available.
|
||||
context.isLazilyAllocatedMemorySupported() &&
|
||||
// Usage consists of attachment flags only.
|
||||
none(tusage & ~TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Usage contains at least one attachment flag.
|
||||
any(tusage & TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Depth resolve cannot use transient attachment because it uses a custom shader.
|
||||
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
|
||||
// restriction.
|
||||
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
|
||||
// and above to be able to support stencil resolve (along with depth).
|
||||
!(any(tusage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
|
||||
|
||||
const VkImageUsageFlags transientFlag =
|
||||
useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U;
|
||||
|
||||
if (any(tusage & TextureUsage::SAMPLEABLE)) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
if (physicalDevice != VK_NULL_HANDLE) {
|
||||
// Validate that the format is actually sampleable.
|
||||
VkFormatProperties props;
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, vkFormat, &props);
|
||||
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
|
||||
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mState->mVkFormat << " is not "
|
||||
"sampleable with optimal tiling." << utils::io::endl;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
}
|
||||
if (any(tusage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | transientFlag;
|
||||
if (any(tusage & TextureUsage::SUBPASS_INPUT)) {
|
||||
usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||
}
|
||||
}
|
||||
if (any(tusage & TextureUsage::STENCIL_ATTACHMENT)) {
|
||||
usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
|
||||
}
|
||||
if (any(tusage & TextureUsage::UPLOADABLE)) {
|
||||
usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
if (any(tusage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||
usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
|
||||
|
||||
// Depth resolves uses a custom shader and therefore needs to be sampleable.
|
||||
if (samples > 1) {
|
||||
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
}
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanTextureState::VulkanTextureState(VkDevice device, VmaAllocator allocator,
|
||||
VulkanCommands* commands, VulkanStagePool& stagePool, VkFormat format,
|
||||
VkImageViewType viewType, uint8_t levels, uint8_t layerCount, VulkanLayout defaultLayout,
|
||||
VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands,
|
||||
VmaAllocator allocator, VkDevice device, VkImage image, VkDeviceMemory deviceMemory,
|
||||
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
|
||||
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat, VkImageUsageFlags usage,
|
||||
bool isProtected)
|
||||
: mVkFormat(format),
|
||||
mViewType(viewType),
|
||||
mFullViewRange{fvkutils::getImageAspect(format), 0, levels, 0, layerCount},
|
||||
mDefaultLayout(defaultLayout),
|
||||
mIsProtected(isProtected),
|
||||
mStagePool(stagePool),
|
||||
mDevice(device),
|
||||
mAllocator(allocator),
|
||||
: mStagePool(stagePool),
|
||||
mCommands(commands),
|
||||
mIsTransientAttachment(false) {}
|
||||
mAllocator(allocator),
|
||||
mDevice(device),
|
||||
mTextureImage(image),
|
||||
mTextureImageMemory(deviceMemory),
|
||||
mVkFormat(format),
|
||||
mViewType(viewType),
|
||||
mFullViewRange{ fvkutils::getImageAspect(format), 0, levels, 0, layerCount },
|
||||
mYcbcr{ ycbcrConversion, isExternalFormat },
|
||||
mDefaultLayout(getDefaultLayoutImpl(usage)),
|
||||
mUsage(usage),
|
||||
mIsProtected(isProtected) {}
|
||||
|
||||
// Constructor for internally passed VkImage
|
||||
VulkanTexture::VulkanTexture(VkDevice device, VmaAllocator allocator,
|
||||
VulkanTextureState::~VulkanTextureState() {
|
||||
if (mTextureImageMemory != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
|
||||
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
|
||||
}
|
||||
clearCachedImageViews();
|
||||
}
|
||||
|
||||
void VulkanTextureState::clearCachedImageViews() noexcept {
|
||||
for (auto entry: mCachedImageViews) {
|
||||
vkDestroyImageView(mDevice, entry.second, VKALLOC);
|
||||
}
|
||||
mCachedImageViews.clear();
|
||||
}
|
||||
|
||||
VkImageView VulkanTextureState::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
||||
VkComponentMapping swizzle) {
|
||||
ImageViewKey const key{ range, viewType, swizzle };
|
||||
if (auto iter = mCachedImageViews.find(key); iter != mCachedImageViews.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
VkSamplerYcbcrConversionInfo conversionInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
|
||||
.conversion = mYcbcr.conversion,
|
||||
};
|
||||
|
||||
VkImageViewCreateInfo viewInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = mYcbcr.conversion != VK_NULL_HANDLE ? &conversionInfo : nullptr,
|
||||
.flags = 0,
|
||||
.image = mTextureImage,
|
||||
.viewType = viewType,
|
||||
.format = mYcbcr.isExternalFormat ? VK_FORMAT_UNDEFINED : mVkFormat,
|
||||
.components = swizzle,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
VkImageView imageView;
|
||||
vkCreateImageView(mDevice, &viewInfo, VKALLOC, &imageView);
|
||||
mCachedImageViews.emplace(key, imageView);
|
||||
return imageView;
|
||||
}
|
||||
|
||||
// Constructor for internally passed VkImage - including swapchain images and external images.
|
||||
VulkanTexture::VulkanTexture(VulkanContext const& context, VkDevice device, VmaAllocator allocator,
|
||||
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
|
||||
VkDeviceMemory memory, VkFormat format, uint8_t samples, uint32_t width,
|
||||
uint32_t height, uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
|
||||
: HwTexture(getSamplerTypeFromDepth(depth), 1, samples, width, height, depth, TextureFormat::UNUSED,
|
||||
tusage),
|
||||
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, device,
|
||||
allocator, commands, stagePool, format, fvkutils::getViewType(SamplerType::SAMPLER_2D),
|
||||
/*mipLevels=*/1, getLayerCountFromDepth(depth), getDefaultLayoutImpl(tusage), any(usage & TextureUsage::PROTECTED))) {
|
||||
mState->mTextureImage = image;
|
||||
mState->mTextureImageMemory = memory;
|
||||
VkDeviceMemory memory, VkFormat format, VkSamplerYcbcrConversion conversion,
|
||||
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage tusage,
|
||||
VulkanStagePool& stagePool)
|
||||
: HwTexture(getSamplerTypeFromDepth(depth), 1, samples, width, height, depth,
|
||||
TextureFormat::UNUSED, tusage),
|
||||
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, stagePool,
|
||||
commands, allocator, device, image, memory, format,
|
||||
fvkutils::getViewType(SamplerType::SAMPLER_2D),
|
||||
/*mipLevels=*/1, getLayerCountFromDepth(depth), conversion,
|
||||
/*isExternalFormat=*/false,
|
||||
getUsage(context, samples, VK_NULL_HANDLE, format, tusage),
|
||||
any(usage & TextureUsage::PROTECTED))) {
|
||||
mPrimaryViewRange = mState->mFullViewRange;
|
||||
}
|
||||
|
||||
@@ -189,21 +306,19 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, SamplerType target,
|
||||
uint8_t levels, TextureFormat tformat, uint8_t samples, uint32_t w, uint32_t h,
|
||||
uint32_t depth, TextureUsage tusage, VulkanStagePool& stagePool)
|
||||
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage),
|
||||
mState(fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, device,
|
||||
allocator, commands, stagePool, fvkutils::getVkFormat(tformat),
|
||||
fvkutils::getViewType(target), levels, getLayerCount(target, depth),
|
||||
VulkanLayout::UNDEFINED, any(usage & TextureUsage::PROTECTED))) {
|
||||
: HwTexture(target, levels, samples, w, h, depth, tformat, tusage) {
|
||||
// Create an appropriately-sized device-only VkImage, but do not fill it yet.
|
||||
VkFormat const vkFormat = fvkutils::getVkFormat(tformat);
|
||||
bool const isProtected = any(tusage & TextureUsage::PROTECTED);
|
||||
VkImageCreateInfo imageInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.imageType = target == SamplerType::SAMPLER_3D ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D,
|
||||
.format = mState->mVkFormat,
|
||||
.format = vkFormat,
|
||||
.extent = {w, h, depth},
|
||||
.mipLevels = levels,
|
||||
.arrayLayers = 1,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = 0,
|
||||
.usage = getUsage(context, samples, physicalDevice, vkFormat, tusage),
|
||||
};
|
||||
if (target == SamplerType::SAMPLER_3D && any(tusage & TextureUsage::ALL_ATTACHMENTS)) {
|
||||
if (context.isImageView2DOn3DImageSupported()) {
|
||||
@@ -233,79 +348,17 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.arrayLayers = depth * 6;
|
||||
imageInfo.extent.depth = 1;
|
||||
}
|
||||
if (any(usage & TextureUsage::PROTECTED)) {
|
||||
if (isProtected) {
|
||||
imageInfo.flags |= VK_IMAGE_CREATE_PROTECTED_BIT;
|
||||
}
|
||||
|
||||
if (any(usage & TextureUsage::BLIT_SRC)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
if (any(usage & TextureUsage::BLIT_DST)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
|
||||
// Determine if we can use the transient usage flag combined with lazily allocated memory.
|
||||
const bool useTransientAttachment =
|
||||
// Lazily allocated memory is available.
|
||||
context.isLazilyAllocatedMemorySupported() &&
|
||||
// Usage consists of attachment flags only.
|
||||
none(tusage & ~TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Usage contains at least one attachment flag.
|
||||
any(tusage & TextureUsage::ALL_ATTACHMENTS) &&
|
||||
// Depth resolve cannot use transient attachment because it uses a custom shader.
|
||||
// TODO: see VulkanDriver::isDepthStencilResolveSupported() to know when to remove this
|
||||
// restriction.
|
||||
// Note that the custom shader does not resolve stencil. We do need to move to vk 1.2
|
||||
// and above to be able to support stencil resolve (along with depth).
|
||||
!(any(usage & TextureUsage::DEPTH_ATTACHMENT) && samples > 1);
|
||||
|
||||
mState->mIsTransientAttachment = useTransientAttachment;
|
||||
|
||||
const VkImageUsageFlags transientFlag =
|
||||
useTransientAttachment ? VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT : 0U;
|
||||
|
||||
if (any(usage & TextureUsage::SAMPLEABLE)) {
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
// Validate that the format is actually sampleable.
|
||||
VkFormatProperties props;
|
||||
vkGetPhysicalDeviceFormatProperties(physicalDevice, mState->mVkFormat, &props);
|
||||
if (!(props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT)) {
|
||||
FVK_LOGW << "Texture usage is SAMPLEABLE but format " << mState->mVkFormat << " is not "
|
||||
"sampleable with optimal tiling." << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
}
|
||||
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | transientFlag;
|
||||
if (any(usage & TextureUsage::SUBPASS_INPUT)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||
}
|
||||
}
|
||||
if (any(usage & TextureUsage::STENCIL_ATTACHMENT)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
|
||||
}
|
||||
if (any(usage & TextureUsage::UPLOADABLE)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | transientFlag;
|
||||
|
||||
// Depth resolves uses a custom shader and therefore needs to be sampleable.
|
||||
if (samples > 1) {
|
||||
imageInfo.usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
// Constrain the sample count according to the sample count masks in VkPhysicalDeviceProperties.
|
||||
// Note that VulkanRenderTarget holds a single MSAA count, so we play it safe if this is used as
|
||||
// any kind of attachment (color or depth).
|
||||
const auto& limits = context.getPhysicalDeviceLimits();
|
||||
auto const& limits = context.getPhysicalDeviceLimits();
|
||||
if (imageInfo.usage & VK_IMAGE_USAGE_SAMPLED_BIT) {
|
||||
samples = fvkutils::reduceSampleCount(samples,
|
||||
fvkutils::isVkDepthFormat(mState->mVkFormat)
|
||||
fvkutils::isVkDepthFormat(vkFormat)
|
||||
? limits.sampledImageDepthSampleCounts
|
||||
: limits.sampledImageColorSampleCounts);
|
||||
}
|
||||
@@ -319,33 +372,36 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
this->samples = samples;
|
||||
imageInfo.samples = (VkSampleCountFlagBits) samples;
|
||||
|
||||
VkResult result = vkCreateImage(mState->mDevice, &imageInfo, VKALLOC, &mState->mTextureImage);
|
||||
VkImage textureImage;
|
||||
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &textureImage);
|
||||
if (result != VK_SUCCESS || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
|
||||
FVK_LOGD << "vkCreateImage: "
|
||||
<< "image = " << mState->mTextureImage << ", "
|
||||
<< "image = " << textureImage << ", "
|
||||
<< "result = " << result << ", "
|
||||
<< "handle = " << utils::io::hex << mState->mTextureImage << utils::io::dec << ", "
|
||||
<< "handle = " << utils::io::hex << textureImage << utils::io::dec << ", "
|
||||
<< "extent = " << w << "x" << h << "x"<< depth << ", "
|
||||
<< "mipLevels = " << int(levels) << ", "
|
||||
<< "TextureUsage = " << static_cast<int>(usage) << ", "
|
||||
<< "TextureUsage = " << static_cast<int>(tusage) << ", "
|
||||
<< "usage = " << imageInfo.usage << ", "
|
||||
<< "samples = " << imageInfo.samples << ", "
|
||||
<< "type = " << imageInfo.imageType << ", "
|
||||
<< "flags = " << imageInfo.flags << ", "
|
||||
<< "target = " << static_cast<int>(target) <<", "
|
||||
<< "format = " << mState->mVkFormat << utils::io::endl;
|
||||
<< "format = " << vkFormat << utils::io::endl;
|
||||
}
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create image."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
|
||||
// Allocate memory for the VkImage and bind it.
|
||||
VkMemoryRequirements memReqs = {};
|
||||
vkGetImageMemoryRequirements(mState->mDevice, mState->mTextureImage, &memReqs);
|
||||
VkMemoryRequirements memReqs;
|
||||
vkGetImageMemoryRequirements(device, textureImage, &memReqs);
|
||||
|
||||
const VkFlags requiredMemoryFlags =
|
||||
bool const useTransientAttachment = imageInfo.usage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
||||
|
||||
VkFlags const requiredMemoryFlags =
|
||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
|
||||
(useTransientAttachment ? VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT : 0U) |
|
||||
(mState->mIsProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : 0U);
|
||||
(isProtected ? VK_MEMORY_PROPERTY_PROTECTED_BIT : 0U);
|
||||
uint32_t memoryTypeIndex
|
||||
= context.selectMemoryType(memReqs.memoryTypeBits, requiredMemoryFlags);
|
||||
|
||||
@@ -357,21 +413,22 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
.allocationSize = memReqs.size,
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
};
|
||||
result = vkAllocateMemory(mState->mDevice, &allocInfo, nullptr, &mState->mTextureImageMemory);
|
||||
VkDeviceMemory textureImageMemory;
|
||||
result = vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to allocate image memory."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
result = vkBindImageMemory(mState->mDevice, mState->mTextureImage, mState->mTextureImageMemory,
|
||||
0);
|
||||
result = vkBindImageMemory(device, textureImage, textureImageMemory, 0);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to bind image."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
|
||||
mState = fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, stagePool,
|
||||
commands, allocator, device, textureImage, textureImageMemory, vkFormat,
|
||||
fvkutils::getViewType(target), levels, getLayerCount(target, depth),
|
||||
VK_NULL_HANDLE /* ycbcrConversion */, false /*isExternalFormat*/, imageInfo.usage,
|
||||
isProtected);
|
||||
|
||||
// Spec out the "primary" VkImageView that shaders use to sample from the image.
|
||||
mPrimaryViewRange = mState->mFullViewRange;
|
||||
|
||||
// Go ahead and create the primary image view.
|
||||
getImageView(mPrimaryViewRange, mState->mViewType, mSwizzle);
|
||||
|
||||
mState->mDefaultLayout = getDefaultLayoutImpl(imageInfo.usage);
|
||||
}
|
||||
|
||||
// Constructor for creating a texture view
|
||||
@@ -380,8 +437,8 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
fvkmemory::resource_ptr<VulkanTexture> src, uint8_t baseLevel,
|
||||
uint8_t levelCount)
|
||||
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
|
||||
src->format, src->usage) {
|
||||
mState = src->mState;
|
||||
src->format, src->usage),
|
||||
mState(src->mState) {
|
||||
mPrimaryViewRange = src->mPrimaryViewRange;
|
||||
mPrimaryViewRange.baseMipLevel = src->mPrimaryViewRange.baseMipLevel + baseLevel;
|
||||
mPrimaryViewRange.levelCount = levelCount;
|
||||
@@ -392,21 +449,10 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||
fvkmemory::resource_ptr<VulkanTexture> src, VkComponentMapping swizzle)
|
||||
: HwTexture(src->target, src->levels, src->samples, src->width, src->height, src->depth,
|
||||
src->format, src->usage) {
|
||||
mState = src->mState;
|
||||
mPrimaryViewRange = src->mPrimaryViewRange;
|
||||
mSwizzle = composeSwizzle(src->mSwizzle, swizzle);
|
||||
}
|
||||
|
||||
VulkanTextureState::~VulkanTextureState() {
|
||||
if (mTextureImageMemory != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
|
||||
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
|
||||
}
|
||||
for (auto entry: mCachedImageViews) {
|
||||
vkDestroyImageView(mDevice, entry.second, VKALLOC);
|
||||
}
|
||||
}
|
||||
src->format, src->usage),
|
||||
mState(src->mState),
|
||||
mPrimaryViewRange(src->mPrimaryViewRange),
|
||||
mSwizzle(composeSwizzle(src->mSwizzle, swizzle)) {}
|
||||
|
||||
void VulkanTexture::updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel) {
|
||||
@@ -513,20 +559,20 @@ void VulkanTexture::updateImageWithBlit(const PixelBufferDescriptor& hostData, u
|
||||
commands.acquire(fvkmemory::resource_ptr<VulkanTexture>::cast(this));
|
||||
|
||||
// TODO: support blit-based format conversion for 3D images and cubemaps.
|
||||
const int layer = 0;
|
||||
constexpr int layer = 0;
|
||||
|
||||
const VkOffset3D rect[2] { {0, 0, 0}, {int32_t(width), int32_t(height), 1} };
|
||||
VkOffset3D const rect[2] { {0, 0, 0}, {int32_t(width), int32_t(height), 1} };
|
||||
|
||||
const VkImageAspectFlags aspect = getImageAspect();
|
||||
VkImageAspectFlags const aspect = getImageAspect();
|
||||
|
||||
const VkImageBlit blitRegions[1] = {{
|
||||
VkImageBlit const blitRegions[1] = {{
|
||||
.srcSubresource = { aspect, 0, 0, 1 },
|
||||
.srcOffsets = { rect[0], rect[1] },
|
||||
.dstSubresource = { aspect, uint32_t(miplevel), layer, 1 },
|
||||
.dstOffsets = { rect[0], rect[1] }
|
||||
}};
|
||||
|
||||
const VkImageSubresourceRange range = { aspect, miplevel, 1, layer, 1 };
|
||||
VkImageSubresourceRange const range = { aspect, miplevel, 1, layer, 1 };
|
||||
|
||||
VulkanLayout const newLayout = VulkanLayout::TRANSFER_DST;
|
||||
VulkanLayout const oldLayout = getLayout(layer, miplevel);
|
||||
@@ -542,41 +588,21 @@ VulkanLayout VulkanTexture::getDefaultLayout() const {
|
||||
return mState->mDefaultLayout;
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
|
||||
range.levelCount = 1;
|
||||
range.layerCount = 1;
|
||||
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D, {});
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getMultiviewAttachmentView(VkImageSubresourceRange range) {
|
||||
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY, {});
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range, VkImageViewType type) {
|
||||
return getImageView(range, type, mSwizzle);
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
||||
VkComponentMapping swizzle) {
|
||||
VulkanTextureState::ImageViewKey const key{ range, viewType, swizzle };
|
||||
auto iter = mState->mCachedImageViews.find(key);
|
||||
if (iter != mState->mCachedImageViews.end()) {
|
||||
return iter->second;
|
||||
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange const& range, VkImageViewType type) {
|
||||
assert_invariant(mState->mYcbcr.conversion == VK_NULL_HANDLE &&
|
||||
"We are not yet supporting external image as attachments.");
|
||||
if (type == VK_IMAGE_VIEW_TYPE_2D) {
|
||||
VkImageSubresourceRange copy = range;
|
||||
copy.levelCount = 1;
|
||||
copy.layerCount = 1;
|
||||
return mState->getImageView(copy, type, {});
|
||||
} else {
|
||||
return mState->getImageView(range, type, {});
|
||||
}
|
||||
VkImageViewCreateInfo viewInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.image = mState->mTextureImage,
|
||||
.viewType = viewType,
|
||||
.format = mState->mVkFormat,
|
||||
.components = swizzle,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
VkImageView imageView;
|
||||
vkCreateImageView(mState->mDevice, &viewInfo, VKALLOC, &imageView);
|
||||
mState->mCachedImageViews.emplace(key, imageView);
|
||||
return imageView;
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getView(VkImageSubresourceRange const& range) {
|
||||
return mState->getImageView(range, mState->mViewType, mSwizzle);
|
||||
}
|
||||
|
||||
VkImageAspectFlags VulkanTexture::getImageAspect() const {
|
||||
@@ -738,6 +764,21 @@ void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanTexture::setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternalFormat) {
|
||||
// Note that this comparison is valid because we only ever create VkSamplerYcbcrConversion from
|
||||
// a cache. So for each set of parameters, there is exactly one conversion (similar to
|
||||
// samplers).
|
||||
VulkanTextureState::Ycbcr ycbcr = {
|
||||
.conversion = conversion,
|
||||
.isExternalFormat = isExternalFormat,
|
||||
};
|
||||
|
||||
if (mState->mYcbcr != ycbcr) {
|
||||
mState->mYcbcr = ycbcr;
|
||||
mState->clearCachedImageViews();
|
||||
}
|
||||
}
|
||||
|
||||
VulkanLayout VulkanTexture::getLayout(uint32_t layer, uint32_t level) const {
|
||||
assert_invariant(level <= 0xffff && layer <= 0xffff);
|
||||
const uint32_t key = (layer << 16) | level;
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include "VulkanBuffer.h"
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "VulkanStagePool.h"
|
||||
#include "vulkan/memory/Resource.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "vulkan/utils/Image.h"
|
||||
@@ -31,10 +34,14 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct VulkanTexture;
|
||||
|
||||
struct VulkanTextureState : public fvkmemory::Resource {
|
||||
VulkanTextureState(VkDevice device, VmaAllocator allocator, VulkanCommands* commands,
|
||||
VulkanStagePool& stagePool, VkFormat format, VkImageViewType viewType, uint8_t levels,
|
||||
uint8_t layerCount, VulkanLayout defaultLayout, bool isProtected);
|
||||
VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator,
|
||||
VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format,
|
||||
VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
|
||||
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat,
|
||||
VkImageUsageFlags usage, bool isProtected);
|
||||
|
||||
~VulkanTextureState();
|
||||
|
||||
@@ -57,29 +64,50 @@ struct VulkanTextureState : public fvkmemory::Resource {
|
||||
// No implicit padding allowed due to it being a hash key.
|
||||
static_assert(sizeof(ImageViewKey) == 40);
|
||||
|
||||
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
|
||||
VkImageView getImageView(VkImageSubresourceRange range, VkImageViewType viewType,
|
||||
VkComponentMapping swizzle);
|
||||
private:
|
||||
void clearCachedImageViews() noexcept;
|
||||
VulkanStagePool& mStagePool;
|
||||
VulkanCommands* const mCommands;
|
||||
VmaAllocator const mAllocator;
|
||||
VkDevice const mDevice;
|
||||
|
||||
// The texture with the sidecar owns the sidecar.
|
||||
fvkmemory::resource_ptr<VulkanTexture> mSidecarMSAA;
|
||||
VkDeviceMemory mTextureImageMemory = VK_NULL_HANDLE;
|
||||
|
||||
VkImage const mTextureImage;
|
||||
VkDeviceMemory const mTextureImageMemory;
|
||||
VkFormat const mVkFormat;
|
||||
VkImageViewType const mViewType;
|
||||
VkImageSubresourceRange const mFullViewRange;
|
||||
VkImage mTextureImage = VK_NULL_HANDLE;
|
||||
VulkanLayout mDefaultLayout;
|
||||
|
||||
bool mIsProtected = false;
|
||||
// Note that this parameter is not constant due to the fact that AHB can force a change in the
|
||||
// conversion matrix per-frame.
|
||||
struct Ycbcr {
|
||||
VkSamplerYcbcrConversion conversion;
|
||||
bool isExternalFormat;
|
||||
|
||||
bool operator==(Ycbcr const& other) const {
|
||||
return conversion == other.conversion && isExternalFormat == other.isExternalFormat;
|
||||
}
|
||||
|
||||
bool operator!=(Ycbcr const& other) const {
|
||||
return !((*this) == other);
|
||||
}
|
||||
|
||||
} mYcbcr;
|
||||
|
||||
VulkanLayout const mDefaultLayout;
|
||||
VkImageUsageFlags const mUsage;
|
||||
bool const mIsProtected;
|
||||
|
||||
// Track the image layout of each subresource using a sparse range map.
|
||||
utils::RangeMap<uint32_t, VulkanLayout> mSubresourceLayouts;
|
||||
|
||||
using ImageViewHash = utils::hash::MurmurHashFn<ImageViewKey>;
|
||||
std::unordered_map<ImageViewKey, VkImageView, ImageViewHash> mCachedImageViews;
|
||||
VulkanStagePool& mStagePool;
|
||||
VkDevice mDevice;
|
||||
VmaAllocator mAllocator;
|
||||
VulkanCommands* mCommands;
|
||||
bool mIsTransientAttachment;
|
||||
|
||||
friend struct VulkanTexture;
|
||||
};
|
||||
|
||||
struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
||||
@@ -91,10 +119,10 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
||||
VulkanStagePool& stagePool);
|
||||
|
||||
// Specialized constructor for internally created textures (e.g. from a swap chain)
|
||||
// The texture will never destroy the given VkImage, but it does manages its subresources.
|
||||
VulkanTexture(VkDevice device, VmaAllocator allocator,
|
||||
VulkanTexture(VulkanContext const& context, VkDevice device, VmaAllocator allocator,
|
||||
fvkmemory::ResourceManager* resourceManager, VulkanCommands* commands, VkImage image,
|
||||
VkDeviceMemory memory, VkFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
|
||||
VkDeviceMemory memory, VkFormat format, VkSamplerYcbcrConversion conversion,
|
||||
uint8_t samples, uint32_t width, uint32_t height, uint32_t depth,
|
||||
TextureUsage tusage, VulkanStagePool& stagePool);
|
||||
|
||||
// Constructor for creating a texture view for wrt specific mip range
|
||||
@@ -113,11 +141,6 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
||||
void updateImage(const PixelBufferDescriptor& data, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint32_t xoffset, uint32_t yoffset, uint32_t zoffset, uint32_t miplevel);
|
||||
|
||||
// Returns the primary image view, which is used for shader sampling.
|
||||
VkImageView getPrimaryImageView() {
|
||||
return getImageView(mPrimaryViewRange, mState->mViewType, mSwizzle);
|
||||
}
|
||||
|
||||
VkImageViewType getViewType() const {
|
||||
return mState->mViewType;
|
||||
}
|
||||
@@ -132,20 +155,11 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
||||
// time of the call.
|
||||
VulkanLayout getDefaultLayout() const;
|
||||
|
||||
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
|
||||
// target attachment. Unlike the primary image view, this always has type VK_IMAGE_VIEW_TYPE_2D
|
||||
// and the identity swizzle.
|
||||
VkImageView getAttachmentView(VkImageSubresourceRange range);
|
||||
// Gets or creates a cached VkImageView for a subresource that can be used as a render
|
||||
// target attachment. Unlike the primary image view, this always the identity swizzle.
|
||||
VkImageView getAttachmentView(VkImageSubresourceRange const& range, VkImageViewType type);
|
||||
|
||||
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
|
||||
// target attachment when rendering with multiview.
|
||||
VkImageView getMultiviewAttachmentView(VkImageSubresourceRange range);
|
||||
|
||||
// This is a workaround for the first few frames where we're waiting for the texture to actually
|
||||
// be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding
|
||||
// imageView needs to be of the right type. Hence, we provide an option to indicate the
|
||||
// view type. Swizzle option does not matter in this case.
|
||||
VkImageView getViewForType(VkImageSubresourceRange const& range, VkImageViewType type);
|
||||
VkImageView getView(VkImageSubresourceRange const& range);
|
||||
|
||||
VkFormat getVkFormat() const {
|
||||
return mState->mVkFormat;
|
||||
@@ -165,7 +179,7 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
||||
}
|
||||
|
||||
bool isTransientAttachment() const {
|
||||
return mState->mIsTransientAttachment;
|
||||
return mState->mUsage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
|
||||
}
|
||||
|
||||
bool getIsProtected() const {
|
||||
@@ -192,6 +206,11 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
||||
// manually (outside of calls to transitionLayout).
|
||||
void setLayout(VkImageSubresourceRange const& range, VulkanLayout newLayout);
|
||||
|
||||
// This is used in the case of external images and external samplers. AHB might update the
|
||||
// conversion per-frame. This implies that we need to invalidate the view cache when that
|
||||
// happens.
|
||||
void setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternal);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
void print() const;
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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 "VulkanYcbcrConversionCache.h"
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <vulkan/vulkan_android.h> // for VkExternalFormatANDROID
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanYcbcrConversionCache::VulkanYcbcrConversionCache(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
|
||||
VulkanYcbcrConversionCache::Params params) noexcept {
|
||||
auto iter = mCache.find(params);
|
||||
if (UTILS_LIKELY(iter != mCache.end())) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
auto const& chroma = params.conversion;
|
||||
TextureSwizzle const swizzleArray[] = { chroma.r, chroma.g, chroma.b, chroma.a };
|
||||
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
|
||||
.format = fvkutils::getVkFormat(params.format),
|
||||
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
|
||||
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
|
||||
.components = fvkutils::getSwizzleMap(swizzleArray),
|
||||
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
|
||||
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
|
||||
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
|
||||
};
|
||||
|
||||
// We could put this in the platform class, but that seems like a bit of an overkill
|
||||
#if defined(__ANDROID__)
|
||||
VkExternalFormatANDROID externalFormat = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
||||
.externalFormat = params.externalFormat,
|
||||
};
|
||||
if (params.externalFormat) {
|
||||
conversionInfo.pNext = &externalFormat;
|
||||
conversionInfo.format = VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
#endif
|
||||
|
||||
VkSamplerYcbcrConversion conversion;
|
||||
VkResult result =
|
||||
vkCreateSamplerYcbcrConversion(mDevice, &conversionInfo, nullptr, &conversion);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS) << "Unable to create Ycbcr Conversion."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
|
||||
mCache.insert({ params, conversion });
|
||||
return conversion;
|
||||
}
|
||||
|
||||
void VulkanYcbcrConversionCache::terminate() noexcept {
|
||||
for (auto& [param, conv]: mCache) {
|
||||
vkDestroySamplerYcbcrConversion(mDevice, conv, VKALLOC);
|
||||
}
|
||||
mCache.clear();
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
// Simple manager for VkSamplerYcbcrConversion objects.
|
||||
class VulkanYcbcrConversionCache {
|
||||
public:
|
||||
struct Params {
|
||||
SamplerYcbcrConversion conversion = {};
|
||||
TextureFormat format = {};
|
||||
uint16_t padding = 0;
|
||||
uint64_t externalFormat = 0;
|
||||
};
|
||||
static_assert(sizeof(Params) == 16);
|
||||
|
||||
explicit VulkanYcbcrConversionCache(VkDevice device);
|
||||
VkSamplerYcbcrConversion getConversion(Params params) noexcept;
|
||||
void terminate() noexcept;
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
|
||||
struct ConversionEqualTo {
|
||||
bool operator()(Params lhs, Params rhs) const noexcept {
|
||||
SamplerYcbcrConversion::EqualTo equal;
|
||||
return equal(lhs.conversion, rhs.conversion) &&
|
||||
lhs.externalFormat == rhs.externalFormat;
|
||||
}
|
||||
};
|
||||
using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
|
||||
tsl::robin_map<Params, VkSamplerYcbcrConversion, ConversionHashFn, ConversionEqualTo> mCache;
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_VULKANYCBCRCONVERSIONCACHE_H
|
||||
@@ -132,6 +132,10 @@ public:
|
||||
return id() == other.id() && type() == other.type();
|
||||
}
|
||||
|
||||
inline bool operator!=(resource_ptr<D> const& other) const {
|
||||
return !((*this) == other);
|
||||
}
|
||||
|
||||
inline explicit operator bool() const {
|
||||
return bool(mRef);
|
||||
}
|
||||
|
||||
@@ -211,6 +211,7 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
|
||||
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
|
||||
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
|
||||
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
|
||||
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
|
||||
#endif
|
||||
// MoltenVk is the only non-conformant implementation we're interested in.
|
||||
#if defined(__APPLE__)
|
||||
@@ -326,7 +327,9 @@ VkInstance createInstance(ExtensionSet const& requiredExts) {
|
||||
}
|
||||
|
||||
VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
VkPhysicalDeviceFeatures2 const& features, uint32_t graphicsQueueFamilyIndex,
|
||||
VkPhysicalDeviceFeatures2 const& features,
|
||||
VkPhysicalDeviceVulkan11Features const& vk11Features,
|
||||
uint32_t graphicsQueueFamilyIndex,
|
||||
uint32_t protectedGraphicsQueueFamilyIndex, ExtensionSet const& deviceExtensions,
|
||||
bool requestImageView2DOn3DImage) {
|
||||
VkDevice device;
|
||||
@@ -359,14 +362,28 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
|
||||
// We could simply enable all supported features, but since that may have performance
|
||||
// consequences let's just enable the features we need.
|
||||
VkPhysicalDeviceFeatures enabledFeatures{
|
||||
VkPhysicalDeviceFeatures enabledFeatures = {
|
||||
.depthClamp = features.features.depthClamp,
|
||||
.samplerAnisotropy = features.features.samplerAnisotropy,
|
||||
.textureCompressionETC2 = features.features.textureCompressionETC2,
|
||||
.textureCompressionBC = features.features.textureCompressionBC,
|
||||
.shaderClipDistance = features.features.shaderClipDistance,
|
||||
};
|
||||
deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
|
||||
|
||||
VkPhysicalDeviceFeatures2 enabledFeatures2 = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||
.features = enabledFeatures,
|
||||
};
|
||||
chainStruct(&deviceCreateInfo, &enabledFeatures2);
|
||||
|
||||
VkPhysicalDeviceVulkan11Features enabledVk11Features = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
|
||||
.multiview = vk11Features.multiview,
|
||||
#if defined(__ANDROID__)
|
||||
.samplerYcbcrConversion = vk11Features.samplerYcbcrConversion,
|
||||
#endif
|
||||
};
|
||||
chainStruct(&deviceCreateInfo, &enabledVk11Features);
|
||||
|
||||
deviceCreateInfo.enabledExtensionCount = (uint32_t) requestExtensions.size();
|
||||
deviceCreateInfo.ppEnabledExtensionNames = requestExtensions.data();
|
||||
@@ -383,7 +400,7 @@ VkDevice createLogicalDevice(VkPhysicalDevice physicalDevice,
|
||||
|
||||
VkPhysicalDeviceMultiviewFeaturesKHR multiview = {
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES_KHR,
|
||||
.multiview = VK_TRUE,
|
||||
.multiview = vk11Features.multiview,
|
||||
.multiviewGeometryShader = VK_FALSE,
|
||||
.multiviewTessellationShader = VK_FALSE,
|
||||
};
|
||||
@@ -734,6 +751,7 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
|
||||
};
|
||||
chainStruct(&context.mPhysicalDeviceFeatures, &queryProtectedMemoryFeatures);
|
||||
chainStruct(&context.mPhysicalDeviceFeatures, &context.mPhysicalDeviceVk11Features);
|
||||
chainStruct(&context.mPhysicalDeviceProperties, &protectedMemoryProperties);
|
||||
|
||||
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
|
||||
@@ -795,10 +813,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
}
|
||||
|
||||
if (mImpl->mDevice == VK_NULL_HANDLE) {
|
||||
mImpl->mDevice =
|
||||
createLogicalDevice(mImpl->mPhysicalDevice, context.mPhysicalDeviceFeatures,
|
||||
mImpl->mGraphicsQueueFamilyIndex, mImpl->mProtectedGraphicsQueueFamilyIndex,
|
||||
deviceExts, requestPortabilitySubsetImageView2DOn3DImage);
|
||||
mImpl->mDevice = createLogicalDevice(mImpl->mPhysicalDevice,
|
||||
context.mPhysicalDeviceFeatures, context.mPhysicalDeviceVk11Features,
|
||||
mImpl->mGraphicsQueueFamilyIndex, mImpl->mProtectedGraphicsQueueFamilyIndex,
|
||||
deviceExts, requestPortabilitySubsetImageView2DOn3DImage);
|
||||
}
|
||||
|
||||
assert_invariant(mImpl->mDevice != VK_NULL_HANDLE);
|
||||
@@ -826,12 +844,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
if (!mImpl->mSharedContext) {
|
||||
context.mDebugUtilsSupported = setContains(instExts, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
context.mDebugMarkersSupported = setContains(deviceExts, VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
|
||||
context.mMultiviewEnabled = setContains(deviceExts, VK_KHR_MULTIVIEW_EXTENSION_NAME);
|
||||
} else {
|
||||
VulkanSharedContext const* scontext = (VulkanSharedContext const*) sharedContext;
|
||||
context.mDebugUtilsSupported = scontext->debugUtilsSupported;
|
||||
context.mDebugMarkersSupported = scontext->debugMarkersSupported;
|
||||
context.mMultiviewEnabled = scontext->multiviewSupported;
|
||||
}
|
||||
|
||||
// Check the availability of lazily allocated memory
|
||||
@@ -977,30 +993,6 @@ VkQueue VulkanPlatform::getProtectedGraphicsQueue() const noexcept {
|
||||
return mImpl->mProtectedGraphicsQueue;
|
||||
}
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadata(
|
||||
ExternalImageHandleRef externalImage) {
|
||||
return getExternalImageMetadataImpl(externalImage, mImpl->mDevice);
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageData(
|
||||
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata,
|
||||
uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
return createExternalImageDataImpl(externalImage, mImpl->mDevice, metadata, memoryTypeIndex,
|
||||
usage);
|
||||
}
|
||||
|
||||
VkSampler VulkanPlatform::createExternalSampler(SamplerYcbcrConversion chroma,
|
||||
SamplerParams sampler, uint32_t internalFormat) {
|
||||
return createExternalSamplerImpl(mImpl->mDevice, chroma, sampler, internalFormat);
|
||||
}
|
||||
|
||||
VkImageView VulkanPlatform::createExternalImageView(SamplerYcbcrConversion chroma,
|
||||
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
|
||||
VkImageViewType viewType, VkComponentMapping swizzle) {
|
||||
return createExternalImageViewImpl(mImpl->mDevice, chroma, internalFormat, image, range,
|
||||
viewType, swizzle);
|
||||
}
|
||||
|
||||
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
|
||||
return getSwapchainInstanceExtensionsImpl();
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
#include <backend/platforms/VulkanPlatformAndroid.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "vulkan/VulkanContext.h"
|
||||
#include "vulkan/vulkan_core.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <private/backend/BackendUtilsAndroid.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include "vulkan/utils/Image.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
@@ -57,7 +57,7 @@ VkFormat transformVkFormat(VkFormat format, bool sRGB) {
|
||||
}
|
||||
|
||||
bool isProtectedFromUsage(uint64_t usage) {
|
||||
return (usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) ? true : false;
|
||||
return usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
|
||||
}
|
||||
|
||||
std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer_Desc& desc,
|
||||
@@ -120,7 +120,9 @@ std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer
|
||||
usage = 0;
|
||||
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) {
|
||||
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||
|
||||
// We shouldn't be using external samplers as input attachments
|
||||
// usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||
}
|
||||
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER) {
|
||||
if (isDepthFormat) {
|
||||
@@ -136,87 +138,38 @@ std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer
|
||||
return { format, usage };
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevice device,
|
||||
VulkanPlatform::ExternalImageMetadata const& metadata, uint32_t memoryTypeIndex,
|
||||
VkImageUsageFlags usage) {
|
||||
VulkanPlatform::ImageData data;
|
||||
|
||||
// if external format we need to specifiy it in the allocation
|
||||
const bool useExternalFormat = metadata.format == VK_FORMAT_UNDEFINED;
|
||||
|
||||
const VkExternalFormatANDROID externalFormat = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
||||
.pNext = nullptr,
|
||||
// pass down the format (external means we don't have it VK defined)
|
||||
.externalFormat = metadata.externalFormat,
|
||||
std::pair<TextureFormat, TextureUsage> getFilamentFormatAndUsage(const AHardwareBuffer_Desc& desc,
|
||||
bool sRGB) {
|
||||
auto const format = mapToFilamentFormat(desc.format, sRGB);
|
||||
return {
|
||||
format,
|
||||
mapToFilamentUsage(desc.usage, format),
|
||||
};
|
||||
const VkExternalMemoryImageCreateInfo externalCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
|
||||
.pNext = useExternalFormat ? &externalFormat : nullptr,
|
||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
|
||||
};
|
||||
|
||||
VkImageCreateInfo imageInfo{ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
|
||||
imageInfo.pNext = &externalCreateInfo;
|
||||
imageInfo.format = metadata.format;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.extent = {
|
||||
metadata.width,
|
||||
metadata.height,
|
||||
1u,
|
||||
};
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = metadata.layers;
|
||||
imageInfo.samples = metadata.samples;
|
||||
imageInfo.usage = usage;
|
||||
|
||||
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &data.first);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateImage failed with error=" << static_cast<int32_t>(result);
|
||||
|
||||
// Allocate the memory
|
||||
VkImportAndroidHardwareBufferInfoANDROID androidHardwareBufferInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
|
||||
.pNext = nullptr,
|
||||
.buffer = buffer,
|
||||
};
|
||||
VkMemoryDedicatedAllocateInfo memoryDedicatedAllocateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
|
||||
.pNext = &androidHardwareBufferInfo,
|
||||
.image = data.first,
|
||||
.buffer = VK_NULL_HANDLE,
|
||||
};
|
||||
VkMemoryAllocateInfo allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.pNext = &memoryDedicatedAllocateInfo,
|
||||
.allocationSize = metadata.allocationSize,
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
};
|
||||
result = vkAllocateMemory(device, &allocInfo, VKALLOC, &data.second);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkAllocateMemory failed with error=" << static_cast<int32_t>(result);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
}// namespace
|
||||
|
||||
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() = default;
|
||||
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
if (aHardwareBuffer) {
|
||||
AHardwareBuffer_release(aHardwareBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
||||
AHardwareBuffer const* buffer, bool sRGB) noexcept {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
auto bufferImpl = const_cast<AHardwareBuffer*>(buffer);
|
||||
AHardwareBuffer_acquire(bufferImpl);
|
||||
|
||||
AHardwareBuffer_Desc hardwareBufferDescription = {};
|
||||
AHardwareBuffer_describe(buffer, &hardwareBufferDescription);
|
||||
|
||||
auto* const p = new (std::nothrow) ExternalImageVulkanAndroid;
|
||||
p->aHardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
|
||||
p->sRGB = sRGB;
|
||||
p->height = hardwareBufferDescription.height;
|
||||
p->width = hardwareBufferDescription.width;
|
||||
TextureFormat textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
|
||||
p->format = textureFormat;
|
||||
p->usage = mapToFilamentUsage(hardwareBufferDescription.usage, textureFormat);
|
||||
return Platform::ExternalImageHandle{ p };
|
||||
}
|
||||
|
||||
@@ -225,23 +178,20 @@ Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
||||
|
||||
VulkanPlatformAndroid::ExternalImageDescAndroid VulkanPlatformAndroid::getExternalImageDesc(
|
||||
ExternalImageHandleRef externalImage) const noexcept {
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
|
||||
auto metadata = extractExternalImageMetadata(externalImage);
|
||||
return {
|
||||
.width = fvkExternalImage->width,
|
||||
.height = fvkExternalImage->height,
|
||||
.format = fvkExternalImage->format,
|
||||
.usage = fvkExternalImage->usage,
|
||||
.width = metadata.width,
|
||||
.height = metadata.height,
|
||||
.format = metadata.filamentFormat,
|
||||
.usage = metadata.filamentUsage,
|
||||
};
|
||||
}
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMetadata(
|
||||
ExternalImageHandleRef externalImage) {
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::extractExternalImageMetadata(
|
||||
ExternalImageHandleRef image) const {
|
||||
auto const* fvkExternalImage = static_cast<ExternalImageVulkanAndroid const*>(image.get());
|
||||
|
||||
ExternalImageMetadata metadata;
|
||||
ExternalImageMetadata metadata = {};
|
||||
AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
|
||||
if (__builtin_available(android 26, *)) {
|
||||
AHardwareBuffer_Desc bufferDesc;
|
||||
@@ -249,16 +199,30 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMet
|
||||
metadata.width = bufferDesc.width;
|
||||
metadata.height = bufferDesc.height;
|
||||
metadata.layers = bufferDesc.layers;
|
||||
metadata.isProtected = isProtectedFromUsage(bufferDesc.usage);
|
||||
std::tie(metadata.format, metadata.usage) =
|
||||
getVKFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
|
||||
}
|
||||
std::tie(metadata.filamentFormat, metadata.filamentUsage) =
|
||||
getFilamentFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
|
||||
|
||||
if (isProtectedFromUsage(bufferDesc.usage)) {
|
||||
metadata.filamentUsage |= TextureUsage::PROTECTED;
|
||||
}
|
||||
|
||||
// TODO: The following seems unnecessary. we should be able to discern directly from the
|
||||
// bufferDesc.
|
||||
if (any(metadata.filamentUsage & TextureUsage::BLIT_SRC)) {
|
||||
metadata.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
|
||||
if (any(metadata.filamentUsage & (TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE))) {
|
||||
metadata.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
}
|
||||
metadata.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
|
||||
VkAndroidHardwareBufferFormatPropertiesANDROID formatInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID,
|
||||
.pNext = nullptr,
|
||||
};
|
||||
VkAndroidHardwareBufferPropertiesANDROID properties = {
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
|
||||
@@ -268,141 +232,116 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMet
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkGetAndroidHardwareBufferProperties failed with error="
|
||||
<< static_cast<int32_t>(result);
|
||||
|
||||
VkFormat bufferPropertiesFormat = transformVkFormat(formatInfo.format, fvkExternalImage->sRGB);
|
||||
FILAMENT_CHECK_POSTCONDITION(metadata.format == bufferPropertiesFormat)
|
||||
<< "mismatched image format( " << metadata.format << ") and queried format("
|
||||
<< bufferPropertiesFormat << ") for external image (AHB)";
|
||||
metadata.externalFormat = formatInfo.externalFormat;
|
||||
|
||||
// Choose either externalFormat > 0 or metadata.format and prefer the latter.
|
||||
if (metadata.externalFormat > 0 && metadata.format != VK_FORMAT_UNDEFINED) {
|
||||
// See VUID-VkImageCreateInfo-pNext-09457
|
||||
metadata.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
metadata.externalFormat = 0;
|
||||
}
|
||||
|
||||
metadata.allocationSize = properties.allocationSize;
|
||||
metadata.memoryTypeBits = properties.memoryTypeBits;
|
||||
|
||||
metadata.ycbcrConversionComponents = formatInfo.samplerYcbcrConversionComponents;
|
||||
metadata.ycbcrModel = formatInfo.suggestedYcbcrModel;
|
||||
metadata.ycbcrRange = formatInfo.suggestedYcbcrRange;
|
||||
metadata.xChromaOffset = formatInfo.suggestedXChromaOffset;
|
||||
metadata.yChromaOffset = formatInfo.suggestedYChromaOffset;
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
VulkanPlatformAndroid::ImageData VulkanPlatformAndroid::createExternalImageData(
|
||||
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata,
|
||||
uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
VulkanPlatformAndroid::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
|
||||
ExternalImageHandleRef externalImage) const {
|
||||
auto const& metadata = extractExternalImageMetadata(externalImage);
|
||||
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
ImageData data = allocateExternalImage(fvkExternalImage->aHardwareBuffer, getDevice(), metadata,
|
||||
memoryTypeIndex, usage);
|
||||
VkResult result = vkBindImageMemory(getDevice(), data.first, data.second, 0);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
|
||||
return data;
|
||||
}
|
||||
AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
|
||||
|
||||
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device, SamplerYcbcrConversion chroma,
|
||||
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
|
||||
VkImageViewType viewType, VkComponentMapping swizzle){
|
||||
VkExternalFormatANDROID externalFormat = {
|
||||
// if external format we need to specifiy it in the allocation
|
||||
bool const useExternalFormat = metadata.format == VK_FORMAT_UNDEFINED;
|
||||
|
||||
VkExternalFormatANDROID const externalFormat = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
||||
.pNext = nullptr,
|
||||
.externalFormat = internalFormat,
|
||||
.externalFormat = metadata.externalFormat,
|
||||
};
|
||||
VkExternalMemoryImageCreateInfo const externalCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
|
||||
.pNext = useExternalFormat ? &externalFormat : nullptr,
|
||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
|
||||
};
|
||||
|
||||
TextureSwizzle const swizzleArray[] = {chroma.r, chroma.g, chroma.b, chroma.a};
|
||||
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
|
||||
.pNext = &externalFormat,
|
||||
.format = VK_FORMAT_UNDEFINED,
|
||||
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
|
||||
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
|
||||
.components = fvkutils::getSwizzleMap(swizzleArray),
|
||||
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
|
||||
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
|
||||
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
|
||||
VkImageCreateInfo const imageInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = &externalCreateInfo,
|
||||
.flags = useExternalFormat ? VK_IMAGE_CREATE_ALIAS_BIT : 0u,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = metadata.format,
|
||||
.extent = {
|
||||
metadata.width,
|
||||
metadata.height,
|
||||
1u,
|
||||
},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = metadata.layers,
|
||||
.samples = metadata.samples,
|
||||
.usage = metadata.usage,
|
||||
};
|
||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||
VkResult result = vkCreateSamplerYcbcrConversion(device, &conversionInfo,
|
||||
nullptr, &conversion);
|
||||
|
||||
VkDevice const device = getDevice();
|
||||
|
||||
VkImage image;
|
||||
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &image);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create Ycbcr Conversion."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
<< "vkCreateImage failed with error=" << static_cast<int32_t>(result);
|
||||
|
||||
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
|
||||
.pNext = nullptr,
|
||||
.conversion = conversion,
|
||||
// Allocate the memory
|
||||
VkImportAndroidHardwareBufferInfoANDROID const androidHardwareBufferInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
|
||||
.buffer = buffer,
|
||||
};
|
||||
|
||||
VkImageViewCreateInfo viewInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = &samplerYcbcrConversionInfo,
|
||||
.flags = 0,
|
||||
VkMemoryDedicatedAllocateInfo const memoryDedicatedAllocateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
|
||||
.pNext = &androidHardwareBufferInfo,
|
||||
.image = image,
|
||||
.viewType = viewType,
|
||||
.format = VK_FORMAT_UNDEFINED,
|
||||
.components = swizzle,
|
||||
.subresourceRange = range,
|
||||
.buffer = VK_NULL_HANDLE,
|
||||
};
|
||||
VkImageView imageView;
|
||||
result = vkCreateImageView(device, &viewInfo, VKALLOC, &imageView);
|
||||
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
vkGetPhysicalDeviceMemoryProperties(getPhysicalDevice(), &memoryProperties);
|
||||
|
||||
|
||||
VkMemoryPropertyFlags const requiredMemoryFlags =
|
||||
any(metadata.filamentUsage & TextureUsage::UPLOADABLE)
|
||||
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
||||
: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
|
||||
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
|
||||
metadata.memoryTypeBits, requiredMemoryFlags);
|
||||
|
||||
VkMemoryAllocateInfo const allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.pNext = &memoryDedicatedAllocateInfo,
|
||||
.allocationSize = metadata.allocationSize,
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
};
|
||||
VkDeviceMemory memory;
|
||||
result = vkAllocateMemory(device, &allocInfo, VKALLOC, &memory);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create VkImageView."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
<< "vkAllocateMemory failed with error=" << static_cast<int32_t>(result);
|
||||
|
||||
return imageView;
|
||||
}
|
||||
|
||||
VkSampler VulkanPlatform::createExternalSamplerImpl(
|
||||
VkDevice device, SamplerYcbcrConversion chroma, SamplerParams params,
|
||||
uint32_t internalFormat) {
|
||||
VkExternalFormatANDROID externalFormat = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
||||
.pNext = nullptr,
|
||||
.externalFormat = internalFormat,
|
||||
};
|
||||
|
||||
TextureSwizzle const swizzleArray[] = {chroma.r, chroma.g, chroma.b, chroma.a};
|
||||
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
|
||||
.pNext = &externalFormat,
|
||||
.format = VK_FORMAT_UNDEFINED,
|
||||
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
|
||||
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
|
||||
.components = fvkutils::getSwizzleMap(swizzleArray),
|
||||
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
|
||||
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
|
||||
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
|
||||
};
|
||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||
VkResult result = vkCreateSamplerYcbcrConversion(device, &conversionInfo,
|
||||
nullptr, &conversion);
|
||||
result = vkBindImageMemory(getDevice(), image, memory, 0);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create Ycbcr Conversion."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
|
||||
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
|
||||
.pNext = nullptr,
|
||||
.conversion = conversion,
|
||||
};
|
||||
|
||||
VkSamplerCreateInfo samplerInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.pNext = &samplerYcbcrConversionInfo,
|
||||
.magFilter = fvkutils::getFilter(params.filterMag),
|
||||
.minFilter = fvkutils::getFilter(params.filterMin),
|
||||
.mipmapMode = fvkutils::getMipmapMode(params.filterMin),
|
||||
.addressModeU = fvkutils::getWrapMode(params.wrapS),
|
||||
.addressModeV = fvkutils::getWrapMode(params.wrapT),
|
||||
.addressModeW = fvkutils::getWrapMode(params.wrapR),
|
||||
.anisotropyEnable = params.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
|
||||
.maxAnisotropy = (float)(1u << params.anisotropyLog2),
|
||||
.compareEnable = fvkutils::getCompareEnable(params.compareMode),
|
||||
.compareOp = fvkutils::getCompareOp(params.compareFunc),
|
||||
.minLod = 0.0f,
|
||||
.maxLod = fvkutils::getMaxLod(params.filterMin),
|
||||
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
|
||||
.unnormalizedCoordinates = VK_FALSE,
|
||||
};
|
||||
VkSampler sampler;
|
||||
result = vkCreateSampler(device, &samplerInfo, VKALLOC, &sampler);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create sampler."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
return sampler;
|
||||
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
|
||||
return { image, memory };
|
||||
}
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const {
|
||||
@@ -430,20 +369,9 @@ VulkanPlatform::SurfaceBundle VulkanPlatformAndroid::createVkSurfaceKHR(void* na
|
||||
// Deprecated platform dependent helper methods
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { return {}; }
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
||||
return ExternalImageMetadata{};
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
return ImageData{};
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
return SurfaceBundle{};
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -63,30 +63,6 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
|
||||
return ret;
|
||||
}
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
||||
return {};
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
return {};
|
||||
}
|
||||
|
||||
VkSampler VulkanPlatform::createExternalSamplerImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma,
|
||||
SamplerParams sampler,
|
||||
uint32_t internalFormat) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
|
||||
VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
|
||||
@@ -84,30 +84,6 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
||||
return {};
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
return {};
|
||||
}
|
||||
|
||||
VkSampler VulkanPlatform::createExternalSamplerImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma,
|
||||
SamplerParams sampler,
|
||||
uint32_t internalFormat) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
|
||||
VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||
VulkanPlatform::ExtensionSet const ret = {
|
||||
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
#define TNT_FILAMENT_BACKEND_VULKANSWAPCHAIN_IMPL_H
|
||||
|
||||
#include "vulkan/VulkanContext.h"
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -668,4 +668,111 @@ VkShaderStageFlags getShaderStageFlags(ShaderStageFlags stageFlags) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
VkSamplerYcbcrModelConversion getYcbcrModelConversion(
|
||||
SamplerYcbcrModelConversion model) {
|
||||
switch (model) {
|
||||
case SamplerYcbcrModelConversion::RGB_IDENTITY:
|
||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY;
|
||||
case SamplerYcbcrModelConversion::YCBCR_IDENTITY:
|
||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY;
|
||||
case SamplerYcbcrModelConversion::YCBCR_709:
|
||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709;
|
||||
case SamplerYcbcrModelConversion::YCBCR_601:
|
||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601;
|
||||
case SamplerYcbcrModelConversion::YCBCR_2020:
|
||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020;
|
||||
default:
|
||||
assert_invariant(false &&
|
||||
"Unknown data type, conversion is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range) {
|
||||
switch (range) {
|
||||
case SamplerYcbcrRange::ITU_FULL:
|
||||
return VK_SAMPLER_YCBCR_RANGE_ITU_FULL;
|
||||
case SamplerYcbcrRange::ITU_NARROW:
|
||||
return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW;
|
||||
default:
|
||||
assert_invariant(false &&
|
||||
"Unknown data type, conversion is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
VkChromaLocation getChromaLocation(ChromaLocation loc) {
|
||||
switch (loc) {
|
||||
case ChromaLocation::COSITED_EVEN:
|
||||
return VK_CHROMA_LOCATION_COSITED_EVEN;
|
||||
case ChromaLocation::MIDPOINT:
|
||||
return VK_CHROMA_LOCATION_MIDPOINT;
|
||||
default:
|
||||
assert_invariant(false &&
|
||||
"Unknown data type, conversion is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
SamplerYcbcrModelConversion getYcbcrModelConversionFilament(VkSamplerYcbcrModelConversion model) {
|
||||
switch (model) {
|
||||
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY:
|
||||
return SamplerYcbcrModelConversion::RGB_IDENTITY;
|
||||
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY:
|
||||
return SamplerYcbcrModelConversion::YCBCR_IDENTITY;
|
||||
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709:
|
||||
return SamplerYcbcrModelConversion::YCBCR_709;
|
||||
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601:
|
||||
return SamplerYcbcrModelConversion::YCBCR_601;
|
||||
case VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020:
|
||||
return SamplerYcbcrModelConversion::YCBCR_2020;
|
||||
default:
|
||||
assert_invariant(false && "Unknown data type, conversion is not supported.");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
SamplerYcbcrRange getYcbcrRangeFilament(VkSamplerYcbcrRange range) {
|
||||
switch (range) {
|
||||
case VK_SAMPLER_YCBCR_RANGE_ITU_FULL:
|
||||
return SamplerYcbcrRange::ITU_FULL;
|
||||
case VK_SAMPLER_YCBCR_RANGE_ITU_NARROW:
|
||||
return SamplerYcbcrRange::ITU_NARROW;
|
||||
default:
|
||||
assert_invariant(false && "Unknown data type, conversion is not supported.");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
ChromaLocation getChromaLocationFilament(VkChromaLocation loc) {
|
||||
switch (loc) {
|
||||
case VK_CHROMA_LOCATION_COSITED_EVEN:
|
||||
return ChromaLocation::COSITED_EVEN;
|
||||
case VK_CHROMA_LOCATION_MIDPOINT:
|
||||
return ChromaLocation::MIDPOINT;
|
||||
default:
|
||||
assert_invariant(false && "Unknown data type, conversion is not supported.");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
TextureSwizzle getSwizzleFilament(VkComponentSwizzle c, uint8_t rgbaIndex) {
|
||||
switch (c) {
|
||||
case VK_COMPONENT_SWIZZLE_ZERO:
|
||||
return TextureSwizzle::SUBSTITUTE_ZERO;
|
||||
case VK_COMPONENT_SWIZZLE_ONE:
|
||||
return TextureSwizzle::SUBSTITUTE_ONE;
|
||||
case VK_COMPONENT_SWIZZLE_IDENTITY:
|
||||
return (TextureSwizzle) (((uint8_t) TextureSwizzle::CHANNEL_0) + rgbaIndex);
|
||||
case VK_COMPONENT_SWIZZLE_R:
|
||||
return TextureSwizzle::CHANNEL_0;
|
||||
case VK_COMPONENT_SWIZZLE_G:
|
||||
return TextureSwizzle::CHANNEL_1;
|
||||
case VK_COMPONENT_SWIZZLE_B:
|
||||
return TextureSwizzle::CHANNEL_2;
|
||||
case VK_COMPONENT_SWIZZLE_A:
|
||||
return TextureSwizzle::CHANNEL_3;
|
||||
default:
|
||||
assert_invariant(false && "Unknown data type, conversion is not supported.");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend::fvkutils
|
||||
|
||||
@@ -69,6 +69,12 @@ VkSamplerYcbcrModelConversion getYcbcrModelConversion(SamplerYcbcrModelConversio
|
||||
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range);
|
||||
VkChromaLocation getChromaLocation(ChromaLocation loc);
|
||||
|
||||
// Ycbcr related functions
|
||||
SamplerYcbcrModelConversion getYcbcrModelConversionFilament(VkSamplerYcbcrModelConversion model);
|
||||
SamplerYcbcrRange getYcbcrRangeFilament(VkSamplerYcbcrRange range);
|
||||
ChromaLocation getChromaLocationFilament(VkChromaLocation loc);
|
||||
TextureSwizzle getSwizzleFilament(VkComponentSwizzle c, uint8_t rgbaIndex);
|
||||
|
||||
inline VkImageViewType getViewType(SamplerType target) {
|
||||
switch (target) {
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
|
||||
@@ -348,6 +348,10 @@ using SamplerBitmask = utils::bitset64;
|
||||
// general.
|
||||
using InputAttachmentBitmask = utils::bitset64;
|
||||
|
||||
constexpr uint8_t MAX_DESCRIPTOR_SET_BITMASK_BITS =
|
||||
std::max(std::max(sizeof(UniformBufferBitmask), sizeof(SamplerBitmask)),
|
||||
sizeof(InputAttachmentBitmask)) * 8;
|
||||
|
||||
template<typename Bitmask>
|
||||
static constexpr uint8_t getVertexStageShift() noexcept {
|
||||
// We assume the bottom half of bits are for vertex stages.
|
||||
|
||||
@@ -193,49 +193,6 @@ uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) {
|
||||
return mostSignificantBit((sampleCount - 1) & mask);
|
||||
}
|
||||
|
||||
VkSamplerYcbcrModelConversion getYcbcrModelConversion(
|
||||
SamplerYcbcrModelConversion model) {
|
||||
switch (model) {
|
||||
case SamplerYcbcrModelConversion::RGB_IDENTITY:
|
||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_RGB_IDENTITY;
|
||||
case SamplerYcbcrModelConversion::YCBCR_IDENTITY:
|
||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_IDENTITY;
|
||||
case SamplerYcbcrModelConversion::YCBCR_709:
|
||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_709;
|
||||
case SamplerYcbcrModelConversion::YCBCR_601:
|
||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_601;
|
||||
case SamplerYcbcrModelConversion::YCBCR_2020:
|
||||
return VK_SAMPLER_YCBCR_MODEL_CONVERSION_YCBCR_2020;
|
||||
default:
|
||||
assert_invariant(false &&
|
||||
"Unknown data type, conversion is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
VkSamplerYcbcrRange getYcbcrRange(SamplerYcbcrRange range) {
|
||||
switch (range) {
|
||||
case SamplerYcbcrRange::ITU_FULL:
|
||||
return VK_SAMPLER_YCBCR_RANGE_ITU_FULL;
|
||||
case SamplerYcbcrRange::ITU_NARROW:
|
||||
return VK_SAMPLER_YCBCR_RANGE_ITU_NARROW;
|
||||
default:
|
||||
assert_invariant(false &&
|
||||
"Unknown data type, conversion is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
VkChromaLocation getChromaLocation(ChromaLocation loc) {
|
||||
switch (loc) {
|
||||
case ChromaLocation::COSITED_EVEN:
|
||||
return VK_CHROMA_LOCATION_COSITED_EVEN;
|
||||
case ChromaLocation::MIDPOINT:
|
||||
return VK_CHROMA_LOCATION_MIDPOINT;
|
||||
default:
|
||||
assert_invariant(false &&
|
||||
"Unknown data type, conversion is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace filament::backend::fvkutils
|
||||
|
||||
bool operator<(const VkImageSubresourceRange& a, const VkImageSubresourceRange& b) {
|
||||
|
||||
@@ -252,12 +252,19 @@ void printDeviceDetails(wgpu::Device const& device) {
|
||||
|
||||
}// namespace
|
||||
|
||||
Driver* WebGPUDriver::create(WebGPUPlatform& platform) noexcept {
|
||||
return new WebGPUDriver(platform);
|
||||
Driver* WebGPUDriver::create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
constexpr size_t defaultSize = FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig validConfig {driverConfig};
|
||||
validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize);
|
||||
return new WebGPUDriver(platform, validConfig);
|
||||
}
|
||||
|
||||
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform) noexcept
|
||||
: mPlatform(platform) {
|
||||
WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept
|
||||
: mPlatform(platform),
|
||||
mHandleAllocator("Handles",
|
||||
driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck,
|
||||
driverConfig.disableHeapHandleTags) {
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
printInstanceDetails(mPlatform.getInstance());
|
||||
#endif
|
||||
@@ -385,7 +392,7 @@ Handle<HwTimerQuery> WebGPUDriver::createTimerQueryS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwIndexBuffer> WebGPUDriver::createIndexBufferS() noexcept {
|
||||
return Handle<HwIndexBuffer>((Handle<HwIndexBuffer>::HandleId) mNextFakeHandle++);
|
||||
return allocHandle<HwIndexBuffer>();
|
||||
}
|
||||
|
||||
Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "DriverBase.h"
|
||||
#include "private/backend/Dispatcher.h"
|
||||
#include "private/backend/Driver.h"
|
||||
#include "private/backend/HandleAllocator.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
@@ -30,6 +31,10 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#ifndef FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB
|
||||
# define FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB 8
|
||||
#endif
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
/**
|
||||
@@ -40,10 +45,10 @@ public:
|
||||
~WebGPUDriver() noexcept override;
|
||||
|
||||
[[nodiscard]] Dispatcher getDispatcher() const noexcept final;
|
||||
[[nodiscard]] static Driver* create(WebGPUPlatform& platform) noexcept;
|
||||
[[nodiscard]] static Driver* create(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
|
||||
|
||||
private:
|
||||
explicit WebGPUDriver(WebGPUPlatform& platform) noexcept;
|
||||
explicit WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfig& driverConfig) noexcept;
|
||||
[[nodiscard]] ShaderModel getShaderModel() const noexcept final;
|
||||
[[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final;
|
||||
|
||||
@@ -74,8 +79,20 @@ private:
|
||||
UTILS_ALWAYS_INLINE inline void methodName##R(RetType, paramsDecl);
|
||||
|
||||
#include "private/backend/DriverAPI.inc"
|
||||
|
||||
/*
|
||||
* Memory management
|
||||
*/
|
||||
|
||||
HandleAllocatorWGPU mHandleAllocator;
|
||||
|
||||
template<typename D>
|
||||
Handle<D> allocHandle() {
|
||||
return mHandleAllocator.allocate<D>();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
|
||||
|
||||
@@ -144,13 +144,13 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
||||
}
|
||||
|
||||
Driver* WebGPUPlatform::createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& /*driverConfig*/) noexcept {
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
if (sharedContext) {
|
||||
FWGPU_LOGW << "sharedContext is ignored/unused in the WebGPU backend. A non-null "
|
||||
"sharedContext was provided, but it will be ignored."
|
||||
<< utils::io::endl;
|
||||
}
|
||||
return WebGPUDriver::create(*this);
|
||||
return WebGPUDriver::create(*this, driverConfig);
|
||||
}
|
||||
|
||||
WebGPUPlatform::WebGPUPlatform()
|
||||
|
||||
@@ -88,12 +88,7 @@ void minLeftMaxRight(inout vec4 a, inout vec4 b, inout vec4 c, inout vec4 d, ino
|
||||
|
||||
// - we can't use a function because offset must be a constant expression
|
||||
// - we can't use line continuation because that's not supported on desktop
|
||||
#ifdef TARGET_WEBGPU_ENVIRONMENT
|
||||
//WebGPU can not handle texelFetchOffset without an error about "ConstOffset is only permitted...".
|
||||
#define makeSample(ij, o) vec4(texelFetch(materialParams_dof, ij+o, 0).rgb, texelFetch(materialParams_alpha, ij+o, 0).r)
|
||||
#else
|
||||
#define makeSample(ij, o) vec4(texelFetchOffset(materialParams_dof, ij, 0, o).rgb, texelFetchOffset(materialParams_alpha, ij, 0, o).r)
|
||||
#endif
|
||||
|
||||
vec4 median(ivec2 ij) {
|
||||
vec4 v[9];
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.58.2"
|
||||
spec.version = "1.59.1"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.58.2/filament-v1.58.2-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.59.1/filament-v1.59.1-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -518,113 +518,28 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob* spirv, std::string* outMsl,
|
||||
}
|
||||
}
|
||||
|
||||
void GLSLPostProcessor::rebindImageSamplerForWGSL(std::vector<uint32_t>& spirv) {
|
||||
constexpr size_t HEADER_SIZE = 5;
|
||||
size_t const dataSize = spirv.size();
|
||||
uint32_t* data = spirv.data();
|
||||
|
||||
struct Descriptor {
|
||||
uint32_t binding = 0xFFFFFFFF;
|
||||
bool fixed = false;
|
||||
};
|
||||
|
||||
std::unordered_map<uint32_t, Descriptor> descriptors;
|
||||
auto getDesc = [&](uint32_t var) -> Descriptor& {
|
||||
using itertype = std::unordered_map<uint32_t, Descriptor>::iterator;
|
||||
itertype itr;
|
||||
if (itr = descriptors.find(var); itr == descriptors.end()) {
|
||||
itr = descriptors.insert({var, {}}).first;
|
||||
}
|
||||
return itr->second;
|
||||
};
|
||||
|
||||
auto pass = [&](uint32_t targetOp, std::function<void(uint32_t)> f) {
|
||||
for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) {
|
||||
uint32_t const firstWord = data[cursor];
|
||||
uint32_t const wordCount = firstWord >> 16;
|
||||
uint32_t const op = firstWord & 0x0000FFFF;
|
||||
if (targetOp == op) {
|
||||
f(cursor+1);
|
||||
}
|
||||
cursor += wordCount;
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t samplerTypeId = 0;
|
||||
|
||||
// First we find the ID used to identify the sampler type and the image type.
|
||||
pass(spv::Op::OpTypeSampler, [&](uint32_t pos) {
|
||||
samplerTypeId = data[pos];
|
||||
});
|
||||
|
||||
// Next we identify all the descriptors and record their set id and binding.
|
||||
pass(spv::Op::OpDecorate, [&](uint32_t pos) {
|
||||
uint32_t const type = data[pos + 1];
|
||||
if (type == spv::Decoration::DecorationBinding) {
|
||||
uint32_t const targetVar = data[pos];
|
||||
uint32_t const binding = data[pos + 2];
|
||||
auto& desc = getDesc(targetVar);
|
||||
desc.binding = binding;
|
||||
// Note these decorations do not need to be written to the output.
|
||||
} else if (type == spv::Decoration::DecorationDescriptorSet) {
|
||||
uint32_t const targetVar = data[pos];
|
||||
uint32_t const set = data[pos + 2];
|
||||
auto& desc = getDesc(targetVar);
|
||||
}
|
||||
});
|
||||
|
||||
// Offset samplers by 2n+1, and everything else by 2n
|
||||
pass(spv::Op::OpLoad, [&](uint32_t pos) {
|
||||
uint32_t const retType = data[pos];
|
||||
uint32_t const targetVar = data[pos + 2];
|
||||
auto& desc = getDesc(targetVar);
|
||||
if (retType == samplerTypeId) {
|
||||
if (!desc.fixed) {
|
||||
//desc.binding = (desc.binding * 2) +1;
|
||||
desc.binding = (desc.binding ) +100;
|
||||
|
||||
desc.fixed = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!desc.fixed) {
|
||||
//desc.binding *=2;
|
||||
// desc.fixed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Write out the offsetted bindings
|
||||
pass(spv::Op::OpDecorate, [&](uint32_t pos) {
|
||||
uint32_t const type = data[pos + 1];
|
||||
if (type == spv::Decoration::DecorationBinding) {
|
||||
uint32_t const targetVar = data[pos];
|
||||
auto const &desc = getDesc(targetVar);
|
||||
data[pos + 2] = desc.binding;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
bool GLSLPostProcessor::spirvToWgsl(SpirvBlob *spirv, std::string *outWsl) {
|
||||
#if FILAMENT_SUPPORTS_WEBGPU
|
||||
//We need to run some opt-passes at all times to transpile to WGSL
|
||||
auto optimizer = createEmptyOptimizer();
|
||||
optimizer->RegisterPass(CreateSplitCombinedImageSamplerPass());
|
||||
optimizeSpirv(optimizer, *spirv);
|
||||
// We need to remove dead code for our variant-filter workaround for WebGPU to work
|
||||
// This is especially relevant for removing the push constants that morphing uses when not disabled
|
||||
spv::spirvbin_t remapper(0);
|
||||
remapper.remap(*spirv, spv::spirvbin_base_t::DCE_ALL);
|
||||
|
||||
//After splitting the image samplers, we need to remap the bindings to separate them.
|
||||
rebindImageSamplerForWGSL(*spirv);
|
||||
|
||||
//Allow non-uniform derivitives due to our nested shaders. See https://github.com/gpuweb/gpuweb/issues/3479
|
||||
const tint::spirv::reader::Options readerOpts{true};
|
||||
//Currently no options we want to use
|
||||
const tint::spirv::reader::Options readerOpts{};
|
||||
tint::wgsl::writer::Options writerOpts{};
|
||||
|
||||
tint::Program tintRead = tint::spirv::reader::Read(*spirv, readerOpts);
|
||||
|
||||
if (tintRead.Diagnostics().ContainsErrors()) {
|
||||
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
|
||||
return false;
|
||||
//TODO remove this if block once combined image sampler conversion works.
|
||||
if (tintRead.Diagnostics().Str().rfind("error: WGSL does not support combined image-samplers") != std::string::npos) {
|
||||
slog.w << "This tint reader error is currently ignored during WebGPU bringup: " << tintRead.Diagnostics().Str() << io::endl;
|
||||
}
|
||||
else {
|
||||
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
tint::Result<tint::wgsl::writer::Output> wgslOut = tint::wgsl::writer::Generate(tintRead,writerOpts);
|
||||
@@ -972,8 +887,10 @@ bool GLSLPostProcessor::fullOptimization(const TShader& tShader,
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createEmptyOptimizer() {
|
||||
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
||||
MaterialBuilder::Optimization optimization, Config const& config) {
|
||||
auto optimizer = std::make_shared<spvtools::Optimizer>(SPV_ENV_UNIVERSAL_1_3);
|
||||
|
||||
optimizer->SetMessageConsumer([](spv_message_level_t level,
|
||||
const char* source, const spv_position_t& position, const char* message) {
|
||||
if (!filterSpvOptimizerMessage(level)) {
|
||||
@@ -982,12 +899,6 @@ std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createEmptyOptimizer() {
|
||||
slog.e << stringifySpvOptimizerMessage(level, source, position, message)
|
||||
<< io::endl;
|
||||
});
|
||||
return optimizer;
|
||||
}
|
||||
|
||||
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
||||
MaterialBuilder::Optimization optimization, Config const& config) {
|
||||
auto optimizer = createEmptyOptimizer();
|
||||
|
||||
if (optimization == MaterialBuilder::Optimization::SIZE) {
|
||||
// When optimizing for size, we don't run the SPIR-V through any size optimization passes
|
||||
@@ -1012,7 +923,7 @@ std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
||||
return optimizer;
|
||||
}
|
||||
|
||||
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) {
|
||||
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const {
|
||||
if (!optimizer->Run(spirv.data(), spirv.size(), &spirv)) {
|
||||
slog.e << "SPIR-V optimizer pass failed" << io::endl;
|
||||
return;
|
||||
@@ -1067,8 +978,7 @@ void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config c
|
||||
RegisterPass(CreateWrapOpKillPass());
|
||||
RegisterPass(CreateDeadBranchElimPass());
|
||||
RegisterPass(CreateMergeReturnPass(), MaterialBuilder::TargetApi::METAL);
|
||||
//TODO Do not run inline pass without merge return pass until we sort out issues
|
||||
RegisterPass(CreateInlineExhaustivePass(), MaterialBuilder::TargetApi::ALL & ~MaterialBuilder::TargetApi::WEBGPU);
|
||||
RegisterPass(CreateInlineExhaustivePass());
|
||||
RegisterPass(CreateAggressiveDCEPass());
|
||||
RegisterPass(CreatePrivateToLocalPass());
|
||||
RegisterPass(CreateLocalSingleBlockLoadStoreElimPass());
|
||||
@@ -1112,8 +1022,7 @@ void GLSLPostProcessor::registerSizePasses(Optimizer& optimizer, Config const& c
|
||||
|
||||
RegisterPass(CreateWrapOpKillPass());
|
||||
RegisterPass(CreateDeadBranchElimPass());
|
||||
//TODO Do not run inline pass without merge return pass until we sort out issues
|
||||
RegisterPass(CreateInlineExhaustivePass(), MaterialBuilder::TargetApi::ALL & ~MaterialBuilder::TargetApi::WEBGPU);
|
||||
RegisterPass(CreateInlineExhaustivePass());
|
||||
RegisterPass(CreateEliminateDeadFunctionsPass());
|
||||
RegisterPass(CreatePrivateToLocalPass());
|
||||
RegisterPass(CreateScalarReplacementPass(0));
|
||||
|
||||
@@ -114,20 +114,14 @@ private:
|
||||
* Retrieve an optimizer instance tuned for the given optimization level and shader configuration.
|
||||
*/
|
||||
using OptimizerPtr = std::shared_ptr<spvtools::Optimizer>;
|
||||
|
||||
static OptimizerPtr createOptimizer(
|
||||
MaterialBuilder::Optimization optimization,
|
||||
Config const& config);
|
||||
|
||||
static OptimizerPtr createEmptyOptimizer();
|
||||
|
||||
static void registerSizePasses(spvtools::Optimizer& optimizer, Config const& config);
|
||||
|
||||
static void registerPerformancePasses(spvtools::Optimizer& optimizer, Config const& config);
|
||||
|
||||
static void optimizeSpirv(OptimizerPtr optimizer, SpirvBlob &spirv);
|
||||
|
||||
static void rebindImageSamplerForWGSL(std::vector<uint32_t>& spirv);
|
||||
void optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const;
|
||||
|
||||
void fixupClipDistance(SpirvBlob& spirv, GLSLPostProcessor::Config const& config) const;
|
||||
|
||||
|
||||
@@ -937,15 +937,6 @@ utils::io::sstream& CodeGenerator::generateSpecializationConstant(utils::io::sst
|
||||
std::string const constantString = std::visit(SpecializationConstantFormatter(), value);
|
||||
|
||||
static const char* types[] = { "int", "float", "bool" };
|
||||
|
||||
// Spec constants aren't fully supported in Tint,
|
||||
// workaround until https://issues.chromium.org/issues/42250586 is resolved
|
||||
if (mTargetApi == TargetApi::WEBGPU) {
|
||||
out << "layout (constant_id = " << id << ") const "
|
||||
<< types[value.index()] << " " << name << "_hack = " << constantString << ";\n"
|
||||
<< types[value.index()] << " " << name << " = " << name << "_hack;\n";
|
||||
return out;
|
||||
}
|
||||
if (mTargetLanguage == MaterialBuilderBase::TargetLanguage::SPIRV) {
|
||||
out << "layout (constant_id = " << id << ") const "
|
||||
<< types[value.index()] << " " << name << " = " << constantString << ";\n";
|
||||
|
||||
Vendored
-2
@@ -172,13 +172,11 @@ SPVTOOLS_OPT_SRC_FILES := \
|
||||
source/opt/remove_unused_interface_variables_pass.cpp \
|
||||
source/opt/replace_desc_array_access_using_var_index.cpp \
|
||||
source/opt/replace_invalid_opc.cpp \
|
||||
source/opt/resolve_binding_conflicts_pass.cpp \
|
||||
source/opt/scalar_analysis.cpp \
|
||||
source/opt/scalar_analysis_simplification.cpp \
|
||||
source/opt/scalar_replacement_pass.cpp \
|
||||
source/opt/set_spec_constant_default_value_pass.cpp \
|
||||
source/opt/simplification_pass.cpp \
|
||||
source/opt/split_combined_image_sampler_pass.cpp \
|
||||
source/opt/spread_volatile_semantics.cpp \
|
||||
source/opt/ssa_rewrite_pass.cpp \
|
||||
source/opt/strength_reduction_pass.cpp \
|
||||
|
||||
Vendored
-1
@@ -201,7 +201,6 @@ cc_library(
|
||||
deps = [
|
||||
":spirv_tools_internal",
|
||||
"@spirv_headers//:spirv_common_headers",
|
||||
"@spirv_headers//:spirv_c_headers",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
Vendored
-5
@@ -771,8 +771,6 @@ static_library("spvtools_opt") {
|
||||
"source/opt/replace_desc_array_access_using_var_index.h",
|
||||
"source/opt/replace_invalid_opc.cpp",
|
||||
"source/opt/replace_invalid_opc.h",
|
||||
"source/opt/resolve_binding_conflicts_pass.cpp",
|
||||
"source/opt/resolve_binding_conflicts_pass.h",
|
||||
"source/opt/scalar_analysis.cpp",
|
||||
"source/opt/scalar_analysis.h",
|
||||
"source/opt/scalar_analysis_nodes.h",
|
||||
@@ -783,8 +781,6 @@ static_library("spvtools_opt") {
|
||||
"source/opt/set_spec_constant_default_value_pass.h",
|
||||
"source/opt/simplification_pass.cpp",
|
||||
"source/opt/simplification_pass.h",
|
||||
"source/opt/split_combined_image_sampler_pass.cpp",
|
||||
"source/opt/split_combined_image_sampler_pass.h",
|
||||
"source/opt/spread_volatile_semantics.cpp",
|
||||
"source/opt/spread_volatile_semantics.h",
|
||||
"source/opt/ssa_rewrite_pass.cpp",
|
||||
@@ -1386,7 +1382,6 @@ if (build_with_chromium && spvtools_build_executables) {
|
||||
"test/libspirv_macros_test.cpp",
|
||||
"test/name_mapper_test.cpp",
|
||||
"test/named_id_test.cpp",
|
||||
"test/op_unknown_test.cpp",
|
||||
"test/opcode_make_test.cpp",
|
||||
"test/opcode_require_capabilities_test.cpp",
|
||||
"test/opcode_split_test.cpp",
|
||||
|
||||
Vendored
-35
@@ -1,40 +1,5 @@
|
||||
Revision history for SPIRV-Tools
|
||||
|
||||
v2025.1 2025-02-28
|
||||
- General
|
||||
- diff: Fix crash in OpString matching (#5988)
|
||||
- Add SPV_AMDX_shader_enqueue version 2 support (#5838)
|
||||
- add support for SPV_INTEL_subgroup_matrix_multiply_accumulate (#5928)
|
||||
- update cmake_minimum_required to 3.22.1 (#5925)
|
||||
- Add OpImageSampleFootprintNV to IsAllowedSampledImageOperand (#5914)
|
||||
- assembler: ensure progress when seeking the version string (#5910)
|
||||
- Optimizer
|
||||
- opt: keep all OpSource instructions (#5901)
|
||||
- [opt] Fix bug opt::InstructionBuilder::AddVariable (#6007)
|
||||
- [OPT] Add SPV_KHR_ray_tracing to allow list (#5941)
|
||||
- opt: keep all OpSource instructions (#5901)
|
||||
- Validator
|
||||
- spirv-val: Add AllowVulkan32BitBitwise option (#6001)
|
||||
- Fix untyped pointer comparison validation (#6004)
|
||||
- spirv-val: Update VUIDs for 308 header (#5990)
|
||||
- spirv-val: fix env parsing for vk1.1spv1.4 (#5985)
|
||||
- Add validation for SPV_NV_linear_swept_spheres. (#5975)
|
||||
- Add validation SPV_NV_cluster_acceleration_structure. (#5974)
|
||||
- Improve the instruction diagnostic for some access chain errors (#5978)
|
||||
- Update physical storage buffer validation to match SPIR-V 1.6.5 (#5977)
|
||||
- Validate SPV_NV_cooperative_vector (#5972)
|
||||
- Fix layout checks with untyped pointers (#5970)
|
||||
- spirv-val: Update mislabeled VUIDs (#5964)
|
||||
- More explicit layout validation (#5958)
|
||||
- spirv-val: Add VK_KHR_maintenance8 support (#5951)
|
||||
- Add EXT_mesh_shader validation support (#5640)
|
||||
- spirv-val: Remove OpenCL ivec3 req (#5940)
|
||||
- spirv-val: Validate zero product workgroup size (#5407)
|
||||
- Relax DebugLine validation (#5916)
|
||||
- Linker
|
||||
- linker: remove LinkOnceODR decorations when linking executables (#5979)
|
||||
- fix: handle LinkOnceODR correctly (#5938)
|
||||
|
||||
v2024.4 2024-12-04
|
||||
- General
|
||||
- Add FPEncoding operand type. (#5726)
|
||||
|
||||
Vendored
+5
-5
@@ -3,18 +3,18 @@ use_relative_paths = True
|
||||
vars = {
|
||||
'github': 'https://github.com',
|
||||
|
||||
'abseil_revision': '9f5fc9d1e934571a1f713b5a00cd5f15db8c71ed',
|
||||
'abseil_revision': '69b58f9227bf91a9315475579df15fbb7c5c612e',
|
||||
|
||||
'effcee_revision': '874b47102c57a8979c0f154cf8e0eab53c0a0502',
|
||||
'effcee_revision': '12241cbc30f20730b656db7fd5a3fa36cd420843',
|
||||
|
||||
'googletest_revision': '3af834740f58ef56058e6f8a1be51f62bc3a6515',
|
||||
'googletest_revision': 'e235eb34c6c4fed790ccdad4b16394301360dcd4',
|
||||
|
||||
# Use protobufs before they gained the dependency on abseil
|
||||
'protobuf_revision': 'v21.12',
|
||||
|
||||
're2_revision': 'c84a140c93352cdabbfb547c531be34515b12228',
|
||||
're2_revision': '6dcd83d60f7944926bfd308cc13979fc53dd69ca',
|
||||
|
||||
'spirv_headers_revision': '0e710677989b4326ac974fd80c5308191ed80965',
|
||||
'spirv_headers_revision': 'e7294a8ebed84f8c5bd3686c68dbe12a4e65b644',
|
||||
}
|
||||
|
||||
deps = {
|
||||
|
||||
-44
@@ -215,50 +215,6 @@ Note that this has some interesting consequences, including:
|
||||
that named ID being output. This may be valid SPIR-V, contrary to the
|
||||
presumed intention of the writer.
|
||||
|
||||
## OpUnknown
|
||||
<a name="op-unknown"></a>
|
||||
|
||||
HLSL has a feature that allows users to specify an exact SPIR-V type using [the
|
||||
`SpirvType` template](https://github.com/microsoft/hlsl-specs/blob/main/proposals/0011-inline-spirv.md#types).
|
||||
This feature allows the user to specify a type opcode that the compiler does
|
||||
not know. In this case, it will be unable to generate assembly using the
|
||||
correct mnemonic.
|
||||
|
||||
In order to represent unknown opcodes in assembly format, the `OpUnknown`
|
||||
pseudo-instruction may be used. The syntax is:
|
||||
|
||||
```
|
||||
OpUnknown(<enumerant>, <WordCount>) <operand 1> ...
|
||||
```
|
||||
|
||||
`enumerant` is the opcode enumerant, and `WordCount` is the number of words in
|
||||
the instruction. These will be assembled into a single word representing the
|
||||
opcode. Operands will be parsed according to the alternate parsing mode
|
||||
described in [Arbitrary Integers](#op-unknown). Named enumerated values cannot
|
||||
be handled by this mode and must be represented using the arbitrary integer
|
||||
syntax.
|
||||
|
||||
It must be used at the beginning of a new instruction, and if there is a result
|
||||
ID it must explicitly be passed in as an operand. This is because the physical
|
||||
layout of a SPIR-V instruction may include a result type operand before the
|
||||
result ID operand, but it depends on the opcode and cannot be inferred for an
|
||||
unknown operand.
|
||||
|
||||
For example, a 32-bit signed integer type could be represented like this:
|
||||
|
||||
```
|
||||
OpUnknown(21, 4) %int_t 32 1
|
||||
```
|
||||
|
||||
An OpStore instruction could be represented as:
|
||||
|
||||
```
|
||||
OpUnknown(62, 3) %9 %12
|
||||
```
|
||||
|
||||
The enumerant and word count must be decimal integers.
|
||||
|
||||
|
||||
## Notes
|
||||
|
||||
* Some enumerants cannot be used by name, because the target instruction
|
||||
|
||||
@@ -740,10 +740,6 @@ SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowLocalSizeId(
|
||||
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowOffsetTextureOperand(
|
||||
spv_validator_options options, bool val);
|
||||
|
||||
// Allow base operands of some bit operations to be non-32-bit wide.
|
||||
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowVulkan32BitBitwise(
|
||||
spv_validator_options options, bool val);
|
||||
|
||||
// Whether friendly names should be used in validation error messages.
|
||||
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetFriendlyNames(
|
||||
spv_validator_options options, bool val);
|
||||
|
||||
@@ -132,11 +132,6 @@ class SPIRV_TOOLS_EXPORT ValidatorOptions {
|
||||
spvValidatorOptionsSetAllowOffsetTextureOperand(options_, val);
|
||||
}
|
||||
|
||||
// Allow base operands of some bit operations to be non-32-bit wide.
|
||||
void SetAllowVulkan32BitBitwise(bool val) {
|
||||
spvValidatorOptionsSetAllowVulkan32BitBitwise(options_, val);
|
||||
}
|
||||
|
||||
// Records whether or not the validator should relax the rules on pointer
|
||||
// usage in logical addressing mode.
|
||||
//
|
||||
|
||||
+2
-56
@@ -240,7 +240,7 @@ class SPIRV_TOOLS_EXPORT Optimizer {
|
||||
|
||||
private:
|
||||
struct SPIRV_TOOLS_LOCAL Impl; // Opaque struct for holding internal data.
|
||||
std::unique_ptr<Impl> impl_; // Unique pointer to internal data.
|
||||
std::unique_ptr<Impl> impl_; // Unique pointer to internal data.
|
||||
};
|
||||
|
||||
// Creates a null pass.
|
||||
@@ -655,7 +655,7 @@ Optimizer::PassToken CreateRedundancyEliminationPass();
|
||||
// element if those elements are accessed individually. The parameter is a
|
||||
// limit on the number of members in the composite variable that the pass will
|
||||
// consider replacing.
|
||||
Optimizer::PassToken CreateScalarReplacementPass(uint32_t size_limit = 0);
|
||||
Optimizer::PassToken CreateScalarReplacementPass(uint32_t size_limit = 100);
|
||||
|
||||
// Create a private to local pass.
|
||||
// This pass looks for variables declared in the private storage class that are
|
||||
@@ -968,60 +968,6 @@ Optimizer::PassToken CreateInvocationInterlockPlacementPass();
|
||||
// Creates a pass to add/remove maximal reconvergence execution mode.
|
||||
// This pass either adds or removes maximal reconvergence from all entry points.
|
||||
Optimizer::PassToken CreateModifyMaximalReconvergencePass(bool add);
|
||||
|
||||
// Creates a pass to split combined image+sampler variables and function
|
||||
// parameters into separate image and sampler parts. Binding numbers and
|
||||
// other decorations are copied.
|
||||
Optimizer::PassToken CreateSplitCombinedImageSamplerPass();
|
||||
|
||||
// Creates a pass to remap bindings to avoid conflicts, assuming the module
|
||||
// is valid for Vulkan. A conflict exits when an entry point uses two distinct
|
||||
// variables with the same descriptor set and binding. Vulkan allows one kind
|
||||
// of conflict: when one varible is an image (or array of images), and the
|
||||
// other is a sampler (or an array of samplers).
|
||||
|
||||
// Conflicts are eliminated by incrementing the binding number of the sampler
|
||||
// part, and then propagating that increment through variables with
|
||||
// higher-numbered bindings until no conflict remains. This handles the case
|
||||
// when multiple shaders may share the same resource variables; this can
|
||||
// introduce holes in binding slots.
|
||||
//
|
||||
// Here's an example where shaders Alpha, Beta, Gamma, Delta collectively use
|
||||
// resource variables %100, %101, %102, %103, %104 all with the same
|
||||
// DescriptorSet and with Bindings as in the following table:
|
||||
//
|
||||
// Before:
|
||||
//
|
||||
// Binding: 0 1 2 3
|
||||
// Alpha: %100,%101
|
||||
// Beta: %100 %102
|
||||
// Gamma: %102 %103
|
||||
// Delta: %103 %104
|
||||
//
|
||||
// The Alpha shader has a conflict where variables %100, %101 have the same
|
||||
// descriptor set and binding. If %100 is a sampler resource variable, then
|
||||
// the conflict is resolved by incrementing the binding number on %100 from 0
|
||||
// to 1. But this causes a new confict for shader Beta because it now uses
|
||||
// both %100 and %102 with binding number 1. That conflict is resolved by
|
||||
// incrementing the binding number on its variable that originally appeared
|
||||
// second (i.e. %102), so %102 gets binding 2. This now produces a conflict
|
||||
// for Gamma between %102 and %103 using binding number 2. Since %103 originally
|
||||
// appeared second (in the view from Gamma), the algorithm bumps %103 to binding
|
||||
// number %103. Now Delta has a conflict between %103 and %104, resulting in
|
||||
// %104 getting the next binding number, 4. The picture afterward is:
|
||||
//
|
||||
// After:
|
||||
//
|
||||
// Binding: 0 1 2 3 4
|
||||
// Alpha: %101 %100
|
||||
// Beta: %100 %102
|
||||
// Gamma: %102 %103
|
||||
// Delta: %103 %104
|
||||
//
|
||||
//
|
||||
// This pass assumes binding numbers are not applid via decoration groups
|
||||
// (OpDecorationGroup).
|
||||
Optimizer::PassToken CreateResolveBindingConflictsPass();
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) 2025 Google LLC
|
||||
#
|
||||
# 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.
|
||||
|
||||
# Fail on any error.
|
||||
set -e
|
||||
|
||||
# This is required to run any git command in the docker since owner will
|
||||
# have changed between the clone environment, and the docker container.
|
||||
# Marking the root of the repo as safe for ownership changes.
|
||||
git config --global --add safe.directory "$PWD"
|
||||
|
||||
echo $(date): Check formatting...
|
||||
./utils/check_code_format.sh ${1:-main}
|
||||
echo $(date): check completed.
|
||||
+30
-9
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) 2025 Google LLC
|
||||
# Copyright (c) 2018 Google LLC.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -12,16 +12,37 @@
|
||||
# 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.
|
||||
#
|
||||
# Android Build Script.
|
||||
|
||||
# Fail on any error.
|
||||
set -e
|
||||
# Display commands being run.
|
||||
set -x
|
||||
|
||||
SCRIPT_DIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )"
|
||||
SRC_ROOT="$( cd "${SCRIPT_DIR}/../.." >/dev/null 2>&1 && pwd )"
|
||||
TARGET_BRANCH="${KOKORO_GITHUB_PULL_REQUEST_TARGET_BRANCH-main}"
|
||||
BUILD_ROOT=$PWD
|
||||
SRC=$PWD/github/SPIRV-Tools
|
||||
|
||||
docker run --rm -i \
|
||||
--volume "${SRC_ROOT}:${SRC_ROOT}" \
|
||||
--workdir "${SRC_ROOT}" \
|
||||
"us-east4-docker.pkg.dev/shaderc-build/radial-docker/ubuntu-24.04-amd64/formatter" \
|
||||
"${SCRIPT_DIR}/build-docker.sh" "${TARGET_BRANCH}"
|
||||
# This is required to run any git command in the docker since owner will
|
||||
# have changed between the clone environment, and the docker container.
|
||||
# Marking the root of the repo as safe for ownership changes.
|
||||
git config --global --add safe.directory $SRC
|
||||
|
||||
# Get clang-format-5.0.0.
|
||||
# Once kokoro upgrades the Ubuntu VMs, we can use 'apt-get install clang-format'
|
||||
curl -L http://releases.llvm.org/5.0.0/clang+llvm-5.0.0-linux-x86_64-ubuntu14.04.tar.xz -o clang-llvm.tar.xz
|
||||
tar xf clang-llvm.tar.xz
|
||||
export PATH=$PWD/clang+llvm-5.0.0-linux-x86_64-ubuntu14.04/bin:$PATH
|
||||
|
||||
cd $SRC
|
||||
git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers
|
||||
git clone https://github.com/google/googletest external/googletest
|
||||
cd external && cd googletest && git reset --hard 1fb1bb23bb8418dc73a5a9a82bbed31dc610fec7 && cd .. && cd ..
|
||||
git clone --depth=1 https://github.com/google/effcee external/effcee
|
||||
git clone --depth=1 https://github.com/google/re2 external/re2
|
||||
# The --fail flag causes the command to fail on HTTP error response codes, like 404.
|
||||
curl -L --fail https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/clang-format-diff.py -o utils/clang-format-diff.py
|
||||
|
||||
echo $(date): Check formatting...
|
||||
./utils/check_code_format.sh;
|
||||
echo $(date): check completed.
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ docker run --rm -i \
|
||||
--env KOKORO_ARTIFACTS_DIR="${KOKORO_ARTIFACTS_DIR}" \
|
||||
--env BUILD_SHA="${BUILD_SHA}" \
|
||||
--entrypoint "${SCRIPT_DIR}/build-docker.sh" \
|
||||
us-east4-docker.pkg.dev/shaderc-build/radial-docker/ubuntu-24.04-amd64/cpp-builder
|
||||
"gcr.io/shaderc-build/radial-build:latest"
|
||||
RESULT=$?
|
||||
|
||||
# This is important. If the permissions are not fixed, kokoro will fail
|
||||
|
||||
+5
-57
@@ -67,9 +67,7 @@ void CompactIds(std::vector<uint32_t>& ids) {
|
||||
ids.resize(write_index);
|
||||
}
|
||||
|
||||
// A mapping from ids in one module to ids in the other.
|
||||
//
|
||||
// Differ contains two of these, for src->dst and dst->src.
|
||||
// A mapping between src and dst ids.
|
||||
class IdMap {
|
||||
public:
|
||||
IdMap(size_t id_bound) { id_map_.resize(id_bound, 0); }
|
||||
@@ -192,7 +190,6 @@ class SrcDstIdMap {
|
||||
IdMap dst_to_src_;
|
||||
};
|
||||
|
||||
// Mappings from ids to instructions and metadata, for a single module's ids.
|
||||
struct IdInstructions {
|
||||
IdInstructions(const opt::Module* module)
|
||||
: inst_map_(module->IdBound(), nullptr),
|
||||
@@ -201,10 +198,6 @@ struct IdInstructions {
|
||||
forward_pointer_map_(module->IdBound()) {
|
||||
// Map ids from all sections to instructions that define them.
|
||||
MapIdsToInstruction(module->ext_inst_imports());
|
||||
MapIdsToInstruction(module->debugs1());
|
||||
MapIdsToInstruction(module->debugs2());
|
||||
MapIdsToInstruction(module->debugs3());
|
||||
MapIdsToInstruction(module->ext_inst_debuginfo());
|
||||
MapIdsToInstruction(module->types_values());
|
||||
for (const opt::Function& function : *module) {
|
||||
function.ForEachInst(
|
||||
@@ -328,8 +321,6 @@ class Differ {
|
||||
// Get various properties from an id. These Helper functions are passed to
|
||||
// `GroupIds` and `GroupIdsAndMatch` below (as the `get_group` argument).
|
||||
uint32_t GroupIdsHelperGetTypeId(const IdInstructions& id_to, uint32_t id);
|
||||
uint32_t GroupIdsHelperGetFunctionTypeId(const IdInstructions& id_to,
|
||||
uint32_t id);
|
||||
spv::StorageClass GroupIdsHelperGetTypePointerStorageClass(
|
||||
const IdInstructions& id_to, uint32_t id);
|
||||
spv::Op GroupIdsHelperGetTypePointerTypeOp(const IdInstructions& id_to,
|
||||
@@ -892,17 +883,6 @@ uint32_t Differ::GroupIdsHelperGetTypeId(const IdInstructions& id_to,
|
||||
return GetInst(id_to, id)->type_id();
|
||||
}
|
||||
|
||||
// Return an `OpFunction` instruction's full `OpTypeFunction` type,
|
||||
// which includes parameter types.
|
||||
//
|
||||
// `GroupIdsHelperGetTypeId` applied to an `OpFunction` only gets the
|
||||
// function's return type, so this is a slightly more precise way to
|
||||
// match up functions by signature.
|
||||
uint32_t Differ::GroupIdsHelperGetFunctionTypeId(const IdInstructions& id_to,
|
||||
uint32_t id) {
|
||||
return GetInst(id_to, id)->GetSingleWordOperand(3);
|
||||
}
|
||||
|
||||
spv::StorageClass Differ::GroupIdsHelperGetTypePointerStorageClass(
|
||||
const IdInstructions& id_to, uint32_t id) {
|
||||
const opt::Instruction* inst = GetInst(id_to, id);
|
||||
@@ -922,24 +902,6 @@ spv::Op Differ::GroupIdsHelperGetTypePointerTypeOp(const IdInstructions& id_to,
|
||||
return type_inst->opcode();
|
||||
}
|
||||
|
||||
// Group unmatched ids in `ids` according to some characteristic,
|
||||
// determined by `get_group`.
|
||||
//
|
||||
// Using `get_group` to compute some sort of key for each id, set
|
||||
// `groups` to map each key to all the ids that have that key.
|
||||
//
|
||||
// For example, to group ids by name, pass `Differ::GetName` as
|
||||
// `get_group`. This will fill `groups` with a map from each name to
|
||||
// all the ids with that name.
|
||||
//
|
||||
// Under the assumption that we're trying to establish new pairings,
|
||||
// ids that are already paired are omitted from `groups`.
|
||||
//
|
||||
// The `is_src` parameter indicates whether `ids` are drawn from the
|
||||
// source module or the destination module.
|
||||
//
|
||||
// The template parameter `T` is the key type, like `std::string` or
|
||||
// `uint32_t`.
|
||||
template <typename T>
|
||||
void Differ::GroupIds(const IdGroup& ids, bool is_src,
|
||||
std::map<T, IdGroup>* groups,
|
||||
@@ -962,10 +924,6 @@ void Differ::GroupIds(const IdGroup& ids, bool is_src,
|
||||
}
|
||||
}
|
||||
|
||||
// Group `src_ids` and `dst_ids` according to `get_group`, and then use
|
||||
// `match_group` to pair up ids in corresponding groups.
|
||||
//
|
||||
// Don't try to pair ids in groups whose key is `invalid_group_key`.
|
||||
template <typename T>
|
||||
void Differ::GroupIdsAndMatch(
|
||||
const IdGroup& src_ids, const IdGroup& dst_ids, T invalid_group_key,
|
||||
@@ -2525,7 +2483,7 @@ void Differ::MatchFunctions() {
|
||||
|
||||
// If there are multiple functions with the same name, group them by
|
||||
// type, and match only if the types match (and are unique).
|
||||
GroupIdsAndMatchByMappedId(src_group, dst_group,
|
||||
GroupIdsAndMatch<uint32_t>(src_group, dst_group, 0,
|
||||
&Differ::GroupIdsHelperGetTypeId,
|
||||
[this](const IdGroup& src_group_by_type_id,
|
||||
const IdGroup& dst_group_by_type_id) {
|
||||
@@ -2568,19 +2526,9 @@ void Differ::MatchFunctions() {
|
||||
dst_match_result, 0);
|
||||
}
|
||||
|
||||
// Best effort match functions with matching return and argument types.
|
||||
GroupIdsAndMatchByMappedId(
|
||||
src_func_ids, dst_func_ids, &Differ::GroupIdsHelperGetFunctionTypeId,
|
||||
[this](const IdGroup& src_group_by_func_type_id,
|
||||
const IdGroup& dst_group_by_func_type_id) {
|
||||
BestEffortMatchFunctions(src_group_by_func_type_id,
|
||||
dst_group_by_func_type_id, src_func_insts_,
|
||||
dst_func_insts_);
|
||||
});
|
||||
|
||||
// Best effort match functions with matching return types.
|
||||
GroupIdsAndMatchByMappedId(
|
||||
src_func_ids, dst_func_ids, &Differ::GroupIdsHelperGetTypeId,
|
||||
// Best effort match functions with matching type.
|
||||
GroupIdsAndMatch<uint32_t>(
|
||||
src_func_ids, dst_func_ids, 0, &Differ::GroupIdsHelperGetTypeId,
|
||||
[this](const IdGroup& src_group_by_type_id,
|
||||
const IdGroup& dst_group_by_type_id) {
|
||||
BestEffortMatchFunctions(src_group_by_type_id, dst_group_by_type_id,
|
||||
|
||||
+2
-4
@@ -676,10 +676,8 @@ spv_result_t RemoveLinkageSpecificInstructions(
|
||||
if (inst->opcode() == spv::Op::OpDecorate &&
|
||||
spv::Decoration(inst->GetSingleWordOperand(1u)) ==
|
||||
spv::Decoration::LinkageAttributes &&
|
||||
(spv::LinkageType(inst->GetSingleWordOperand(3u)) ==
|
||||
spv::LinkageType::Export ||
|
||||
spv::LinkageType(inst->GetSingleWordOperand(3u)) ==
|
||||
spv::LinkageType::LinkOnceODR)) {
|
||||
spv::LinkageType(inst->GetSingleWordOperand(3u)) ==
|
||||
spv::LinkageType::Export) {
|
||||
linked_context->KillInst(&*inst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,13 +109,11 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
remove_unused_interface_variables_pass.h
|
||||
replace_desc_array_access_using_var_index.h
|
||||
replace_invalid_opc.h
|
||||
resolve_binding_conflicts_pass.h
|
||||
scalar_analysis.h
|
||||
scalar_analysis_nodes.h
|
||||
scalar_replacement_pass.h
|
||||
set_spec_constant_default_value_pass.h
|
||||
simplification_pass.h
|
||||
split_combined_image_sampler_pass.h
|
||||
spread_volatile_semantics.h
|
||||
ssa_rewrite_pass.h
|
||||
strength_reduction_pass.h
|
||||
@@ -227,13 +225,11 @@ set(SPIRV_TOOLS_OPT_SOURCES
|
||||
remove_unused_interface_variables_pass.cpp
|
||||
replace_desc_array_access_using_var_index.cpp
|
||||
replace_invalid_opc.cpp
|
||||
resolve_binding_conflicts_pass.cpp
|
||||
scalar_analysis.cpp
|
||||
scalar_analysis_simplification.cpp
|
||||
scalar_replacement_pass.cpp
|
||||
set_spec_constant_default_value_pass.cpp
|
||||
simplification_pass.cpp
|
||||
split_combined_image_sampler_pass.cpp
|
||||
spread_volatile_semantics.cpp
|
||||
ssa_rewrite_pass.cpp
|
||||
strength_reduction_pass.cpp
|
||||
|
||||
@@ -276,7 +276,6 @@ CopyPropagateArrays::GetSourceObjectIfAny(uint32_t result) {
|
||||
case spv::Op::OpCompositeConstruct:
|
||||
return BuildMemoryObjectFromCompositeConstruct(result_inst);
|
||||
case spv::Op::OpCopyObject:
|
||||
case spv::Op::OpCopyLogical:
|
||||
return GetSourceObjectIfAny(result_inst->GetSingleWordInOperand(0));
|
||||
case spv::Op::OpCompositeInsert:
|
||||
return BuildMemoryObjectFromInsert(result_inst);
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ uint32_t InstructionFolder::UnaryOperate(spv::Op opcode,
|
||||
if (s_operand == std::numeric_limits<int32_t>::min()) {
|
||||
return s_operand;
|
||||
}
|
||||
return static_cast<uint32_t>(-s_operand);
|
||||
return -s_operand;
|
||||
}
|
||||
case spv::Op::OpNot:
|
||||
return ~operand;
|
||||
|
||||
+19
-167
@@ -2454,31 +2454,6 @@ FoldingRule RedundantFDiv() {
|
||||
FloatConstantKind kind0 = getFloatConstantKind(constants[0]);
|
||||
FloatConstantKind kind1 = getFloatConstantKind(constants[1]);
|
||||
|
||||
if (kind0 == FloatConstantKind::Zero || kind1 == FloatConstantKind::One) {
|
||||
inst->SetOpcode(spv::Op::OpCopyObject);
|
||||
inst->SetInOperands(
|
||||
{{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
FoldingRule RedundantFMod() {
|
||||
return [](IRContext* context, Instruction* inst,
|
||||
const std::vector<const analysis::Constant*>& constants) {
|
||||
assert(inst->opcode() == spv::Op::OpFMod &&
|
||||
"Wrong opcode. Should be OpFMod.");
|
||||
assert(constants.size() == 2);
|
||||
|
||||
if (!inst->IsFloatingPointFoldingAllowed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FloatConstantKind kind0 = getFloatConstantKind(constants[0]);
|
||||
FloatConstantKind kind1 = getFloatConstantKind(constants[1]);
|
||||
|
||||
if (kind0 == FloatConstantKind::Zero) {
|
||||
inst->SetOpcode(spv::Op::OpCopyObject);
|
||||
inst->SetInOperands(
|
||||
@@ -2487,15 +2462,9 @@ FoldingRule RedundantFMod() {
|
||||
}
|
||||
|
||||
if (kind1 == FloatConstantKind::One) {
|
||||
auto type = context->get_type_mgr()->GetType(inst->type_id());
|
||||
std::vector<uint32_t> zero_words;
|
||||
zero_words.resize(ElementWidth(type) / 32);
|
||||
auto const_mgr = context->get_constant_mgr();
|
||||
auto zero = const_mgr->GetConstant(type, std::move(zero_words));
|
||||
auto zero_id = const_mgr->GetDefiningInstruction(zero)->result_id();
|
||||
|
||||
inst->SetOpcode(spv::Op::OpCopyObject);
|
||||
inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {zero_id}}});
|
||||
inst->SetInOperands(
|
||||
{{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2538,18 +2507,24 @@ FoldingRule RedundantFMix() {
|
||||
};
|
||||
}
|
||||
|
||||
// Returns a folding rule that folds the instruction to operand |foldToArg|
|
||||
// (0 or 1) if operand |arg| (0 or 1) is a zero constant.
|
||||
FoldingRule RedundantBinaryOpWithZeroOperand(uint32_t arg, uint32_t foldToArg) {
|
||||
return [arg, foldToArg](
|
||||
IRContext* context, Instruction* inst,
|
||||
const std::vector<const analysis::Constant*>& constants) {
|
||||
assert(constants.size() == 2);
|
||||
// This rule handles addition of zero for integers.
|
||||
FoldingRule RedundantIAdd() {
|
||||
return [](IRContext* context, Instruction* inst,
|
||||
const std::vector<const analysis::Constant*>& constants) {
|
||||
assert(inst->opcode() == spv::Op::OpIAdd &&
|
||||
"Wrong opcode. Should be OpIAdd.");
|
||||
|
||||
if (constants[arg] && constants[arg]->IsZero()) {
|
||||
auto operand = inst->GetSingleWordInOperand(foldToArg);
|
||||
auto operand_type = constants[arg]->type();
|
||||
uint32_t operand = std::numeric_limits<uint32_t>::max();
|
||||
const analysis::Type* operand_type = nullptr;
|
||||
if (constants[0] && constants[0]->IsZero()) {
|
||||
operand = inst->GetSingleWordInOperand(1);
|
||||
operand_type = constants[0]->type();
|
||||
} else if (constants[1] && constants[1]->IsZero()) {
|
||||
operand = inst->GetSingleWordInOperand(0);
|
||||
operand_type = constants[1]->type();
|
||||
}
|
||||
|
||||
if (operand != std::numeric_limits<uint32_t>::max()) {
|
||||
const analysis::Type* inst_type =
|
||||
context->get_type_mgr()->GetType(inst->type_id());
|
||||
if (inst_type->IsSame(operand_type)) {
|
||||
@@ -2564,117 +2539,6 @@ FoldingRule RedundantBinaryOpWithZeroOperand(uint32_t arg, uint32_t foldToArg) {
|
||||
};
|
||||
}
|
||||
|
||||
// This rule handles any of RedundantBinaryRhs0Ops with a 0 or vector 0 on the
|
||||
// right-hand side (a | 0 => a).
|
||||
static const constexpr spv::Op RedundantBinaryRhs0Ops[] = {
|
||||
spv::Op::OpBitwiseOr,
|
||||
spv::Op::OpBitwiseXor,
|
||||
spv::Op::OpShiftRightLogical,
|
||||
spv::Op::OpShiftRightArithmetic,
|
||||
spv::Op::OpShiftLeftLogical,
|
||||
spv::Op::OpIAdd,
|
||||
spv::Op::OpISub};
|
||||
FoldingRule RedundantBinaryRhs0(spv::Op op) {
|
||||
assert(std::find(std::begin(RedundantBinaryRhs0Ops),
|
||||
std::end(RedundantBinaryRhs0Ops),
|
||||
op) != std::end(RedundantBinaryRhs0Ops) &&
|
||||
"Wrong opcode.");
|
||||
(void)op;
|
||||
return RedundantBinaryOpWithZeroOperand(1, 0);
|
||||
}
|
||||
|
||||
// This rule handles any of RedundantBinaryLhs0Ops with a 0 or vector 0 on the
|
||||
// left-hand side (0 | a => a).
|
||||
static const constexpr spv::Op RedundantBinaryLhs0Ops[] = {
|
||||
spv::Op::OpBitwiseOr, spv::Op::OpBitwiseXor, spv::Op::OpIAdd};
|
||||
FoldingRule RedundantBinaryLhs0(spv::Op op) {
|
||||
assert(std::find(std::begin(RedundantBinaryLhs0Ops),
|
||||
std::end(RedundantBinaryLhs0Ops),
|
||||
op) != std::end(RedundantBinaryLhs0Ops) &&
|
||||
"Wrong opcode.");
|
||||
(void)op;
|
||||
return RedundantBinaryOpWithZeroOperand(0, 1);
|
||||
}
|
||||
|
||||
// This rule handles shifts and divisions of 0 or vector 0 by any amount
|
||||
// (0 >> a => 0).
|
||||
static const constexpr spv::Op RedundantBinaryLhs0To0Ops[] = {
|
||||
spv::Op::OpShiftRightLogical,
|
||||
spv::Op::OpShiftRightArithmetic,
|
||||
spv::Op::OpShiftLeftLogical,
|
||||
spv::Op::OpSDiv,
|
||||
spv::Op::OpUDiv,
|
||||
spv::Op::OpSMod,
|
||||
spv::Op::OpUMod};
|
||||
FoldingRule RedundantBinaryLhs0To0(spv::Op op) {
|
||||
assert(std::find(std::begin(RedundantBinaryLhs0To0Ops),
|
||||
std::end(RedundantBinaryLhs0To0Ops),
|
||||
op) != std::end(RedundantBinaryLhs0To0Ops) &&
|
||||
"Wrong opcode.");
|
||||
(void)op;
|
||||
return RedundantBinaryOpWithZeroOperand(0, 0);
|
||||
}
|
||||
|
||||
// Returns true if all elements in |c| are 1.
|
||||
bool IsAllInt1(const analysis::Constant* c) {
|
||||
if (auto composite = c->AsCompositeConstant()) {
|
||||
auto& components = composite->GetComponents();
|
||||
return std::all_of(std::begin(components), std::end(components), IsAllInt1);
|
||||
} else if (c->AsIntConstant()) {
|
||||
return c->GetSignExtendedValue() == 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// This rule handles divisions by 1 or vector 1 (a / 1 => a).
|
||||
FoldingRule RedundantSUDiv() {
|
||||
return [](IRContext* context, Instruction* inst,
|
||||
const std::vector<const analysis::Constant*>& constants) {
|
||||
assert(constants.size() == 2);
|
||||
assert((inst->opcode() == spv::Op::OpUDiv ||
|
||||
inst->opcode() == spv::Op::OpSDiv) &&
|
||||
"Wrong opcode.");
|
||||
|
||||
if (constants[1] && IsAllInt1(constants[1])) {
|
||||
auto operand = inst->GetSingleWordInOperand(0);
|
||||
auto operand_type = constants[1]->type();
|
||||
|
||||
const analysis::Type* inst_type =
|
||||
context->get_type_mgr()->GetType(inst->type_id());
|
||||
if (inst_type->IsSame(operand_type)) {
|
||||
inst->SetOpcode(spv::Op::OpCopyObject);
|
||||
} else {
|
||||
inst->SetOpcode(spv::Op::OpBitcast);
|
||||
}
|
||||
inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {operand}}});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
// This rule handles modulo from division by 1 or vector 1 (a % 1 => 0).
|
||||
FoldingRule RedundantSUMod() {
|
||||
return [](IRContext* context, Instruction* inst,
|
||||
const std::vector<const analysis::Constant*>& constants) {
|
||||
assert(constants.size() == 2);
|
||||
assert((inst->opcode() == spv::Op::OpUMod ||
|
||||
inst->opcode() == spv::Op::OpSMod) &&
|
||||
"Wrong opcode.");
|
||||
|
||||
if (constants[1] && IsAllInt1(constants[1])) {
|
||||
auto type = context->get_type_mgr()->GetType(inst->type_id());
|
||||
auto zero_id = context->get_constant_mgr()->GetNullConstId(type);
|
||||
|
||||
inst->SetOpcode(spv::Op::OpCopyObject);
|
||||
inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {zero_id}}});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
// This rule look for a dot with a constant vector containing a single 1 and
|
||||
// the rest 0s. This is the same as doing an extract.
|
||||
FoldingRule DotProductDoingExtract() {
|
||||
@@ -3012,17 +2876,6 @@ void FoldingRules::AddFoldingRules() {
|
||||
// Note that the order in which rules are added to the list matters. If a rule
|
||||
// applies to the instruction, the rest of the rules will not be attempted.
|
||||
// Take that into consideration.
|
||||
for (auto op : RedundantBinaryRhs0Ops)
|
||||
rules_[op].push_back(RedundantBinaryRhs0(op));
|
||||
for (auto op : RedundantBinaryLhs0Ops)
|
||||
rules_[op].push_back(RedundantBinaryLhs0(op));
|
||||
for (auto op : RedundantBinaryLhs0To0Ops)
|
||||
rules_[op].push_back(RedundantBinaryLhs0To0(op));
|
||||
rules_[spv::Op::OpSDiv].push_back(RedundantSUDiv());
|
||||
rules_[spv::Op::OpUDiv].push_back(RedundantSUDiv());
|
||||
rules_[spv::Op::OpSMod].push_back(RedundantSUMod());
|
||||
rules_[spv::Op::OpUMod].push_back(RedundantSUMod());
|
||||
|
||||
rules_[spv::Op::OpBitcast].push_back(BitCastScalarOrVector());
|
||||
|
||||
rules_[spv::Op::OpCompositeConstruct].push_back(
|
||||
@@ -3054,8 +2907,6 @@ void FoldingRules::AddFoldingRules() {
|
||||
rules_[spv::Op::OpFDiv].push_back(MergeDivMulArithmetic());
|
||||
rules_[spv::Op::OpFDiv].push_back(MergeDivNegateArithmetic());
|
||||
|
||||
rules_[spv::Op::OpFMod].push_back(RedundantFMod());
|
||||
|
||||
rules_[spv::Op::OpFMul].push_back(RedundantFMul());
|
||||
rules_[spv::Op::OpFMul].push_back(MergeMulMulArithmetic());
|
||||
rules_[spv::Op::OpFMul].push_back(MergeMulDivArithmetic());
|
||||
@@ -3070,6 +2921,7 @@ void FoldingRules::AddFoldingRules() {
|
||||
rules_[spv::Op::OpFSub].push_back(MergeSubAddArithmetic());
|
||||
rules_[spv::Op::OpFSub].push_back(MergeSubSubArithmetic());
|
||||
|
||||
rules_[spv::Op::OpIAdd].push_back(RedundantIAdd());
|
||||
rules_[spv::Op::OpIAdd].push_back(MergeAddNegateArithmetic());
|
||||
rules_[spv::Op::OpIAdd].push_back(MergeAddAddArithmetic());
|
||||
rules_[spv::Op::OpIAdd].push_back(MergeAddSubArithmetic());
|
||||
|
||||
+1
-20
@@ -17,7 +17,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
@@ -40,7 +39,6 @@ class Function {
|
||||
public:
|
||||
using iterator = UptrVectorIterator<BasicBlock>;
|
||||
using const_iterator = UptrVectorIterator<BasicBlock, true>;
|
||||
using ParamList = std::vector<std::unique_ptr<Instruction>>;
|
||||
|
||||
// Creates a function instance declared by the given OpFunction instruction
|
||||
// |def_inst|.
|
||||
@@ -79,23 +77,6 @@ class Function {
|
||||
// Does nothing if the function doesn't have such a parameter.
|
||||
inline void RemoveParameter(uint32_t id);
|
||||
|
||||
// Rewrites the function parameters by calling a replacer callback.
|
||||
// The replacer takes two parameters: an expiring unique pointer to a current
|
||||
// instruction, and a back-inserter into a new list of unique pointers to
|
||||
// instructions. The replacer is called for each current parameter, in order.
|
||||
// Not valid to call while also iterating through the parameter list, e.g.
|
||||
// within the ForEachParam method.
|
||||
using RewriteParamFn = std::function<void(
|
||||
std::unique_ptr<Instruction>&&, std::back_insert_iterator<ParamList>&)>;
|
||||
void RewriteParams(RewriteParamFn& replacer) {
|
||||
ParamList new_params;
|
||||
auto appender = std::back_inserter(new_params);
|
||||
for (auto& param : params_) {
|
||||
replacer(std::move(param), appender);
|
||||
}
|
||||
params_ = std::move(new_params);
|
||||
}
|
||||
|
||||
// Saves the given function end instruction.
|
||||
inline void SetFunctionEnd(std::unique_ptr<Instruction> end_inst);
|
||||
|
||||
@@ -216,7 +197,7 @@ class Function {
|
||||
// The OpFunction instruction that begins the definition of this function.
|
||||
std::unique_ptr<Instruction> def_inst_;
|
||||
// All parameters to this function.
|
||||
ParamList params_;
|
||||
std::vector<std::unique_ptr<Instruction>> params_;
|
||||
// All debug instructions in this function's header.
|
||||
InstructionList debug_insts_in_header_;
|
||||
// All basic blocks inside this function in specification order
|
||||
|
||||
@@ -55,11 +55,6 @@ Pass::Status InlineExhaustivePass::InlineExhaustive(Function* func) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
FixDebugDeclares(func);
|
||||
}
|
||||
|
||||
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,6 @@ namespace {
|
||||
constexpr int kSpvFunctionCallFunctionId = 2;
|
||||
constexpr int kSpvFunctionCallArgumentId = 3;
|
||||
constexpr int kSpvReturnValueId = 0;
|
||||
constexpr int kSpvDebugDeclareVarInIdx = 3;
|
||||
constexpr int kSpvAccessChainBaseInIdx = 0;
|
||||
} // namespace
|
||||
|
||||
uint32_t InlinePass::AddPointerToType(uint32_t type_id,
|
||||
@@ -860,68 +858,5 @@ void InlinePass::InitializeInline() {
|
||||
|
||||
InlinePass::InlinePass() {}
|
||||
|
||||
void InlinePass::FixDebugDeclares(Function* func) {
|
||||
std::map<uint32_t, Instruction*> access_chains;
|
||||
std::vector<Instruction*> debug_declare_insts;
|
||||
|
||||
func->ForEachInst([&access_chains, &debug_declare_insts](Instruction* inst) {
|
||||
if (inst->opcode() == spv::Op::OpAccessChain) {
|
||||
access_chains[inst->result_id()] = inst;
|
||||
}
|
||||
if (inst->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
|
||||
debug_declare_insts.push_back(inst);
|
||||
}
|
||||
});
|
||||
|
||||
for (auto& inst : debug_declare_insts) {
|
||||
FixDebugDeclare(inst, access_chains);
|
||||
}
|
||||
}
|
||||
|
||||
void InlinePass::FixDebugDeclare(
|
||||
Instruction* dbg_declare_inst,
|
||||
const std::map<uint32_t, Instruction*>& access_chains) {
|
||||
do {
|
||||
uint32_t var_id =
|
||||
dbg_declare_inst->GetSingleWordInOperand(kSpvDebugDeclareVarInIdx);
|
||||
|
||||
// The def-use chains are not kept up to date while inlining, so we need to
|
||||
// get the variable by traversing the functions.
|
||||
auto it = access_chains.find(var_id);
|
||||
if (it == access_chains.end()) {
|
||||
return;
|
||||
}
|
||||
Instruction* access_chain = it->second;
|
||||
|
||||
// If the variable id in the debug declare is an access chain, it is
|
||||
// invalid. it needs to be fixed up. The debug declare will be updated so
|
||||
// that its Var operand becomes the base of the access chain. The indexes of
|
||||
// the access chain are prepended before the indexes of the debug declare.
|
||||
|
||||
std::vector<Operand> operands;
|
||||
for (int i = 0; i < kSpvDebugDeclareVarInIdx; i++) {
|
||||
operands.push_back(dbg_declare_inst->GetInOperand(i));
|
||||
}
|
||||
|
||||
uint32_t access_chain_base =
|
||||
access_chain->GetSingleWordInOperand(kSpvAccessChainBaseInIdx);
|
||||
operands.push_back(Operand(SPV_OPERAND_TYPE_ID, {access_chain_base}));
|
||||
operands.push_back(
|
||||
dbg_declare_inst->GetInOperand(kSpvDebugDeclareVarInIdx + 1));
|
||||
|
||||
for (uint32_t i = kSpvAccessChainBaseInIdx + 1;
|
||||
i < access_chain->NumInOperands(); ++i) {
|
||||
operands.push_back(access_chain->GetInOperand(i));
|
||||
}
|
||||
|
||||
for (uint32_t i = kSpvDebugDeclareVarInIdx + 2;
|
||||
i < dbg_declare_inst->NumInOperands(); ++i) {
|
||||
operands.push_back(dbg_declare_inst->GetInOperand(i));
|
||||
}
|
||||
|
||||
dbg_declare_inst->SetInOperands(std::move(operands));
|
||||
} while (true);
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
@@ -150,12 +150,6 @@ class InlinePass : public Pass {
|
||||
// Initialize state for optimization of |module|
|
||||
void InitializeInline();
|
||||
|
||||
// Fixes invalid debug declare functions in `func` that were caused by
|
||||
// inlining. This function cannot be called while in the middle of inlining
|
||||
// because it needs to be able to find the instructions that define an
|
||||
// id.
|
||||
void FixDebugDeclares(Function* func);
|
||||
|
||||
// Map from function's result id to function.
|
||||
std::unordered_map<uint32_t, Function*> id2function_;
|
||||
|
||||
@@ -247,11 +241,6 @@ class InlinePass : public Pass {
|
||||
// structural dominance.
|
||||
void UpdateSingleBlockLoopContinueTarget(
|
||||
uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* new_blocks);
|
||||
|
||||
// Replaces the `var` operand of `dbg_declare_inst` and updates the indexes
|
||||
// accordingly, if it is the id of an access chain in `access_chains`.
|
||||
void FixDebugDeclare(Instruction* dbg_declare_inst,
|
||||
const std::map<uint32_t, Instruction*>& access_chains);
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
|
||||
+6
-67
@@ -15,7 +15,6 @@
|
||||
#ifndef SOURCE_OPT_IR_BUILDER_H_
|
||||
#define SOURCE_OPT_IR_BUILDER_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
@@ -481,11 +480,8 @@ class InstructionBuilder {
|
||||
return AddInstruction(std::move(select));
|
||||
}
|
||||
|
||||
Instruction* AddOpcodeAccessChain(spv::Op opcode, uint32_t type_id,
|
||||
uint32_t base_ptr_id,
|
||||
const std::vector<uint32_t>& ids) {
|
||||
assert(opcode == spv::Op::OpAccessChain ||
|
||||
opcode == spv::Op::OpInBoundsAccessChain);
|
||||
Instruction* AddAccessChain(uint32_t type_id, uint32_t base_ptr_id,
|
||||
std::vector<uint32_t> ids) {
|
||||
std::vector<Operand> operands;
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {base_ptr_id}});
|
||||
|
||||
@@ -494,22 +490,12 @@ class InstructionBuilder {
|
||||
}
|
||||
|
||||
// TODO(1841): Handle id overflow.
|
||||
std::unique_ptr<Instruction> new_inst(new Instruction(
|
||||
GetContext(), opcode, type_id, GetContext()->TakeNextId(), operands));
|
||||
std::unique_ptr<Instruction> new_inst(
|
||||
new Instruction(GetContext(), spv::Op::OpAccessChain, type_id,
|
||||
GetContext()->TakeNextId(), operands));
|
||||
return AddInstruction(std::move(new_inst));
|
||||
}
|
||||
|
||||
Instruction* AddAccessChain(uint32_t type_id, uint32_t base_ptr_id,
|
||||
const std::vector<uint32_t>& ids) {
|
||||
return AddOpcodeAccessChain(spv::Op::OpAccessChain, type_id, base_ptr_id,
|
||||
ids);
|
||||
}
|
||||
Instruction* AddInBoundsAccessChain(uint32_t type_id, uint32_t base_ptr_id,
|
||||
const std::vector<uint32_t>& ids) {
|
||||
return AddOpcodeAccessChain(spv::Op::OpInBoundsAccessChain, type_id,
|
||||
base_ptr_id, ids);
|
||||
}
|
||||
|
||||
Instruction* AddLoad(uint32_t type_id, uint32_t base_ptr_id,
|
||||
uint32_t alignment = 0) {
|
||||
std::vector<Operand> operands;
|
||||
@@ -528,19 +514,9 @@ class InstructionBuilder {
|
||||
return AddInstruction(std::move(new_inst));
|
||||
}
|
||||
|
||||
Instruction* AddCopyObject(uint32_t type_id, uint32_t value_id) {
|
||||
std::vector<Operand> operands{{SPV_OPERAND_TYPE_ID, {value_id}}};
|
||||
|
||||
// TODO(1841): Handle id overflow.
|
||||
std::unique_ptr<Instruction> new_inst(
|
||||
new Instruction(GetContext(), spv::Op::OpCopyObject, type_id,
|
||||
GetContext()->TakeNextId(), operands));
|
||||
return AddInstruction(std::move(new_inst));
|
||||
}
|
||||
|
||||
Instruction* AddVariable(uint32_t type_id, uint32_t storage_class) {
|
||||
std::vector<Operand> operands;
|
||||
operands.push_back({SPV_OPERAND_TYPE_STORAGE_CLASS, {storage_class}});
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {storage_class}});
|
||||
std::unique_ptr<Instruction> new_inst(
|
||||
new Instruction(GetContext(), spv::Op::OpVariable, type_id,
|
||||
GetContext()->TakeNextId(), operands));
|
||||
@@ -596,26 +572,6 @@ class InstructionBuilder {
|
||||
return AddInstruction(std::move(new_inst));
|
||||
}
|
||||
|
||||
Instruction* AddDecoration(uint32_t target_id, spv::Decoration d,
|
||||
const std::vector<uint32_t>& literals) {
|
||||
std::vector<Operand> operands;
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {target_id}});
|
||||
operands.push_back({SPV_OPERAND_TYPE_DECORATION, {uint32_t(d)}});
|
||||
for (uint32_t literal : literals) {
|
||||
operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {literal}});
|
||||
}
|
||||
|
||||
std::unique_ptr<Instruction> new_inst(
|
||||
new Instruction(GetContext(), spv::Op::OpDecorate, 0, 0, operands));
|
||||
// Decorations are annotation instructions. Add it via the IR context,
|
||||
// so the decoration manager will be updated.
|
||||
// Decorations don't belong to basic blocks, so there is no need
|
||||
// to update the instruction to block mapping.
|
||||
Instruction* result = new_inst.get();
|
||||
GetContext()->AddAnnotationInst(std::move(new_inst));
|
||||
return result;
|
||||
}
|
||||
|
||||
Instruction* AddNaryExtendedInstruction(
|
||||
uint32_t result_type, uint32_t set, uint32_t instruction,
|
||||
const std::vector<uint32_t>& ext_operands) {
|
||||
@@ -637,23 +593,6 @@ class InstructionBuilder {
|
||||
return AddInstruction(std::move(new_inst));
|
||||
}
|
||||
|
||||
Instruction* AddSampledImage(uint32_t sampled_image_type_id,
|
||||
uint32_t image_id, uint32_t sampler_id) {
|
||||
std::vector<Operand> operands;
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {image_id}});
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {sampler_id}});
|
||||
|
||||
uint32_t result_id = GetContext()->TakeNextId();
|
||||
if (result_id == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Instruction> new_inst(
|
||||
new Instruction(GetContext(), spv::Op::OpSampledImage,
|
||||
sampled_image_type_id, result_id, operands));
|
||||
return AddInstruction(std::move(new_inst));
|
||||
}
|
||||
|
||||
// Inserts the new instruction before the insertion point.
|
||||
Instruction* AddInstruction(std::unique_ptr<Instruction>&& insn) {
|
||||
Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
|
||||
|
||||
@@ -564,7 +564,6 @@ void IRContext::AddCombinatorsForCapability(uint32_t capability) {
|
||||
(uint32_t)spv::Op::OpCompositeConstruct,
|
||||
(uint32_t)spv::Op::OpCompositeExtract,
|
||||
(uint32_t)spv::Op::OpCompositeInsert,
|
||||
(uint32_t)spv::Op::OpCopyLogical,
|
||||
(uint32_t)spv::Op::OpCopyObject,
|
||||
(uint32_t)spv::Op::OpTranspose,
|
||||
(uint32_t)spv::Op::OpSampledImage,
|
||||
|
||||
+3
-17
@@ -189,7 +189,7 @@ Optimizer& Optimizer::RegisterPerformancePasses(bool preserve_interface) {
|
||||
.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
||||
.RegisterPass(CreateLocalSingleStoreElimPass())
|
||||
.RegisterPass(CreateAggressiveDCEPass(preserve_interface))
|
||||
.RegisterPass(CreateScalarReplacementPass(0))
|
||||
.RegisterPass(CreateScalarReplacementPass())
|
||||
.RegisterPass(CreateLocalAccessChainConvertPass())
|
||||
.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
||||
.RegisterPass(CreateLocalSingleStoreElimPass())
|
||||
@@ -203,7 +203,7 @@ Optimizer& Optimizer::RegisterPerformancePasses(bool preserve_interface) {
|
||||
.RegisterPass(CreateRedundancyEliminationPass())
|
||||
.RegisterPass(CreateCombineAccessChainsPass())
|
||||
.RegisterPass(CreateSimplificationPass())
|
||||
.RegisterPass(CreateScalarReplacementPass(0))
|
||||
.RegisterPass(CreateScalarReplacementPass())
|
||||
.RegisterPass(CreateLocalAccessChainConvertPass())
|
||||
.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
||||
.RegisterPass(CreateLocalSingleStoreElimPass())
|
||||
@@ -401,7 +401,7 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag,
|
||||
RegisterPass(CreateLoopUnswitchPass());
|
||||
} else if (pass_name == "scalar-replacement") {
|
||||
if (pass_args.size() == 0) {
|
||||
RegisterPass(CreateScalarReplacementPass(0));
|
||||
RegisterPass(CreateScalarReplacementPass());
|
||||
} else {
|
||||
int limit = -1;
|
||||
if (pass_args.find_first_not_of("0123456789") == std::string::npos) {
|
||||
@@ -637,10 +637,6 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag,
|
||||
}
|
||||
} else if (pass_name == "trim-capabilities") {
|
||||
RegisterPass(CreateTrimCapabilitiesPass());
|
||||
} else if (pass_name == "split-combined-image-sampler") {
|
||||
RegisterPass(CreateSplitCombinedImageSamplerPass());
|
||||
} else if (pass_name == "resolve-binding-conflicts") {
|
||||
RegisterPass(CreateResolveBindingConflictsPass());
|
||||
} else {
|
||||
Errorf(consumer(), nullptr, {},
|
||||
"Unknown flag '--%s'. Use --help for a list of valid flags",
|
||||
@@ -1192,16 +1188,6 @@ Optimizer::PassToken CreateOpExtInstWithForwardReferenceFixupPass() {
|
||||
MakeUnique<opt::OpExtInstWithForwardReferenceFixupPass>());
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateSplitCombinedImageSamplerPass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::SplitCombinedImageSamplerPass>());
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateResolveBindingConflictsPass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::ResolveBindingConflictsPass>());
|
||||
}
|
||||
|
||||
} // namespace spvtools
|
||||
|
||||
extern "C" {
|
||||
|
||||
-2
@@ -74,11 +74,9 @@
|
||||
#include "source/opt/remove_unused_interface_variables_pass.h"
|
||||
#include "source/opt/replace_desc_array_access_using_var_index.h"
|
||||
#include "source/opt/replace_invalid_opc.h"
|
||||
#include "source/opt/resolve_binding_conflicts_pass.h"
|
||||
#include "source/opt/scalar_replacement_pass.h"
|
||||
#include "source/opt/set_spec_constant_default_value_pass.h"
|
||||
#include "source/opt/simplification_pass.h"
|
||||
#include "source/opt/split_combined_image_sampler_pass.h"
|
||||
#include "source/opt/spread_volatile_semantics.h"
|
||||
#include "source/opt/ssa_rewrite_pass.h"
|
||||
#include "source/opt/strength_reduction_pass.h"
|
||||
|
||||
@@ -1,328 +0,0 @@
|
||||
// Copyright (c) 2025 Google LLC
|
||||
//
|
||||
// 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 "source/opt/resolve_binding_conflicts_pass.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "source/opt/decoration_manager.h"
|
||||
#include "source/opt/def_use_manager.h"
|
||||
#include "source/opt/instruction.h"
|
||||
#include "source/opt/ir_builder.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "spirv/unified1/spirv.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
// A VarBindingInfo contains the binding information for a single resource
|
||||
// variable.
|
||||
//
|
||||
// Exactly one such object is created per resource variable in the
|
||||
// module. In particular, when a resource variable is statically used by
|
||||
// more than one entry point, those entry points share the same VarBindingInfo
|
||||
// object for that variable.
|
||||
struct VarBindingInfo {
|
||||
const Instruction* const var;
|
||||
const uint32_t descriptor_set;
|
||||
Instruction* const binding_decoration;
|
||||
|
||||
// Returns the binding number.
|
||||
uint32_t binding() const {
|
||||
return binding_decoration->GetSingleWordInOperand(2);
|
||||
}
|
||||
// Sets the binding number to 'b'.
|
||||
void updateBinding(uint32_t b) { binding_decoration->SetOperand(2, {b}); }
|
||||
};
|
||||
|
||||
// The bindings in the same descriptor set that are used by an entry point.
|
||||
using BindingList = std::vector<VarBindingInfo*>;
|
||||
// A map from descriptor set number to the list of bindings in that descriptor
|
||||
// set, as used by a particular entry point.
|
||||
using DescriptorSets = std::unordered_map<uint32_t, BindingList>;
|
||||
|
||||
IRContext::Analysis ResolveBindingConflictsPass::GetPreservedAnalyses() {
|
||||
// All analyses are kept up to date.
|
||||
// At most this modifies the Binding numbers on variables.
|
||||
return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping |
|
||||
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
|
||||
IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis |
|
||||
IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap |
|
||||
IRContext::kAnalysisScalarEvolution |
|
||||
IRContext::kAnalysisRegisterPressure |
|
||||
IRContext::kAnalysisValueNumberTable |
|
||||
IRContext::kAnalysisStructuredCFG | IRContext::kAnalysisBuiltinVarId |
|
||||
IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisConstants |
|
||||
IRContext::kAnalysisTypes | IRContext::kAnalysisDebugInfo |
|
||||
IRContext::kAnalysisLiveness;
|
||||
}
|
||||
|
||||
// Orders variable binding info objects.
|
||||
// * The binding number is most signficant;
|
||||
// * Then a sampler-like object compares greater than non-sampler like object.
|
||||
// * Otherwise compare based on variable ID.
|
||||
// This provides a total order among bindings in a descriptor set for a valid
|
||||
// Vulkan module.
|
||||
bool Less(const VarBindingInfo* const lhs, const VarBindingInfo* const rhs) {
|
||||
if (lhs->binding() < rhs->binding()) return true;
|
||||
if (lhs->binding() > rhs->binding()) return false;
|
||||
|
||||
// Examine types.
|
||||
// In valid Vulkan the only conflict can occur between
|
||||
// images and samplers. We only care about a specific
|
||||
// comparison when one is a image-like thing and the other
|
||||
// is a sampler-like thing of the same shape. So unwrap
|
||||
// types until we hit one of those two.
|
||||
|
||||
auto* def_use_mgr = lhs->var->context()->get_def_use_mgr();
|
||||
|
||||
// Returns the type found by iteratively following pointer pointee type,
|
||||
// or array element type.
|
||||
auto unwrap = [&def_use_mgr](Instruction* ty) {
|
||||
bool keep_going = true;
|
||||
do {
|
||||
switch (ty->opcode()) {
|
||||
case spv::Op::OpTypePointer:
|
||||
ty = def_use_mgr->GetDef(ty->GetSingleWordInOperand(1));
|
||||
break;
|
||||
case spv::Op::OpTypeArray:
|
||||
case spv::Op::OpTypeRuntimeArray:
|
||||
ty = def_use_mgr->GetDef(ty->GetSingleWordInOperand(0));
|
||||
break;
|
||||
default:
|
||||
keep_going = false;
|
||||
break;
|
||||
}
|
||||
} while (keep_going);
|
||||
return ty;
|
||||
};
|
||||
|
||||
auto* lhs_ty = unwrap(def_use_mgr->GetDef(lhs->var->type_id()));
|
||||
auto* rhs_ty = unwrap(def_use_mgr->GetDef(rhs->var->type_id()));
|
||||
if (lhs_ty->opcode() == rhs_ty->opcode()) {
|
||||
// Pick based on variable ID.
|
||||
return lhs->var->result_id() < rhs->var->result_id();
|
||||
}
|
||||
// A sampler is always greater than an image.
|
||||
if (lhs_ty->opcode() == spv::Op::OpTypeSampler) {
|
||||
return false;
|
||||
}
|
||||
if (rhs_ty->opcode() == spv::Op::OpTypeSampler) {
|
||||
return true;
|
||||
}
|
||||
// Pick based on variable ID.
|
||||
return lhs->var->result_id() < rhs->var->result_id();
|
||||
}
|
||||
|
||||
// Summarizes the caller-callee relationships between functions in a module.
|
||||
class CallGraph {
|
||||
public:
|
||||
// Returns the list of all functions statically reachable from entry points,
|
||||
// where callees precede callers.
|
||||
const std::vector<uint32_t>& CalleesBeforeCallers() const {
|
||||
return visit_order_;
|
||||
}
|
||||
// Returns the list functions called from a given function.
|
||||
const std::unordered_set<uint32_t>& Callees(uint32_t caller) {
|
||||
return calls_[caller];
|
||||
}
|
||||
|
||||
CallGraph(IRContext& context) {
|
||||
// Populate calls_.
|
||||
std::queue<uint32_t> callee_queue;
|
||||
for (const auto& fn : *context.module()) {
|
||||
auto& callees = calls_[fn.result_id()];
|
||||
context.AddCalls(&fn, &callee_queue);
|
||||
while (!callee_queue.empty()) {
|
||||
callees.insert(callee_queue.front());
|
||||
callee_queue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Perform depth-first search, starting from each entry point.
|
||||
// Populates visit_order_.
|
||||
for (const auto& ep : context.module()->entry_points()) {
|
||||
Visit(ep.GetSingleWordInOperand(1));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Visits a function, recursively visiting its callees. Adds this ID
|
||||
// to the visit_order after all callees have been visited.
|
||||
void Visit(uint32_t func_id) {
|
||||
if (visited_.count(func_id)) {
|
||||
return;
|
||||
}
|
||||
visited_.insert(func_id);
|
||||
for (auto callee_id : calls_[func_id]) {
|
||||
Visit(callee_id);
|
||||
}
|
||||
visit_order_.push_back(func_id);
|
||||
}
|
||||
|
||||
// Maps the ID of a function to the IDs of functions it calls.
|
||||
std::unordered_map<uint32_t, std::unordered_set<uint32_t>> calls_;
|
||||
|
||||
// IDs of visited functions;
|
||||
std::unordered_set<uint32_t> visited_;
|
||||
// IDs of functions, where callees precede callers.
|
||||
std::vector<uint32_t> visit_order_;
|
||||
};
|
||||
|
||||
// Returns vector binding info for all resource variables in the module.
|
||||
auto GetVarBindings(IRContext& context) {
|
||||
std::vector<VarBindingInfo> vars;
|
||||
auto* deco_mgr = context.get_decoration_mgr();
|
||||
for (auto& inst : context.module()->types_values()) {
|
||||
if (inst.opcode() == spv::Op::OpVariable) {
|
||||
Instruction* descriptor_set_deco = nullptr;
|
||||
Instruction* binding_deco = nullptr;
|
||||
for (auto* deco : deco_mgr->GetDecorationsFor(inst.result_id(), false)) {
|
||||
switch (static_cast<spv::Decoration>(deco->GetSingleWordInOperand(1))) {
|
||||
case spv::Decoration::DescriptorSet:
|
||||
assert(!descriptor_set_deco);
|
||||
descriptor_set_deco = deco;
|
||||
break;
|
||||
case spv::Decoration::Binding:
|
||||
assert(!binding_deco);
|
||||
binding_deco = deco;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (descriptor_set_deco && binding_deco) {
|
||||
vars.push_back({&inst, descriptor_set_deco->GetSingleWordInOperand(2),
|
||||
binding_deco});
|
||||
}
|
||||
}
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
// Merges the bindings from source into sink. Maintains order and uniqueness
|
||||
// within a list of bindings.
|
||||
void Merge(DescriptorSets& sink, const DescriptorSets& source) {
|
||||
for (auto index_and_bindings : source) {
|
||||
const uint32_t index = index_and_bindings.first;
|
||||
const BindingList& src1 = index_and_bindings.second;
|
||||
const BindingList& src2 = sink[index];
|
||||
BindingList merged;
|
||||
merged.resize(src1.size() + src2.size());
|
||||
auto merged_end = std::merge(src1.begin(), src1.end(), src2.begin(),
|
||||
src2.end(), merged.begin(), Less);
|
||||
auto unique_end = std::unique(merged.begin(), merged_end);
|
||||
merged.resize(unique_end - merged.begin());
|
||||
sink[index] = std::move(merged);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolves conflicts within this binding list, so the binding number on an
|
||||
// item is at least one more than the binding number on the previous item.
|
||||
// When this does not yet hold, increase the binding number on the second
|
||||
// item in the pair. Returns true if any changes were applied.
|
||||
bool ResolveConflicts(BindingList& bl) {
|
||||
bool changed = false;
|
||||
for (size_t i = 1; i < bl.size(); i++) {
|
||||
const auto prev_num = bl[i - 1]->binding();
|
||||
if (prev_num >= bl[i]->binding()) {
|
||||
bl[i]->updateBinding(prev_num + 1);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
Pass::Status ResolveBindingConflictsPass::Process() {
|
||||
// Assumes the descriptor set and binding decorations are not provided
|
||||
// via decoration groups. Decoration groups were deprecated in SPIR-V 1.3
|
||||
// Revision 6. I have not seen any compiler generate them. --dneto
|
||||
|
||||
auto vars = GetVarBindings(*context());
|
||||
|
||||
// Maps a function ID to the variables used directly or indirectly by the
|
||||
// function, organized into descriptor sets. Each descriptor set
|
||||
// consists of a BindingList of distinct variables.
|
||||
std::unordered_map<uint32_t, DescriptorSets> used_vars;
|
||||
|
||||
// Determine variables directly used by functions.
|
||||
auto* def_use_mgr = context()->get_def_use_mgr();
|
||||
for (auto& var : vars) {
|
||||
std::unordered_set<uint32_t> visited_functions_for_var;
|
||||
def_use_mgr->ForEachUser(var.var, [&](Instruction* user) {
|
||||
if (auto* block = context()->get_instr_block(user)) {
|
||||
auto* fn = block->GetParent();
|
||||
assert(fn);
|
||||
const auto fn_id = fn->result_id();
|
||||
if (visited_functions_for_var.insert(fn_id).second) {
|
||||
used_vars[fn_id][var.descriptor_set].push_back(&var);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sort within a descriptor set by binding number.
|
||||
for (auto& sets_for_fn : used_vars) {
|
||||
for (auto& ds : sets_for_fn.second) {
|
||||
BindingList& bindings = ds.second;
|
||||
std::stable_sort(bindings.begin(), bindings.end(), Less);
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate from callees to callers.
|
||||
CallGraph call_graph(*context());
|
||||
for (const uint32_t caller : call_graph.CalleesBeforeCallers()) {
|
||||
DescriptorSets& caller_ds = used_vars[caller];
|
||||
for (const uint32_t callee : call_graph.Callees(caller)) {
|
||||
Merge(caller_ds, used_vars[callee]);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, the descriptor sets associated with each entry point
|
||||
// capture exactly the set of resource variables statically used
|
||||
// by the static call tree of that entry point.
|
||||
|
||||
// Resolve conflicts.
|
||||
// VarBindingInfo objects may be shared between the bindings lists.
|
||||
// Updating a binding in one list can require updating another list later.
|
||||
// So repeat updates until settling.
|
||||
|
||||
// The union of BindingLists across all entry points.
|
||||
std::vector<BindingList*> ep_bindings;
|
||||
|
||||
for (auto& ep : context()->module()->entry_points()) {
|
||||
for (auto& ds : used_vars[ep.GetSingleWordInOperand(1)]) {
|
||||
BindingList& bindings = ds.second;
|
||||
ep_bindings.push_back(&bindings);
|
||||
}
|
||||
}
|
||||
bool modified = false;
|
||||
bool found_conflict;
|
||||
do {
|
||||
found_conflict = false;
|
||||
for (BindingList* bl : ep_bindings) {
|
||||
found_conflict |= ResolveConflicts(*bl);
|
||||
}
|
||||
modified |= found_conflict;
|
||||
} while (found_conflict);
|
||||
|
||||
return modified ? Pass::Status::SuccessWithChange
|
||||
: Pass::Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (c) 2025 Google LLC
|
||||
//
|
||||
// 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 LIBSPIRV_OPT_RESOLVE_BINDING_CONFLICTS_PASS_H_
|
||||
#define LIBSPIRV_OPT_RESOLVE_BINDING_CONFLICTS_PASS_H_
|
||||
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "source/diagnostic.h"
|
||||
#include "source/opt/pass.h"
|
||||
#include "source/util/small_vector.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
class ResolveBindingConflictsPass : public Pass {
|
||||
public:
|
||||
virtual ~ResolveBindingConflictsPass() override = default;
|
||||
const char* name() const override { return "resolve-binding-conflicts"; }
|
||||
IRContext::Analysis GetPreservedAnalyses() override;
|
||||
Status Process() override;
|
||||
};
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // LIBSPIRV_OPT_RESOLVE_BINDING_CONFLICTS_PASS_H_
|
||||
@@ -33,7 +33,7 @@ namespace opt {
|
||||
// Documented in optimizer.hpp
|
||||
class ScalarReplacementPass : public MemPass {
|
||||
private:
|
||||
static constexpr uint32_t kDefaultLimit = 0;
|
||||
static constexpr uint32_t kDefaultLimit = 100;
|
||||
|
||||
public:
|
||||
ScalarReplacementPass(uint32_t limit = kDefaultLimit)
|
||||
|
||||
@@ -1,648 +0,0 @@
|
||||
// Copyright (c) 2025 Google LLC
|
||||
//
|
||||
// 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 "source/opt/split_combined_image_sampler_pass.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
#include "source/opt/instruction.h"
|
||||
#include "source/opt/ir_builder.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/opt/type_manager.h"
|
||||
#include "source/opt/types.h"
|
||||
#include "source/util/make_unique.h"
|
||||
#include "source/util/string_utils.h"
|
||||
#include "spirv/unified1/spirv.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
#define CHECK(cond) \
|
||||
{ \
|
||||
if ((cond) != SPV_SUCCESS) return Pass::Status::Failure; \
|
||||
}
|
||||
|
||||
#define CHECK_STATUS(cond) \
|
||||
{ \
|
||||
if (auto c = (cond); c != SPV_SUCCESS) return c; \
|
||||
}
|
||||
|
||||
IRContext::Analysis SplitCombinedImageSamplerPass::GetPreservedAnalyses() {
|
||||
return
|
||||
// def use manager is updated
|
||||
IRContext::kAnalysisDefUse
|
||||
|
||||
// decorations are updated
|
||||
| IRContext::kAnalysisDecorations
|
||||
|
||||
// control flow is not changed
|
||||
| IRContext::kAnalysisCFG //
|
||||
| IRContext::kAnalysisLoopAnalysis //
|
||||
| IRContext::kAnalysisStructuredCFG
|
||||
|
||||
// type manager is updated
|
||||
| IRContext::kAnalysisTypes;
|
||||
}
|
||||
|
||||
Pass::Status SplitCombinedImageSamplerPass::Process() {
|
||||
def_use_mgr_ = context()->get_def_use_mgr();
|
||||
type_mgr_ = context()->get_type_mgr();
|
||||
|
||||
FindCombinedTextureSamplers();
|
||||
if (combined_types_to_remove_.empty() && !sampled_image_used_as_param_) {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
CHECK(RemapFunctions());
|
||||
CHECK(RemapVars());
|
||||
CHECK(RemoveDeadTypes());
|
||||
|
||||
def_use_mgr_ = nullptr;
|
||||
type_mgr_ = nullptr;
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
spvtools::DiagnosticStream SplitCombinedImageSamplerPass::Fail() {
|
||||
return std::move(
|
||||
spvtools::DiagnosticStream({}, consumer(), "", SPV_ERROR_INVALID_BINARY)
|
||||
<< "split-combined-image-sampler: ");
|
||||
}
|
||||
|
||||
void SplitCombinedImageSamplerPass::FindCombinedTextureSamplers() {
|
||||
for (auto& inst : context()->types_values()) {
|
||||
RegisterGlobal(inst.result_id());
|
||||
switch (inst.opcode()) {
|
||||
case spv::Op::OpTypeSampler:
|
||||
// Modules can't have duplicate sampler types.
|
||||
assert(!sampler_type_);
|
||||
sampler_type_ = &inst;
|
||||
break;
|
||||
|
||||
case spv::Op::OpTypeSampledImage:
|
||||
if (!first_sampled_image_type_) {
|
||||
first_sampled_image_type_ = &inst;
|
||||
}
|
||||
combined_types_.insert(inst.result_id());
|
||||
def_use_mgr_->WhileEachUser(inst.result_id(), [&](Instruction* i) {
|
||||
sampled_image_used_as_param_ |=
|
||||
i->opcode() == spv::Op::OpTypeFunction;
|
||||
return !sampled_image_used_as_param_;
|
||||
});
|
||||
break;
|
||||
|
||||
case spv::Op::OpTypeArray:
|
||||
case spv::Op::OpTypeRuntimeArray: {
|
||||
auto pointee_id = inst.GetSingleWordInOperand(0);
|
||||
if (combined_types_.find(pointee_id) != combined_types_.end()) {
|
||||
combined_types_.insert(inst.result_id());
|
||||
combined_types_to_remove_.push_back(inst.result_id());
|
||||
}
|
||||
} break;
|
||||
|
||||
case spv::Op::OpTypePointer: {
|
||||
auto sc =
|
||||
static_cast<spv::StorageClass>(inst.GetSingleWordInOperand(0));
|
||||
if (sc == spv::StorageClass::UniformConstant) {
|
||||
auto pointee_id = inst.GetSingleWordInOperand(1);
|
||||
if (combined_types_.find(pointee_id) != combined_types_.end()) {
|
||||
combined_types_.insert(inst.result_id());
|
||||
combined_types_to_remove_.push_back(inst.result_id());
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case spv::Op::OpVariable:
|
||||
if (combined_types_.find(inst.type_id()) != combined_types_.end()) {
|
||||
ordered_vars_.push_back(&inst);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Instruction* SplitCombinedImageSamplerPass::GetSamplerType() {
|
||||
if (!sampler_type_) {
|
||||
analysis::Sampler s;
|
||||
uint32_t sampler_type_id = type_mgr_->GetTypeInstruction(&s);
|
||||
sampler_type_ = def_use_mgr_->GetDef(sampler_type_id);
|
||||
assert(first_sampled_image_type_);
|
||||
sampler_type_->InsertBefore(first_sampled_image_type_);
|
||||
RegisterNewGlobal(sampler_type_->result_id());
|
||||
}
|
||||
return sampler_type_;
|
||||
}
|
||||
|
||||
spv_result_t SplitCombinedImageSamplerPass::RemapVars() {
|
||||
for (Instruction* var : ordered_vars_) {
|
||||
CHECK_STATUS(RemapVar(var));
|
||||
}
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
std::pair<Instruction*, Instruction*> SplitCombinedImageSamplerPass::SplitType(
|
||||
Instruction& combined_kind_type) {
|
||||
if (auto where = type_remap_.find(combined_kind_type.result_id());
|
||||
where != type_remap_.end()) {
|
||||
auto& type_remap = where->second;
|
||||
return {type_remap.image_kind_type, type_remap.sampler_kind_type};
|
||||
}
|
||||
|
||||
switch (combined_kind_type.opcode()) {
|
||||
case spv::Op::OpTypeSampledImage: {
|
||||
auto* image_type =
|
||||
def_use_mgr_->GetDef(combined_kind_type.GetSingleWordInOperand(0));
|
||||
auto* sampler_type = GetSamplerType();
|
||||
type_remap_[combined_kind_type.result_id()] = {&combined_kind_type,
|
||||
image_type, sampler_type};
|
||||
return {image_type, sampler_type};
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpTypePointer: {
|
||||
auto sc = static_cast<spv::StorageClass>(
|
||||
combined_kind_type.GetSingleWordInOperand(0));
|
||||
if (sc == spv::StorageClass::UniformConstant) {
|
||||
auto* pointee =
|
||||
def_use_mgr_->GetDef(combined_kind_type.GetSingleWordInOperand(1));
|
||||
auto [image_pointee, sampler_pointee] = SplitType(*pointee);
|
||||
// These would be null if the pointee is an image type or a sampler
|
||||
// type. Don't decompose them. Currently this method does not check the
|
||||
// assumption that it is being only called on combined types. So code
|
||||
// this defensively.
|
||||
if (image_pointee && sampler_pointee) {
|
||||
auto* ptr_image = MakeUniformConstantPointer(image_pointee);
|
||||
auto* ptr_sampler = MakeUniformConstantPointer(sampler_pointee);
|
||||
type_remap_[combined_kind_type.result_id()] = {
|
||||
&combined_kind_type, ptr_image, ptr_sampler};
|
||||
return {ptr_image, ptr_sampler};
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpTypeArray: {
|
||||
const auto* array_ty =
|
||||
type_mgr_->GetType(combined_kind_type.result_id())->AsArray();
|
||||
assert(array_ty);
|
||||
const auto* sampled_image_ty = array_ty->element_type()->AsSampledImage();
|
||||
assert(sampled_image_ty);
|
||||
|
||||
const analysis::Type* image_ty = sampled_image_ty->image_type();
|
||||
assert(image_ty);
|
||||
analysis::Array array_image_ty(image_ty, array_ty->length_info());
|
||||
const uint32_t array_image_ty_id =
|
||||
type_mgr_->GetTypeInstruction(&array_image_ty);
|
||||
auto* array_image_ty_inst = def_use_mgr_->GetDef(array_image_ty_id);
|
||||
if (!IsKnownGlobal(array_image_ty_id)) {
|
||||
array_image_ty_inst->InsertBefore(&combined_kind_type);
|
||||
RegisterNewGlobal(array_image_ty_id);
|
||||
// GetTypeInstruction also updated the def-use manager.
|
||||
}
|
||||
|
||||
analysis::Array sampler_array_ty(
|
||||
type_mgr_->GetType(GetSamplerType()->result_id()),
|
||||
array_ty->length_info());
|
||||
const uint32_t array_sampler_ty_id =
|
||||
type_mgr_->GetTypeInstruction(&sampler_array_ty);
|
||||
auto* array_sampler_ty_inst = def_use_mgr_->GetDef(array_sampler_ty_id);
|
||||
if (!IsKnownGlobal(array_sampler_ty_id)) {
|
||||
array_sampler_ty_inst->InsertBefore(&combined_kind_type);
|
||||
RegisterNewGlobal(array_sampler_ty_id);
|
||||
// GetTypeInstruction also updated the def-use manager.
|
||||
}
|
||||
return {array_image_ty_inst, array_sampler_ty_inst};
|
||||
}
|
||||
case spv::Op::OpTypeRuntimeArray: {
|
||||
// This is like the sized-array case, but there is no length parameter.
|
||||
auto* array_ty =
|
||||
type_mgr_->GetType(combined_kind_type.result_id())->AsRuntimeArray();
|
||||
assert(array_ty);
|
||||
auto* sampled_image_ty = array_ty->element_type()->AsSampledImage();
|
||||
assert(sampled_image_ty);
|
||||
|
||||
const analysis::Type* image_ty = sampled_image_ty->image_type();
|
||||
assert(image_ty);
|
||||
analysis::RuntimeArray array_image_ty(image_ty);
|
||||
const uint32_t array_image_ty_id =
|
||||
type_mgr_->GetTypeInstruction(&array_image_ty);
|
||||
auto* array_image_ty_inst = def_use_mgr_->GetDef(array_image_ty_id);
|
||||
if (!IsKnownGlobal(array_image_ty_id)) {
|
||||
array_image_ty_inst->InsertBefore(&combined_kind_type);
|
||||
RegisterNewGlobal(array_image_ty_id);
|
||||
// GetTypeInstruction also updated the def-use manager.
|
||||
}
|
||||
|
||||
analysis::RuntimeArray sampler_array_ty(
|
||||
type_mgr_->GetType(GetSamplerType()->result_id()));
|
||||
const uint32_t array_sampler_ty_id =
|
||||
type_mgr_->GetTypeInstruction(&sampler_array_ty);
|
||||
auto* array_sampler_ty_inst = def_use_mgr_->GetDef(array_sampler_ty_id);
|
||||
if (!IsKnownGlobal(array_sampler_ty_id)) {
|
||||
array_sampler_ty_inst->InsertBefore(&combined_kind_type);
|
||||
RegisterNewGlobal(array_sampler_ty_id);
|
||||
// GetTypeInstruction also updated the def-use manager.
|
||||
}
|
||||
return {array_image_ty_inst, array_sampler_ty_inst};
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
spv_result_t SplitCombinedImageSamplerPass::RemapVar(
|
||||
Instruction* combined_var) {
|
||||
InstructionBuilder builder(context(), combined_var,
|
||||
IRContext::kAnalysisDefUse);
|
||||
|
||||
// Create an image variable, and a sampler variable.
|
||||
auto* combined_var_type = def_use_mgr_->GetDef(combined_var->type_id());
|
||||
auto [ptr_image_ty, ptr_sampler_ty] = SplitType(*combined_var_type);
|
||||
assert(ptr_image_ty);
|
||||
assert(ptr_sampler_ty);
|
||||
Instruction* sampler_var = builder.AddVariable(
|
||||
ptr_sampler_ty->result_id(), SpvStorageClassUniformConstant);
|
||||
Instruction* image_var = builder.AddVariable(ptr_image_ty->result_id(),
|
||||
SpvStorageClassUniformConstant);
|
||||
modified_ = true;
|
||||
return RemapUses(combined_var, image_var, sampler_var);
|
||||
}
|
||||
|
||||
spv_result_t SplitCombinedImageSamplerPass::RemapUses(
|
||||
Instruction* combined, Instruction* image_part, Instruction* sampler_part) {
|
||||
// The instructions to delete.
|
||||
std::unordered_set<Instruction*> dead_insts;
|
||||
// The insertion point should be updated before using this builder.
|
||||
// We needed *something* here.
|
||||
InstructionBuilder builder(context(), combined, IRContext::kAnalysisDefUse);
|
||||
|
||||
// This code must maintain the SPIR-V "Data rule" about sampled image values:
|
||||
// > All OpSampledImage instructions, or instructions that load an image or
|
||||
// > sampler reference, must be in the same block in which their Result <id>
|
||||
// > are consumed.
|
||||
//
|
||||
// When the code below inserts OpSampledImage instructions, it is always
|
||||
// either:
|
||||
// - in the same block as the previous OpSampledImage instruction it is
|
||||
// replacing, or
|
||||
// - in the same block as the instruction using sampled image value it is
|
||||
// replacing.
|
||||
//
|
||||
// Assuming that rule is already honoured by the module, these updates will
|
||||
// continue to honour the rule.
|
||||
|
||||
// Represents a single use of a value to be remapped.
|
||||
struct RemapUse {
|
||||
uint32_t used_id; // The ID that is being used.
|
||||
Instruction* user;
|
||||
uint32_t index;
|
||||
Instruction* image_part; // The image part of the replacement.
|
||||
Instruction* sampler_part; // The sampler part of the replacement.
|
||||
};
|
||||
// The work list of uses to be remapped.
|
||||
std::vector<RemapUse> uses;
|
||||
|
||||
// Adds remap records for each use of a value to be remapped.
|
||||
// Also schedules the original value for deletion.
|
||||
auto add_remap = [this, &dead_insts, &uses](Instruction* combined_arg,
|
||||
Instruction* image_part_arg,
|
||||
Instruction* sampler_part_arg) {
|
||||
const uint32_t used_combined_id = combined_arg->result_id();
|
||||
|
||||
def_use_mgr_->ForEachUse(
|
||||
combined_arg, [&](Instruction* user, uint32_t use_index) {
|
||||
uses.push_back({used_combined_id, user, use_index, image_part_arg,
|
||||
sampler_part_arg});
|
||||
});
|
||||
dead_insts.insert(combined_arg);
|
||||
};
|
||||
|
||||
add_remap(combined, image_part, sampler_part);
|
||||
|
||||
// Use index-based iteration because we can add to the work list as we go
|
||||
// along, and reallocation would invalidate ordinary iterators.
|
||||
for (size_t use_index = 0; use_index < uses.size(); ++use_index) {
|
||||
auto& use = uses[use_index];
|
||||
switch (use.user->opcode()) {
|
||||
case spv::Op::OpCopyObject: {
|
||||
// Append the uses of this OpCopyObject to the work list.
|
||||
add_remap(use.user, image_part, sampler_part);
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpLoad: {
|
||||
assert(use.index == 2 && "variable used as non-pointer index on load");
|
||||
Instruction* load = use.user;
|
||||
|
||||
// Assume the loaded value is a sampled image.
|
||||
assert(def_use_mgr_->GetDef(load->type_id())->opcode() ==
|
||||
spv::Op::OpTypeSampledImage);
|
||||
|
||||
// Create loads for the image part and sampler part.
|
||||
builder.SetInsertPoint(load);
|
||||
auto* image = builder.AddLoad(PointeeTypeId(use.image_part),
|
||||
use.image_part->result_id());
|
||||
auto* sampler = builder.AddLoad(PointeeTypeId(use.sampler_part),
|
||||
use.sampler_part->result_id());
|
||||
|
||||
// Move decorations, such as RelaxedPrecision.
|
||||
auto* deco_mgr = context()->get_decoration_mgr();
|
||||
deco_mgr->CloneDecorations(load->result_id(), image->result_id());
|
||||
deco_mgr->CloneDecorations(load->result_id(), sampler->result_id());
|
||||
deco_mgr->RemoveDecorationsFrom(load->result_id());
|
||||
|
||||
// Create a sampled image from the loads of the two parts.
|
||||
auto* sampled_image = builder.AddSampledImage(
|
||||
load->type_id(), image->result_id(), sampler->result_id());
|
||||
// Replace the original sampled image value with the new one.
|
||||
std::unordered_set<Instruction*> users;
|
||||
def_use_mgr_->ForEachUse(
|
||||
load, [&users, sampled_image](Instruction* user, uint32_t index) {
|
||||
user->SetOperand(index, {sampled_image->result_id()});
|
||||
users.insert(user);
|
||||
});
|
||||
for (auto* user : users) {
|
||||
def_use_mgr_->AnalyzeInstUse(user);
|
||||
}
|
||||
dead_insts.insert(load);
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpDecorate: {
|
||||
assert(use.index == 0 && "variable used as non-target index");
|
||||
builder.SetInsertPoint(use.user);
|
||||
spv::Decoration deco{use.user->GetSingleWordInOperand(1)};
|
||||
std::vector<uint32_t> literals;
|
||||
for (uint32_t i = 2; i < use.user->NumInOperands(); i++) {
|
||||
literals.push_back(use.user->GetSingleWordInOperand(i));
|
||||
}
|
||||
builder.AddDecoration(use.image_part->result_id(), deco, literals);
|
||||
builder.AddDecoration(use.sampler_part->result_id(), deco, literals);
|
||||
// KillInst will delete names and decorations, so don't schedule a
|
||||
// deletion of this instruction.
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpEntryPoint: {
|
||||
// The entry point lists variables in the shader interface, i.e.
|
||||
// module-scope variables referenced by the static call tree rooted
|
||||
// at the entry point. (It can be a proper superset). Before SPIR-V
|
||||
// 1.4, only Input and Output variables are listed; in 1.4 and later,
|
||||
// module-scope variables in all storage classes are listed.
|
||||
// If a combined image+sampler is listed by the entry point, then
|
||||
// the separated image and sampler variables should be.
|
||||
assert(use.index >= 3 &&
|
||||
"variable used in OpEntryPoint but not as an interface ID");
|
||||
use.user->SetOperand(use.index, {use.image_part->result_id()});
|
||||
use.user->InsertOperand(
|
||||
use.user->NumOperands(),
|
||||
{SPV_OPERAND_TYPE_ID, {use.sampler_part->result_id()}});
|
||||
def_use_mgr_->AnalyzeInstUse(use.user);
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpName: {
|
||||
// Synthesize new names from the old.
|
||||
const auto name = use.user->GetOperand(1).AsString();
|
||||
AddOpName(use.image_part->result_id(), name + "_image");
|
||||
AddOpName(use.sampler_part->result_id(), name + "_sampler");
|
||||
|
||||
// KillInst will delete names and decorations, so don't schedule a
|
||||
// deletion of this instruction.
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpFunctionCall: {
|
||||
// Replace each combined arg with two args: the image part, then the
|
||||
// sampler part.
|
||||
// The combined value could have been used twice in the argument list.
|
||||
// Moving things around now will invalidate the 'use' list above.
|
||||
// So don't trust the use index value.
|
||||
auto& call = *use.user;
|
||||
// The insert API only takes absolute arg IDs, not "in" arg IDs.
|
||||
const auto first_arg_operand_index = 3; // Skip the callee ID
|
||||
for (uint32_t i = first_arg_operand_index; i < call.NumOperands();
|
||||
++i) {
|
||||
if (use.used_id == call.GetSingleWordOperand(i)) {
|
||||
call.SetOperand(i, {use.sampler_part->result_id()});
|
||||
call.InsertOperand(
|
||||
i, {SPV_OPERAND_TYPE_ID, {use.image_part->result_id()}});
|
||||
++i;
|
||||
}
|
||||
}
|
||||
def_use_mgr_->AnalyzeInstUse(&call);
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpAccessChain:
|
||||
case spv::Op::OpInBoundsAccessChain: {
|
||||
auto* original_access_chain = use.user;
|
||||
builder.SetInsertPoint(original_access_chain);
|
||||
// It can only be the base pointer
|
||||
assert(use.index == 2);
|
||||
|
||||
// Replace the original access chain with access chains for the image
|
||||
// part and the sampler part.
|
||||
std::vector<uint32_t> indices;
|
||||
for (uint32_t i = 3; i < original_access_chain->NumOperands(); i++) {
|
||||
indices.push_back(original_access_chain->GetSingleWordOperand(i));
|
||||
}
|
||||
|
||||
auto [result_image_part_ty, result_sampler_part_ty] =
|
||||
SplitType(*def_use_mgr_->GetDef(original_access_chain->type_id()));
|
||||
auto* result_image_part = builder.AddOpcodeAccessChain(
|
||||
use.user->opcode(), result_image_part_ty->result_id(),
|
||||
use.image_part->result_id(), indices);
|
||||
auto* result_sampler_part = builder.AddOpcodeAccessChain(
|
||||
use.user->opcode(), result_sampler_part_ty->result_id(),
|
||||
use.sampler_part->result_id(), indices);
|
||||
|
||||
// Remap uses of the original access chain.
|
||||
add_remap(original_access_chain, result_image_part,
|
||||
result_sampler_part);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
uint32_t used_type_id = def_use_mgr_->GetDef(use.used_id)->type_id();
|
||||
auto* used_type = def_use_mgr_->GetDef(used_type_id);
|
||||
if (used_type->opcode() == spv::Op::OpTypeSampledImage) {
|
||||
// This value being used is a sampled image value. But it's
|
||||
// being replaced, so recreate it here.
|
||||
// Example: used by OpImage, OpImageSampleExplicitLod, etc.
|
||||
builder.SetInsertPoint(use.user);
|
||||
auto* sampled_image =
|
||||
builder.AddSampledImage(used_type_id, use.image_part->result_id(),
|
||||
use.sampler_part->result_id());
|
||||
use.user->SetOperand(use.index, {sampled_image->result_id()});
|
||||
def_use_mgr_->AnalyzeInstUse(use.user);
|
||||
break;
|
||||
}
|
||||
return Fail() << "unhandled user: " << *use.user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto* inst : dead_insts) {
|
||||
KillInst(inst);
|
||||
}
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
spv_result_t SplitCombinedImageSamplerPass::RemapFunctions() {
|
||||
// Remap function types. A combined type can appear as a parameter, but not as
|
||||
// the return type.
|
||||
{
|
||||
std::unordered_set<Instruction*> dead_insts;
|
||||
for (auto& inst : context()->types_values()) {
|
||||
if (inst.opcode() != spv::Op::OpTypeFunction) {
|
||||
continue;
|
||||
}
|
||||
analysis::Function* f_ty =
|
||||
type_mgr_->GetType(inst.result_id())->AsFunction();
|
||||
std::vector<const analysis::Type*> new_params;
|
||||
for (const auto* param_ty : f_ty->param_types()) {
|
||||
const auto param_ty_id = type_mgr_->GetId(param_ty);
|
||||
if (combined_types_.find(param_ty_id) != combined_types_.end()) {
|
||||
auto* param_type = def_use_mgr_->GetDef(param_ty_id);
|
||||
auto [image_type, sampler_type] = SplitType(*param_type);
|
||||
assert(image_type);
|
||||
assert(sampler_type);
|
||||
// The image and sampler types must already exist, so there is no
|
||||
// need to move them to the right spot.
|
||||
new_params.push_back(type_mgr_->GetType(image_type->result_id()));
|
||||
new_params.push_back(type_mgr_->GetType(sampler_type->result_id()));
|
||||
} else {
|
||||
new_params.push_back(param_ty);
|
||||
}
|
||||
}
|
||||
if (new_params.size() != f_ty->param_types().size()) {
|
||||
// Replace this type.
|
||||
analysis::Function new_f_ty(f_ty->return_type(), new_params);
|
||||
const uint32_t new_f_ty_id = type_mgr_->GetTypeInstruction(&new_f_ty);
|
||||
std::unordered_set<Instruction*> users;
|
||||
def_use_mgr_->ForEachUse(
|
||||
&inst,
|
||||
[&users, new_f_ty_id](Instruction* user, uint32_t use_index) {
|
||||
user->SetOperand(use_index, {new_f_ty_id});
|
||||
users.insert(user);
|
||||
});
|
||||
for (auto* user : users) {
|
||||
def_use_mgr_->AnalyzeInstUse(user);
|
||||
}
|
||||
dead_insts.insert(&inst);
|
||||
}
|
||||
}
|
||||
for (auto* inst : dead_insts) {
|
||||
KillInst(inst);
|
||||
}
|
||||
}
|
||||
|
||||
// Rewite OpFunctionParameter in function definitions.
|
||||
for (Function& fn : *context()->module()) {
|
||||
// Rewrite the function parameters and record their replacements.
|
||||
struct Replacement {
|
||||
Instruction* combined;
|
||||
Instruction* image;
|
||||
Instruction* sampler;
|
||||
};
|
||||
std::vector<Replacement> replacements;
|
||||
|
||||
Function::RewriteParamFn rewriter =
|
||||
[&](std::unique_ptr<Instruction>&& param,
|
||||
std::back_insert_iterator<Function::ParamList>& appender) {
|
||||
if (combined_types_.count(param->type_id()) == 0) {
|
||||
appender = std::move(param);
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace this parameter with two new parameters.
|
||||
auto* combined_inst = param.release();
|
||||
auto* combined_type = def_use_mgr_->GetDef(combined_inst->type_id());
|
||||
auto [image_type, sampler_type] = SplitType(*combined_type);
|
||||
auto image_param = MakeUnique<Instruction>(
|
||||
context(), spv::Op::OpFunctionParameter, image_type->result_id(),
|
||||
context()->TakeNextId(), Instruction::OperandList{});
|
||||
auto sampler_param = MakeUnique<Instruction>(
|
||||
context(), spv::Op::OpFunctionParameter,
|
||||
sampler_type->result_id(), context()->TakeNextId(),
|
||||
Instruction::OperandList{});
|
||||
replacements.push_back(
|
||||
{combined_inst, image_param.get(), sampler_param.get()});
|
||||
appender = std::move(image_param);
|
||||
appender = std::move(sampler_param);
|
||||
};
|
||||
fn.RewriteParams(rewriter);
|
||||
|
||||
for (auto& r : replacements) {
|
||||
modified_ = true;
|
||||
def_use_mgr_->AnalyzeInstDefUse(r.image);
|
||||
def_use_mgr_->AnalyzeInstDefUse(r.sampler);
|
||||
CHECK_STATUS(RemapUses(r.combined, r.image, r.sampler));
|
||||
}
|
||||
}
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
Instruction* SplitCombinedImageSamplerPass::MakeUniformConstantPointer(
|
||||
Instruction* pointee) {
|
||||
uint32_t ptr_id = type_mgr_->FindPointerToType(
|
||||
pointee->result_id(), spv::StorageClass::UniformConstant);
|
||||
auto* ptr = def_use_mgr_->GetDef(ptr_id);
|
||||
if (!IsKnownGlobal(ptr_id)) {
|
||||
// The pointer type was created at the end. Put it right after the
|
||||
// pointee.
|
||||
ptr->InsertBefore(pointee);
|
||||
pointee->InsertBefore(ptr);
|
||||
RegisterNewGlobal(ptr_id);
|
||||
// FindPointerToType also updated the def-use manager.
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void SplitCombinedImageSamplerPass::AddOpName(uint32_t id,
|
||||
const std::string& name) {
|
||||
std::unique_ptr<Instruction> opname{new Instruction{
|
||||
context(),
|
||||
spv::Op::OpName,
|
||||
0u,
|
||||
0u,
|
||||
{{SPV_OPERAND_TYPE_ID, {id}},
|
||||
{SPV_OPERAND_TYPE_LITERAL_STRING,
|
||||
utils::MakeVector<spvtools::opt::Operand::OperandData>(name)}}}};
|
||||
|
||||
context()->AddDebug2Inst(std::move(opname));
|
||||
}
|
||||
|
||||
spv_result_t SplitCombinedImageSamplerPass::RemoveDeadTypes() {
|
||||
for (auto dead_type_id : combined_types_to_remove_) {
|
||||
if (auto* ty = def_use_mgr_->GetDef(dead_type_id)) {
|
||||
KillInst(ty);
|
||||
}
|
||||
}
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
void SplitCombinedImageSamplerPass::KillInst(Instruction* inst) {
|
||||
// IRContext::KillInst will remove associated debug instructions and
|
||||
// decorations. It will delete the object only if it is already in a list.
|
||||
const bool was_in_list = inst->IsInAList();
|
||||
context()->KillInst(inst);
|
||||
if (!was_in_list) {
|
||||
// Avoid leaking
|
||||
delete inst;
|
||||
}
|
||||
modified_ = true;
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
@@ -1,169 +0,0 @@
|
||||
// Copyright (c) 2025 Google LLC
|
||||
//
|
||||
// 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 LIBSPIRV_OPT_SPLIT_COMBINED_IMAGE_SAMPLER_PASS_H_
|
||||
#define LIBSPIRV_OPT_SPLIT_COMBINED_IMAGE_SAMPLER_PASS_H_
|
||||
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "source/diagnostic.h"
|
||||
#include "source/opt/decoration_manager.h"
|
||||
#include "source/opt/def_use_manager.h"
|
||||
#include "source/opt/pass.h"
|
||||
#include "source/opt/type_manager.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
// Replaces each combined-image sampler variable with an image variable
|
||||
// and a sampler variable. Similar for function parameters.
|
||||
//
|
||||
// Copy the descriptor set and binding number. Vulkan allows this, surprisingly.
|
||||
class SplitCombinedImageSamplerPass : public Pass {
|
||||
public:
|
||||
virtual ~SplitCombinedImageSamplerPass() override = default;
|
||||
const char* name() const override { return "split-combined-image-sampler"; }
|
||||
IRContext::Analysis GetPreservedAnalyses() override;
|
||||
Status Process() override;
|
||||
|
||||
private:
|
||||
// Records failure for the current module, and returns a stream
|
||||
// that can be used to provide user error information to the message
|
||||
// consumer.
|
||||
spvtools::DiagnosticStream Fail();
|
||||
|
||||
// Find variables that contain combined texture-samplers, or arrays of them.
|
||||
// Also populate known_globals_.
|
||||
void FindCombinedTextureSamplers();
|
||||
|
||||
// Returns the sampler type. If it does not yet exist, then it is created
|
||||
// and placed before the first sampled image type.
|
||||
Instruction* GetSamplerType();
|
||||
|
||||
// Remaps function types and function declarations. Each
|
||||
// pointer-to-sampled-image-type operand is replaced with a pair of
|
||||
// pointer-to-image-type and pointer-to-sampler-type pair.
|
||||
// Updates the def-use manager and type manager.
|
||||
spv_result_t RemapFunctions();
|
||||
// Remap resource variables.
|
||||
// Updates the def-use manager.
|
||||
spv_result_t RemapVars();
|
||||
// Remap a single resource variable for combined var.
|
||||
// Updates the def-use manager and the decorations manager.
|
||||
spv_result_t RemapVar(Instruction* combined_var);
|
||||
// Transitively remaps uses of the combined object with uses of the
|
||||
// decomposed image and sampler parts. The combined object can be sampled
|
||||
// image value, a pointer to one, an array of one, or a pointer to an array
|
||||
// of one. The image and sampler parts have corresponding shapes.
|
||||
// Updates the def-use manager and the decorations manager.
|
||||
spv_result_t RemapUses(Instruction* combined, Instruction* image_part,
|
||||
Instruction* sampler_part);
|
||||
// Removes types that are no longer referenced.
|
||||
spv_result_t RemoveDeadTypes();
|
||||
|
||||
// Returns the type instruction for a UniformConstant pointer to the given
|
||||
// pointee type. If it does not yet exist, the new type instruction is created
|
||||
// and placed immediately after the pointee type instruction. Updates def-use
|
||||
// and type managers, and the set of known globals.
|
||||
Instruction* MakeUniformConstantPointer(Instruction* pointee);
|
||||
|
||||
// Returns the ID of the pointee type for a pointer value instruction.
|
||||
uint32_t PointeeTypeId(Instruction* ptr_value) {
|
||||
auto* ptr_ty = def_use_mgr_->GetDef(ptr_value->type_id());
|
||||
assert(ptr_ty->opcode() == spv::Op::OpTypePointer);
|
||||
return ptr_ty->GetSingleWordInOperand(1);
|
||||
}
|
||||
|
||||
// Creates a new OpName instruction mapping the given name to the given
|
||||
// string, and adds it to the module at the end of the OpName and OpMemberName
|
||||
// section.
|
||||
void AddOpName(uint32_t id, const std::string& name);
|
||||
|
||||
// Cached from the IRContext. Valid while Process() is running.
|
||||
analysis::DefUseManager* def_use_mgr_ = nullptr;
|
||||
// Cached from the IRContext. Valid while Process() is running.
|
||||
analysis::TypeManager* type_mgr_ = nullptr;
|
||||
|
||||
// Did processing modify the module?
|
||||
bool modified_ = false;
|
||||
Pass::Status Ok() {
|
||||
return modified_ ? Pass::Status::SuccessWithChange
|
||||
: Pass::Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
// The first OpTypeSampledImage instruction in the module, if one exists.
|
||||
Instruction* first_sampled_image_type_ = nullptr;
|
||||
// An OpTypeSampler instruction, if one existed already, or if we created one.
|
||||
Instruction* sampler_type_ = nullptr;
|
||||
|
||||
// The known types and module-scope values.
|
||||
// We use this to know when a new such value was created.
|
||||
std::unordered_set<uint32_t> known_globals_;
|
||||
bool IsKnownGlobal(uint32_t id) const {
|
||||
return known_globals_.find(id) != known_globals_.end();
|
||||
}
|
||||
void RegisterGlobal(uint32_t id) { known_globals_.insert(id); }
|
||||
void RegisterNewGlobal(uint32_t id) {
|
||||
modified_ = true;
|
||||
RegisterGlobal(id);
|
||||
}
|
||||
|
||||
// Deletes an instruction and associated debug and decoration instructions.
|
||||
// Updates the def-use manager.
|
||||
void KillInst(Instruction* inst);
|
||||
|
||||
// Combined types. The known combined sampled-image type,
|
||||
// and recursively pointers or arrays of them.
|
||||
std::unordered_set<uint32_t> combined_types_;
|
||||
// The pre-existing types this pass should remove: pointer to
|
||||
// combined type, array of combined type, pointer to array of combined type.
|
||||
std::vector<uint32_t> combined_types_to_remove_;
|
||||
// Is an OpTypeSampledImage used as a function parameter? Those should be
|
||||
// transformed.
|
||||
bool sampled_image_used_as_param_ = false;
|
||||
|
||||
// Remaps a combined-kind type to corresponding sampler-kind and image-kind
|
||||
// of type.
|
||||
struct TypeRemapInfo {
|
||||
// The instruction for the combined type, pointer to combined type,
|
||||
// or point to array of combined type.
|
||||
Instruction* combined_kind_type;
|
||||
// The corresponding image type, with the same shape of indirection as the
|
||||
// combined_kind_type.
|
||||
Instruction* image_kind_type;
|
||||
// The corresponding sampler type, with the same shape of indirection as the
|
||||
// combined_kind_type.
|
||||
Instruction* sampler_kind_type;
|
||||
};
|
||||
// Maps the ID of a combined-image-sampler type kind to its corresponding
|
||||
// split parts.
|
||||
std::unordered_map<uint32_t, TypeRemapInfo> type_remap_;
|
||||
|
||||
// Returns the image-like and sampler-like types of the same indirection shape
|
||||
// as the given combined-like type. If combined_kind_type is not a combined
|
||||
// type or a pointer to one, or an array of one or a pointer to an array of
|
||||
// one, then returns a pair of null pointer. Either both components are
|
||||
// non-null, or both components are null. Updates the def-use manager and the
|
||||
// type manager if new instructions are created.
|
||||
std::pair<Instruction*, Instruction*> SplitType(
|
||||
Instruction& combined_kind_type);
|
||||
|
||||
// The combined-image-sampler variables to be replaced.
|
||||
std::vector<Instruction*> ordered_vars_;
|
||||
};
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
#endif // LIBSPIRV_OPT_SPLIT_COMBINED_IMAGE_SAMPLER_PASS_H_
|
||||
@@ -241,37 +241,6 @@ Handler_OpTypePointer_StorageUniformBufferBlock16(
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<spv::Capability>
|
||||
Handler_OpTypePointer_StorageBuffer16BitAccess(const Instruction* instruction) {
|
||||
assert(instruction->opcode() == spv::Op::OpTypePointer &&
|
||||
"This handler only support OpTypePointer opcodes.");
|
||||
|
||||
// Requires StorageBuffer, ShaderRecordBufferKHR or PhysicalStorageBuffer
|
||||
// storage classes.
|
||||
spv::StorageClass storage_class = spv::StorageClass(
|
||||
instruction->GetSingleWordInOperand(kOpTypePointerStorageClassIndex));
|
||||
if (storage_class != spv::StorageClass::StorageBuffer &&
|
||||
storage_class != spv::StorageClass::ShaderRecordBufferKHR &&
|
||||
storage_class != spv::StorageClass::PhysicalStorageBuffer) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto* decoration_mgr = instruction->context()->get_decoration_mgr();
|
||||
const bool matchesCondition =
|
||||
AnyTypeOf(instruction, [decoration_mgr](const Instruction* item) {
|
||||
if (!decoration_mgr->HasDecoration(item->result_id(),
|
||||
spv::Decoration::Block)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return AnyTypeOf(item, is16bitType);
|
||||
});
|
||||
|
||||
return matchesCondition
|
||||
? std::optional(spv::Capability::StorageBuffer16BitAccess)
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<spv::Capability> Handler_OpTypePointer_StorageUniform16(
|
||||
const Instruction* instruction) {
|
||||
assert(instruction->opcode() == spv::Op::OpTypePointer &&
|
||||
@@ -419,7 +388,7 @@ Handler_OpImageSparseRead_StorageImageReadWithoutFormat(
|
||||
}
|
||||
|
||||
// Opcode of interest to determine capabilities requirements.
|
||||
constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 14> kOpcodeHandlers{{
|
||||
constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 13> kOpcodeHandlers{{
|
||||
// clang-format off
|
||||
{spv::Op::OpImageRead, Handler_OpImageRead_StorageImageReadWithoutFormat},
|
||||
{spv::Op::OpImageWrite, Handler_OpImageWrite_StorageImageWriteWithoutFormat},
|
||||
@@ -434,7 +403,6 @@ constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 14> kOpcodeHandlers{{
|
||||
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniform16},
|
||||
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniform16},
|
||||
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniformBufferBlock16},
|
||||
{spv::Op::OpTypePointer, Handler_OpTypePointer_StorageBuffer16BitAccess},
|
||||
// clang-format on
|
||||
}};
|
||||
|
||||
|
||||
@@ -99,7 +99,6 @@ class TrimCapabilitiesPass : public Pass {
|
||||
spv::Capability::RayTraversalPrimitiveCullingKHR,
|
||||
spv::Capability::Shader,
|
||||
spv::Capability::ShaderClockKHR,
|
||||
spv::Capability::StorageBuffer16BitAccess,
|
||||
spv::Capability::StorageImageReadWithoutFormat,
|
||||
spv::Capability::StorageImageWriteWithoutFormat,
|
||||
spv::Capability::StorageInputOutput16,
|
||||
|
||||
@@ -495,8 +495,6 @@ uint32_t TypeManager::GetTypeInstruction(const Type* type) {
|
||||
break;
|
||||
}
|
||||
context()->AddType(std::move(typeInst));
|
||||
// TODO(dneto): This next call to AnalyzeDefUse is redundant becaues
|
||||
// IRContext::AddType already does it.
|
||||
context()->AnalyzeDefUse(&*--context()->types_values_end());
|
||||
AttachDecorations(id, type);
|
||||
return id;
|
||||
|
||||
+13
-26
@@ -45,43 +45,28 @@ uint32_t ValueNumberTable::AssignValueNumber(Instruction* inst) {
|
||||
return value;
|
||||
}
|
||||
|
||||
auto assign_new_number = [this](Instruction* i) {
|
||||
const auto new_value = TakeNextValueNumber();
|
||||
id_to_value_[i->result_id()] = new_value;
|
||||
return new_value;
|
||||
};
|
||||
|
||||
// If the instruction has other side effects, then it must
|
||||
// have its own value number.
|
||||
if (!context()->IsCombinatorInstruction(inst) &&
|
||||
!inst->IsCommonDebugInstr()) {
|
||||
return assign_new_number(inst);
|
||||
}
|
||||
|
||||
// OpSampledImage and OpImage must remain in the same basic block in which
|
||||
// they are used, because of this we will assign each one it own value number.
|
||||
if (!context()->IsCombinatorInstruction(inst) &&
|
||||
!inst->IsCommonDebugInstr()) {
|
||||
value = TakeNextValueNumber();
|
||||
id_to_value_[inst->result_id()] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
switch (inst->opcode()) {
|
||||
case spv::Op::OpSampledImage:
|
||||
case spv::Op::OpImage:
|
||||
case spv::Op::OpVariable:
|
||||
return assign_new_number(inst);
|
||||
value = TakeNextValueNumber();
|
||||
id_to_value_[inst->result_id()] = value;
|
||||
return value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// A load that yields an image, sampler, or sampled image must remain in
|
||||
// the same basic block. So assign it its own value number.
|
||||
if (inst->IsLoad()) {
|
||||
switch (context()->get_def_use_mgr()->GetDef(inst->type_id())->opcode()) {
|
||||
case spv::Op::OpTypeSampledImage:
|
||||
case spv::Op::OpTypeImage:
|
||||
case spv::Op::OpTypeSampler:
|
||||
return assign_new_number(inst);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If it is a load from memory that can be modified, we have to assume the
|
||||
// memory has been modified, so we give it a new value number.
|
||||
//
|
||||
@@ -89,7 +74,9 @@ uint32_t ValueNumberTable::AssignValueNumber(Instruction* inst) {
|
||||
// read only. However, if this is ever relaxed because we analyze stores, we
|
||||
// will have to add a new case for volatile loads.
|
||||
if (inst->IsLoad() && !inst->IsReadOnlyLoad()) {
|
||||
return assign_new_number(inst);
|
||||
value = TakeNextValueNumber();
|
||||
id_to_value_[inst->result_id()] = value;
|
||||
return value;
|
||||
}
|
||||
|
||||
analysis::DecorationManager* dec_mgr = context()->get_decoration_mgr();
|
||||
|
||||
@@ -131,11 +131,6 @@ void spvValidatorOptionsSetAllowOffsetTextureOperand(
|
||||
options->allow_offset_texture_operand = val;
|
||||
}
|
||||
|
||||
void spvValidatorOptionsSetAllowVulkan32BitBitwise(
|
||||
spv_validator_options options, bool val) {
|
||||
options->allow_vulkan_32_bit_bitwise = val;
|
||||
}
|
||||
|
||||
void spvValidatorOptionsSetFriendlyNames(spv_validator_options options,
|
||||
bool val) {
|
||||
options->use_friendly_names = val;
|
||||
|
||||
@@ -49,7 +49,6 @@ struct spv_validator_options_t {
|
||||
skip_block_layout(false),
|
||||
allow_localsizeid(false),
|
||||
allow_offset_texture_operand(false),
|
||||
allow_vulkan_32_bit_bitwise(false),
|
||||
before_hlsl_legalization(false),
|
||||
use_friendly_names(true) {}
|
||||
|
||||
@@ -63,7 +62,6 @@ struct spv_validator_options_t {
|
||||
bool skip_block_layout;
|
||||
bool allow_localsizeid;
|
||||
bool allow_offset_texture_operand;
|
||||
bool allow_vulkan_32_bit_bitwise;
|
||||
bool before_hlsl_legalization;
|
||||
bool use_friendly_names;
|
||||
};
|
||||
|
||||
-128
@@ -515,124 +515,6 @@ spv_result_t encodeInstructionStartingWithImmediate(
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
/// @brief Translate an instruction started by OpUnknown and the following
|
||||
/// operands to binary form
|
||||
///
|
||||
/// @param[in] grammar the grammar to use for compilation
|
||||
/// @param[in, out] context the dynamic compilation info
|
||||
/// @param[out] pInst returned binary Opcode
|
||||
///
|
||||
/// @return result code
|
||||
spv_result_t encodeInstructionStartingWithOpUnknown(
|
||||
const spvtools::AssemblyGrammar& grammar,
|
||||
spvtools::AssemblyContext* context, spv_instruction_t* pInst) {
|
||||
spv_position_t nextPosition = {};
|
||||
|
||||
uint16_t opcode;
|
||||
uint16_t wordCount;
|
||||
|
||||
// The '(' character.
|
||||
if (context->advance())
|
||||
return context->diagnostic() << "Expected '(', found end of stream.";
|
||||
if ('(' != context->peek()) {
|
||||
return context->diagnostic() << "'(' expected after OpUnknown but found '"
|
||||
<< context->peek() << "'.";
|
||||
}
|
||||
context->seekForward(1);
|
||||
|
||||
// The opcode enumerant.
|
||||
if (context->advance())
|
||||
return context->diagnostic()
|
||||
<< "Expected opcode enumerant, found end of stream.";
|
||||
std::string opcodeString;
|
||||
spv_result_t error = context->getWord(&opcodeString, &nextPosition);
|
||||
if (error) return context->diagnostic(error) << "Internal Error";
|
||||
|
||||
if (!spvtools::utils::ParseNumber(opcodeString.c_str(), &opcode)) {
|
||||
return context->diagnostic()
|
||||
<< "Invalid opcode enumerant: \"" << opcodeString << "\".";
|
||||
}
|
||||
|
||||
context->setPosition(nextPosition);
|
||||
|
||||
// The ',' character.
|
||||
if (context->advance())
|
||||
return context->diagnostic() << "Expected ',', found end of stream.";
|
||||
if (',' != context->peek()) {
|
||||
return context->diagnostic()
|
||||
<< "',' expected after opcode enumerant but found '"
|
||||
<< context->peek() << "'.";
|
||||
}
|
||||
context->seekForward(1);
|
||||
|
||||
// The number of words.
|
||||
if (context->advance())
|
||||
return context->diagnostic()
|
||||
<< "Expected number of words, found end of stream.";
|
||||
std::string wordCountString;
|
||||
error = context->getWord(&wordCountString, &nextPosition);
|
||||
if (error) return context->diagnostic(error) << "Internal Error";
|
||||
|
||||
if (!spvtools::utils::ParseNumber(wordCountString.c_str(), &wordCount)) {
|
||||
return context->diagnostic()
|
||||
<< "Invalid number of words: \"" << wordCountString << "\".";
|
||||
}
|
||||
|
||||
if (wordCount == 0) {
|
||||
return context->diagnostic() << "Number of words (which includes the "
|
||||
"opcode) must be greater than zero.";
|
||||
}
|
||||
|
||||
context->setPosition(nextPosition);
|
||||
|
||||
// The ')' character.
|
||||
if (context->advance())
|
||||
return context->diagnostic() << "Expected ')', found end of stream.";
|
||||
if (')' != context->peek()) {
|
||||
return context->diagnostic()
|
||||
<< "')' expected after number of words but found '"
|
||||
<< context->peek() << "'.";
|
||||
}
|
||||
context->seekForward(1);
|
||||
|
||||
pInst->opcode = static_cast<spv::Op>(opcode);
|
||||
context->binaryEncodeU32(spvOpcodeMake(wordCount, pInst->opcode), pInst);
|
||||
|
||||
wordCount--; // Subtract the opcode from the number of words left to read.
|
||||
|
||||
while (wordCount-- > 0) {
|
||||
if (context->advance() == SPV_END_OF_STREAM) {
|
||||
return context->diagnostic() << "Expected " << wordCount + 1
|
||||
<< " more operands, found end of stream.";
|
||||
}
|
||||
if (context->isStartOfNewInst()) {
|
||||
std::string invalid;
|
||||
context->getWord(&invalid, &nextPosition);
|
||||
return context->diagnostic()
|
||||
<< "Unexpected start of new instruction: \"" << invalid
|
||||
<< "\". Expected " << wordCount + 1 << " more operands";
|
||||
}
|
||||
|
||||
std::string operandValue;
|
||||
if ((error = context->getWord(&operandValue, &nextPosition)))
|
||||
return context->diagnostic(error) << "Internal Error";
|
||||
|
||||
if (operandValue == "=")
|
||||
return context->diagnostic() << "OpUnknown not allowed before =.";
|
||||
|
||||
// Needed to pass to spvTextEncodeOpcode(), but it shouldn't ever be
|
||||
// expanded.
|
||||
spv_operand_pattern_t dummyExpectedOperands;
|
||||
error = spvTextEncodeOperand(
|
||||
grammar, context, SPV_OPERAND_TYPE_OPTIONAL_CIV, operandValue.c_str(),
|
||||
pInst, &dummyExpectedOperands);
|
||||
if (error) return error;
|
||||
context->setPosition(nextPosition);
|
||||
}
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
/// @brief Translate single Opcode and operands to binary form
|
||||
///
|
||||
/// @param[in] grammar the grammar to use for compilation
|
||||
@@ -692,16 +574,6 @@ spv_result_t spvTextEncodeOpcode(const spvtools::AssemblyGrammar& grammar,
|
||||
}
|
||||
}
|
||||
|
||||
if (opcodeName == "OpUnknown") {
|
||||
if (!result_id.empty()) {
|
||||
return context->diagnostic()
|
||||
<< "OpUnknown not allowed in assignment. Use an explicit result "
|
||||
"id operand instead.";
|
||||
}
|
||||
context->setPosition(nextPosition);
|
||||
return encodeInstructionStartingWithOpUnknown(grammar, context, pInst);
|
||||
}
|
||||
|
||||
// NOTE: The table contains Opcode names without the "Op" prefix.
|
||||
const char* pInstName = opcodeName.data() + 2;
|
||||
|
||||
|
||||
@@ -118,9 +118,6 @@ spv_result_t getWord(spv_text text, spv_position position, std::string* word) {
|
||||
break;
|
||||
case ' ':
|
||||
case ';':
|
||||
case ',':
|
||||
case '(':
|
||||
case ')':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
|
||||
@@ -43,7 +43,6 @@ namespace utils {
|
||||
template <class T, size_t small_size>
|
||||
class SmallVector {
|
||||
public:
|
||||
using value_type = T;
|
||||
using iterator = T*;
|
||||
using const_iterator = const T*;
|
||||
|
||||
|
||||
+6
-8
@@ -17,12 +17,13 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "source/util/string_utils.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace utils {
|
||||
|
||||
@@ -47,9 +48,8 @@ std::pair<std::string, std::string> SplitFlagArgs(const std::string& flag);
|
||||
|
||||
// Encodes a string as a sequence of words, using the SPIR-V encoding, appending
|
||||
// to an existing vector.
|
||||
template <class VectorType = std::vector<uint32_t>>
|
||||
inline void AppendToVector(const std::string& input, VectorType* result) {
|
||||
static_assert(std::is_same<uint32_t, typename VectorType::value_type>::value);
|
||||
inline void AppendToVector(const std::string& input,
|
||||
std::vector<uint32_t>* result) {
|
||||
uint32_t word = 0;
|
||||
size_t num_bytes = input.size();
|
||||
// SPIR-V strings are null-terminated. The byte_index == num_bytes
|
||||
@@ -70,10 +70,8 @@ inline void AppendToVector(const std::string& input, VectorType* result) {
|
||||
}
|
||||
|
||||
// Encodes a string as a sequence of words, using the SPIR-V encoding.
|
||||
template <class VectorType = std::vector<uint32_t>>
|
||||
inline VectorType MakeVector(const std::string& input) {
|
||||
static_assert(std::is_same<uint32_t, typename VectorType::value_type>::value);
|
||||
VectorType result;
|
||||
inline std::vector<uint32_t> MakeVector(const std::string& input) {
|
||||
std::vector<uint32_t> result;
|
||||
AppendToVector(input, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
+2
-2
@@ -30,14 +30,14 @@ spv_result_t ValidateBaseType(ValidationState_t& _, const Instruction* inst,
|
||||
|
||||
if (!_.IsIntScalarType(base_type) && !_.IsIntVectorType(base_type)) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< _.VkErrorID(4781)
|
||||
<< "Expected int scalar or vector type for Base operand: "
|
||||
<< spvOpcodeString(opcode);
|
||||
}
|
||||
|
||||
// Vulkan has a restriction to 32 bit for base
|
||||
if (spvIsVulkanEnv(_.context()->target_env)) {
|
||||
if (_.GetBitWidth(base_type) != 32 &&
|
||||
!_.options()->allow_vulkan_32_bit_bitwise) {
|
||||
if (_.GetBitWidth(base_type) != 32) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< _.VkErrorID(4781)
|
||||
<< "Expected 32-bit int type for Base operand: "
|
||||
|
||||
+1
-209
@@ -509,15 +509,6 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
|
||||
// Check offset.
|
||||
if (offset == 0xffffffff)
|
||||
return fail(memberIdx) << "is missing an Offset decoration";
|
||||
|
||||
if (opcode == spv::Op::OpTypeRuntimeArray &&
|
||||
ordered_member_idx != member_offsets.size() - 1) {
|
||||
return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(struct_id))
|
||||
<< vstate.VkErrorID(4680) << "Structure id " << struct_id
|
||||
<< " has a runtime array at offset " << offset
|
||||
<< ", but other members at larger offsets";
|
||||
}
|
||||
|
||||
if (!scalar_block_layout && relaxed_block_layout &&
|
||||
opcode == spv::Op::OpTypeVector) {
|
||||
// In relaxed block layout, the vector offset must be aligned to the
|
||||
@@ -1925,7 +1916,7 @@ spv_result_t CheckComponentDecoration(ValidationState_t& vstate,
|
||||
if (!vstate.IsIntScalarOrVectorType(type_id) &&
|
||||
!vstate.IsFloatScalarOrVectorType(type_id)) {
|
||||
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
|
||||
<< vstate.VkErrorID(10583)
|
||||
<< vstate.VkErrorID(4924)
|
||||
<< "Component decoration specified for type "
|
||||
<< vstate.getIdName(type_id) << " that is not a scalar or vector";
|
||||
}
|
||||
@@ -2092,204 +2083,6 @@ spv_result_t CheckDecorationsFromDecoration(ValidationState_t& vstate) {
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
bool AllowsLayout(ValidationState_t& vstate, const spv::StorageClass sc) {
|
||||
switch (sc) {
|
||||
case spv::StorageClass::StorageBuffer:
|
||||
case spv::StorageClass::Uniform:
|
||||
case spv::StorageClass::PhysicalStorageBuffer:
|
||||
case spv::StorageClass::PushConstant:
|
||||
// Always explicitly laid out.
|
||||
return true;
|
||||
case spv::StorageClass::UniformConstant:
|
||||
return false;
|
||||
case spv::StorageClass::Workgroup:
|
||||
return vstate.HasCapability(
|
||||
spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
|
||||
case spv::StorageClass::Function:
|
||||
case spv::StorageClass::Private:
|
||||
return vstate.version() <= SPV_SPIRV_VERSION_WORD(1, 4);
|
||||
case spv::StorageClass::Input:
|
||||
case spv::StorageClass::Output:
|
||||
// Block is used generally and mesh shaders use Offset.
|
||||
return true;
|
||||
default:
|
||||
// TODO: Some storage classes in ray tracing use explicit layout
|
||||
// decorations, but it is not well documented which. For now treat other
|
||||
// storage classes as allowed to be laid out. See Vulkan internal issue
|
||||
// 4192.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool UsesExplicitLayout(ValidationState_t& vstate, uint32_t type_id,
|
||||
std::unordered_map<uint32_t, bool>& cache) {
|
||||
if (type_id == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cache.count(type_id)) {
|
||||
return cache[type_id];
|
||||
}
|
||||
|
||||
bool res = false;
|
||||
const auto type_inst = vstate.FindDef(type_id);
|
||||
if (type_inst->opcode() == spv::Op::OpTypeStruct ||
|
||||
type_inst->opcode() == spv::Op::OpTypeArray ||
|
||||
type_inst->opcode() == spv::Op::OpTypeRuntimeArray ||
|
||||
type_inst->opcode() == spv::Op::OpTypePointer ||
|
||||
type_inst->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
|
||||
const auto& id_decs = vstate.id_decorations();
|
||||
const auto iter = id_decs.find(type_id);
|
||||
if (iter != id_decs.end()) {
|
||||
bool allowLayoutDecorations = false;
|
||||
if (type_inst->opcode() == spv::Op::OpTypePointer) {
|
||||
const auto sc = type_inst->GetOperandAs<spv::StorageClass>(1);
|
||||
allowLayoutDecorations = AllowsLayout(vstate, sc);
|
||||
}
|
||||
if (!allowLayoutDecorations) {
|
||||
res = std::any_of(
|
||||
iter->second.begin(), iter->second.end(), [](const Decoration& d) {
|
||||
return d.dec_type() == spv::Decoration::Block ||
|
||||
d.dec_type() == spv::Decoration::BufferBlock ||
|
||||
d.dec_type() == spv::Decoration::Offset ||
|
||||
d.dec_type() == spv::Decoration::ArrayStride ||
|
||||
d.dec_type() == spv::Decoration::MatrixStride;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
switch (type_inst->opcode()) {
|
||||
case spv::Op::OpTypeStruct:
|
||||
for (uint32_t i = 1; !res && i < type_inst->operands().size(); i++) {
|
||||
res = UsesExplicitLayout(
|
||||
vstate, type_inst->GetOperandAs<uint32_t>(i), cache);
|
||||
}
|
||||
break;
|
||||
case spv::Op::OpTypeArray:
|
||||
case spv::Op::OpTypeRuntimeArray:
|
||||
res = UsesExplicitLayout(vstate, type_inst->GetOperandAs<uint32_t>(1),
|
||||
cache);
|
||||
break;
|
||||
case spv::Op::OpTypePointer: {
|
||||
const auto sc = type_inst->GetOperandAs<spv::StorageClass>(1);
|
||||
if (!AllowsLayout(vstate, sc)) {
|
||||
res = UsesExplicitLayout(
|
||||
vstate, type_inst->GetOperandAs<uint32_t>(2), cache);
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache[type_id] = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
spv_result_t CheckInvalidVulkanExplicitLayout(ValidationState_t& vstate) {
|
||||
if (!spvIsVulkanEnv(vstate.context()->target_env)) {
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
std::unordered_map<uint32_t, bool> cache;
|
||||
for (const auto& inst : vstate.ordered_instructions()) {
|
||||
const auto type_id = inst.type_id();
|
||||
const auto type_inst = vstate.FindDef(type_id);
|
||||
uint32_t fail_id = 0;
|
||||
// Variables are the main place to check for improper decorations, but some
|
||||
// untyped pointer instructions must also be checked since those types may
|
||||
// never be instantiated by a variable. Unlike verifying a valid layout,
|
||||
// physical storage buffer does not need checked here since it is always
|
||||
// explicitly laid out.
|
||||
switch (inst.opcode()) {
|
||||
case spv::Op::OpVariable:
|
||||
case spv::Op::OpUntypedVariableKHR: {
|
||||
const auto sc = inst.GetOperandAs<spv::StorageClass>(2);
|
||||
auto check_id = type_id;
|
||||
if (inst.opcode() == spv::Op::OpUntypedVariableKHR) {
|
||||
if (inst.operands().size() > 3) {
|
||||
check_id = inst.GetOperandAs<uint32_t>(3);
|
||||
}
|
||||
}
|
||||
if (!AllowsLayout(vstate, sc) &&
|
||||
UsesExplicitLayout(vstate, check_id, cache)) {
|
||||
fail_id = check_id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpUntypedAccessChainKHR:
|
||||
case spv::Op::OpUntypedInBoundsAccessChainKHR:
|
||||
case spv::Op::OpUntypedPtrAccessChainKHR:
|
||||
case spv::Op::OpUntypedInBoundsPtrAccessChainKHR: {
|
||||
// Check both the base type and return type. The return type may have an
|
||||
// invalid array stride.
|
||||
const auto sc = type_inst->GetOperandAs<spv::StorageClass>(1);
|
||||
const auto base_type_id = inst.GetOperandAs<uint32_t>(2);
|
||||
if (!AllowsLayout(vstate, sc)) {
|
||||
if (UsesExplicitLayout(vstate, base_type_id, cache)) {
|
||||
fail_id = base_type_id;
|
||||
} else if (UsesExplicitLayout(vstate, type_id, cache)) {
|
||||
fail_id = type_id;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpUntypedArrayLengthKHR: {
|
||||
// Check the data type.
|
||||
const auto ptr_ty_id =
|
||||
vstate.FindDef(inst.GetOperandAs<uint32_t>(3))->type_id();
|
||||
const auto ptr_ty = vstate.FindDef(ptr_ty_id);
|
||||
const auto sc = ptr_ty->GetOperandAs<spv::StorageClass>(1);
|
||||
const auto base_type_id = inst.GetOperandAs<uint32_t>(2);
|
||||
if (!AllowsLayout(vstate, sc) &&
|
||||
UsesExplicitLayout(vstate, base_type_id, cache)) {
|
||||
fail_id = base_type_id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpLoad: {
|
||||
const auto ptr_id = inst.GetOperandAs<uint32_t>(2);
|
||||
const auto ptr_type = vstate.FindDef(vstate.FindDef(ptr_id)->type_id());
|
||||
if (ptr_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
|
||||
// For untyped pointers check the return type for an invalid layout.
|
||||
const auto sc = ptr_type->GetOperandAs<spv::StorageClass>(1);
|
||||
if (!AllowsLayout(vstate, sc) &&
|
||||
UsesExplicitLayout(vstate, type_id, cache)) {
|
||||
fail_id = type_id;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpStore: {
|
||||
const auto ptr_id = inst.GetOperandAs<uint32_t>(1);
|
||||
const auto ptr_type = vstate.FindDef(vstate.FindDef(ptr_id)->type_id());
|
||||
if (ptr_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) {
|
||||
// For untyped pointers, check the type of the data operand for an
|
||||
// invalid layout.
|
||||
const auto sc = ptr_type->GetOperandAs<spv::StorageClass>(1);
|
||||
const auto data_type_id = vstate.GetOperandTypeId(&inst, 2);
|
||||
if (!AllowsLayout(vstate, sc) &&
|
||||
UsesExplicitLayout(vstate, data_type_id, cache)) {
|
||||
fail_id = inst.GetOperandAs<uint32_t>(2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (fail_id != 0) {
|
||||
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
|
||||
<< "Invalid explicit layout decorations on type for operand "
|
||||
<< vstate.getIdName(fail_id);
|
||||
}
|
||||
}
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
spv_result_t ValidateDecorations(ValidationState_t& vstate) {
|
||||
@@ -2301,7 +2094,6 @@ spv_result_t ValidateDecorations(ValidationState_t& vstate) {
|
||||
if (auto error = CheckVulkanMemoryModelDeprecatedDecorations(vstate))
|
||||
return error;
|
||||
if (auto error = CheckDecorationsFromDecoration(vstate)) return error;
|
||||
if (auto error = CheckInvalidVulkanExplicitLayout(vstate)) return error;
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -258,8 +258,7 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
|
||||
_.HasCapability(spv::Capability::VariablePointers) &&
|
||||
sc == spv::StorageClass::Workgroup;
|
||||
const bool uc_ptr = sc == spv::StorageClass::UniformConstant;
|
||||
if (!_.options()->before_hlsl_legalization && !ssbo_vptr &&
|
||||
!wg_vptr && !uc_ptr) {
|
||||
if (!ssbo_vptr && !wg_vptr && !uc_ptr) {
|
||||
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
||||
<< "Pointer operand " << _.getIdName(argument_id)
|
||||
<< " must be a memory object declaration";
|
||||
|
||||
+84
-101
@@ -48,29 +48,6 @@ bool is_interface_variable(const Instruction* inst, bool is_spv_1_4) {
|
||||
}
|
||||
}
|
||||
|
||||
// Special validation for varibles that are between shader stages
|
||||
spv_result_t ValidateInputOutputInterfaceVariables(ValidationState_t& _,
|
||||
const Instruction* var) {
|
||||
auto var_pointer = _.FindDef(var->GetOperandAs<uint32_t>(0));
|
||||
uint32_t pointer_id = var_pointer->GetOperandAs<uint32_t>(2);
|
||||
|
||||
const auto isPhysicalStorageBuffer = [](const Instruction* insn) {
|
||||
return insn->opcode() == spv::Op::OpTypePointer &&
|
||||
insn->GetOperandAs<spv::StorageClass>(1) ==
|
||||
spv::StorageClass::PhysicalStorageBuffer;
|
||||
};
|
||||
|
||||
if (_.ContainsType(pointer_id, isPhysicalStorageBuffer)) {
|
||||
return _.diag(SPV_ERROR_INVALID_ID, var)
|
||||
<< _.VkErrorID(9557) << "Input/Output interface variable id <"
|
||||
<< var->id()
|
||||
<< "> contains a PhysicalStorageBuffer pointer, which is not "
|
||||
"allowed. If you want to interface shader stages with a "
|
||||
"PhysicalStorageBuffer, cast to a uint64 or uvec2 instead.";
|
||||
}
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
// Checks that \c var is listed as an interface in all the entry points that use
|
||||
// it.
|
||||
spv_result_t check_interface_variable(ValidationState_t& _,
|
||||
@@ -130,12 +107,6 @@ spv_result_t check_interface_variable(ValidationState_t& _,
|
||||
}
|
||||
}
|
||||
|
||||
if (var->GetOperandAs<spv::StorageClass>(2) == spv::StorageClass::Input ||
|
||||
var->GetOperandAs<spv::StorageClass>(2) == spv::StorageClass::Output) {
|
||||
if (auto error = ValidateInputOutputInterfaceVariables(_, var))
|
||||
return error;
|
||||
}
|
||||
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -164,20 +135,17 @@ spv_result_t NumConsumedLocations(ValidationState_t& _, const Instruction* type,
|
||||
}
|
||||
break;
|
||||
case spv::Op::OpTypeMatrix:
|
||||
// Matrices consume locations equivalent to arrays.
|
||||
if (auto error = NumConsumedLocations(
|
||||
_, _.FindDef(type->GetOperandAs<uint32_t>(1)), num_locations)) {
|
||||
return error;
|
||||
}
|
||||
// Matrices consume locations equal to the underlying vector type for
|
||||
// each column.
|
||||
NumConsumedLocations(_, _.FindDef(type->GetOperandAs<uint32_t>(1)),
|
||||
num_locations);
|
||||
*num_locations *= type->GetOperandAs<uint32_t>(2);
|
||||
break;
|
||||
case spv::Op::OpTypeArray: {
|
||||
// Arrays consume locations equal to the underlying type times the number
|
||||
// of elements in the vector.
|
||||
if (auto error = NumConsumedLocations(
|
||||
_, _.FindDef(type->GetOperandAs<uint32_t>(1)), num_locations)) {
|
||||
return error;
|
||||
}
|
||||
NumConsumedLocations(_, _.FindDef(type->GetOperandAs<uint32_t>(1)),
|
||||
num_locations);
|
||||
bool is_int = false;
|
||||
bool is_const = false;
|
||||
uint32_t value = 0;
|
||||
@@ -247,31 +215,10 @@ uint32_t NumConsumedComponents(ValidationState_t& _, const Instruction* type) {
|
||||
NumConsumedComponents(_, _.FindDef(type->GetOperandAs<uint32_t>(1)));
|
||||
num_components *= type->GetOperandAs<uint32_t>(2);
|
||||
break;
|
||||
case spv::Op::OpTypeMatrix:
|
||||
// Matrices consume all components of the location.
|
||||
// Round up to next multiple of 4.
|
||||
num_components =
|
||||
NumConsumedComponents(_, _.FindDef(type->GetOperandAs<uint32_t>(1)));
|
||||
num_components *= type->GetOperandAs<uint32_t>(2);
|
||||
num_components = ((num_components + 3) / 4) * 4;
|
||||
break;
|
||||
case spv::Op::OpTypeArray: {
|
||||
// Arrays consume all components of the location.
|
||||
// Round up to next multiple of 4.
|
||||
num_components =
|
||||
NumConsumedComponents(_, _.FindDef(type->GetOperandAs<uint32_t>(1)));
|
||||
|
||||
bool is_int = false;
|
||||
bool is_const = false;
|
||||
uint32_t value = 0;
|
||||
// Attempt to evaluate the number of array elements.
|
||||
std::tie(is_int, is_const, value) =
|
||||
_.EvalInt32IfConst(type->GetOperandAs<uint32_t>(2));
|
||||
if (is_int && is_const) num_components *= value;
|
||||
|
||||
num_components = ((num_components + 3) / 4) * 4;
|
||||
return num_components;
|
||||
}
|
||||
case spv::Op::OpTypeArray:
|
||||
// Skip the array.
|
||||
return NumConsumedComponents(_,
|
||||
_.FindDef(type->GetOperandAs<uint32_t>(1)));
|
||||
case spv::Op::OpTypePointer:
|
||||
if (_.addressing_model() ==
|
||||
spv::AddressingModel::PhysicalStorageBuffer64 &&
|
||||
@@ -354,10 +301,9 @@ spv_result_t GetLocationsForVariable(
|
||||
}
|
||||
}
|
||||
|
||||
// Vulkan 15.1.3 (Interface Matching): Tessellation control and mesh
|
||||
// per-vertex outputs and tessellation control, evaluation and geometry
|
||||
// per-vertex inputs have a layer of arraying that is not included in
|
||||
// interface matching.
|
||||
// Vulkan 14.1.3: Tessellation control and mesh per-vertex outputs and
|
||||
// tessellation control, evaluation and geometry per-vertex inputs have a
|
||||
// layer of arraying that is not included in interface matching.
|
||||
bool is_arrayed = false;
|
||||
switch (entry_point->GetOperandAs<spv::ExecutionModel>(0)) {
|
||||
case spv::ExecutionModel::TessellationControl:
|
||||
@@ -411,33 +357,51 @@ spv_result_t GetLocationsForVariable(
|
||||
|
||||
const std::string storage_class = is_output ? "output" : "input";
|
||||
if (has_location) {
|
||||
auto sub_type = type;
|
||||
bool is_int = false;
|
||||
bool is_const = false;
|
||||
uint32_t array_size = 1;
|
||||
// If the variable is still arrayed, mark the locations/components per
|
||||
// index.
|
||||
if (type->opcode() == spv::Op::OpTypeArray) {
|
||||
// Determine the array size if possible and get the element type.
|
||||
std::tie(is_int, is_const, array_size) =
|
||||
_.EvalInt32IfConst(type->GetOperandAs<uint32_t>(2));
|
||||
if (!is_int || !is_const) array_size = 1;
|
||||
auto sub_type_id = type->GetOperandAs<uint32_t>(1);
|
||||
sub_type = _.FindDef(sub_type_id);
|
||||
}
|
||||
|
||||
uint32_t num_locations = 0;
|
||||
if (auto error = NumConsumedLocations(_, type, &num_locations))
|
||||
if (auto error = NumConsumedLocations(_, sub_type, &num_locations))
|
||||
return error;
|
||||
uint32_t num_components = NumConsumedComponents(_, type);
|
||||
uint32_t num_components = NumConsumedComponents(_, sub_type);
|
||||
|
||||
uint32_t start = location * 4;
|
||||
uint32_t end = (location + num_locations) * 4;
|
||||
if (num_components % 4 != 0) {
|
||||
start += component;
|
||||
end = start + num_components;
|
||||
}
|
||||
for (uint32_t array_idx = 0; array_idx < array_size; ++array_idx) {
|
||||
uint32_t array_location = location + (num_locations * array_idx);
|
||||
uint32_t start = array_location * 4;
|
||||
if (kMaxLocations <= start) {
|
||||
// Too many locations, give up.
|
||||
break;
|
||||
}
|
||||
|
||||
if (kMaxLocations <= start) {
|
||||
// Too many locations, give up.
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
uint32_t end = (array_location + num_locations) * 4;
|
||||
if (num_components != 0) {
|
||||
start += component;
|
||||
end = array_location * 4 + component + num_components;
|
||||
}
|
||||
|
||||
auto locs = locations;
|
||||
if (has_index && index == 1) locs = output_index1_locations;
|
||||
auto locs = locations;
|
||||
if (has_index && index == 1) locs = output_index1_locations;
|
||||
|
||||
for (uint32_t i = start; i < end; ++i) {
|
||||
if (!locs->insert(i).second) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
|
||||
<< (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
|
||||
<< "Entry-point has conflicting " << storage_class
|
||||
<< " location assignment at location " << i / 4 << ", component "
|
||||
<< i % 4;
|
||||
for (uint32_t i = start; i < end; ++i) {
|
||||
if (!locs->insert(i).second) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
|
||||
<< (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
|
||||
<< "Entry-point has conflicting " << storage_class
|
||||
<< " location assignment at location " << i / 4
|
||||
<< ", component " << i % 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -496,19 +460,38 @@ spv_result_t GetLocationsForVariable(
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t end = (location + num_locations) * 4;
|
||||
if (num_components % 4 != 0) {
|
||||
start += component;
|
||||
end = location * 4 + component + num_components;
|
||||
}
|
||||
|
||||
for (uint32_t l = start; l < end; ++l) {
|
||||
if (!locations->insert(l).second) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
|
||||
<< (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
|
||||
<< "Entry-point has conflicting " << storage_class
|
||||
<< " location assignment at location " << l / 4
|
||||
<< ", component " << l % 4;
|
||||
if (member->opcode() == spv::Op::OpTypeArray && num_components >= 1 &&
|
||||
num_components < 4) {
|
||||
// When an array has an element that takes less than a location in
|
||||
// size, calculate the used locations in a strided manner.
|
||||
for (uint32_t l = location; l < num_locations + location; ++l) {
|
||||
for (uint32_t c = component; c < component + num_components; ++c) {
|
||||
uint32_t check = 4 * l + c;
|
||||
if (!locations->insert(check).second) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
|
||||
<< (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
|
||||
<< "Entry-point has conflicting " << storage_class
|
||||
<< " location assignment at location " << l
|
||||
<< ", component " << c;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: There is a hole here is the member is an array of 3- or
|
||||
// 4-element vectors of 64-bit types.
|
||||
uint32_t end = (location + num_locations) * 4;
|
||||
if (num_components != 0) {
|
||||
start += component;
|
||||
end = location * 4 + component + num_components;
|
||||
}
|
||||
for (uint32_t l = start; l < end; ++l) {
|
||||
if (!locations->insert(l).second) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, entry_point)
|
||||
<< (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721))
|
||||
<< "Entry-point has conflicting " << storage_class
|
||||
<< " location assignment at location " << l / 4
|
||||
<< ", component " << l % 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-39
@@ -380,16 +380,6 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
|
||||
<< _.VkErrorID(4708)
|
||||
<< "Memory accesses with PhysicalStorageBuffer must use Aligned.";
|
||||
}
|
||||
} else {
|
||||
// even if there are other masks, the Aligned operand will be next
|
||||
const uint32_t aligned_value = inst->GetOperandAs<uint32_t>(index + 1);
|
||||
const bool is_power_of_two =
|
||||
aligned_value && !(aligned_value & (aligned_value - 1));
|
||||
if (!is_power_of_two) {
|
||||
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
||||
<< "Memory accesses Aligned operand value " << aligned_value
|
||||
<< " is not a power of two.";
|
||||
}
|
||||
}
|
||||
|
||||
return SPV_SUCCESS;
|
||||
@@ -2791,42 +2781,17 @@ spv_result_t ValidatePtrComparison(ValidationState_t& _,
|
||||
|
||||
const auto op1 = _.FindDef(inst->GetOperandAs<uint32_t>(2u));
|
||||
const auto op2 = _.FindDef(inst->GetOperandAs<uint32_t>(3u));
|
||||
if (!op1 || !op2 || op1->type_id() != op2->type_id()) {
|
||||
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
||||
<< "The types of Operand 1 and Operand 2 must match";
|
||||
}
|
||||
const auto op1_type = _.FindDef(op1->type_id());
|
||||
const auto op2_type = _.FindDef(op2->type_id());
|
||||
if (!op1_type || (op1_type->opcode() != spv::Op::OpTypePointer &&
|
||||
op1_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
|
||||
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
||||
<< "Operand type must be a pointer";
|
||||
}
|
||||
|
||||
if (!op2_type || (op2_type->opcode() != spv::Op::OpTypePointer &&
|
||||
op2_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) {
|
||||
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
||||
<< "Operand type must be a pointer";
|
||||
}
|
||||
|
||||
if (inst->opcode() == spv::Op::OpPtrDiff) {
|
||||
if (op1->type_id() != op2->type_id()) {
|
||||
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
||||
<< "The types of Operand 1 and Operand 2 must match";
|
||||
}
|
||||
} else {
|
||||
const auto either_untyped =
|
||||
op1_type->opcode() == spv::Op::OpTypeUntypedPointerKHR ||
|
||||
op2_type->opcode() == spv::Op::OpTypeUntypedPointerKHR;
|
||||
if (either_untyped) {
|
||||
const auto sc1 = op1_type->GetOperandAs<spv::StorageClass>(1);
|
||||
const auto sc2 = op2_type->GetOperandAs<spv::StorageClass>(1);
|
||||
if (sc1 != sc2) {
|
||||
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
||||
<< "Pointer storage classes must match";
|
||||
}
|
||||
} else if (op1->type_id() != op2->type_id()) {
|
||||
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
||||
<< "The types of Operand 1 and Operand 2 must match";
|
||||
}
|
||||
}
|
||||
|
||||
spv::StorageClass sc = op1_type->GetOperandAs<spv::StorageClass>(1u);
|
||||
if (_.addressing_model() == spv::AddressingModel::Logical) {
|
||||
if (sc != spv::StorageClass::Workgroup &&
|
||||
|
||||
@@ -66,7 +66,8 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _,
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< spvOpcodeString(opcode)
|
||||
<< ": Memory Semantics can have at most one of the following "
|
||||
"bits set: Acquire, Release, AcquireRelease or "
|
||||
"bits "
|
||||
"set: Acquire, Release, AcquireRelease or "
|
||||
"SequentiallyConsistent";
|
||||
}
|
||||
|
||||
@@ -175,8 +176,10 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _,
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< _.VkErrorID(4732) << spvOpcodeString(opcode)
|
||||
<< ": Vulkan specification requires Memory Semantics to have "
|
||||
"one of the following bits set: Acquire, Release, "
|
||||
"AcquireRelease or SequentiallyConsistent";
|
||||
"one "
|
||||
"of the following bits set: Acquire, Release, "
|
||||
"AcquireRelease "
|
||||
"or SequentiallyConsistent";
|
||||
} else if (opcode != spv::Op::OpMemoryBarrier &&
|
||||
num_memory_order_set_bits) {
|
||||
// should leave only atomics and control barriers for Vulkan env
|
||||
@@ -200,20 +203,11 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _,
|
||||
"storage class";
|
||||
}
|
||||
|
||||
if (opcode == spv::Op::OpControlBarrier && value) {
|
||||
if (!num_memory_order_set_bits) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< _.VkErrorID(10609) << spvOpcodeString(opcode)
|
||||
<< ": Vulkan specification requires non-zero Memory Semantics "
|
||||
"to have one of the following bits set: Acquire, Release, "
|
||||
"AcquireRelease or SequentiallyConsistent";
|
||||
}
|
||||
if (!includes_storage_class) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< _.VkErrorID(4650) << spvOpcodeString(opcode)
|
||||
<< ": expected Memory Semantics to include a Vulkan-supported "
|
||||
"storage class if Memory Semantics is not None";
|
||||
}
|
||||
if (opcode == spv::Op::OpControlBarrier && value && !includes_storage_class) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< _.VkErrorID(4650) << spvOpcodeString(opcode)
|
||||
<< ": expected Memory Semantics to include a Vulkan-supported "
|
||||
"storage class if Memory Semantics is not None";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-6
@@ -2311,8 +2311,6 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-None-04644);
|
||||
case 4645:
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-None-04645);
|
||||
case 10609:
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-10609);
|
||||
case 4650:
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-04650);
|
||||
case 4651:
|
||||
@@ -2417,6 +2415,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-Component-04922);
|
||||
case 4923:
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-Component-04923);
|
||||
case 4924:
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-Component-04924);
|
||||
case 6201:
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-Flat-06201);
|
||||
case 6202:
|
||||
@@ -2525,8 +2525,6 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-OpEntryPoint-08722);
|
||||
case 8973:
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-Pointer-08973);
|
||||
case 9557:
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-Input-09557);
|
||||
case 9638:
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-OpTypeImage-09638);
|
||||
case 9658:
|
||||
@@ -2536,8 +2534,6 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
|
||||
case 10213:
|
||||
// This use to be a standalone, but maintenance8 will set allow_offset_texture_operand now
|
||||
return VUID_WRAP(VUID-RuntimeSpirv-Offset-10213);
|
||||
case 10583:
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-Component-10583);
|
||||
default:
|
||||
return ""; // unknown id
|
||||
}
|
||||
|
||||
-1
@@ -116,7 +116,6 @@ set(TEST_SOURCES
|
||||
libspirv_macros_test.cpp
|
||||
named_id_test.cpp
|
||||
name_mapper_test.cpp
|
||||
op_unknown_test.cpp
|
||||
opcode_make_test.cpp
|
||||
opcode_require_capabilities_test.cpp
|
||||
opcode_split_test.cpp
|
||||
|
||||
@@ -30,8 +30,6 @@ list(APPEND DIFF_TEST_FILES
|
||||
"diff_files/different_decorations_vertex_autogen.cpp"
|
||||
"diff_files/different_function_parameter_count_autogen.cpp"
|
||||
"diff_files/extra_if_block_autogen.cpp"
|
||||
"diff_files/function_group_by_full_type_autogen.cpp"
|
||||
"diff_files/function_group_by_mapped_id_autogen.cpp"
|
||||
"diff_files/index_signedness_autogen.cpp"
|
||||
"diff_files/int_vs_uint_constants_autogen.cpp"
|
||||
"diff_files/large_functions_large_diffs_autogen.cpp"
|
||||
@@ -46,6 +44,5 @@ list(APPEND DIFF_TEST_FILES
|
||||
"diff_files/spec_constant_composite_autogen.cpp"
|
||||
"diff_files/spec_constant_op_autogen.cpp"
|
||||
"diff_files/spec_constant_specid_autogen.cpp"
|
||||
"diff_files/string_in_ext_inst_autogen.cpp"
|
||||
"diff_files/unrelated_shaders_autogen.cpp"
|
||||
)
|
||||
|
||||
-194
@@ -1,194 +0,0 @@
|
||||
// GENERATED FILE - DO NOT EDIT.
|
||||
// Generated by generate_tests.py
|
||||
//
|
||||
// Copyright (c) 2022 Google LLC.
|
||||
//
|
||||
// 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 "../diff_test_utils.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace diff {
|
||||
namespace {
|
||||
|
||||
// Use functions' `OpFunctionType` to group before grouping by return type only.
|
||||
constexpr char kSrc[] = R"( OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%bool = OpTypeBool
|
||||
%int = OpTypeInt 32 1
|
||||
%int_0 = OpConstantNull %int
|
||||
|
||||
%fn_int = OpTypeFunction %bool %int
|
||||
%fn_int_int = OpTypeFunction %bool %int %int
|
||||
|
||||
%f = OpFunction %bool None %fn_int
|
||||
%fp1 = OpFunctionParameter %int
|
||||
%f_prologue = OpLabel
|
||||
%fr = OpIEqual %bool %fp1 %int_0
|
||||
OpReturnValue %fr
|
||||
OpFunctionEnd
|
||||
|
||||
%g = OpFunction %bool None %fn_int_int
|
||||
%gp1 = OpFunctionParameter %int
|
||||
%gp2 = OpFunctionParameter %int
|
||||
%g_prologue = OpLabel
|
||||
%gr = OpIEqual %bool %gp1 %int_0
|
||||
OpReturnValue %gr
|
||||
OpFunctionEnd)";
|
||||
constexpr char kDst[] =
|
||||
R"(;; Use functions' `OpFunctionType` to group before grouping by return type only.
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%bool = OpTypeBool
|
||||
%int = OpTypeInt 32 1
|
||||
%int_0 = OpConstantNull %int
|
||||
|
||||
%fn_int = OpTypeFunction %bool %int
|
||||
%fn_int_int = OpTypeFunction %bool %int %int
|
||||
|
||||
%g = OpFunction %bool None %fn_int_int
|
||||
%gp1 = OpFunctionParameter %int
|
||||
%gp2 = OpFunctionParameter %int
|
||||
%g_prologue = OpLabel
|
||||
%gr = OpIEqual %bool %gp1 %int_0
|
||||
OpReturnValue %gr
|
||||
OpFunctionEnd
|
||||
|
||||
%f = OpFunction %bool None %fn_int
|
||||
%fp1 = OpFunctionParameter %int
|
||||
%f_prologue = OpLabel
|
||||
%fr = OpIEqual %bool %fp1 %int_0
|
||||
OpReturnValue %fr
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
TEST(DiffTest, FunctionGroupByFullType) {
|
||||
constexpr char kDiff[] = R"( ; SPIR-V
|
||||
; Version: 1.6
|
||||
; Generator: Khronos SPIR-V Tools Assembler; 0
|
||||
; Bound: 15
|
||||
; Schema: 0
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%1 = OpTypeBool
|
||||
%2 = OpTypeInt 32 1
|
||||
%3 = OpConstantNull %2
|
||||
%4 = OpTypeFunction %1 %2
|
||||
%5 = OpTypeFunction %1 %2 %2
|
||||
%6 = OpFunction %1 None %4
|
||||
%7 = OpFunctionParameter %2
|
||||
%8 = OpLabel
|
||||
%9 = OpIEqual %1 %7 %3
|
||||
OpReturnValue %9
|
||||
OpFunctionEnd
|
||||
%10 = OpFunction %1 None %5
|
||||
%11 = OpFunctionParameter %2
|
||||
%12 = OpFunctionParameter %2
|
||||
%13 = OpLabel
|
||||
%14 = OpIEqual %1 %11 %3
|
||||
OpReturnValue %14
|
||||
OpFunctionEnd
|
||||
)";
|
||||
Options options;
|
||||
DoStringDiffTest(kSrc, kDst, kDiff, options);
|
||||
}
|
||||
|
||||
TEST(DiffTest, FunctionGroupByFullTypeNoDebug) {
|
||||
constexpr char kSrcNoDebug[] = R"( OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%bool = OpTypeBool
|
||||
%int = OpTypeInt 32 1
|
||||
%int_0 = OpConstantNull %int
|
||||
|
||||
%fn_int = OpTypeFunction %bool %int
|
||||
%fn_int_int = OpTypeFunction %bool %int %int
|
||||
|
||||
%f = OpFunction %bool None %fn_int
|
||||
%fp1 = OpFunctionParameter %int
|
||||
%f_prologue = OpLabel
|
||||
%fr = OpIEqual %bool %fp1 %int_0
|
||||
OpReturnValue %fr
|
||||
OpFunctionEnd
|
||||
|
||||
%g = OpFunction %bool None %fn_int_int
|
||||
%gp1 = OpFunctionParameter %int
|
||||
%gp2 = OpFunctionParameter %int
|
||||
%g_prologue = OpLabel
|
||||
%gr = OpIEqual %bool %gp1 %int_0
|
||||
OpReturnValue %gr
|
||||
OpFunctionEnd
|
||||
)";
|
||||
constexpr char kDstNoDebug[] = R"( OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%bool = OpTypeBool
|
||||
%int = OpTypeInt 32 1
|
||||
%int_0 = OpConstantNull %int
|
||||
|
||||
%fn_int = OpTypeFunction %bool %int
|
||||
%fn_int_int = OpTypeFunction %bool %int %int
|
||||
|
||||
%g = OpFunction %bool None %fn_int_int
|
||||
%gp1 = OpFunctionParameter %int
|
||||
%gp2 = OpFunctionParameter %int
|
||||
%g_prologue = OpLabel
|
||||
%gr = OpIEqual %bool %gp1 %int_0
|
||||
OpReturnValue %gr
|
||||
OpFunctionEnd
|
||||
|
||||
%f = OpFunction %bool None %fn_int
|
||||
%fp1 = OpFunctionParameter %int
|
||||
%f_prologue = OpLabel
|
||||
%fr = OpIEqual %bool %fp1 %int_0
|
||||
OpReturnValue %fr
|
||||
OpFunctionEnd
|
||||
)";
|
||||
constexpr char kDiff[] = R"( ; SPIR-V
|
||||
; Version: 1.6
|
||||
; Generator: Khronos SPIR-V Tools Assembler; 0
|
||||
; Bound: 15
|
||||
; Schema: 0
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%1 = OpTypeBool
|
||||
%2 = OpTypeInt 32 1
|
||||
%3 = OpConstantNull %2
|
||||
%4 = OpTypeFunction %1 %2
|
||||
%5 = OpTypeFunction %1 %2 %2
|
||||
%6 = OpFunction %1 None %4
|
||||
%7 = OpFunctionParameter %2
|
||||
%8 = OpLabel
|
||||
%9 = OpIEqual %1 %7 %3
|
||||
OpReturnValue %9
|
||||
OpFunctionEnd
|
||||
%10 = OpFunction %1 None %5
|
||||
%11 = OpFunctionParameter %2
|
||||
%12 = OpFunctionParameter %2
|
||||
%13 = OpLabel
|
||||
%14 = OpIEqual %1 %11 %3
|
||||
OpReturnValue %14
|
||||
OpFunctionEnd
|
||||
)";
|
||||
Options options;
|
||||
DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace diff
|
||||
} // namespace spvtools
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user