Compare commits

..
Author SHA1 Message Date
Andy Hovingh edfdc7fe44 vk: external sampler work 2025-04-08 15:30:56 -07:00
Powei Feng eb483501f0 vk: preparing for Ycbcr conversion (#8596)
- Move utils functions to the right files
 - Add conversion from vk formats to filament
 - Refactor how to enable multiview and external sampler in
   VulkanPlatform
 - Make VuklanDescriptorSet and VuklanDescriptorSetLayout more
   general to allow for mutability
 - Reorder vulkan files in CMakeLists.txt to be sorted in
   alphabetical order.
2025-04-08 14:57:21 -07:00
Powei Feng c54fe53936 vk: add YcbcrConversion to VulkanTexture (#8588)
- Refactor VulkanTextureState and VulkanTexture so that most
  state fields are consts.
- Add a field for VkYcbcrConversion.
- Clear the image view cache when YcbcrConversion changes
- Make sure that getAttachmentView and getView are better defined
  and called.
2025-04-08 14:57:12 -07:00
Powei Feng b915e2f35f vk: add ycbcr conversion cache (#8557)
Introduce a cache for Ycbcr conversion, which is needed for
external textures on Android.
2025-04-08 14:57:04 -07:00
Sungun Park c24e5089c4 Bump version to 1.59.1 2025-03-31 22:23:27 +00:00
Sungun Park 87dbd59275 Release Filament 1.59.0 2025-03-31 22:23:12 +00:00
Juan Caldas 3c02eb9a63 WebGPU initial handle implementation (#8566)
* initial handle pass
2025-03-31 18:03:04 -04:00
141 changed files with 1979 additions and 9396 deletions
+2 -2
View File
@@ -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
+3
View File
@@ -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 -1
View File
@@ -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.
+30 -26
View File
@@ -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 {
+4
View File
@@ -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,
};
}
+14 -10
View File
@@ -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
+112 -49
View File
@@ -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);
}
+19 -7
View File
@@ -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;
+24 -9
View File
@@ -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;
+210 -169
View File
@@ -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;
+56 -37
View File
@@ -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) {
+12 -5
View File
@@ -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 {
+20 -3
View File
@@ -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()
-5
View File
@@ -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];
+2 -2
View File
@@ -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 = {
+20 -111
View File
@@ -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));
+1 -7
View File
@@ -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";
-2
View File
@@ -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 \
-1
View File
@@ -201,7 +201,6 @@ cc_library(
deps = [
":spirv_tools_internal",
"@spirv_headers//:spirv_common_headers",
"@spirv_headers//:spirv_c_headers",
],
)
-5
View File
@@ -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",
-35
View File
@@ -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)
+5 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
-4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
-65
View File
@@ -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
-11
View File
@@ -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
View File
@@ -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));
-1
View File
@@ -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
View File
@@ -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
View File
@@ -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,
-2
View File
@@ -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
View File
@@ -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
View File
@@ -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;
-3
View File
@@ -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':
-1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+1 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
)
@@ -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