Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad42ee66b1 | ||
|
|
b2e77d6847 | ||
|
|
784b1650c3 |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.58.2'
|
||||
implementation 'com.google.android.filament:filament-android:1.58.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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.58.0'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,10 +7,6 @@ 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.58.2
|
||||
|
||||
- engine: Generate 1D instead of 3D LUTs for color grading whenever possible.
|
||||
|
||||
## v1.58.1
|
||||
|
||||
|
||||
|
||||
+4
-10
@@ -46,8 +46,9 @@ import static com.google.android.filament.Asserts.assertFloat4In;
|
||||
*
|
||||
* <h1>Performance</h1>
|
||||
*
|
||||
* Creating a new ColorGrading object may be more expensive than other Filament objects as a LUT may
|
||||
* need to be generated. The generation of this LUT, if necessary, may happen on the CPU.
|
||||
* Creating a new ColorGrading object may be more expensive than other Filament objects as a
|
||||
* 3D LUT may need to be generated. The generation of a 3D LUT, if necessary, may happen on
|
||||
* the CPU.
|
||||
*
|
||||
* <h1>Ordering</h1>
|
||||
*
|
||||
@@ -159,10 +160,6 @@ public class ColorGrading {
|
||||
* quality level will use a 32x32x32 10 bit LUT, a high quality will use a 32x32x32 16 bit
|
||||
* LUT, and a ultra quality will use a 64x64x64 16 bit LUT.
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* This overrides the values set by format() and dimensions().
|
||||
*
|
||||
* The default quality is {@link QualityLevel#MEDIUM}.
|
||||
*
|
||||
* @param qualityLevel The desired quality of the color grading process
|
||||
@@ -178,8 +175,6 @@ public class ColorGrading {
|
||||
* When color grading is implemented using a 3D LUT, this sets the texture format of
|
||||
* of the LUT. This overrides the value set by quality().
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* The default is INTEGER
|
||||
*
|
||||
* @param format The desired format of the 3D LUT.
|
||||
@@ -195,8 +190,6 @@ public class ColorGrading {
|
||||
* When color grading is implemented using a 3D LUT, this sets the dimension of the LUT.
|
||||
* This overrides the value set by quality().
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* The default is 32
|
||||
*
|
||||
* @param dim The desired dimension of the LUT. Between 16 and 64.
|
||||
@@ -623,3 +616,4 @@ public class ColorGrading {
|
||||
|
||||
private static native long nBuilderBuild(long nativeBuilder, long nativeEngine);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ package com.google.android.filament;
|
||||
* <li>DisplayRangeToneMapper</li>
|
||||
* </ul>
|
||||
* </ul>
|
||||
*
|
||||
* You can create custom tone mapping operators by subclassing ToneMapper.
|
||||
*/
|
||||
public class ToneMapper {
|
||||
private final long mNativeObject;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.58.2
|
||||
VERSION_NAME=1.58.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -256,18 +256,16 @@ struct DescriptorSetLayoutBinding {
|
||||
DescriptorFlags flags = DescriptorFlags::NONE;
|
||||
uint16_t count = 0;
|
||||
|
||||
// TODO: uncomment when needed. Note that this class is used as hash key. We need to ensure
|
||||
// no uninitialized padding bytes.
|
||||
// uint8_t externalSamplerDataIndex = EXTERNAL_SAMPLER_DATA_INDEX_UNUSED;
|
||||
uint8_t externalSamplerDataIndex = EXTERNAL_SAMPLER_DATA_INDEX_UNUSED;
|
||||
|
||||
friend inline bool operator==(DescriptorSetLayoutBinding const& lhs,
|
||||
friend inline bool operator==(
|
||||
DescriptorSetLayoutBinding const& lhs,
|
||||
DescriptorSetLayoutBinding const& rhs) noexcept {
|
||||
return lhs.type == rhs.type &&
|
||||
lhs.flags == rhs.flags &&
|
||||
lhs.count == rhs.count &&
|
||||
lhs.stageFlags == rhs.stageFlags;
|
||||
// lhs.stageFlags == rhs.stageFlags &&
|
||||
// lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
|
||||
lhs.stageFlags == rhs.stageFlags &&
|
||||
lhs.externalSamplerDataIndex == rhs.externalSamplerDataIndex;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1140,9 +1138,7 @@ static_assert(sizeof(ExternalSamplerDatum) == 12);
|
||||
|
||||
struct DescriptorSetLayout {
|
||||
utils::FixedCapacityVector<DescriptorSetLayoutBinding> bindings;
|
||||
|
||||
// TODO: uncomment when needed
|
||||
// utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
|
||||
utils::FixedCapacityVector<ExternalSamplerDatum> externalSamplerData;
|
||||
};
|
||||
|
||||
//! blending equation function
|
||||
|
||||
@@ -149,13 +149,6 @@ public:
|
||||
* - PlatformEGLAndroid
|
||||
*/
|
||||
bool assertNativeWindowIsValid = false;
|
||||
|
||||
/**
|
||||
* The action to take if a Drawable cannot be acquired. If true, the
|
||||
* frame is aborted instead of panic. This is only supported for:
|
||||
* - PlatformMetal
|
||||
*/
|
||||
bool metalDisablePanicOnDrawableFailure = false;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include <backend/platforms/PlatformEGL.h>
|
||||
|
||||
#include <utils/android/PerformanceHintManager.h>
|
||||
#include <utils/compiler.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
@@ -44,21 +43,8 @@ public:
|
||||
PlatformEGLAndroid() noexcept;
|
||||
~PlatformEGLAndroid() noexcept override;
|
||||
|
||||
/**
|
||||
* Creates an ExternalImage from a EGLImageKHR
|
||||
*/
|
||||
ExternalImageHandle createExternalImage(AHardwareBuffer const* buffer, bool sRGB) noexcept;
|
||||
|
||||
struct ExternalImageDescAndroid {
|
||||
uint32_t width; // Texture width
|
||||
uint32_t height; // Texture height
|
||||
TextureFormat format;// Texture format
|
||||
TextureUsage usage; // Texture usage flags
|
||||
};
|
||||
|
||||
ExternalImageDescAndroid getExternalImageDesc(ExternalImageHandle externalImage) noexcept;
|
||||
|
||||
protected:
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Platform Interface
|
||||
|
||||
@@ -71,6 +57,11 @@ protected:
|
||||
Driver* createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept override;
|
||||
|
||||
/**
|
||||
* Creates an ExternalImage from a EGLImageKHR
|
||||
*/
|
||||
ExternalImageHandle createExternalImage(AHardwareBuffer const *buffer, bool sRGB) noexcept;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// OpenGLPlatform Interface
|
||||
|
||||
@@ -104,26 +95,15 @@ protected:
|
||||
*/
|
||||
AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept override;
|
||||
|
||||
OpenGLPlatform::ExternalTexture* createExternalImageTexture() noexcept override;
|
||||
void destroyExternalImageTexture(ExternalTexture* texture) noexcept override;
|
||||
bool setExternalImage(ExternalImageHandleRef externalImage, ExternalTexture* texture) noexcept override;
|
||||
|
||||
struct ExternalImageEGLAndroid : public ExternalImageEGL {
|
||||
AHardwareBuffer* aHardwareBuffer = nullptr;
|
||||
uint32_t width; // Texture width
|
||||
uint32_t height; // Texture height
|
||||
TextureFormat format;// Texture format
|
||||
TextureUsage usage; // Texture usage flags
|
||||
bool sRGB = false;
|
||||
|
||||
protected:
|
||||
~ExternalImageEGLAndroid() override;
|
||||
};
|
||||
|
||||
bool setExternalImage(ExternalImageHandleRef externalImage,
|
||||
ExternalTexture* texture) noexcept override;
|
||||
bool setImage(ExternalImageEGLAndroid const* eglExternalImage,
|
||||
ExternalTexture* texture) noexcept;
|
||||
|
||||
protected:
|
||||
bool makeCurrent(ContextType type,
|
||||
SwapChain* drawSwapChain,
|
||||
|
||||
@@ -368,16 +368,8 @@ public:
|
||||
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
|
||||
VkImageViewType viewType, VkComponentMapping swizzle);
|
||||
|
||||
protected:
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
|
||||
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept;
|
||||
|
||||
private:
|
||||
// Platform dependent helper methods
|
||||
static ExtensionSet getSwapchainInstanceExtensionsImpl();
|
||||
static ExtensionSet getSwapchainInstanceExtensions();
|
||||
|
||||
static ExternalImageMetadata getExternalImageMetadataImpl(ExternalImageHandleRef externalImage,
|
||||
VkDevice device);
|
||||
@@ -394,7 +386,8 @@ private:
|
||||
VkComponentMapping swizzle);
|
||||
|
||||
// Platform dependent helper methods
|
||||
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
|
||||
using SurfaceBundle = std::tuple<VkSurfaceKHR, VkExtent2D>;
|
||||
static SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) noexcept;
|
||||
|
||||
friend struct VulkanPlatformPrivate;
|
||||
|
||||
@@ -17,45 +17,27 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||
#define TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
namespace filament::backend {
|
||||
namespace filament::backend::fvkandroid {
|
||||
|
||||
class VulkanPlatformAndroid : public VulkanPlatform {
|
||||
public:
|
||||
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;
|
||||
};
|
||||
|
||||
Platform::ExternalImageHandle createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept;
|
||||
|
||||
virtual ExternalImageMetadata getExternalImageMetadata(ExternalImageHandleRef externalImage);
|
||||
|
||||
using ImageData = VulkanPlatform::ImageData;
|
||||
virtual ImageData createExternalImageData(ExternalImageHandleRef externalImage,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
||||
VkImageUsageFlags usage);
|
||||
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:
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
|
||||
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept;
|
||||
~ExternalImageVulkanAndroid() override;
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
Platform::ExternalImageHandle createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept;
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||
} // namespace filament::backend::fvkandroid
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_PLATFORMS_VULKAN_PLATFORM_ANDROID_H
|
||||
|
||||
@@ -55,11 +55,7 @@
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
#if defined(__ANDROID__)
|
||||
#include "backend/platforms/VulkanPlatformAndroid.h"
|
||||
#else
|
||||
#include "backend/platforms/VulkanPlatform.h"
|
||||
#endif
|
||||
#include "backend/platforms/VulkanPlatform.h"
|
||||
#endif
|
||||
|
||||
#if defined (FILAMENT_SUPPORTS_METAL)
|
||||
@@ -108,11 +104,7 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
|
||||
}
|
||||
if (*backend == Backend::VULKAN) {
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
#if defined(__ANDROID__)
|
||||
return new VulkanPlatformAndroid();
|
||||
#else
|
||||
return new VulkanPlatform();
|
||||
#endif
|
||||
return new VulkanPlatform();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
|
||||
@@ -45,9 +45,6 @@ PlatformMetal::~PlatformMetal() noexcept {
|
||||
}
|
||||
|
||||
Driver* PlatformMetal::createDriver(void* /*sharedContext*/, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
pImpl->mDrawableFailureBehavior = driverConfig.metalDisablePanicOnDrawableFailure
|
||||
? DrawableFailureBehavior::ABORT_FRAME
|
||||
: DrawableFailureBehavior::PANIC;
|
||||
return MetalDriverFactory::create(this, driverConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ size_t NoopDriver::getMaxUniformBufferSize() {
|
||||
}
|
||||
|
||||
size_t NoopDriver::getMaxTextureSize(SamplerType target) {
|
||||
return 4096u;
|
||||
return 2048u;
|
||||
}
|
||||
|
||||
size_t NoopDriver::getMaxArrayTextureLayers() {
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <backend/platforms/PlatformEGL.h>
|
||||
#include <backend/platforms/PlatformEGLAndroid.h>
|
||||
|
||||
#include <private/backend/BackendUtilsAndroid.h>
|
||||
#include <private/backend/VirtualMachineEnv.h>
|
||||
|
||||
#include "opengl/GLUtils.h"
|
||||
@@ -35,8 +34,6 @@
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
@@ -234,133 +231,26 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
||||
return driver;
|
||||
}
|
||||
|
||||
PlatformEGLAndroid::ExternalImageEGLAndroid::~ExternalImageEGLAndroid() {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
if (aHardwareBuffer) {
|
||||
AHardwareBuffer_release(aHardwareBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
PlatformEGLAndroid::ExternalImageEGLAndroid::~ExternalImageEGLAndroid() = default;
|
||||
|
||||
Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
auto* const p = new (std::nothrow) ExternalImageEGLAndroid;
|
||||
auto hardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
|
||||
AHardwareBuffer_acquire(hardwareBuffer);
|
||||
p->aHardwareBuffer = hardwareBuffer;
|
||||
p->sRGB = sRGB;
|
||||
AHardwareBuffer_Desc hardwareBufferDescription = {};
|
||||
AHardwareBuffer_describe(hardwareBuffer, &hardwareBufferDescription);
|
||||
p->height = hardwareBufferDescription.height;
|
||||
p->width = hardwareBufferDescription.width;
|
||||
auto textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
|
||||
p->usage = mapToFilamentUsage(hardwareBufferDescription.usage, textureFormat);
|
||||
return ExternalImageHandle{ p };
|
||||
}
|
||||
|
||||
return Platform::ExternalImageHandle{};
|
||||
}
|
||||
|
||||
PlatformEGLAndroid::ExternalImageDescAndroid PlatformEGLAndroid::getExternalImageDesc(
|
||||
ExternalImageHandle externalImage) noexcept {
|
||||
auto const* const eglExternalImage =
|
||||
static_cast<ExternalImageEGLAndroid const*>(externalImage.get());
|
||||
ExternalImageDescAndroid metadata = {};
|
||||
if (!eglExternalImage) {
|
||||
return metadata;
|
||||
}
|
||||
metadata.height = eglExternalImage->height;
|
||||
metadata.width = eglExternalImage->width;
|
||||
metadata.format = eglExternalImage->format;
|
||||
metadata.usage = eglExternalImage->usage;
|
||||
return metadata;
|
||||
Platform::ExternalImageHandle PlatformEGLAndroid::createExternalImage(AHardwareBuffer const* buffer, bool sRGB) noexcept {
|
||||
auto* const p = new(std::nothrow) ExternalImageEGLAndroid;
|
||||
p->aHardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
|
||||
p->sRGB = sRGB;
|
||||
return ExternalImageHandle{ p };
|
||||
}
|
||||
|
||||
bool PlatformEGLAndroid::setExternalImage(ExternalImageHandleRef externalImage,
|
||||
UTILS_UNUSED_IN_RELEASE ExternalTexture* texture) noexcept {
|
||||
auto const* const eglExternalImage =
|
||||
static_cast<ExternalImageEGLAndroid const*>(externalImage.get());
|
||||
auto const* const eglExternalImage = static_cast<ExternalImageEGLAndroid const*>(externalImage.get());
|
||||
if (eglExternalImage->aHardwareBuffer) {
|
||||
return PlatformEGLAndroid::setImage(eglExternalImage, texture);
|
||||
// TODO: implement PlatformEGLAndroid::setExternalImage w/ AHardwareBuffer
|
||||
return true;
|
||||
}
|
||||
// not a AHardwareBuffer, fallback to the inherited version
|
||||
return PlatformEGL::setExternalImage(externalImage, texture);
|
||||
}
|
||||
|
||||
OpenGLPlatform::ExternalTexture* PlatformEGLAndroid::createExternalImageTexture() noexcept {
|
||||
ExternalTexture* outTexture = new (std::nothrow) ExternalTexture{};
|
||||
glGenTextures(1, &outTexture->id);
|
||||
return outTexture;
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::destroyExternalImageTexture(ExternalTexture* texture) noexcept {
|
||||
glDeleteTextures(1, &texture->id);
|
||||
delete texture;
|
||||
}
|
||||
|
||||
bool PlatformEGLAndroid::setImage(ExternalImageEGLAndroid const* eglExternalImage,
|
||||
UTILS_UNUSED_IN_RELEASE ExternalTexture* texture) noexcept {
|
||||
AHardwareBuffer* hardwareBuffer = eglExternalImage->aHardwareBuffer;
|
||||
|
||||
// Get the EGL client buffer from AHardwareBuffer
|
||||
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(hardwareBuffer);
|
||||
EGLint imageAttrs[] = {
|
||||
EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
|
||||
EGL_NONE, EGL_NONE, // Reserve space
|
||||
EGL_NONE, EGL_NONE, // Reserve space
|
||||
EGL_NONE // Ensure the list always ends with EGL_NONE
|
||||
};
|
||||
int attrIndex = 2;
|
||||
if (eglExternalImage->sRGB) {
|
||||
imageAttrs[attrIndex++] = EGL_GL_COLORSPACE;
|
||||
imageAttrs[attrIndex++] = EGL_GL_COLORSPACE_SRGB;
|
||||
}
|
||||
|
||||
if (static_cast<bool>(eglExternalImage->usage & TextureUsage::PROTECTED)) {
|
||||
imageAttrs[attrIndex++] = EGL_PROTECTED_CONTENT_EXT;
|
||||
imageAttrs[attrIndex++] = EGL_TRUE;
|
||||
}
|
||||
// Create an EGLImage from the client buffer
|
||||
EGLImageKHR eglImage = eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT,
|
||||
EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs);
|
||||
if (eglImage == EGL_NO_IMAGE_KHR) {
|
||||
// Handle error
|
||||
slog.e << "Failed to create EGL image" << io::endl;
|
||||
glDeleteTextures(1, &texture->id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create and bind the OpenGL texture
|
||||
GLint prevActiveTexture, prevTexture;
|
||||
glGetIntegerv(GL_ACTIVE_TEXTURE, &prevActiveTexture);
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D, &prevTexture);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(texture->target, texture->id);
|
||||
GLenum error = glGetError();
|
||||
if (UTILS_UNLIKELY(error != GL_NO_ERROR)) {
|
||||
slog.e << "Error after glBindTexture: " << error << io::endl;
|
||||
glDeleteTextures(1, &texture->id);
|
||||
eglDestroyImageKHR(eglGetCurrentDisplay(), eglImage);
|
||||
glActiveTexture(prevActiveTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, prevTexture);
|
||||
return false;
|
||||
}
|
||||
glEGLImageTargetTexture2DOES(texture->target, static_cast<GLeglImageOES>(eglImage));
|
||||
error = glGetError();
|
||||
if (UTILS_UNLIKELY(error != GL_NO_ERROR)) {
|
||||
slog.e << "Error after glEGLImageTargetTexture2DOES: " << error << io::endl;
|
||||
glDeleteTextures(1, &texture->id);
|
||||
eglDestroyImageKHR(eglGetCurrentDisplay(), eglImage);
|
||||
glActiveTexture(prevActiveTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, prevTexture);
|
||||
return false;
|
||||
}
|
||||
glActiveTexture(prevActiveTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, prevTexture);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlatformEGLAndroid::setPresentationTime(int64_t presentationTimeInNanosecond) noexcept {
|
||||
EGLSurface currentDrawSurface = eglGetCurrentSurface(EGL_DRAW);
|
||||
if (currentDrawSurface != EGL_NO_SURFACE) {
|
||||
|
||||
@@ -1149,7 +1149,7 @@ size_t VulkanDriver::getMaxUniformBufferSize() {
|
||||
|
||||
size_t VulkanDriver::getMaxTextureSize(SamplerType) {
|
||||
// TODO: return the actual size instead of hardcoded value
|
||||
return 4096;
|
||||
return 2048;
|
||||
}
|
||||
|
||||
size_t VulkanDriver::getMaxArrayTextureLayers() {
|
||||
|
||||
@@ -55,61 +55,63 @@ VkComponentMapping composeSwizzle(VkComponentMapping const& prev, VkComponentMap
|
||||
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 };
|
||||
for (auto& out: vals) {
|
||||
switch (out) {
|
||||
case VK_COMPONENT_SWIZZLE_R:
|
||||
out = prev.r;
|
||||
break;
|
||||
case VK_COMPONENT_SWIZZLE_G:
|
||||
out = prev.g;
|
||||
break;
|
||||
case VK_COMPONENT_SWIZZLE_B:
|
||||
out = prev.b;
|
||||
break;
|
||||
case VK_COMPONENT_SWIZZLE_A:
|
||||
out = prev.a;
|
||||
break;
|
||||
// Do not modify the value
|
||||
case VK_COMPONENT_SWIZZLE_IDENTITY:
|
||||
case VK_COMPONENT_SWIZZLE_ZERO:
|
||||
case VK_COMPONENT_SWIZZLE_ONE:
|
||||
// Below is not exposed in Vulkan's API, but needs to be there for compilation.
|
||||
case VK_COMPONENT_SWIZZLE_MAX_ENUM:
|
||||
break;
|
||||
}
|
||||
auto const compose = [](VkComponentSwizzle out, VkComponentMapping const& prev,
|
||||
uint8_t channelIndex) {
|
||||
// We need to first change all identities to its equivalent channel.
|
||||
if (out == VK_COMPONENT_SWIZZLE_IDENTITY) {
|
||||
out = IDENTITY[channelIndex];
|
||||
}
|
||||
return { vals[0], vals[1], vals[2], vals[3] };
|
||||
switch (out) {
|
||||
case VK_COMPONENT_SWIZZLE_R:
|
||||
out = prev.r;
|
||||
break;
|
||||
case VK_COMPONENT_SWIZZLE_G:
|
||||
out = prev.g;
|
||||
break;
|
||||
case VK_COMPONENT_SWIZZLE_B:
|
||||
out = prev.b;
|
||||
break;
|
||||
case VK_COMPONENT_SWIZZLE_A:
|
||||
out = prev.a;
|
||||
break;
|
||||
case VK_COMPONENT_SWIZZLE_IDENTITY:
|
||||
case VK_COMPONENT_SWIZZLE_ZERO:
|
||||
case VK_COMPONENT_SWIZZLE_ONE:
|
||||
return out;
|
||||
// Below is not exposed in Vulkan's API, but needs to be there for compilation.
|
||||
case VK_COMPONENT_SWIZZLE_MAX_ENUM:
|
||||
break;
|
||||
}
|
||||
// If the result correctly corresponds to the identity, just return identity.
|
||||
if (IDENTITY[channelIndex] == out) {
|
||||
return VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
auto const identityToChannel = [](VkComponentMapping const& mapping) -> VkComponentMapping {
|
||||
VkComponentSwizzle vals[4] = { mapping.r, mapping.g, mapping.b, mapping.a };
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
if (vals[i] != VK_COMPONENT_SWIZZLE_IDENTITY) {
|
||||
continue;
|
||||
}
|
||||
vals[i] = IDENTITY[i];
|
||||
auto const identityToChannel = [](VkComponentSwizzle val, uint8_t channelIndex) {
|
||||
if (val != VK_COMPONENT_SWIZZLE_IDENTITY) {
|
||||
return val;
|
||||
}
|
||||
return { vals[0], vals[1], vals[2], vals[3] };
|
||||
};
|
||||
auto const channelToIdentity = [](VkComponentMapping const& mapping) -> VkComponentMapping {
|
||||
VkComponentSwizzle vals[4] = { mapping.r, mapping.g, mapping.b, mapping.a };
|
||||
for (uint8_t i = 0; i < 4; i++) {
|
||||
if (IDENTITY[i] != vals[i]) {
|
||||
continue;
|
||||
}
|
||||
vals[i] = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
}
|
||||
return { vals[0], vals[1], vals[2], vals[3] };
|
||||
return IDENTITY[channelIndex];
|
||||
};
|
||||
|
||||
// We make sure all all identities are mapped into respective channels so that actual channel
|
||||
// mapping will be passed onto the output.
|
||||
VkComponentMapping const prevExplicit = identityToChannel(prev);
|
||||
VkComponentMapping const nextExplicit = identityToChannel(next);
|
||||
return channelToIdentity(compose(prevExplicit, nextExplicit));
|
||||
VkComponentMapping const prevExplicit = {
|
||||
identityToChannel(prev.r, 0),
|
||||
identityToChannel(prev.g, 1),
|
||||
identityToChannel(prev.b, 2),
|
||||
identityToChannel(prev.a, 3),
|
||||
};
|
||||
|
||||
// Note that the channel index corresponds to the VkComponentMapping struct layout.
|
||||
return {
|
||||
compose(next.r, prevExplicit, 0),
|
||||
compose(next.g, prevExplicit, 1),
|
||||
compose(next.b, prevExplicit, 2),
|
||||
compose(next.a, prevExplicit, 3),
|
||||
};
|
||||
}
|
||||
|
||||
inline VulkanLayout getDefaultLayoutImpl(TextureUsage usage) {
|
||||
@@ -151,6 +153,7 @@ uint8_t getLayerCountFromDepth(uint32_t const depth) {
|
||||
return getLayerCount(getSamplerTypeFromDepth(depth), depth);
|
||||
}
|
||||
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanTextureState::VulkanTextureState(VkDevice device, VmaAllocator allocator,
|
||||
@@ -246,19 +249,12 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
mState->mIsTransientAttachment = useTransientAttachment;
|
||||
|
||||
const VkImageUsageFlags transientFlag =
|
||||
|
||||
@@ -1000,15 +1000,6 @@ VkImageView VulkanPlatform::createExternalImageView(SamplerYcbcrConversion chrom
|
||||
return createExternalImageViewImpl(mImpl->mDevice, chroma, internalFormat, image, range,
|
||||
viewType, swizzle);
|
||||
}
|
||||
|
||||
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
|
||||
return getSwapchainInstanceExtensionsImpl();
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
return createVkSurfaceKHRImpl(nativeWindow, instance, flags);
|
||||
}
|
||||
#undef SWAPCHAIN_RET_FUNC
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include <backend/platforms/VulkanPlatformAndroid.h>
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/platforms/VulkanPlatformAndroid.h>
|
||||
#include <private/backend/BackendUtilsAndroid.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
@@ -156,7 +157,7 @@ VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevic
|
||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
|
||||
};
|
||||
|
||||
VkImageCreateInfo imageInfo{ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
|
||||
VkImageCreateInfo imageInfo{.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||||
imageInfo.pNext = &externalCreateInfo;
|
||||
imageInfo.format = metadata.format;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
@@ -199,12 +200,14 @@ VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevic
|
||||
return data;
|
||||
}
|
||||
|
||||
}// namespace
|
||||
} // namespace
|
||||
|
||||
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() = default;
|
||||
namespace fvkandroid {
|
||||
|
||||
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
||||
AHardwareBuffer const* buffer, bool sRGB) noexcept {
|
||||
ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() = default;
|
||||
|
||||
Platform::ExternalImageHandle createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
AHardwareBuffer_Desc hardwareBufferDescription = {};
|
||||
AHardwareBuffer_describe(buffer, &hardwareBufferDescription);
|
||||
@@ -223,10 +226,12 @@ Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
||||
return Platform::ExternalImageHandle{};
|
||||
}
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMetadata(
|
||||
ExternalImageHandleRef externalImage) {
|
||||
} // namespace fvkandroid
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
static_cast<fvkandroid::ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
|
||||
ExternalImageMetadata metadata;
|
||||
AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
|
||||
@@ -240,7 +245,7 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMet
|
||||
std::tie(metadata.format, metadata.usage) =
|
||||
getVKFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
|
||||
}
|
||||
|
||||
|
||||
metadata.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
VkAndroidHardwareBufferFormatPropertiesANDROID formatInfo = {
|
||||
@@ -251,11 +256,11 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMet
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
|
||||
.pNext = &formatInfo,
|
||||
};
|
||||
VkResult result = vkGetAndroidHardwareBufferPropertiesANDROID(getDevice(), buffer, &properties);
|
||||
VkResult result = vkGetAndroidHardwareBufferPropertiesANDROID(device, buffer, &properties);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkGetAndroidHardwareBufferProperties failed with error="
|
||||
<< static_cast<int32_t>(result);
|
||||
|
||||
<< "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("
|
||||
@@ -266,14 +271,14 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMet
|
||||
return metadata;
|
||||
}
|
||||
|
||||
VulkanPlatformAndroid::ImageData VulkanPlatformAndroid::createExternalImageData(
|
||||
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata,
|
||||
uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
ImageData data = allocateExternalImage(fvkExternalImage->aHardwareBuffer, getDevice(), metadata,
|
||||
static_cast<fvkandroid::ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
ImageData data = allocateExternalImage(fvkExternalImage->aHardwareBuffer, device, metadata,
|
||||
memoryTypeIndex, usage);
|
||||
VkResult result = vkBindImageMemory(getDevice(), data.first, data.second, 0);
|
||||
VkResult result = vkBindImageMemory(device, data.first, data.second, 0);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
|
||||
return data;
|
||||
@@ -392,14 +397,14 @@ VkSampler VulkanPlatform::createExternalSamplerImpl(
|
||||
return sampler;
|
||||
}
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const {
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() {
|
||||
return {
|
||||
VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatformAndroid::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) const noexcept {
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
VkExtent2D extent;
|
||||
|
||||
@@ -411,26 +416,6 @@ VulkanPlatform::SurfaceBundle VulkanPlatformAndroid::createVkSurfaceKHR(void* na
|
||||
vkCreateAndroidSurfaceKHR(instance, &createInfo, VKALLOC, (VkSurfaceKHR*) &surface);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateAndroidSurfaceKHR with error=" << static_cast<int32_t>(result);
|
||||
return { surface, extent };
|
||||
return {surface, extent};
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -52,7 +52,7 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() {
|
||||
ExtensionSet const ret = {
|
||||
#if defined(__APPLE__)
|
||||
VK_MVK_MACOS_SURFACE_EXTENSION_NAME, // TODO: replace with VK_EXT_metal_surface
|
||||
@@ -87,7 +87,7 @@ VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
#if defined(__APPLE__)
|
||||
|
||||
@@ -108,7 +108,7 @@ VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() {
|
||||
VulkanPlatform::ExtensionSet const ret = {
|
||||
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME,
|
||||
@@ -126,7 +126,7 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
|
||||
return ret;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHR(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
|
||||
|
||||
@@ -651,11 +651,11 @@ uint8_t WebGPUDriver::getMaxDrawBuffers() {
|
||||
return MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT;
|
||||
}
|
||||
|
||||
size_t WebGPUDriver::getMaxUniformBufferSize() {
|
||||
size_t WebGPUDriver::getMaxUniformBufferSize(SamplerType) {
|
||||
return 16384u;
|
||||
}
|
||||
|
||||
size_t WebGPUDriver::getMaxTextureSize(SamplerType target) {
|
||||
size_t WebGPUDriver::getMaxTextureSize() {
|
||||
return 2048u;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,8 +64,9 @@ class ColorSpace;
|
||||
* Performance
|
||||
* ===========
|
||||
*
|
||||
* Creating a new ColorGrading object may be more expensive than other Filament objects as a LUT may
|
||||
* need to be generated. The generation of this LUT, if necessary, may happen on the CPU.
|
||||
* Creating a new ColorGrading object may be more expensive than other Filament objects as a
|
||||
* 3D LUT may need to be generated. The generation of a 3D LUT, if necessary, may happen on
|
||||
* the CPU.
|
||||
*
|
||||
* Ordering
|
||||
* ========
|
||||
@@ -154,9 +155,6 @@ public:
|
||||
* 3D texture. For instance, a low quality level will use a 16x16x16 10 bit LUT, a medium
|
||||
* quality level will use a 32x32x32 10 bit LUT, a high quality will use a 32x32x32 16 bit
|
||||
* LUT, and a ultra quality will use a 64x64x64 16 bit LUT.
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* This overrides the values set by format() and dimensions().
|
||||
*
|
||||
* The default quality is medium.
|
||||
@@ -171,8 +169,6 @@ public:
|
||||
* When color grading is implemented using a 3D LUT, this sets the texture format of
|
||||
* of the LUT. This overrides the value set by quality().
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* The default is INTEGER
|
||||
*
|
||||
* @param format The desired format of the 3D LUT.
|
||||
@@ -185,8 +181,6 @@ public:
|
||||
* When color grading is implemented using a 3D LUT, this sets the dimension of the LUT.
|
||||
* This overrides the value set by quality().
|
||||
*
|
||||
* This setting has no effect if generating a 1D LUT.
|
||||
*
|
||||
* The default is 32
|
||||
*
|
||||
* @param dim The desired dimension of the LUT. Between 16 and 64.
|
||||
|
||||
@@ -317,15 +317,6 @@ public:
|
||||
*/
|
||||
size_t metalUploadBufferSizeBytes = 512 * 1024;
|
||||
|
||||
/**
|
||||
* The action to take if a Drawable cannot be acquired.
|
||||
*
|
||||
* Each frame rendered requires a CAMetalDrawable texture, which is
|
||||
* presented on-screen at the completion of each frame. These are
|
||||
* limited and provided round-robin style by the system.
|
||||
*/
|
||||
bool metalDisablePanicOnDrawableFailure = false;
|
||||
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL and Metal backends.
|
||||
|
||||
@@ -68,21 +68,6 @@ struct UTILS_PUBLIC ToneMapper {
|
||||
* function applied ("linear")
|
||||
*/
|
||||
virtual math::float3 operator()(math::float3 c) const noexcept = 0;
|
||||
|
||||
/**
|
||||
* If true, then this function holds that f(x) = vec3(f(x.r), f(x.g), f(x.b))
|
||||
*
|
||||
* This may be used to indicate that the color grading's LUT only requires a 1D texture instead
|
||||
* of a 3D texture, potentially saving a significant amount of memory and generation time.
|
||||
*/
|
||||
virtual bool isOneDimensional() const noexcept { return false; }
|
||||
|
||||
/**
|
||||
* True if this tonemapper only works in low-dynamic-range.
|
||||
*
|
||||
* This may be used to indicate that the color grading's LUT doesn't need to be log encoded.
|
||||
*/
|
||||
virtual bool isLDR() const noexcept { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -94,8 +79,6 @@ struct UTILS_PUBLIC LinearToneMapper final : public ToneMapper {
|
||||
~LinearToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 c) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return true; }
|
||||
bool isLDR() const noexcept override { return true; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -108,8 +91,6 @@ struct UTILS_PUBLIC ACESToneMapper final : public ToneMapper {
|
||||
~ACESToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 c) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return false; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -123,8 +104,6 @@ struct UTILS_PUBLIC ACESLegacyToneMapper final : public ToneMapper {
|
||||
~ACESLegacyToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 c) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return false; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -138,8 +117,6 @@ struct UTILS_PUBLIC FilmicToneMapper final : public ToneMapper {
|
||||
~FilmicToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 x) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return true; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -152,8 +129,6 @@ struct UTILS_PUBLIC PBRNeutralToneMapper final : public ToneMapper {
|
||||
~PBRNeutralToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 x) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return false; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -175,8 +150,6 @@ struct UTILS_PUBLIC AgxToneMapper final : public ToneMapper {
|
||||
~AgxToneMapper() noexcept final;
|
||||
|
||||
math::float3 operator()(math::float3 x) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return false; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
|
||||
AgxLook look;
|
||||
};
|
||||
@@ -221,8 +194,6 @@ struct UTILS_PUBLIC GenericToneMapper final : public ToneMapper {
|
||||
GenericToneMapper& operator=(GenericToneMapper&& rhs) noexcept;
|
||||
|
||||
math::float3 operator()(math::float3 x) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return true; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
|
||||
/** Returns the contrast of the curve as a strictly positive value. */
|
||||
float getContrast() const noexcept;
|
||||
@@ -285,8 +256,6 @@ struct UTILS_PUBLIC DisplayRangeToneMapper final : public ToneMapper {
|
||||
~DisplayRangeToneMapper() noexcept override;
|
||||
|
||||
math::float3 operator()(math::float3 c) const noexcept override;
|
||||
bool isOneDimensional() const noexcept override { return false; }
|
||||
bool isLDR() const noexcept override { return false; }
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -2306,11 +2306,36 @@ void PostProcessManager::colorGradingPrepareSubpass(DriverApi& driver,
|
||||
const FColorGrading* colorGrading, ColorGradingConfig const& colorGradingConfig,
|
||||
VignetteOptions const& vignetteOptions, uint32_t const width, uint32_t const height) noexcept {
|
||||
|
||||
auto& material = getPostProcessMaterial("colorGradingAsSubpass");
|
||||
FMaterialInstance* const mi =
|
||||
configureColorGradingMaterial(material, colorGrading, colorGradingConfig,
|
||||
vignetteOptions, width, height);
|
||||
float4 const vignetteParameters = getVignetteParameters(vignetteOptions, width, height);
|
||||
|
||||
auto const& material = getPostProcessMaterial("colorGradingAsSubpass");
|
||||
FMaterialInstance* const mi = PostProcessMaterial::getMaterialInstance(mEngine, material);
|
||||
|
||||
mi->setParameter("lut", colorGrading->getHwHandle(), {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR,
|
||||
.wrapS = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.wrapT = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.wrapR = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.anisotropyLog2 = 0
|
||||
});
|
||||
const float lutDimension = float(colorGrading->getDimension());
|
||||
mi->setParameter("lutSize", float2{
|
||||
0.5f / lutDimension, (lutDimension - 1.0f) / lutDimension,
|
||||
});
|
||||
|
||||
const float temporalNoise = mUniformDistribution(mEngine.getRandomEngine());
|
||||
|
||||
mi->setParameter("vignette", vignetteParameters);
|
||||
mi->setParameter("vignetteColor", vignetteOptions.color);
|
||||
mi->setParameter("dithering", colorGradingConfig.dithering);
|
||||
mi->setParameter("outputLuminance", colorGradingConfig.outputLuminance);
|
||||
mi->setParameter("temporalNoise", temporalNoise);
|
||||
mi->commit(driver);
|
||||
|
||||
// load both variants
|
||||
material.getMaterial(mEngine, PostProcessVariant::OPAQUE);
|
||||
material.getMaterial(mEngine, PostProcessVariant::TRANSLUCENT);
|
||||
}
|
||||
|
||||
void PostProcessManager::colorGradingSubpass(DriverApi& driver,
|
||||
@@ -2462,14 +2487,22 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
|
||||
auto const& out = resources.getRenderPassInfo();
|
||||
|
||||
auto const& input = resources.getDescriptor(data.input);
|
||||
auto const& output = resources.getDescriptor(data.output);
|
||||
auto const& material = getPostProcessMaterial("colorGrading");
|
||||
|
||||
PostProcessVariant const variant = colorGradingConfig.translucent ?
|
||||
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE;
|
||||
|
||||
auto& material = getPostProcessMaterial("colorGrading");
|
||||
FMaterialInstance* const mi =
|
||||
configureColorGradingMaterial(material, colorGrading, colorGradingConfig,
|
||||
vignetteOptions, output.width, output.height);
|
||||
PostProcessMaterial::getMaterialInstance(mEngine, material, variant);
|
||||
|
||||
mi->setParameter("lut", colorGrading->getHwHandle(), {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR
|
||||
});
|
||||
const float lutDimension = float(colorGrading->getDimension());
|
||||
mi->setParameter("lutSize", float2{
|
||||
0.5f / lutDimension, (lutDimension - 1.0f) / lutDimension,
|
||||
});
|
||||
mi->setParameter("colorBuffer", colorTexture, { /* shader uses texelFetch */ });
|
||||
mi->setParameter("bloomBuffer", bloomTexture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
@@ -2501,7 +2534,19 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
bloomParameters.y = 1.0f - bloomParameters.x;
|
||||
}
|
||||
|
||||
auto const& input = resources.getDescriptor(data.input);
|
||||
auto const& output = resources.getDescriptor(data.output);
|
||||
float4 const vignetteParameters = getVignetteParameters(
|
||||
vignetteOptions, output.width, output.height);
|
||||
|
||||
const float temporalNoise = mUniformDistribution(mEngine.getRandomEngine());
|
||||
|
||||
mi->setParameter("dithering", colorGradingConfig.dithering);
|
||||
mi->setParameter("bloom", bloomParameters);
|
||||
mi->setParameter("vignette", vignetteParameters);
|
||||
mi->setParameter("vignetteColor", vignetteOptions.color);
|
||||
mi->setParameter("outputLuminance", colorGradingConfig.outputLuminance);
|
||||
mi->setParameter("temporalNoise", temporalNoise);
|
||||
mi->setParameter("viewport", float4{
|
||||
float(vp.left) / input.width,
|
||||
float(vp.bottom) / input.height,
|
||||
@@ -2509,9 +2554,7 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::colorGrading(FrameGraph& fg,
|
||||
float(vp.height) / input.height
|
||||
});
|
||||
|
||||
commitAndRenderFullScreenQuad(driver, out, mi,
|
||||
colorGradingConfig.translucent
|
||||
? PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE);
|
||||
commitAndRenderFullScreenQuad(driver, out, mi, variant);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -2649,70 +2692,11 @@ void PostProcessManager::configureTemporalAntiAliasingMaterial(
|
||||
setConstantParameter(ma, "varianceGamma", taaOptions.varianceGamma);
|
||||
if (dirty) {
|
||||
ma->invalidate();
|
||||
// TODO: call Material::compile(), we can't do that now because it works only
|
||||
// TODO: call Material::compile(), we can't si that now because it works only
|
||||
// with surface materials
|
||||
}
|
||||
}
|
||||
|
||||
FMaterialInstance* PostProcessManager::configureColorGradingMaterial(
|
||||
PostProcessMaterial& material, FColorGrading const* colorGrading,
|
||||
ColorGradingConfig const& colorGradingConfig, VignetteOptions const& vignetteOptions,
|
||||
uint32_t const width, uint32_t const height) noexcept {
|
||||
FMaterial* const ma = material.getMaterial(mEngine);
|
||||
bool dirty = false;
|
||||
|
||||
auto setConstantParameter =
|
||||
[&dirty](FMaterial* const material, std::string_view const name, auto value) noexcept {
|
||||
auto id = material->getSpecializationConstantId(name);
|
||||
if (id.has_value()) {
|
||||
if (material->setConstant(id.value(), value)) {
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setConstantParameter(ma, "isOneDimensional", colorGrading->isOneDimensional());
|
||||
setConstantParameter(ma, "isLDR", colorGrading->isLDR());
|
||||
|
||||
if (dirty) {
|
||||
ma->invalidate();
|
||||
// TODO: call Material::compile(), we can't do that now because it works only
|
||||
// with surface materials
|
||||
}
|
||||
|
||||
PostProcessVariant const variant = colorGradingConfig.translucent ?
|
||||
PostProcessVariant::TRANSLUCENT : PostProcessVariant::OPAQUE;
|
||||
FMaterialInstance* const mi =
|
||||
PostProcessMaterial::getMaterialInstance(mEngine, material, variant);
|
||||
|
||||
const SamplerParams params = {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR,
|
||||
.wrapS = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.wrapT = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.wrapR = SamplerWrapMode::CLAMP_TO_EDGE,
|
||||
.anisotropyLog2 = 0
|
||||
};
|
||||
|
||||
mi->setParameter("lut", colorGrading->getHwHandle(), params);
|
||||
|
||||
const float lutDimension = float(colorGrading->getDimension());
|
||||
mi->setParameter("lutSize", float2{
|
||||
0.5f / lutDimension, (lutDimension - 1.0f) / lutDimension,
|
||||
});
|
||||
|
||||
const float temporalNoise = mUniformDistribution(mEngine.getRandomEngine());
|
||||
|
||||
float4 const vignetteParameters = getVignetteParameters(vignetteOptions, width, height);
|
||||
mi->setParameter("vignette", vignetteParameters);
|
||||
mi->setParameter("vignetteColor", vignetteOptions.color);
|
||||
mi->setParameter("dithering", colorGradingConfig.dithering);
|
||||
mi->setParameter("outputLuminance", colorGradingConfig.outputLuminance);
|
||||
mi->setParameter("temporalNoise", temporalNoise);
|
||||
|
||||
return mi;
|
||||
}
|
||||
|
||||
FrameGraphId<FrameGraphTexture> PostProcessManager::taa(FrameGraph& fg,
|
||||
FrameGraphId<FrameGraphTexture> input,
|
||||
FrameGraphId<FrameGraphTexture> const depth,
|
||||
|
||||
@@ -401,13 +401,6 @@ public:
|
||||
FMaterialInstance const* mi,
|
||||
PostProcessVariant variant = PostProcessVariant::OPAQUE) const noexcept;
|
||||
|
||||
// Sets the necessary spec constants and uniforms common to both colorGrading.mat and
|
||||
// colorGradingAsSubpass.mat.
|
||||
FMaterialInstance* configureColorGradingMaterial(
|
||||
PostProcessMaterial& material, FColorGrading const* colorGrading,
|
||||
ColorGradingConfig const& colorGradingConfig, VignetteOptions const& vignetteOptions,
|
||||
uint32_t const width, uint32_t const height) noexcept;
|
||||
|
||||
private:
|
||||
backend::RenderPrimitiveHandle mFullScreenQuadRph;
|
||||
backend::VertexBufferInfoHandle mFullScreenQuadVbih;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <tuple>
|
||||
|
||||
namespace filament {
|
||||
@@ -580,11 +581,8 @@ static float3 luminanceScaling(float3 x,
|
||||
// Quality
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static std::tuple<TextureFormat, PixelDataFormat, PixelDataType> selectLutTextureParams(
|
||||
ColorGrading::LutFormat const lutFormat, const bool isOneDimensional) noexcept {
|
||||
if (isOneDimensional) {
|
||||
return { TextureFormat::R16F, PixelDataFormat::R, PixelDataType::HALF };
|
||||
}
|
||||
static std::tuple<TextureFormat, PixelDataFormat, PixelDataType>
|
||||
selectLutTextureParams(ColorGrading::LutFormat const lutFormat) noexcept {
|
||||
// We use RGBA16F for high quality modes instead of RGB16F because RGB16F
|
||||
// is not supported everywhere
|
||||
switch (lutFormat) {
|
||||
@@ -660,44 +658,27 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) {
|
||||
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
// XXX: The following two conditions also only hold true as long as the input and output color
|
||||
// spaces are the same, but we currently don't check that. We must revise these conditions if we
|
||||
// ever handle this case.
|
||||
mIsOneDimensional = !builder->hasAdjustments && !builder->luminanceScaling
|
||||
&& builder->toneMapper->isOneDimensional()
|
||||
&& engine.features.engine.color_grading.use_1d_lut;
|
||||
mIsLDR = mIsOneDimensional && builder->toneMapper->isLDR();
|
||||
|
||||
const Config config = {
|
||||
mIsOneDimensional ? 512u : builder->dimension,
|
||||
adaptationTransform(builder->whiteBalance),
|
||||
selectColorGradingTransformIn(builder->toneMapping),
|
||||
selectColorGradingTransformOut(builder->toneMapping),
|
||||
selectColorGradingLuminance(builder->toneMapping),
|
||||
selectOETF(builder->outputColorSpace),
|
||||
};
|
||||
|
||||
mDimension = config.lutDimension;
|
||||
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t depth;
|
||||
if (mIsOneDimensional) {
|
||||
width = config.lutDimension;
|
||||
height = 1;
|
||||
depth = 1;
|
||||
} else {
|
||||
width = config.lutDimension;
|
||||
height = config.lutDimension;
|
||||
depth = config.lutDimension;
|
||||
Config c;
|
||||
// This lock protects the data inside Config, which is written to by the Filament thread,
|
||||
// and read from multiple Job threads.
|
||||
Mutex configLock;
|
||||
{
|
||||
std::lock_guard<Mutex> const lock(configLock);
|
||||
c.lutDimension = builder->dimension;
|
||||
c.adaptationTransform = adaptationTransform(builder->whiteBalance);
|
||||
c.colorGradingIn = selectColorGradingTransformIn(builder->toneMapping);
|
||||
c.colorGradingOut = selectColorGradingTransformOut(builder->toneMapping);
|
||||
c.colorGradingLuminance = selectColorGradingLuminance(builder->toneMapping);
|
||||
c.oetf = selectOETF(builder->outputColorSpace);
|
||||
}
|
||||
|
||||
size_t lutElementCount = width * height * depth;
|
||||
size_t elementSize = mIsOneDimensional ? sizeof(half) : sizeof(half4);
|
||||
mDimension = c.lutDimension;
|
||||
|
||||
size_t lutElementCount = c.lutDimension * c.lutDimension * c.lutDimension;
|
||||
size_t elementSize = sizeof(half4);
|
||||
void* data = malloc(lutElementCount * elementSize);
|
||||
|
||||
auto [textureFormat, format, type] =
|
||||
selectLutTextureParams(builder->format, mIsOneDimensional);
|
||||
auto [textureFormat, format, type] = selectLutTextureParams(builder->format);
|
||||
assert_invariant(FTexture::isTextureFormatSupported(engine, textureFormat));
|
||||
assert_invariant(FTexture::validatePixelFormatAndType(textureFormat, format, type));
|
||||
|
||||
@@ -707,185 +688,170 @@ FColorGrading::FColorGrading(FEngine& engine, const Builder& builder) {
|
||||
converted = malloc(lutElementCount * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
auto hdrColorAt = [builder, config](size_t r, size_t g, size_t b) {
|
||||
float3 v = float3{r, g, b} * (1.0f / float(config.lutDimension - 1u));
|
||||
|
||||
// LogC encoding
|
||||
v = LogC_to_linear(v);
|
||||
|
||||
// Kill negative values near 0.0f due to imprecision in the log conversion
|
||||
v = max(v, 0.0f);
|
||||
|
||||
if (builder->hasAdjustments) {
|
||||
// Exposure
|
||||
v = adjustExposure(v, builder->exposure);
|
||||
|
||||
// Purkinje shift ("low-light" vision)
|
||||
v = scotopicAdaptation(v, builder->nightAdaptation);
|
||||
}
|
||||
|
||||
// Move to color grading color space
|
||||
v = config.colorGradingIn * v;
|
||||
|
||||
if (builder->hasAdjustments) {
|
||||
// White balance
|
||||
v = chromaticAdaptation(v, config.adaptationTransform);
|
||||
|
||||
// Kill negative values before the next transforms
|
||||
v = max(v, 0.0f);
|
||||
|
||||
// Channel mixer
|
||||
v = channelMixer(v, builder->outRed, builder->outGreen, builder->outBlue);
|
||||
|
||||
// Shadows/mid-tones/highlights
|
||||
v = tonalRanges(v, config.colorGradingLuminance,
|
||||
builder->shadows, builder->midtones, builder->highlights,
|
||||
builder->tonalRanges);
|
||||
|
||||
// The adjustments below behave better in log space
|
||||
v = linear_to_LogC(v);
|
||||
|
||||
// ASC CDL
|
||||
v = colorDecisionList(v, builder->slope, builder->offset, builder->power);
|
||||
|
||||
// Contrast in log space
|
||||
v = contrast(v, builder->contrast);
|
||||
|
||||
// Back to linear space
|
||||
v = LogC_to_linear(v);
|
||||
|
||||
// Vibrance in linear space
|
||||
v = vibrance(v, config.colorGradingLuminance, builder->vibrance);
|
||||
|
||||
// Saturation in linear space
|
||||
v = saturation(v, config.colorGradingLuminance, builder->saturation);
|
||||
|
||||
// Kill negative values before curves
|
||||
v = max(v, 0.0f);
|
||||
|
||||
// RGB curves
|
||||
v = curves(v,
|
||||
builder->shadowGamma, builder->midPoint, builder->highlightScale);
|
||||
}
|
||||
|
||||
// Tone mapping
|
||||
if (builder->luminanceScaling) {
|
||||
v = luminanceScaling(v, *builder->toneMapper, config.colorGradingLuminance);
|
||||
} else {
|
||||
v = (*builder->toneMapper)(v);
|
||||
}
|
||||
|
||||
// Go back to display color space
|
||||
v = config.colorGradingOut * v;
|
||||
|
||||
// Apply gamut mapping
|
||||
if (builder->gamutMapping) {
|
||||
// TODO: This should depend on the output color space
|
||||
v = gamutMapping_sRGB(v);
|
||||
}
|
||||
|
||||
// TODO: We should convert to the output color space if we use a working
|
||||
// color space that's not sRGB
|
||||
// TODO: Allow the user to customize the output color space
|
||||
|
||||
// We need to clamp for the output transfer function
|
||||
v = saturate(v);
|
||||
|
||||
// Apply OETF
|
||||
v = config.oetf(v);
|
||||
|
||||
return v;
|
||||
};
|
||||
|
||||
//auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (mIsOneDimensional) {
|
||||
half* UTILS_RESTRICT p = (half*) data;
|
||||
if (mIsLDR) {
|
||||
for (size_t rgb = 0; rgb < config.lutDimension; rgb++) {
|
||||
float3 v = float3(rgb) * (1.0f / float(config.lutDimension - 1u));
|
||||
|
||||
v = (*builder->toneMapper)(float3(v));
|
||||
|
||||
// We need to clamp for the output transfer function
|
||||
v = saturate(v);
|
||||
|
||||
// Apply OETF
|
||||
v = config.oetf(v);
|
||||
|
||||
*p++ = half(v.r);
|
||||
// Multithreadedly generate the tone mapping 3D look-up table using 32 jobs
|
||||
// Slices are 8 KiB (128 cache lines) apart.
|
||||
// This takes about 3-6ms on Android in Release
|
||||
JobSystem& js = engine.getJobSystem();
|
||||
auto *slices = js.createJob();
|
||||
for (size_t b = 0; b < c.lutDimension; b++) {
|
||||
auto *job = js.createJob(slices,
|
||||
[data, converted, b, &c, &configLock, builder](JobSystem&, JobSystem::Job*) {
|
||||
Config config;
|
||||
{
|
||||
std::lock_guard<Mutex> lock(configLock);
|
||||
config = c;
|
||||
}
|
||||
} else {
|
||||
for (size_t rgb = 0; rgb < config.lutDimension; rgb++) {
|
||||
*p++ = half(hdrColorAt(rgb, rgb, rgb).r);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Multithreadedly generate the tone mapping 3D look-up table using 32 jobs
|
||||
// Slices are 8 KiB (128 cache lines) apart.
|
||||
// This takes about 3-6ms on Android in Release
|
||||
JobSystem& js = engine.getJobSystem();
|
||||
auto *slices = js.createJob();
|
||||
for (size_t b = 0; b < config.lutDimension; b++) {
|
||||
auto* job = js.createJob(slices,
|
||||
[data, converted, b, &config, builder, &hdrColorAt](
|
||||
JobSystem&, JobSystem::Job*) {
|
||||
half4* UTILS_RESTRICT p =
|
||||
(half4*) data + b * config.lutDimension * config.lutDimension;
|
||||
for (size_t g = 0; g < config.lutDimension; g++) {
|
||||
for (size_t r = 0; r < config.lutDimension; r++) {
|
||||
*p++ = half4{hdrColorAt(r, g, b), 0.0f};
|
||||
half4* UTILS_RESTRICT p = (half4*) data + b * config.lutDimension * config.lutDimension;
|
||||
for (size_t g = 0; g < config.lutDimension; g++) {
|
||||
for (size_t r = 0; r < config.lutDimension; r++) {
|
||||
float3 v = float3{r, g, b} * (1.0f / float(config.lutDimension - 1u));
|
||||
|
||||
// LogC encoding
|
||||
v = LogC_to_linear(v);
|
||||
|
||||
// Kill negative values near 0.0f due to imprecision in the log conversion
|
||||
v = max(v, 0.0f);
|
||||
|
||||
if (builder->hasAdjustments) {
|
||||
// Exposure
|
||||
v = adjustExposure(v, builder->exposure);
|
||||
|
||||
// Purkinje shift ("low-light" vision)
|
||||
v = scotopicAdaptation(v, builder->nightAdaptation);
|
||||
}
|
||||
}
|
||||
|
||||
if (converted) {
|
||||
uint32_t* const UTILS_RESTRICT dst = (uint32_t*) converted +
|
||||
b * config.lutDimension * config.lutDimension;
|
||||
half4* UTILS_RESTRICT src = (half4*) data +
|
||||
b * config.lutDimension * config.lutDimension;
|
||||
// we use a vectorize width of 8 because, on ARMv8 it allows the compiler to
|
||||
// write eight 32-bits results in one go.
|
||||
const size_t count = (config.lutDimension * config.lutDimension) & ~0x7u; // tell the compiler that we're a multiple of 8
|
||||
// Move to color grading color space
|
||||
v = c.colorGradingIn * v;
|
||||
|
||||
if (builder->hasAdjustments) {
|
||||
// White balance
|
||||
v = chromaticAdaptation(v, config.adaptationTransform);
|
||||
|
||||
// Kill negative values before the next transforms
|
||||
v = max(v, 0.0f);
|
||||
|
||||
// Channel mixer
|
||||
v = channelMixer(v, builder->outRed, builder->outGreen, builder->outBlue);
|
||||
|
||||
// Shadows/mid-tones/highlights
|
||||
v = tonalRanges(v, c.colorGradingLuminance,
|
||||
builder->shadows, builder->midtones, builder->highlights,
|
||||
builder->tonalRanges);
|
||||
|
||||
// The adjustments below behave better in log space
|
||||
v = linear_to_LogC(v);
|
||||
|
||||
// ASC CDL
|
||||
v = colorDecisionList(v, builder->slope, builder->offset, builder->power);
|
||||
|
||||
// Contrast in log space
|
||||
v = contrast(v, builder->contrast);
|
||||
|
||||
// Back to linear space
|
||||
v = LogC_to_linear(v);
|
||||
|
||||
// Vibrance in linear space
|
||||
v = vibrance(v, c.colorGradingLuminance, builder->vibrance);
|
||||
|
||||
// Saturation in linear space
|
||||
v = saturation(v, c.colorGradingLuminance, builder->saturation);
|
||||
|
||||
// Kill negative values before curves
|
||||
v = max(v, 0.0f);
|
||||
|
||||
// RGB curves
|
||||
v = curves(v,
|
||||
builder->shadowGamma, builder->midPoint, builder->highlightScale);
|
||||
}
|
||||
|
||||
// Tone mapping
|
||||
if (builder->luminanceScaling) {
|
||||
v = luminanceScaling(v, *builder->toneMapper, c.colorGradingLuminance);
|
||||
} else {
|
||||
v = (*builder->toneMapper)(v);
|
||||
}
|
||||
|
||||
// Go back to display color space
|
||||
v = c.colorGradingOut * v;
|
||||
|
||||
// Apply gamut mapping
|
||||
if (builder->gamutMapping) {
|
||||
// TODO: This should depend on the output color space
|
||||
v = gamutMapping_sRGB(v);
|
||||
}
|
||||
|
||||
// TODO: We should convert to the output color space if we use a working
|
||||
// color space that's not sRGB
|
||||
// TODO: Allow the user to customize the output color space
|
||||
|
||||
// We need to clamp for the output transfer function
|
||||
v = saturate(v);
|
||||
|
||||
// Apply OETF
|
||||
v = c.oetf(v);
|
||||
|
||||
*p++ = half4{v, 0.0f};
|
||||
}
|
||||
}
|
||||
|
||||
if (converted) {
|
||||
uint32_t* const UTILS_RESTRICT dst = (uint32_t*) converted +
|
||||
b * config.lutDimension * config.lutDimension;
|
||||
half4* UTILS_RESTRICT src = (half4*) data +
|
||||
b * config.lutDimension * config.lutDimension;
|
||||
// we use a vectorize width of 8 because, on ARMv8 it allows the compiler to write eight
|
||||
// 32-bits results in one go.
|
||||
const size_t count = (config.lutDimension * config.lutDimension) & ~0x7u; // tell the compiler that we're a multiple of 8
|
||||
#if defined(__clang__)
|
||||
#pragma clang loop vectorize_width(8)
|
||||
#pragma clang loop vectorize_width(8)
|
||||
#endif
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
float4 v{src[i]};
|
||||
uint32_t pr = uint32_t(std::floor(v.x * 1023.0f + 0.5f));
|
||||
uint32_t pg = uint32_t(std::floor(v.y * 1023.0f + 0.5f));
|
||||
uint32_t pb = uint32_t(std::floor(v.z * 1023.0f + 0.5f));
|
||||
dst[i] = (pb << 20u) | (pg << 10u) | pr;
|
||||
}
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
float4 v{src[i]};
|
||||
uint32_t pr = uint32_t(std::floor(v.x * 1023.0f + 0.5f));
|
||||
uint32_t pg = uint32_t(std::floor(v.y * 1023.0f + 0.5f));
|
||||
uint32_t pb = uint32_t(std::floor(v.z * 1023.0f + 0.5f));
|
||||
dst[i] = (pb << 20u) | (pg << 10u) | pr;
|
||||
}
|
||||
});
|
||||
js.run(job);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Should we do a runAndRetain() and defer the wait() + texture creation until
|
||||
// getHwHandle() is invoked?
|
||||
js.runAndWait(slices);
|
||||
});
|
||||
js.run(job);
|
||||
}
|
||||
|
||||
// TODO: Should we do a runAndRetain() and defer the wait() + texture creation until
|
||||
// getHwHandle() is invoked?
|
||||
js.runAndWait(slices);
|
||||
|
||||
//std::chrono::duration<float, std::milli> duration = std::chrono::steady_clock::now() - now;
|
||||
//slog.d << "LUT generation time: " << duration.count() << " ms" << io::endl;
|
||||
|
||||
mLutHandle = driver.createTexture(
|
||||
SamplerType::SAMPLER_3D,
|
||||
1,
|
||||
textureFormat,
|
||||
1,
|
||||
c.lutDimension,
|
||||
c.lutDimension,
|
||||
c.lutDimension,
|
||||
TextureUsage::DEFAULT
|
||||
);
|
||||
|
||||
if (converted) {
|
||||
free(data);
|
||||
data = converted;
|
||||
elementSize = sizeof(uint32_t);
|
||||
}
|
||||
|
||||
// Create texture.
|
||||
mLutHandle = driver.createTexture(SamplerType::SAMPLER_3D, 1, textureFormat, 1,
|
||||
width, height, depth, TextureUsage::DEFAULT);
|
||||
|
||||
driver.update3DImage(mLutHandle, 0,
|
||||
0, 0, 0,
|
||||
width, height, depth,
|
||||
c.lutDimension, c.lutDimension, c.lutDimension,
|
||||
PixelBufferDescriptor{
|
||||
data, lutElementCount * elementSize, format, type,
|
||||
[](void* buffer, size_t, void*) { free(buffer); }
|
||||
});
|
||||
data, lutElementCount * elementSize,format, type,
|
||||
[](void* buffer, size_t, void*) { free(buffer); }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
FColorGrading::~FColorGrading() noexcept = default;
|
||||
|
||||
@@ -42,15 +42,12 @@ public:
|
||||
void terminate(FEngine& engine);
|
||||
|
||||
backend::TextureHandle getHwHandle() const noexcept { return mLutHandle; }
|
||||
|
||||
uint32_t getDimension() const noexcept { return mDimension; }
|
||||
bool isOneDimensional() const noexcept { return mIsOneDimensional; }
|
||||
bool isLDR() const noexcept { return mIsLDR; }
|
||||
|
||||
private:
|
||||
backend::TextureHandle mLutHandle;
|
||||
uint32_t mDimension;
|
||||
bool mIsOneDimensional;
|
||||
bool mIsLDR;
|
||||
};
|
||||
|
||||
FILAMENT_DOWNCAST(ColorGrading)
|
||||
|
||||
@@ -138,7 +138,6 @@ Engine* FEngine::create(Builder const& builder) {
|
||||
.forceGLES2Context = instance->getConfig().forceGLES2Context,
|
||||
.stereoscopicType = instance->getConfig().stereoscopicType,
|
||||
.assertNativeWindowIsValid = instance->features.backend.opengl.assert_native_window_is_valid,
|
||||
.metalDisablePanicOnDrawableFailure = instance->getConfig().metalDisablePanicOnDrawableFailure,
|
||||
};
|
||||
instance->mDriver = platform->createDriver(sharedContext, driverConfig);
|
||||
|
||||
@@ -734,7 +733,6 @@ int FEngine::loop() {
|
||||
.forceGLES2Context = mConfig.forceGLES2Context,
|
||||
.stereoscopicType = mConfig.stereoscopicType,
|
||||
.assertNativeWindowIsValid = features.backend.opengl.assert_native_window_is_valid,
|
||||
.metalDisablePanicOnDrawableFailure = mConfig.metalDisablePanicOnDrawableFailure,
|
||||
};
|
||||
mDriver = mPlatform->createDriver(mSharedGLContext, driverConfig);
|
||||
|
||||
|
||||
@@ -687,9 +687,6 @@ public:
|
||||
|
||||
struct {
|
||||
struct {
|
||||
struct {
|
||||
bool use_1d_lut = false;
|
||||
} color_grading;
|
||||
struct {
|
||||
bool use_shadow_atlas = false;
|
||||
} shadows;
|
||||
@@ -722,9 +719,6 @@ public:
|
||||
{ "backend.opengl.assert_native_window_is_valid",
|
||||
"Asserts that the ANativeWindow is valid when rendering starts.",
|
||||
&features.backend.opengl.assert_native_window_is_valid, true },
|
||||
{ "engine.color_grading.use_1d_lut",
|
||||
"Uses a 1D LUT for color grading.",
|
||||
&features.engine.color_grading.use_1d_lut, false },
|
||||
{ "engine.shadows.use_shadow_atlas",
|
||||
"Uses an array of atlases to store shadow maps.",
|
||||
&features.engine.shadows.use_shadow_atlas, false },
|
||||
|
||||
@@ -153,10 +153,9 @@ Texture::Builder& Texture::Builder::name(const char* name, size_t const len) noe
|
||||
}
|
||||
|
||||
Texture* Texture::Builder::build(Engine& engine) {
|
||||
if (mImpl->mTarget != SamplerType::SAMPLER_EXTERNAL) {
|
||||
FILAMENT_CHECK_PRECONDITION(Texture::isTextureFormatSupported(engine, mImpl->mFormat))
|
||||
<< "Texture format " << uint16_t(mImpl->mFormat) << " not supported on this platform";
|
||||
}
|
||||
FILAMENT_CHECK_PRECONDITION(Texture::isTextureFormatSupported(engine, mImpl->mFormat))
|
||||
<< "Texture format " << uint16_t(mImpl->mFormat) << " not supported on this platform";
|
||||
|
||||
const bool isProtectedTexturesSupported =
|
||||
downcast(engine).getDriverApi().isProtectedTexturesSupported();
|
||||
const bool useProtectedMemory = bool(mImpl->mUsage & TextureUsage::PROTECTED);
|
||||
|
||||
@@ -1,29 +1,13 @@
|
||||
vec3 LogC_to_linear(const vec3 x) {
|
||||
vec3 colorGrade(mediump sampler3D lut, const vec3 x) {
|
||||
// Alexa LogC EI 1000
|
||||
const float a = 5.555556;
|
||||
const float b = 0.047996;
|
||||
const float c = 0.244161 / log2(10.0);
|
||||
const float d = 0.386036;
|
||||
return c * log2(a * x + b) + d;
|
||||
}
|
||||
vec3 logc = c * log2(a * x + b) + d;
|
||||
|
||||
vec3 colorGrade3D(mediump sampler3D lut, const vec3 v) {
|
||||
return textureLod(lut, v, 0.0).rgb;
|
||||
}
|
||||
// Remap to sample pixel centers
|
||||
logc = materialParams.lutSize.x + logc * materialParams.lutSize.y;
|
||||
|
||||
vec3 colorGrade1D(mediump sampler3D lut, const vec3 v) {
|
||||
return vec3(
|
||||
textureLod(lut, vec3(v.r, 0.5, 0.5), 0.0).r,
|
||||
textureLod(lut, vec3(v.g, 0.5, 0.5), 0.0).r,
|
||||
textureLod(lut, vec3(v.b, 0.5, 0.5), 0.0).r);
|
||||
}
|
||||
|
||||
vec3 colorGrade(mediump sampler3D lut, vec3 v) {
|
||||
if (!materialConstants_isLDR) {
|
||||
v = LogC_to_linear(v);
|
||||
}
|
||||
// Remap to sample pixel centers.
|
||||
v = materialParams.lutSize.x + v * materialParams.lutSize.y;
|
||||
return materialConstants_isOneDimensional
|
||||
? colorGrade1D(lut, v) : colorGrade3D(lut, v);
|
||||
return textureLod(lut, logc, 0.0).rgb;
|
||||
}
|
||||
|
||||
@@ -66,16 +66,6 @@ material {
|
||||
precision : high
|
||||
}
|
||||
],
|
||||
constants : [
|
||||
{
|
||||
type : bool,
|
||||
name : isOneDimensional
|
||||
},
|
||||
{
|
||||
type : bool,
|
||||
name : isLDR
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
vertex
|
||||
],
|
||||
@@ -164,7 +154,7 @@ void postProcess(inout PostProcessInputs postProcess) {
|
||||
}
|
||||
|
||||
// Color grading
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
|
||||
// output in premultiplied alpha
|
||||
#if !POST_PROCESS_OPAQUE
|
||||
|
||||
@@ -32,16 +32,6 @@ material {
|
||||
name : vignetteColor
|
||||
}
|
||||
],
|
||||
constants : [
|
||||
{
|
||||
type : bool,
|
||||
name : isOneDimensional
|
||||
},
|
||||
{
|
||||
type : bool,
|
||||
name : isLDR
|
||||
}
|
||||
],
|
||||
subpasses : [
|
||||
{
|
||||
type : subpassInput,
|
||||
@@ -113,7 +103,7 @@ void postProcess(inout PostProcessInputs postProcess) {
|
||||
}
|
||||
|
||||
// Color grading
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
color.rgb = colorGrade(materialParams_lut, color.rgb);
|
||||
|
||||
// output in premultiplied alpha
|
||||
#if !POST_PROCESS_OPAQUE
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.58.2"
|
||||
spec.version = "1.58.0"
|
||||
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.58.0/filament-v1.58.0-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
## Capabilities
|
||||
|
||||
fgviewer is a library and web application for real-time visualization of the frame graph in Filament.
|
||||
fgviewer is a library and web application for real-time visualization of the frame graph in Filament.
|
||||
It displays active passes and resource usage, providing insights into the rendering pipeline.
|
||||
|
||||
## Setup for Desktop
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
utils::CString name;
|
||||
utils::CString value;
|
||||
};
|
||||
|
||||
|
||||
Resource(ResourceId id, utils::CString name,
|
||||
std::vector<Property> properties);
|
||||
|
||||
|
||||
@@ -17,22 +17,14 @@
|
||||
|
||||
#include "ApiHandler.h"
|
||||
|
||||
#include <__chrono/duration.h>
|
||||
#include <fgviewer/DebugServer.h>
|
||||
#include <fgviewer/JsonWriter.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <CivetServer.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace filament::fgviewer {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -21,45 +21,36 @@
|
||||
|
||||
#include <CivetServer.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Hash.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace filament::fgviewer {
|
||||
|
||||
// If set to 0, this serves HTML from a resgen resource. Use 1 only during local development, which
|
||||
// serves files directly from the source code tree.
|
||||
#define SERVE_FROM_SOURCE_TREE 0
|
||||
|
||||
#if SERVE_FROM_SOURCE_TREE
|
||||
|
||||
namespace {
|
||||
std::string const BASE_URL = "libs/fgviewer/web";
|
||||
} // anonymous
|
||||
|
||||
#else
|
||||
|
||||
#include "fgviewer_resources.h"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
struct Asset {
|
||||
std::string_view mime;
|
||||
std::string_view data;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string_view, Asset> ASSET_MAP;
|
||||
|
||||
} // anonymous
|
||||
|
||||
#endif // SERVE_FROM_SOURCE_TREE
|
||||
|
||||
namespace filament::fgviewer {
|
||||
|
||||
using namespace utils;
|
||||
|
||||
std::string_view const DebugServer::kSuccessHeader =
|
||||
|
||||
@@ -16,11 +16,6 @@
|
||||
|
||||
#include <fgviewer/FrameGraphInfo.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::fgviewer {
|
||||
|
||||
FrameGraphInfo::FrameGraphInfo(utils::CString viewName):
|
||||
|
||||
@@ -14,15 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <fgviewer/FrameGraphInfo.h>
|
||||
#include <fgviewer/JsonWriter.h>
|
||||
#include <fgviewer/FrameGraphInfo.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <ostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::fgviewer {
|
||||
|
||||
|
||||
@@ -223,8 +223,8 @@ class FrameGraphSidePanel extends LitElement {
|
||||
return html`
|
||||
<menu-section title="${title}">
|
||||
${this.framegraphs.map(({ fgid, name }) => html`
|
||||
<div class="framegraph"
|
||||
@click="${() => this._handleFrameGraphClick(fgid)}"
|
||||
<div class="framegraph"
|
||||
@click="${() => this._handleFrameGraphClick(fgid)}"
|
||||
data-id="${fgid}">
|
||||
${fgid === this.selectedFrameGraph ? '● ' : ''}${name}
|
||||
</div>
|
||||
@@ -440,15 +440,15 @@ class FrameGraphTable extends LitElement {
|
||||
return html`
|
||||
<tr id="resource-${resourceIndex}">
|
||||
<th class="sticky-col resource ${selectedStyle}" @click="${onClickResource}">
|
||||
${hasSubresources
|
||||
${hasSubresources
|
||||
? html`
|
||||
<span class="toggle-icon"
|
||||
@click="${(e) => { e.stopPropagation(); this._toggleCollapse(resourceIndex); }}">
|
||||
${isExpanded ? '▼' : '▶'}
|
||||
</span>`
|
||||
</span>`
|
||||
: nothing}
|
||||
${resource.name}
|
||||
${hasSubresources && !isExpanded ? html`(${subresourceIds.length})` : nothing}
|
||||
${hasSubresources && isExpanded ? nothing : html`(${subresourceIds.length})`}
|
||||
</th>
|
||||
${this._renderResourceUsage(allPasses, resourceIds, DEFAULT_COLOR)}
|
||||
</tr>
|
||||
@@ -592,12 +592,12 @@ class FrameGraphViewer extends LitElement {
|
||||
return html`
|
||||
<framegraph-sidepanel id="sidepanel"
|
||||
?connected="${this.connected}"
|
||||
selected-framegraph="${this.selectedFrameGraph}"
|
||||
selected-framegraph="${this.selectedFrameGraph}"
|
||||
selected-resource="${this.selectedResourceId}">
|
||||
</framegraph-sidepanel>
|
||||
<framegraph-table id="table"
|
||||
<framegraph-table id="table"
|
||||
?connected="${this.connected}"
|
||||
selected-framegraph="${this.selectedFrameGraph}"
|
||||
selected-framegraph="${this.selectedFrameGraph}"
|
||||
selected-resource="${this.selectedResourceId}">
|
||||
</framegraph-table>
|
||||
`;
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
namespace filament {
|
||||
|
||||
// update this when a new version of filament wouldn't work with older materials
|
||||
static constexpr size_t MATERIAL_VERSION = 58;
|
||||
static constexpr size_t MATERIAL_VERSION = 57;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
|
||||
@@ -535,6 +535,7 @@ bool GLSLPostProcessor::spirvToWgsl(SpirvBlob *spirv, std::string *outWsl) {
|
||||
//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;
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
|
||||
@@ -919,6 +920,10 @@ std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
||||
optimizer->RegisterPass(CreateRedundancyEliminationPass());
|
||||
optimizer->RegisterPass(CreateAggressiveDCEPass());
|
||||
}
|
||||
if (config.targetApi == MaterialBuilder::TargetApi::WEBGPU) {
|
||||
optimizer->RegisterPass(CreateSplitCombinedImageSamplerPass());
|
||||
optimizer->RegisterPass(CreateResolveBindingConflictsPass());
|
||||
}
|
||||
|
||||
return optimizer;
|
||||
}
|
||||
|
||||
@@ -1049,6 +1049,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
|
||||
|
||||
|
||||
switch (targetApi) {
|
||||
// TODO: Handle webgpu here
|
||||
case TargetApi::WEBGPU:
|
||||
assert(!spirv.empty());
|
||||
assert(wgsl.length() > 0);
|
||||
|
||||
@@ -245,6 +245,20 @@ function(add_demo NAME)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(add_demo_plain NAME)
|
||||
include_directories(${GENERATION_ROOT})
|
||||
add_executable(${NAME} ${NAME}.cpp)
|
||||
target_compile_options(${NAME} PRIVATE ${COMPILER_FLAGS})
|
||||
set_target_properties(${NAME} PROPERTIES FOLDER Samples)
|
||||
|
||||
# This is needed after XCode 15.3
|
||||
if (APPLE)
|
||||
set_target_properties(${NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set_target_properties(${NAME} PROPERTIES INSTALL_RPATH /usr/local/lib)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
||||
if (NOT ANDROID)
|
||||
add_demo(animation)
|
||||
add_demo(depthtesting)
|
||||
@@ -280,6 +294,9 @@ if (NOT ANDROID)
|
||||
add_demo(vbotest)
|
||||
add_demo(viewtest)
|
||||
|
||||
add_demo_plain(spirv_test)
|
||||
target_link_libraries(spirv_test PRIVATE SPIRV-Headers utils)
|
||||
|
||||
# Sample app specific
|
||||
target_link_libraries(frame_generator PRIVATE imageio)
|
||||
target_link_libraries(gltf_viewer PRIVATE gltf-demo-resources uberarchive gltfio viewer)
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "spirv/unified1/spirv.hpp"
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <functional>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
size_t getFileSize(const std::string& filename) {
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return -1;
|
||||
}
|
||||
size_t size = file.tellg();
|
||||
file.close();
|
||||
return size;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> transform(std::vector<uint32_t> const& spirv) {
|
||||
constexpr size_t HEADER_SIZE = 5;
|
||||
size_t const dataSize = spirv.size();
|
||||
std::vector<uint32_t> output;
|
||||
output.resize(dataSize);
|
||||
uint32_t const* data = (uint32_t*) spirv.data();
|
||||
uint32_t* outputData = output.data();
|
||||
memcpy(&outputData[0], &data[0], HEADER_SIZE * 4);
|
||||
|
||||
struct Descriptor {
|
||||
uint32_t binding = 0xFFFFFFFF;
|
||||
uint32_t set = 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, 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(wordCount-1, cursor+1);
|
||||
}
|
||||
cursor += wordCount;
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t samplerTypeId = 0;
|
||||
uint32_t imageTypeId = 0;
|
||||
|
||||
// First we find the ID used to identify the sampler type and the image type.
|
||||
pass(spv::Op::OpTypeSampler, [&](uint32_t count, uint32_t pos) {
|
||||
samplerTypeId = data[pos];
|
||||
});
|
||||
|
||||
pass(spv::Op::OpTypeImage, [&](uint32_t count, uint32_t pos) {
|
||||
imageTypeId = data[pos];
|
||||
});
|
||||
|
||||
// Next we identify all the descriptors and record their set id and binding.
|
||||
pass(spv::Op::OpDecorate, [&](uint32_t count, 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);
|
||||
desc.set = set;
|
||||
}
|
||||
});
|
||||
|
||||
// If any descriptor is loaded to be a sampler, we offset it by a fixed amount. Note that it
|
||||
// could be "loaded" multiple times, so we use a boolean to skip if it's already offsetted.
|
||||
constexpr uint32_t SAMPLER_BINDING_OFFSET = 100;
|
||||
pass(spv::Op::OpLoad, [&](uint32_t count, uint32_t pos) {
|
||||
uint32_t const retType = data[pos];
|
||||
uint32_t const targetVar = data[pos + 2];
|
||||
if (retType == samplerTypeId) {
|
||||
auto& desc = getDesc(targetVar);
|
||||
if (!desc.fixed) {
|
||||
desc.binding += SAMPLER_BINDING_OFFSET;
|
||||
desc.fixed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Write out the offsetted bindings and copy the rest.
|
||||
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;
|
||||
|
||||
uint32_t* out = &outputData[cursor];
|
||||
std::memcpy(out, &data[cursor], wordCount * 4);
|
||||
|
||||
if (op == spv::Op::OpDecorate && data[cursor + 2] == spv::Decoration::DecorationBinding) {
|
||||
uint32_t const targetVar = data[cursor + 1];
|
||||
auto const& modified = getDesc(targetVar);
|
||||
out[3] = modified.binding;
|
||||
}
|
||||
cursor += wordCount;
|
||||
}
|
||||
|
||||
// Note that you should be able to do this in-place (without making another vector).
|
||||
return output;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
utils::slog.e <<"need input and output spirv filenames" << utils::io::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string infname {argv[1]};
|
||||
size_t const fsize = getFileSize(infname);
|
||||
size_t const wordCount = fsize / 4;
|
||||
|
||||
std::ifstream inputFile(infname, std::ios::binary);
|
||||
|
||||
std::vector<uint32_t> words;
|
||||
words.resize(wordCount);
|
||||
inputFile.read((char*) words.data(), fsize);
|
||||
inputFile.close();
|
||||
|
||||
std::string outfname {argv[2]};
|
||||
std::vector<uint32_t> output = transform(words);
|
||||
std::ofstream outputFile(outfname, std::ios::binary);
|
||||
outputFile.write((char const*) output.data(), output.size() * 4);
|
||||
outputFile.close();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Vendored
+1
-1
@@ -14,7 +14,7 @@ set(DAWN_SPIRV_HEADERS_DIR ${EXTERNAL}/spirv-headers)
|
||||
set(DAWN_GLSLANG_DIR ${EXTERNAL}/glslang/)
|
||||
set(DAWN_DXC_ENABLE_ASSERTS_IN_NDEBUG OFF)
|
||||
|
||||
set(TINT_BUILD_CMD_TOOLS OFF)
|
||||
#set(TINT_BUILD_CMD_TOOLS OFF)
|
||||
|
||||
if (FILAMENT_BUILD_FILAMAT)
|
||||
set(TINT_BUILD_SPV_READER ON)
|
||||
|
||||
Vendored
+2
@@ -172,11 +172,13 @@ SPVTOOLS_OPT_SRC_FILES := \
|
||||
source/opt/remove_unused_interface_variables_pass.cpp \
|
||||
source/opt/replace_desc_array_access_using_var_index.cpp \
|
||||
source/opt/replace_invalid_opc.cpp \
|
||||
source/opt/resolve_binding_conflicts_pass.cpp \
|
||||
source/opt/scalar_analysis.cpp \
|
||||
source/opt/scalar_analysis_simplification.cpp \
|
||||
source/opt/scalar_replacement_pass.cpp \
|
||||
source/opt/set_spec_constant_default_value_pass.cpp \
|
||||
source/opt/simplification_pass.cpp \
|
||||
source/opt/split_combined_image_sampler_pass.cpp \
|
||||
source/opt/spread_volatile_semantics.cpp \
|
||||
source/opt/ssa_rewrite_pass.cpp \
|
||||
source/opt/strength_reduction_pass.cpp \
|
||||
|
||||
Vendored
+1
@@ -201,6 +201,7 @@ cc_library(
|
||||
deps = [
|
||||
":spirv_tools_internal",
|
||||
"@spirv_headers//:spirv_common_headers",
|
||||
"@spirv_headers//:spirv_c_headers",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
Vendored
+5
@@ -771,6 +771,8 @@ 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",
|
||||
@@ -781,6 +783,8 @@ 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",
|
||||
@@ -1382,6 +1386,7 @@ if (build_with_chromium && spvtools_build_executables) {
|
||||
"test/libspirv_macros_test.cpp",
|
||||
"test/name_mapper_test.cpp",
|
||||
"test/named_id_test.cpp",
|
||||
"test/op_unknown_test.cpp",
|
||||
"test/opcode_make_test.cpp",
|
||||
"test/opcode_require_capabilities_test.cpp",
|
||||
"test/opcode_split_test.cpp",
|
||||
|
||||
Vendored
+35
@@ -1,5 +1,40 @@
|
||||
Revision history for SPIRV-Tools
|
||||
|
||||
v2025.1 2025-02-28
|
||||
- General
|
||||
- diff: Fix crash in OpString matching (#5988)
|
||||
- Add SPV_AMDX_shader_enqueue version 2 support (#5838)
|
||||
- add support for SPV_INTEL_subgroup_matrix_multiply_accumulate (#5928)
|
||||
- update cmake_minimum_required to 3.22.1 (#5925)
|
||||
- Add OpImageSampleFootprintNV to IsAllowedSampledImageOperand (#5914)
|
||||
- assembler: ensure progress when seeking the version string (#5910)
|
||||
- Optimizer
|
||||
- opt: keep all OpSource instructions (#5901)
|
||||
- [opt] Fix bug opt::InstructionBuilder::AddVariable (#6007)
|
||||
- [OPT] Add SPV_KHR_ray_tracing to allow list (#5941)
|
||||
- opt: keep all OpSource instructions (#5901)
|
||||
- Validator
|
||||
- spirv-val: Add AllowVulkan32BitBitwise option (#6001)
|
||||
- Fix untyped pointer comparison validation (#6004)
|
||||
- spirv-val: Update VUIDs for 308 header (#5990)
|
||||
- spirv-val: fix env parsing for vk1.1spv1.4 (#5985)
|
||||
- Add validation for SPV_NV_linear_swept_spheres. (#5975)
|
||||
- Add validation SPV_NV_cluster_acceleration_structure. (#5974)
|
||||
- Improve the instruction diagnostic for some access chain errors (#5978)
|
||||
- Update physical storage buffer validation to match SPIR-V 1.6.5 (#5977)
|
||||
- Validate SPV_NV_cooperative_vector (#5972)
|
||||
- Fix layout checks with untyped pointers (#5970)
|
||||
- spirv-val: Update mislabeled VUIDs (#5964)
|
||||
- More explicit layout validation (#5958)
|
||||
- spirv-val: Add VK_KHR_maintenance8 support (#5951)
|
||||
- Add EXT_mesh_shader validation support (#5640)
|
||||
- spirv-val: Remove OpenCL ivec3 req (#5940)
|
||||
- spirv-val: Validate zero product workgroup size (#5407)
|
||||
- Relax DebugLine validation (#5916)
|
||||
- Linker
|
||||
- linker: remove LinkOnceODR decorations when linking executables (#5979)
|
||||
- fix: handle LinkOnceODR correctly (#5938)
|
||||
|
||||
v2024.4 2024-12-04
|
||||
- General
|
||||
- Add FPEncoding operand type. (#5726)
|
||||
|
||||
Vendored
+5
-5
@@ -3,18 +3,18 @@ use_relative_paths = True
|
||||
vars = {
|
||||
'github': 'https://github.com',
|
||||
|
||||
'abseil_revision': '69b58f9227bf91a9315475579df15fbb7c5c612e',
|
||||
'abseil_revision': '310e6f4f0f202da13720fdd6cb0095e10a98fe1c',
|
||||
|
||||
'effcee_revision': '12241cbc30f20730b656db7fd5a3fa36cd420843',
|
||||
'effcee_revision': '874b47102c57a8979c0f154cf8e0eab53c0a0502',
|
||||
|
||||
'googletest_revision': 'e235eb34c6c4fed790ccdad4b16394301360dcd4',
|
||||
'googletest_revision': '4902ea2d7c6faed89b6facee00baa34bb108fc0d',
|
||||
|
||||
# Use protobufs before they gained the dependency on abseil
|
||||
'protobuf_revision': 'v21.12',
|
||||
|
||||
're2_revision': '6dcd83d60f7944926bfd308cc13979fc53dd69ca',
|
||||
're2_revision': 'c84a140c93352cdabbfb547c531be34515b12228',
|
||||
|
||||
'spirv_headers_revision': 'e7294a8ebed84f8c5bd3686c68dbe12a4e65b644',
|
||||
'spirv_headers_revision': 'ddd2c099be25e3fec7cdc14106c17aca99512082',
|
||||
}
|
||||
|
||||
deps = {
|
||||
|
||||
+44
@@ -215,6 +215,50 @@ 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,6 +740,10 @@ 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,6 +132,11 @@ 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.
|
||||
//
|
||||
|
||||
+56
-2
@@ -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 = 100);
|
||||
Optimizer::PassToken CreateScalarReplacementPass(uint32_t size_limit = 0);
|
||||
|
||||
// Create a private to local pass.
|
||||
// This pass looks for variables declared in the private storage class that are
|
||||
@@ -968,6 +968,60 @@ 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_
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
#!/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.
|
||||
+9
-30
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Copyright (c) 2018 Google LLC.
|
||||
# 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.
|
||||
@@ -12,37 +12,16 @@
|
||||
# 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
|
||||
|
||||
BUILD_ROOT=$PWD
|
||||
SRC=$PWD/github/SPIRV-Tools
|
||||
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}"
|
||||
|
||||
# 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.
|
||||
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}"
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ docker run --rm -i \
|
||||
--env KOKORO_ARTIFACTS_DIR="${KOKORO_ARTIFACTS_DIR}" \
|
||||
--env BUILD_SHA="${BUILD_SHA}" \
|
||||
--entrypoint "${SCRIPT_DIR}/build-docker.sh" \
|
||||
"gcr.io/shaderc-build/radial-build:latest"
|
||||
us-east4-docker.pkg.dev/shaderc-build/radial-docker/ubuntu-24.04-amd64/cpp-builder
|
||||
RESULT=$?
|
||||
|
||||
# This is important. If the permissions are not fixed, kokoro will fail
|
||||
|
||||
+57
-5
@@ -67,7 +67,9 @@ void CompactIds(std::vector<uint32_t>& ids) {
|
||||
ids.resize(write_index);
|
||||
}
|
||||
|
||||
// A mapping between src and dst ids.
|
||||
// A mapping from ids in one module to ids in the other.
|
||||
//
|
||||
// Differ contains two of these, for src->dst and dst->src.
|
||||
class IdMap {
|
||||
public:
|
||||
IdMap(size_t id_bound) { id_map_.resize(id_bound, 0); }
|
||||
@@ -190,6 +192,7 @@ 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),
|
||||
@@ -198,6 +201,10 @@ 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(
|
||||
@@ -321,6 +328,8 @@ 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,
|
||||
@@ -883,6 +892,17 @@ 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);
|
||||
@@ -902,6 +922,24 @@ 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,
|
||||
@@ -924,6 +962,10 @@ 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,
|
||||
@@ -2483,7 +2525,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).
|
||||
GroupIdsAndMatch<uint32_t>(src_group, dst_group, 0,
|
||||
GroupIdsAndMatchByMappedId(src_group, dst_group,
|
||||
&Differ::GroupIdsHelperGetTypeId,
|
||||
[this](const IdGroup& src_group_by_type_id,
|
||||
const IdGroup& dst_group_by_type_id) {
|
||||
@@ -2526,9 +2568,19 @@ void Differ::MatchFunctions() {
|
||||
dst_match_result, 0);
|
||||
}
|
||||
|
||||
// Best effort match functions with matching type.
|
||||
GroupIdsAndMatch<uint32_t>(
|
||||
src_func_ids, dst_func_ids, 0, &Differ::GroupIdsHelperGetTypeId,
|
||||
// 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,
|
||||
[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,
|
||||
|
||||
+4
-2
@@ -676,8 +676,10 @@ 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::Export ||
|
||||
spv::LinkageType(inst->GetSingleWordOperand(3u)) ==
|
||||
spv::LinkageType::LinkOnceODR)) {
|
||||
linked_context->KillInst(&*inst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,11 +109,13 @@ 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
|
||||
@@ -225,11 +227,13 @@ 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,6 +276,7 @@ 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);
|
||||
|
||||
+167
-19
@@ -2454,6 +2454,31 @@ 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(
|
||||
@@ -2462,9 +2487,15 @@ FoldingRule RedundantFDiv() {
|
||||
}
|
||||
|
||||
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, {inst->GetSingleWordInOperand(0)}}});
|
||||
inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {zero_id}}});
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2507,24 +2538,18 @@ FoldingRule RedundantFMix() {
|
||||
};
|
||||
}
|
||||
|
||||
// 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.");
|
||||
// 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);
|
||||
|
||||
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 (constants[arg] && constants[arg]->IsZero()) {
|
||||
auto operand = inst->GetSingleWordInOperand(foldToArg);
|
||||
auto operand_type = constants[arg]->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)) {
|
||||
@@ -2539,6 +2564,117 @@ FoldingRule RedundantIAdd() {
|
||||
};
|
||||
}
|
||||
|
||||
// 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() {
|
||||
@@ -2876,6 +3012,17 @@ 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(
|
||||
@@ -2907,6 +3054,8 @@ 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());
|
||||
@@ -2921,7 +3070,6 @@ 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());
|
||||
|
||||
+20
-1
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
@@ -39,6 +40,7 @@ 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|.
|
||||
@@ -77,6 +79,23 @@ 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);
|
||||
|
||||
@@ -197,7 +216,7 @@ class Function {
|
||||
// The OpFunction instruction that begins the definition of this function.
|
||||
std::unique_ptr<Instruction> def_inst_;
|
||||
// All parameters to this function.
|
||||
std::vector<std::unique_ptr<Instruction>> params_;
|
||||
ParamList params_;
|
||||
// All debug instructions in this function's header.
|
||||
InstructionList debug_insts_in_header_;
|
||||
// All basic blocks inside this function in specification order
|
||||
|
||||
@@ -55,6 +55,11 @@ Pass::Status InlineExhaustivePass::InlineExhaustive(Function* func) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
FixDebugDeclares(func);
|
||||
}
|
||||
|
||||
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ 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,
|
||||
@@ -858,5 +860,68 @@ void InlinePass::InitializeInline() {
|
||||
|
||||
InlinePass::InlinePass() {}
|
||||
|
||||
void InlinePass::FixDebugDeclares(Function* func) {
|
||||
std::map<uint32_t, Instruction*> access_chains;
|
||||
std::vector<Instruction*> debug_declare_insts;
|
||||
|
||||
func->ForEachInst([&access_chains, &debug_declare_insts](Instruction* inst) {
|
||||
if (inst->opcode() == spv::Op::OpAccessChain) {
|
||||
access_chains[inst->result_id()] = inst;
|
||||
}
|
||||
if (inst->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) {
|
||||
debug_declare_insts.push_back(inst);
|
||||
}
|
||||
});
|
||||
|
||||
for (auto& inst : debug_declare_insts) {
|
||||
FixDebugDeclare(inst, access_chains);
|
||||
}
|
||||
}
|
||||
|
||||
void InlinePass::FixDebugDeclare(
|
||||
Instruction* dbg_declare_inst,
|
||||
const std::map<uint32_t, Instruction*>& access_chains) {
|
||||
do {
|
||||
uint32_t var_id =
|
||||
dbg_declare_inst->GetSingleWordInOperand(kSpvDebugDeclareVarInIdx);
|
||||
|
||||
// The def-use chains are not kept up to date while inlining, so we need to
|
||||
// get the variable by traversing the functions.
|
||||
auto it = access_chains.find(var_id);
|
||||
if (it == access_chains.end()) {
|
||||
return;
|
||||
}
|
||||
Instruction* access_chain = it->second;
|
||||
|
||||
// If the variable id in the debug declare is an access chain, it is
|
||||
// invalid. it needs to be fixed up. The debug declare will be updated so
|
||||
// that its Var operand becomes the base of the access chain. The indexes of
|
||||
// the access chain are prepended before the indexes of the debug declare.
|
||||
|
||||
std::vector<Operand> operands;
|
||||
for (int i = 0; i < kSpvDebugDeclareVarInIdx; i++) {
|
||||
operands.push_back(dbg_declare_inst->GetInOperand(i));
|
||||
}
|
||||
|
||||
uint32_t access_chain_base =
|
||||
access_chain->GetSingleWordInOperand(kSpvAccessChainBaseInIdx);
|
||||
operands.push_back(Operand(SPV_OPERAND_TYPE_ID, {access_chain_base}));
|
||||
operands.push_back(
|
||||
dbg_declare_inst->GetInOperand(kSpvDebugDeclareVarInIdx + 1));
|
||||
|
||||
for (uint32_t i = kSpvAccessChainBaseInIdx + 1;
|
||||
i < access_chain->NumInOperands(); ++i) {
|
||||
operands.push_back(access_chain->GetInOperand(i));
|
||||
}
|
||||
|
||||
for (uint32_t i = kSpvDebugDeclareVarInIdx + 2;
|
||||
i < dbg_declare_inst->NumInOperands(); ++i) {
|
||||
operands.push_back(dbg_declare_inst->GetInOperand(i));
|
||||
}
|
||||
|
||||
dbg_declare_inst->SetInOperands(std::move(operands));
|
||||
} while (true);
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
@@ -150,6 +150,12 @@ 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_;
|
||||
|
||||
@@ -241,6 +247,11 @@ 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
|
||||
|
||||
+67
-6
@@ -15,6 +15,7 @@
|
||||
#ifndef SOURCE_OPT_IR_BUILDER_H_
|
||||
#define SOURCE_OPT_IR_BUILDER_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
@@ -480,8 +481,11 @@ class InstructionBuilder {
|
||||
return AddInstruction(std::move(select));
|
||||
}
|
||||
|
||||
Instruction* AddAccessChain(uint32_t type_id, uint32_t base_ptr_id,
|
||||
std::vector<uint32_t> ids) {
|
||||
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);
|
||||
std::vector<Operand> operands;
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {base_ptr_id}});
|
||||
|
||||
@@ -490,12 +494,22 @@ class InstructionBuilder {
|
||||
}
|
||||
|
||||
// TODO(1841): Handle id overflow.
|
||||
std::unique_ptr<Instruction> new_inst(
|
||||
new Instruction(GetContext(), spv::Op::OpAccessChain, type_id,
|
||||
GetContext()->TakeNextId(), operands));
|
||||
std::unique_ptr<Instruction> new_inst(new Instruction(
|
||||
GetContext(), opcode, 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;
|
||||
@@ -514,9 +528,19 @@ 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_ID, {storage_class}});
|
||||
operands.push_back({SPV_OPERAND_TYPE_STORAGE_CLASS, {storage_class}});
|
||||
std::unique_ptr<Instruction> new_inst(
|
||||
new Instruction(GetContext(), spv::Op::OpVariable, type_id,
|
||||
GetContext()->TakeNextId(), operands));
|
||||
@@ -572,6 +596,26 @@ 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) {
|
||||
@@ -593,6 +637,23 @@ class InstructionBuilder {
|
||||
return AddInstruction(std::move(new_inst));
|
||||
}
|
||||
|
||||
Instruction* AddSampledImage(uint32_t sampled_image_type_id,
|
||||
uint32_t image_id, uint32_t sampler_id) {
|
||||
std::vector<Operand> operands;
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {image_id}});
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {sampler_id}});
|
||||
|
||||
uint32_t result_id = GetContext()->TakeNextId();
|
||||
if (result_id == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Instruction> new_inst(
|
||||
new Instruction(GetContext(), spv::Op::OpSampledImage,
|
||||
sampled_image_type_id, result_id, operands));
|
||||
return AddInstruction(std::move(new_inst));
|
||||
}
|
||||
|
||||
// Inserts the new instruction before the insertion point.
|
||||
Instruction* AddInstruction(std::unique_ptr<Instruction>&& insn) {
|
||||
Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
|
||||
|
||||
@@ -564,6 +564,7 @@ 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,
|
||||
|
||||
+17
-3
@@ -189,7 +189,7 @@ Optimizer& Optimizer::RegisterPerformancePasses(bool preserve_interface) {
|
||||
.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
||||
.RegisterPass(CreateLocalSingleStoreElimPass())
|
||||
.RegisterPass(CreateAggressiveDCEPass(preserve_interface))
|
||||
.RegisterPass(CreateScalarReplacementPass())
|
||||
.RegisterPass(CreateScalarReplacementPass(0))
|
||||
.RegisterPass(CreateLocalAccessChainConvertPass())
|
||||
.RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
|
||||
.RegisterPass(CreateLocalSingleStoreElimPass())
|
||||
@@ -203,7 +203,7 @@ Optimizer& Optimizer::RegisterPerformancePasses(bool preserve_interface) {
|
||||
.RegisterPass(CreateRedundancyEliminationPass())
|
||||
.RegisterPass(CreateCombineAccessChainsPass())
|
||||
.RegisterPass(CreateSimplificationPass())
|
||||
.RegisterPass(CreateScalarReplacementPass())
|
||||
.RegisterPass(CreateScalarReplacementPass(0))
|
||||
.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());
|
||||
RegisterPass(CreateScalarReplacementPass(0));
|
||||
} else {
|
||||
int limit = -1;
|
||||
if (pass_args.find_first_not_of("0123456789") == std::string::npos) {
|
||||
@@ -637,6 +637,10 @@ 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",
|
||||
@@ -1188,6 +1192,16 @@ Optimizer::PassToken CreateOpExtInstWithForwardReferenceFixupPass() {
|
||||
MakeUnique<opt::OpExtInstWithForwardReferenceFixupPass>());
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateSplitCombinedImageSamplerPass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::SplitCombinedImageSamplerPass>());
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateResolveBindingConflictsPass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::ResolveBindingConflictsPass>());
|
||||
}
|
||||
|
||||
} // namespace spvtools
|
||||
|
||||
extern "C" {
|
||||
|
||||
+2
@@ -74,9 +74,11 @@
|
||||
#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"
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
// 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
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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 = 100;
|
||||
static constexpr uint32_t kDefaultLimit = 0;
|
||||
|
||||
public:
|
||||
ScalarReplacementPass(uint32_t limit = kDefaultLimit)
|
||||
|
||||
@@ -0,0 +1,648 @@
|
||||
// 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
|
||||
@@ -0,0 +1,169 @@
|
||||
// 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,6 +241,37 @@ 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 &&
|
||||
@@ -388,7 +419,7 @@ Handler_OpImageSparseRead_StorageImageReadWithoutFormat(
|
||||
}
|
||||
|
||||
// Opcode of interest to determine capabilities requirements.
|
||||
constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 13> kOpcodeHandlers{{
|
||||
constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 14> kOpcodeHandlers{{
|
||||
// clang-format off
|
||||
{spv::Op::OpImageRead, Handler_OpImageRead_StorageImageReadWithoutFormat},
|
||||
{spv::Op::OpImageWrite, Handler_OpImageWrite_StorageImageWriteWithoutFormat},
|
||||
@@ -403,6 +434,7 @@ constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 13> 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,6 +99,7 @@ class TrimCapabilitiesPass : public Pass {
|
||||
spv::Capability::RayTraversalPrimitiveCullingKHR,
|
||||
spv::Capability::Shader,
|
||||
spv::Capability::ShaderClockKHR,
|
||||
spv::Capability::StorageBuffer16BitAccess,
|
||||
spv::Capability::StorageImageReadWithoutFormat,
|
||||
spv::Capability::StorageImageWriteWithoutFormat,
|
||||
spv::Capability::StorageInputOutput16,
|
||||
|
||||
@@ -495,6 +495,8 @@ 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;
|
||||
|
||||
@@ -131,6 +131,11 @@ 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,6 +49,7 @@ 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) {}
|
||||
|
||||
@@ -62,6 +63,7 @@ struct spv_validator_options_t {
|
||||
bool skip_block_layout;
|
||||
bool allow_localsizeid;
|
||||
bool allow_offset_texture_operand;
|
||||
bool allow_vulkan_32_bit_bitwise;
|
||||
bool before_hlsl_legalization;
|
||||
bool use_friendly_names;
|
||||
};
|
||||
|
||||
+128
@@ -515,6 +515,124 @@ 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
|
||||
@@ -574,6 +692,16 @@ spv_result_t spvTextEncodeOpcode(const spvtools::AssemblyGrammar& grammar,
|
||||
}
|
||||
}
|
||||
|
||||
if (opcodeName == "OpUnknown") {
|
||||
if (!result_id.empty()) {
|
||||
return context->diagnostic()
|
||||
<< "OpUnknown not allowed in assignment. Use an explicit result "
|
||||
"id operand instead.";
|
||||
}
|
||||
context->setPosition(nextPosition);
|
||||
return encodeInstructionStartingWithOpUnknown(grammar, context, pInst);
|
||||
}
|
||||
|
||||
// NOTE: The table contains Opcode names without the "Op" prefix.
|
||||
const char* pInstName = opcodeName.data() + 2;
|
||||
|
||||
|
||||
@@ -118,6 +118,9 @@ spv_result_t getWord(spv_text text, spv_position position, std::string* word) {
|
||||
break;
|
||||
case ' ':
|
||||
case ';':
|
||||
case ',':
|
||||
case '(':
|
||||
case ')':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace utils {
|
||||
template <class T, size_t small_size>
|
||||
class SmallVector {
|
||||
public:
|
||||
using value_type = T;
|
||||
using iterator = T*;
|
||||
using const_iterator = const T*;
|
||||
|
||||
|
||||
+8
-6
@@ -17,13 +17,12 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "source/util/string_utils.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace utils {
|
||||
|
||||
@@ -48,8 +47,9 @@ 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.
|
||||
inline void AppendToVector(const std::string& input,
|
||||
std::vector<uint32_t>* result) {
|
||||
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);
|
||||
uint32_t word = 0;
|
||||
size_t num_bytes = input.size();
|
||||
// SPIR-V strings are null-terminated. The byte_index == num_bytes
|
||||
@@ -70,8 +70,10 @@ inline void AppendToVector(const std::string& input,
|
||||
}
|
||||
|
||||
// Encodes a string as a sequence of words, using the SPIR-V encoding.
|
||||
inline std::vector<uint32_t> MakeVector(const std::string& input) {
|
||||
std::vector<uint32_t> result;
|
||||
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;
|
||||
AppendToVector(input, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
+2
-2
@@ -30,14 +30,14 @@ spv_result_t ValidateBaseType(ValidationState_t& _, const Instruction* inst,
|
||||
|
||||
if (!_.IsIntScalarType(base_type) && !_.IsIntVectorType(base_type)) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< _.VkErrorID(4781)
|
||||
<< "Expected int scalar or vector type for Base operand: "
|
||||
<< spvOpcodeString(opcode);
|
||||
}
|
||||
|
||||
// Vulkan has a restriction to 32 bit for base
|
||||
if (spvIsVulkanEnv(_.context()->target_env)) {
|
||||
if (_.GetBitWidth(base_type) != 32) {
|
||||
if (_.GetBitWidth(base_type) != 32 &&
|
||||
!_.options()->allow_vulkan_32_bit_bitwise) {
|
||||
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
||||
<< _.VkErrorID(4781)
|
||||
<< "Expected 32-bit int type for Base operand: "
|
||||
|
||||
+200
-1
@@ -1916,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(4924)
|
||||
<< vstate.VkErrorID(10583)
|
||||
<< "Component decoration specified for type "
|
||||
<< vstate.getIdName(type_id) << " that is not a scalar or vector";
|
||||
}
|
||||
@@ -2083,6 +2083,204 @@ 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) {
|
||||
@@ -2094,6 +2292,7 @@ spv_result_t ValidateDecorations(ValidationState_t& vstate) {
|
||||
if (auto error = CheckVulkanMemoryModelDeprecatedDecorations(vstate))
|
||||
return error;
|
||||
if (auto error = CheckDecorationsFromDecoration(vstate)) return error;
|
||||
if (auto error = CheckInvalidVulkanExplicitLayout(vstate)) return error;
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -258,7 +258,8 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
|
||||
_.HasCapability(spv::Capability::VariablePointers) &&
|
||||
sc == spv::StorageClass::Workgroup;
|
||||
const bool uc_ptr = sc == spv::StorageClass::UniformConstant;
|
||||
if (!ssbo_vptr && !wg_vptr && !uc_ptr) {
|
||||
if (!_.options()->before_hlsl_legalization && !ssbo_vptr &&
|
||||
!wg_vptr && !uc_ptr) {
|
||||
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
||||
<< "Pointer operand " << _.getIdName(argument_id)
|
||||
<< " must be a memory object declaration";
|
||||
|
||||
+105
-86
@@ -48,6 +48,29 @@ 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& _,
|
||||
@@ -107,6 +130,12 @@ 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;
|
||||
}
|
||||
|
||||
@@ -135,17 +164,22 @@ spv_result_t NumConsumedLocations(ValidationState_t& _, const Instruction* type,
|
||||
}
|
||||
break;
|
||||
case spv::Op::OpTypeMatrix:
|
||||
// Matrices consume locations equal to the underlying vector type for
|
||||
// each column.
|
||||
NumConsumedLocations(_, _.FindDef(type->GetOperandAs<uint32_t>(1)),
|
||||
num_locations);
|
||||
// Matrices consume locations equivalent to arrays of 4-component vectors.
|
||||
if (_.ContainsSizedIntOrFloatType(type->id(), spv::Op::OpTypeInt, 64) ||
|
||||
_.ContainsSizedIntOrFloatType(type->id(), spv::Op::OpTypeFloat, 64)) {
|
||||
*num_locations = 2;
|
||||
} else {
|
||||
*num_locations = 1;
|
||||
}
|
||||
*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.
|
||||
NumConsumedLocations(_, _.FindDef(type->GetOperandAs<uint32_t>(1)),
|
||||
num_locations);
|
||||
if (auto error = NumConsumedLocations(
|
||||
_, _.FindDef(type->GetOperandAs<uint32_t>(1)), num_locations)) {
|
||||
return error;
|
||||
}
|
||||
bool is_int = false;
|
||||
bool is_const = false;
|
||||
uint32_t value = 0;
|
||||
@@ -215,10 +249,31 @@ 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::OpTypeArray:
|
||||
// Skip the array.
|
||||
return NumConsumedComponents(_,
|
||||
_.FindDef(type->GetOperandAs<uint32_t>(1)));
|
||||
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::OpTypePointer:
|
||||
if (_.addressing_model() ==
|
||||
spv::AddressingModel::PhysicalStorageBuffer64 &&
|
||||
@@ -301,9 +356,10 @@ spv_result_t GetLocationsForVariable(
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
bool is_arrayed = false;
|
||||
switch (entry_point->GetOperandAs<spv::ExecutionModel>(0)) {
|
||||
case spv::ExecutionModel::TessellationControl:
|
||||
@@ -357,51 +413,33 @@ 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))
|
||||
return error;
|
||||
uint32_t num_components = NumConsumedComponents(_, type);
|
||||
|
||||
uint32_t start = location * 4;
|
||||
uint32_t end = (location + num_locations) * 4;
|
||||
if (num_components % 4 != 0) {
|
||||
start += component;
|
||||
end = start + num_components;
|
||||
}
|
||||
|
||||
uint32_t num_locations = 0;
|
||||
if (auto error = NumConsumedLocations(_, sub_type, &num_locations))
|
||||
return error;
|
||||
uint32_t num_components = NumConsumedComponents(_, sub_type);
|
||||
if (kMaxLocations <= start) {
|
||||
// Too many locations, give up.
|
||||
return SPV_SUCCESS;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
auto locs = locations;
|
||||
if (has_index && index == 1) locs = output_index1_locations;
|
||||
|
||||
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;
|
||||
|
||||
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 {
|
||||
@@ -460,38 +498,19 @@ spv_result_t GetLocationsForVariable(
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+39
-4
@@ -380,6 +380,16 @@ 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;
|
||||
@@ -2781,17 +2791,42 @@ 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 &&
|
||||
|
||||
+4
-2
@@ -2415,8 +2415,6 @@ 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,6 +2523,8 @@ 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:
|
||||
@@ -2534,6 +2534,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
|
||||
case 10213:
|
||||
// This use to be a standalone, but maintenance8 will set allow_offset_texture_operand now
|
||||
return VUID_WRAP(VUID-RuntimeSpirv-Offset-10213);
|
||||
case 10583:
|
||||
return VUID_WRAP(VUID-StandaloneSpirv-Component-10583);
|
||||
default:
|
||||
return ""; // unknown id
|
||||
}
|
||||
|
||||
+1
@@ -116,6 +116,7 @@ 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,6 +30,8 @@ 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"
|
||||
@@ -44,5 +46,6 @@ list(APPEND DIFF_TEST_FILES
|
||||
"diff_files/spec_constant_composite_autogen.cpp"
|
||||
"diff_files/spec_constant_op_autogen.cpp"
|
||||
"diff_files/spec_constant_specid_autogen.cpp"
|
||||
"diff_files/string_in_ext_inst_autogen.cpp"
|
||||
"diff_files/unrelated_shaders_autogen.cpp"
|
||||
)
|
||||
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
// 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
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
;; 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
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
;; 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
|
||||
|
||||
%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
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
// 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 {
|
||||
|
||||
// Don't forget to map between source and destination ids when grouping
|
||||
// functions by return type.
|
||||
constexpr char kSrc[] = R"( OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%void = OpTypeVoid
|
||||
%1 = OpTypeInt 32 0
|
||||
%2 = OpTypeInt 32 1
|
||||
|
||||
%fn_uint = OpTypeFunction %1
|
||||
%fn_int = OpTypeFunction %2
|
||||
|
||||
%uint_42 = OpConstant %1 42
|
||||
%int_1729 = OpConstant %2 1729
|
||||
|
||||
%f = OpFunction %1 None %fn_uint
|
||||
%f_prologue = OpLabel
|
||||
OpReturnValue %uint_42
|
||||
OpFunctionEnd
|
||||
|
||||
%g = OpFunction %2 None %fn_int
|
||||
%g_prologue = OpLabel
|
||||
OpReturnValue %int_1729
|
||||
OpFunctionEnd)";
|
||||
constexpr char kDst[] =
|
||||
R"(;; Don't forget to map between source and destination ids when grouping functions by return type.
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%void = OpTypeVoid
|
||||
%2 = OpTypeInt 32 0
|
||||
%1 = OpTypeInt 32 1
|
||||
|
||||
%fn_uint = OpTypeFunction %2
|
||||
%fn_int = OpTypeFunction %1
|
||||
|
||||
%uint_42 = OpConstant %2 42
|
||||
%int_1729 = OpConstant %1 1729
|
||||
|
||||
%f = OpFunction %2 None %fn_uint
|
||||
%f_prologue = OpLabel
|
||||
OpReturnValue %uint_42
|
||||
OpFunctionEnd
|
||||
|
||||
%g = OpFunction %1 None %fn_int
|
||||
%g_prologue = OpLabel
|
||||
OpReturnValue %int_1729
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
TEST(DiffTest, FunctionGroupByMappedId) {
|
||||
constexpr char kDiff[] = R"( ; SPIR-V
|
||||
; Version: 1.6
|
||||
; Generator: Khronos SPIR-V Tools Assembler; 0
|
||||
; Bound: 12
|
||||
; Schema: 0
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%3 = OpTypeVoid
|
||||
%1 = OpTypeInt 32 0
|
||||
%2 = OpTypeInt 32 1
|
||||
%4 = OpTypeFunction %1
|
||||
%5 = OpTypeFunction %2
|
||||
%6 = OpConstant %1 42
|
||||
%7 = OpConstant %2 1729
|
||||
%8 = OpFunction %1 None %4
|
||||
%9 = OpLabel
|
||||
OpReturnValue %6
|
||||
OpFunctionEnd
|
||||
%10 = OpFunction %2 None %5
|
||||
%11 = OpLabel
|
||||
OpReturnValue %7
|
||||
OpFunctionEnd
|
||||
)";
|
||||
Options options;
|
||||
DoStringDiffTest(kSrc, kDst, kDiff, options);
|
||||
}
|
||||
|
||||
TEST(DiffTest, FunctionGroupByMappedIdNoDebug) {
|
||||
constexpr char kSrcNoDebug[] = R"( OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%void = OpTypeVoid
|
||||
%1 = OpTypeInt 32 0
|
||||
%2 = OpTypeInt 32 1
|
||||
|
||||
%fn_uint = OpTypeFunction %1
|
||||
%fn_int = OpTypeFunction %2
|
||||
|
||||
%uint_42 = OpConstant %1 42
|
||||
%int_1729 = OpConstant %2 1729
|
||||
|
||||
%f = OpFunction %1 None %fn_uint
|
||||
%f_prologue = OpLabel
|
||||
OpReturnValue %uint_42
|
||||
OpFunctionEnd
|
||||
|
||||
%g = OpFunction %2 None %fn_int
|
||||
%g_prologue = OpLabel
|
||||
OpReturnValue %int_1729
|
||||
OpFunctionEnd
|
||||
)";
|
||||
constexpr char kDstNoDebug[] = R"( OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%void = OpTypeVoid
|
||||
%2 = OpTypeInt 32 0
|
||||
%1 = OpTypeInt 32 1
|
||||
|
||||
%fn_uint = OpTypeFunction %2
|
||||
%fn_int = OpTypeFunction %1
|
||||
|
||||
%uint_42 = OpConstant %2 42
|
||||
%int_1729 = OpConstant %1 1729
|
||||
|
||||
%f = OpFunction %2 None %fn_uint
|
||||
%f_prologue = OpLabel
|
||||
OpReturnValue %uint_42
|
||||
OpFunctionEnd
|
||||
|
||||
%g = OpFunction %1 None %fn_int
|
||||
%g_prologue = OpLabel
|
||||
OpReturnValue %int_1729
|
||||
OpFunctionEnd
|
||||
)";
|
||||
constexpr char kDiff[] = R"( ; SPIR-V
|
||||
; Version: 1.6
|
||||
; Generator: Khronos SPIR-V Tools Assembler; 0
|
||||
; Bound: 12
|
||||
; Schema: 0
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%3 = OpTypeVoid
|
||||
%1 = OpTypeInt 32 0
|
||||
%2 = OpTypeInt 32 1
|
||||
%4 = OpTypeFunction %1
|
||||
%5 = OpTypeFunction %2
|
||||
%6 = OpConstant %1 42
|
||||
%7 = OpConstant %2 1729
|
||||
%8 = OpFunction %1 None %4
|
||||
%9 = OpLabel
|
||||
OpReturnValue %6
|
||||
OpFunctionEnd
|
||||
%10 = OpFunction %2 None %5
|
||||
%11 = OpLabel
|
||||
OpReturnValue %7
|
||||
OpFunctionEnd
|
||||
)";
|
||||
Options options;
|
||||
DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace diff
|
||||
} // namespace spvtools
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
;; Don't forget to map between source and destination ids when grouping functions by return type.
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
%void = OpTypeVoid
|
||||
%2 = OpTypeInt 32 0
|
||||
%1 = OpTypeInt 32 1
|
||||
|
||||
%fn_uint = OpTypeFunction %2
|
||||
%fn_int = OpTypeFunction %1
|
||||
|
||||
%uint_42 = OpConstant %2 42
|
||||
%int_1729 = OpConstant %1 1729
|
||||
|
||||
%f = OpFunction %2 None %fn_uint
|
||||
%f_prologue = OpLabel
|
||||
OpReturnValue %uint_42
|
||||
OpFunctionEnd
|
||||
|
||||
%g = OpFunction %1 None %fn_int
|
||||
%g_prologue = OpLabel
|
||||
OpReturnValue %int_1729
|
||||
OpFunctionEnd
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user