Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f366ac3a35 | ||
|
|
f78770c7fb | ||
|
|
feca632486 | ||
|
|
68652347b3 | ||
|
|
585c10d9ba | ||
|
|
7e7a481320 | ||
|
|
b9a6cc5462 | ||
|
|
755912e676 | ||
|
|
42ce76e285 | ||
|
|
f903683c92 | ||
|
|
546ba48043 | ||
|
|
c81af315b2 | ||
|
|
ea64db4218 | ||
|
|
0c8df766d0 | ||
|
|
8f58743405 | ||
|
|
af079b42a6 | ||
|
|
7f7bceb970 | ||
|
|
625603d8d4 | ||
|
|
d2d5d62a20 | ||
|
|
5e9be5dd2d | ||
|
|
df897b3fb2 | ||
|
|
8c396caba0 | ||
|
|
6b91f30389 | ||
|
|
de5d0e55af |
@@ -92,27 +92,33 @@ jobs:
|
||||
|
||||
test-renderdiff:
|
||||
name: test-renderdiff
|
||||
runs-on: ubuntu-22.04-4core
|
||||
|
||||
runs-on: macos-14-xlarge
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- uses: ./.github/actions/ubuntu-apt-add-src
|
||||
- name: Set up Homebrew
|
||||
id: set-up-homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install python prereqs
|
||||
run: pip install mako setuptools pyyaml
|
||||
- name: Run script
|
||||
run: |
|
||||
echo "Disabled renderdiff due to Mesa -> Currently planned outage: 2025-03-16 -> 2025-03-22"
|
||||
# source ./build/linux/ci-common.sh && bash test/renderdiff/test.sh
|
||||
bash test/renderdiff/test.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
path: ./out/renderdiff_tests
|
||||
|
||||
validate-wgsl-pipeline:
|
||||
name: validate-wgsl-pipeline
|
||||
runs-on: ubuntu-22.04-4core
|
||||
validate-wgsl-webgpu:
|
||||
name: validate-wgsl-webgpu
|
||||
runs-on: ubuntu-22.04-8core
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.6
|
||||
- name: Run build script
|
||||
run: source ./build/linux/ci-common.sh && ./build.sh -W debug test_filamat
|
||||
run: source ./build/linux/ci-common.sh && ./build.sh -W debug test_filamat filament
|
||||
- name: Run test
|
||||
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
run: ./out/cmake-debug/libs/filamat/test_filamat --gtest_filter=MaterialCompiler.Wgsl*
|
||||
|
||||
+12
-6
@@ -139,14 +139,14 @@ else()
|
||||
set(LINUX FALSE)
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
if (NOT FILAMENT_OSMESA_PATH STREQUAL "")
|
||||
if (NOT EXISTS ${FILAMENT_OSMESA_PATH}/)
|
||||
message(FATAL_ERROR "Cannot find specified OSMesa build directory: ${FILAMENT_OSMESA_PATH}")
|
||||
endif()
|
||||
set(FILAMENT_SUPPORTS_OSMESA TRUE)
|
||||
if (NOT FILAMENT_OSMESA_PATH STREQUAL "")
|
||||
if (NOT EXISTS ${FILAMENT_OSMESA_PATH}/)
|
||||
message(FATAL_ERROR "Cannot find specified OSMesa build directory: ${FILAMENT_OSMESA_PATH}")
|
||||
endif()
|
||||
set(FILAMENT_SUPPORTS_OSMESA TRUE)
|
||||
endif()
|
||||
|
||||
if (LINUX)
|
||||
if (FILAMENT_SUPPORTS_WAYLAND)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_WAYLAND)
|
||||
set(FILAMENT_SUPPORTS_X11 FALSE)
|
||||
@@ -184,6 +184,12 @@ if (NOT ANDROID AND NOT WEBGL AND NOT IOS AND NOT FILAMENT_LINUX_IS_MOBILE)
|
||||
set(IS_HOST_PLATFORM TRUE)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
if (FILAMENT_SUPPORTS_OSMESA)
|
||||
add_definitions(-DFILAMENT_SUPPORTS_OSMESA)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
# Link statically against c/c++ lib to avoid missing redistriburable such as
|
||||
# "VCRUNTIME140.dll not found. Try reinstalling the app.", but give users
|
||||
|
||||
@@ -7,3 +7,6 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- materials: five custom variables (varyings) are now available on the condition that the `color` attribute is not requested (b/404930099). [⚠️ **New Material Version**]
|
||||
|
||||
|
||||
@@ -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.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.58.2'
|
||||
pod 'Filament', '~> 1.58.1'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.58.2
|
||||
VERSION_NAME=1.58.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -200,8 +200,9 @@ WEBGPU_ANDROID_GRADLE_OPTION=""
|
||||
EGL_ON_LINUX_OPTION="-DFILAMENT_SUPPORTS_EGL_ON_LINUX=OFF"
|
||||
|
||||
MATDBG_OPTION="-DFILAMENT_ENABLE_MATDBG=OFF"
|
||||
FGVIEWER_OPTION="-DFILAMENT_ENABLE_FGVIEWER=OFF"
|
||||
MATDBG_GRADLE_OPTION=""
|
||||
FGVIEWER_OPTION="-DFILAMENT_ENABLE_FGVIEWER=OFF"
|
||||
FGVIEWER_GRADLE_OPTION=""
|
||||
|
||||
MATOPT_OPTION=""
|
||||
MATOPT_GRADLE_OPTION=""
|
||||
@@ -533,6 +534,7 @@ function build_android {
|
||||
${VULKAN_ANDROID_GRADLE_OPTION} \
|
||||
${WEBGPU_ANDROID_GRADLE_OPTION} \
|
||||
${MATDBG_GRADLE_OPTION} \
|
||||
${FGVIEWER_GRADLE_OPTION} \
|
||||
${MATOPT_GRADLE_OPTION} \
|
||||
:filament-android:assembleDebug \
|
||||
:gltfio-android:assembleDebug \
|
||||
@@ -584,6 +586,7 @@ function build_android {
|
||||
${VULKAN_ANDROID_GRADLE_OPTION} \
|
||||
${WEBGPU_ANDROID_GRADLE_OPTION} \
|
||||
${MATDBG_GRADLE_OPTION} \
|
||||
${FGVIEWER_GRADLE_OPTION} \
|
||||
${MATOPT_GRADLE_OPTION} \
|
||||
:filament-android:assembleRelease \
|
||||
:gltfio-android:assembleRelease \
|
||||
|
||||
@@ -31,7 +31,7 @@ if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
sudo apt-get install mesa-common-dev libxi-dev libxxf86vm-dev
|
||||
|
||||
# For dawn
|
||||
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev
|
||||
sudo apt-get install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-xcb-dev
|
||||
|
||||
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${GITHUB_CLANG_VERSION} 100
|
||||
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-${GITHUB_CLANG_VERSION} 100
|
||||
|
||||
+23
-15
@@ -13,7 +13,7 @@ This document is part of the [Filament project](https://github.com/google/filame
|
||||
## Authors
|
||||
|
||||
- [Romain Guy](https://github.com/romainguy), [@romainguy](https://twitter.com/romainguy)
|
||||
- [Mathias Agopian](https://github.com/pixelflinger), [@darthmoosious](https://twitter.com/darthmoosious)
|
||||
- [Mathias Agopian](https://github.com/pixelflinger), [@pixelflinger](https://bsky.app/profile/pixelflinger.bsky.social)
|
||||
|
||||
# Overview
|
||||
|
||||
@@ -78,26 +78,27 @@ in table [standardProperties].
|
||||
Property | Definition
|
||||
-----------------------:|:---------------------
|
||||
**baseColor** | Diffuse albedo for non-metallic surfaces, and specular color for metallic surfaces
|
||||
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
|
||||
**roughness** | Perceived smoothness (1.0) or roughness (0.0) of a surface. Smooth surfaces exhibit sharp reflections
|
||||
**metallic** | Whether a surface appears to be dielectric (0.0) or conductor (1.0). Often used as a binary value (0 or 1)
|
||||
**reflectance** | Fresnel reflectance at normal incidence for dielectric surfaces. This directly controls the strength of the reflections
|
||||
**sheenColor** | Strength of the sheen layer
|
||||
**sheenRoughness** | Perceived smoothness or roughness of the sheen layer
|
||||
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
|
||||
**clearCoat** | Strength of the clear coat layer
|
||||
**clearCoatRoughness** | Perceived smoothness or roughness of the clear coat layer
|
||||
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
|
||||
**anisotropy** | Amount of anisotropy in either the tangent or bitangent direction
|
||||
**anisotropyDirection** | Local surface direction in tangent space
|
||||
**ambientOcclusion** | Defines how much of the ambient light is accessible to a surface point. It is a per-pixel shadowing factor between 0.0 and 1.0
|
||||
**normal** | A detail normal used to perturb the surface using _bump mapping_ (_normal mapping_)
|
||||
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
|
||||
**clearCoatNormal** | A detail normal used to perturb the clear coat layer using _bump mapping_ (_normal mapping_)
|
||||
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
|
||||
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
|
||||
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
|
||||
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
|
||||
**absorption** | Absorption factor for refractive objects
|
||||
**microThickness** | Thickness of the thin layer of refractive objects
|
||||
**thickness** | Thickness of the solid volume of refractive objects
|
||||
**sheenColor** | Strength of the sheen layer
|
||||
**sheenRoughness** | Perceived smoothness or roughness of the sheen layer
|
||||
**emissive** | Additional diffuse albedo to simulate emissive surfaces (such as neons, etc.) This property is mostly useful in an HDR pipeline with a bloom pass
|
||||
**normal** | A detail normal used to perturb the surface using _bump mapping_ (_normal mapping_)
|
||||
**postLightingColor** | Additional color that can be blended with the result of the lighting computations. See `postLightingBlending`
|
||||
**absorption** | Absorption factor for refractive objects
|
||||
**transmission** | Defines how much of the diffuse light of a dielectric is transmitted through the object, in other words this defines how transparent an object is
|
||||
**ior** | Index of refraction, either for refractive objects or as an alternative to reflectance
|
||||
**microThickness** | Thickness of the thin layer of refractive objects
|
||||
**bentNormal** | A normal pointing in the average unoccluded direction. Can be used to improve indirect lighting quality
|
||||
**shadowStrength** | Strength factor between 0 and 1 for all shadows received by this material
|
||||
[Table [standardProperties]: Properties of the standard model]
|
||||
|
||||
The type and range of each property is described in table [standardPropertiesTypes].
|
||||
@@ -1272,6 +1273,9 @@ Description
|
||||
when selecting any shading model that is not `unlit`. See the shader sections of this document
|
||||
for more information on how to access these attributes from the shaders.
|
||||
|
||||
!!! Note: Interaction with custom variables
|
||||
When the `color` attribute is specified, only four custom variables are available instead of five.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
parameters : [
|
||||
@@ -1302,7 +1306,7 @@ Type
|
||||
: array of `string`
|
||||
|
||||
Value
|
||||
: Up to 4 strings, each must be a valid GLSL identifier.
|
||||
: Up to 5 strings, each must be a valid GLSL identifier.
|
||||
|
||||
Description
|
||||
: Defines custom interpolants (or variables) that are output by the material's vertex shader.
|
||||
@@ -1318,6 +1322,10 @@ Description
|
||||
particular if `default` is specified the default precision is used is the fragment shader
|
||||
(`mediump`) and in the vertex shader (`highp`).
|
||||
|
||||
!!! Warning: Interaction with required attributes
|
||||
If the `color` attribute is specified in the `required` list, then only four variables can be used
|
||||
instead of five.
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JSON
|
||||
material {
|
||||
name : Skybox,
|
||||
|
||||
@@ -108,8 +108,12 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformCocoaTouchGL.mm)
|
||||
list(APPEND SRCS src/opengl/platforms/CocoaTouchExternalImage.mm)
|
||||
elseif (APPLE)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformCocoaGL.mm)
|
||||
list(APPEND SRCS src/opengl/platforms/CocoaExternalImage.mm)
|
||||
if (FILAMENT_SUPPORTS_OSMESA)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformOSMesa.cpp)
|
||||
else()
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformCocoaGL.mm)
|
||||
list(APPEND SRCS src/opengl/platforms/CocoaExternalImage.mm)
|
||||
endif()
|
||||
elseif (WEBGL)
|
||||
list(APPEND SRCS src/opengl/platforms/PlatformWebGL.cpp)
|
||||
elseif (LINUX)
|
||||
@@ -173,10 +177,12 @@ endif()
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
list(APPEND SRCS
|
||||
include/backend/platforms/VulkanPlatform.h
|
||||
src/vulkan/caching/VulkanDescriptorSetManager.cpp
|
||||
src/vulkan/caching/VulkanDescriptorSetManager.h
|
||||
src/vulkan/caching/VulkanPipelineLayoutCache.cpp
|
||||
src/vulkan/caching/VulkanPipelineLayoutCache.h
|
||||
src/vulkan/VulkanDescriptorSetCache.cpp
|
||||
src/vulkan/VulkanDescriptorSetCache.h
|
||||
src/vulkan/VulkanDescriptorSetLayoutCache.cpp
|
||||
src/vulkan/VulkanDescriptorSetLayoutCache.h
|
||||
src/vulkan/VulkanPipelineLayoutCache.cpp
|
||||
src/vulkan/VulkanPipelineLayoutCache.h
|
||||
src/vulkan/memory/ResourceManager.cpp
|
||||
src/vulkan/memory/ResourceManager.h
|
||||
src/vulkan/memory/ResourcePointer.h
|
||||
@@ -416,9 +422,12 @@ set(LINUX_LINKER_OPTIMIZATION_FLAGS
|
||||
-Wl,--exclude-libs,bluegl
|
||||
)
|
||||
|
||||
if (LINUX AND FILAMENT_SUPPORTS_OSMESA)
|
||||
set(OSMESA_COMPILE_FLAGS
|
||||
-I${FILAMENT_OSMESA_PATH}/include/GL)
|
||||
if (FILAMENT_SUPPORTS_OSMESA)
|
||||
if (LINUX)
|
||||
set(OSMESA_COMPILE_FLAGS -I${FILAMENT_OSMESA_PATH}/include/GL)
|
||||
elseif (APPLE)
|
||||
set(OSMESA_COMPILE_FLAGS -I${FILAMENT_OSMESA_PATH}/include)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
PlatformCocoaGL();
|
||||
~PlatformCocoaGL() noexcept override;
|
||||
|
||||
ExternalImageHandle createExternalImage(void* cvPixelBuffer) noexcept;
|
||||
ExternalImageHandle UTILS_PUBLIC createExternalImage(void* cvPixelBuffer) noexcept;
|
||||
|
||||
protected:
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
PlatformCocoaTouchGL();
|
||||
~PlatformCocoaTouchGL() noexcept override;
|
||||
|
||||
ExternalImageHandle createExternalImage(void* cvPixelBuffer) noexcept;
|
||||
ExternalImageHandle UTILS_PUBLIC createExternalImage(void* cvPixelBuffer) noexcept;
|
||||
|
||||
// --------------------------------------------------------------------------------------------
|
||||
// Platform Interface
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
/**
|
||||
* Creates an ExternalImage from a EGLImageKHR
|
||||
*/
|
||||
ExternalImageHandle createExternalImage(EGLImageKHR eglImage) noexcept;
|
||||
ExternalImageHandle UTILS_PUBLIC createExternalImage(EGLImageKHR eglImage) noexcept;
|
||||
|
||||
protected:
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -47,16 +47,16 @@ public:
|
||||
/**
|
||||
* Creates an ExternalImage from a EGLImageKHR
|
||||
*/
|
||||
ExternalImageHandle createExternalImage(AHardwareBuffer const* buffer, bool sRGB) noexcept;
|
||||
ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer, bool sRGB) noexcept;
|
||||
|
||||
struct ExternalImageDescAndroid {
|
||||
struct UTILS_PUBLIC 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;
|
||||
ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(ExternalImageHandle externalImage) noexcept;
|
||||
|
||||
protected:
|
||||
// --------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -21,7 +21,12 @@
|
||||
|
||||
#include "bluegl/BlueGL.h"
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <osmesa.h>
|
||||
#elif defined(__APPLE__)
|
||||
#undef GLAPI
|
||||
#include <GL/osmesa.h>
|
||||
#endif
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
@@ -26,6 +26,20 @@ namespace filament::backend {
|
||||
|
||||
class VulkanPlatformAndroid : public VulkanPlatform {
|
||||
public:
|
||||
Platform::ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept;
|
||||
|
||||
struct UTILS_PUBLIC ExternalImageDescAndroid {
|
||||
uint32_t width; // Texture width
|
||||
uint32_t height; // Texture height
|
||||
TextureFormat format;// Texture format
|
||||
TextureUsage usage; // Texture usage flags
|
||||
};
|
||||
|
||||
ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(
|
||||
ExternalImageHandleRef externalImage) const noexcept;
|
||||
|
||||
protected:
|
||||
struct ExternalImageVulkanAndroid : public Platform::ExternalImage {
|
||||
AHardwareBuffer* aHardwareBuffer = nullptr;
|
||||
bool sRGB = false;
|
||||
@@ -38,9 +52,6 @@ public:
|
||||
~ExternalImageVulkanAndroid() override;
|
||||
};
|
||||
|
||||
Platform::ExternalImageHandle createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept;
|
||||
|
||||
virtual ExternalImageMetadata getExternalImageMetadata(ExternalImageHandleRef externalImage);
|
||||
|
||||
using ImageData = VulkanPlatform::ImageData;
|
||||
@@ -48,7 +59,6 @@ public:
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
||||
VkImageUsageFlags usage);
|
||||
|
||||
protected:
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
|
||||
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
#include <utils/Systrace.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
// We need to keep this up top for the linux (X11) name collisions.
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
#include "backend/platforms/WebGPUPlatform.h"
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <sys/system_properties.h>
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
@@ -30,7 +35,11 @@
|
||||
#endif
|
||||
#elif defined(__APPLE__)
|
||||
#if defined(FILAMENT_SUPPORTS_OPENGL) && !defined(FILAMENT_USE_EXTERNAL_GLES3)
|
||||
#include <backend/platforms/PlatformCocoaGL.h>
|
||||
#if defined(FILAMENT_SUPPORTS_OSMESA)
|
||||
#include <backend/platforms/PlatformOSMesa.h>
|
||||
#else
|
||||
#include <backend/platforms/PlatformCocoaGL.h>
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
#if defined(FILAMENT_SUPPORTS_X11)
|
||||
@@ -69,9 +78,6 @@ filament::backend::Platform* createDefaultMetalPlatform();
|
||||
#endif
|
||||
|
||||
#include "noop/PlatformNoop.h"
|
||||
#if defined(FILAMENT_SUPPORTS_WEBGPU)
|
||||
#include "backend/platforms/WebGPUPlatform.h"
|
||||
#endif
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -140,7 +146,11 @@ Platform* PlatformFactory::create(Backend* backend) noexcept {
|
||||
#elif defined(FILAMENT_IOS)
|
||||
return new PlatformCocoaTouchGL();
|
||||
#elif defined(__APPLE__)
|
||||
return new PlatformCocoaGL();
|
||||
#if defined(FILAMENT_SUPPORTS_OSMESA)
|
||||
return new PlatformOSMesa();
|
||||
#else
|
||||
return new PlatformCocoaGL();
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
#if defined(FILAMENT_SUPPORTS_X11)
|
||||
return new PlatformGLX();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
Driver* PlatformNoop::createDriver(void* const sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
Driver* PlatformNoop::createDriver(void* sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
return NoopDriver::create();
|
||||
}
|
||||
|
||||
|
||||
@@ -148,8 +148,8 @@ using namespace utils;
|
||||
namespace filament::backend {
|
||||
|
||||
Driver* OpenGLDriverFactory::create(
|
||||
OpenGLPlatform* const platform,
|
||||
void* const sharedGLContext,
|
||||
OpenGLPlatform* platform,
|
||||
void* sharedGLContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
return OpenGLDriver::create(platform, sharedGLContext, driverConfig);
|
||||
}
|
||||
@@ -159,10 +159,10 @@ using namespace GLUtils;
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
UTILS_NOINLINE
|
||||
OpenGLDriver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
void* const /*sharedGLContext*/, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
OpenGLDriver* OpenGLDriver::create(OpenGLPlatform* platform,
|
||||
void* /*sharedGLContext*/, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
assert_invariant(platform);
|
||||
OpenGLPlatform* const ec = platform;
|
||||
OpenGLPlatform* ec = platform;
|
||||
|
||||
#if 0
|
||||
// this is useful for development, but too verbose even for debug builds
|
||||
@@ -230,7 +230,7 @@ OpenGLDriver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
constexpr size_t defaultSize = FILAMENT_OPENGL_HANDLE_ARENA_SIZE_IN_MB * 1024U * 1024U;
|
||||
Platform::DriverConfig validConfig{ driverConfig };
|
||||
validConfig.handleArenaSize = std::max(driverConfig.handleArenaSize, defaultSize);
|
||||
OpenGLDriver* const driver = new(std::nothrow) OpenGLDriver(ec, validConfig);
|
||||
OpenGLDriver* driver = new(std::nothrow) OpenGLDriver(ec, validConfig);
|
||||
return driver;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ PlatformCocoaTouchGL::~PlatformCocoaTouchGL() noexcept {
|
||||
delete pImpl;
|
||||
}
|
||||
|
||||
Driver* PlatformCocoaTouchGL::createDriver(void* const sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
Driver* PlatformCocoaTouchGL::createDriver(void* sharedGLContext, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
EAGLSharegroup* sharegroup = (__bridge EAGLSharegroup*) sharedGLContext;
|
||||
|
||||
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3 sharegroup:sharegroup];
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace filament::backend {
|
||||
|
||||
using namespace backend;
|
||||
|
||||
Driver* PlatformGLX::createDriver(void* const sharedGLContext,
|
||||
Driver* PlatformGLX::createDriver(void* sharedGLContext,
|
||||
const DriverConfig& driverConfig) noexcept {
|
||||
loadLibraries();
|
||||
// Get the display device
|
||||
|
||||
@@ -22,9 +22,11 @@
|
||||
#include <dlfcn.h>
|
||||
#include <memory>
|
||||
|
||||
#if defined(__linux__)
|
||||
// This is to ensure that linking during compilation will not fail even if
|
||||
// OSMesaGetProcAddress is not linked.
|
||||
__attribute__((weak)) OSMESAproc OSMesaGetProcAddress(char const*);
|
||||
#endif
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -48,20 +50,27 @@ struct OSMesaSwapchain {
|
||||
|
||||
struct OSMesaAPI {
|
||||
private:
|
||||
using CreateContextFunc = OSMesaContext (*)(GLenum format, OSMesaContext);
|
||||
using CreateContextAttribsFunc = OSMesaContext (*)(const int *, OSMesaContext);
|
||||
using DestroyContextFunc = GLboolean (*)(OSMesaContext);
|
||||
using MakeCurrentFunc = GLboolean (*)(OSMesaContext ctx, void* buffer, GLenum type,
|
||||
GLsizei width, GLsizei height);
|
||||
using GetProcAddressFunc = OSMESAproc (*)(const char* funcName);
|
||||
|
||||
public:
|
||||
CreateContextFunc fOSMesaCreateContext;
|
||||
CreateContextAttribsFunc fOSMesaCreateContextAttribs;
|
||||
DestroyContextFunc fOSMesaDestroyContext;
|
||||
MakeCurrentFunc fOSMesaMakeCurrent;
|
||||
GetProcAddressFunc fOSMesaGetProcAddress;
|
||||
|
||||
OSMesaAPI() {
|
||||
constexpr char const* libraryNames[] = {"libOSMesa.so", "libosmesa.so"};
|
||||
static constexpr char const* libraryNames[] = {
|
||||
#if defined(__linux__)
|
||||
"libOSMesa.so",
|
||||
"libosmesa.so",
|
||||
#elif defined(__APPLE__)
|
||||
"libOSMesa.dylib",
|
||||
#endif
|
||||
};
|
||||
for (char const* libName: libraryNames) {
|
||||
mLib = dlopen(libName, RTLD_GLOBAL | RTLD_NOW);
|
||||
if (mLib) {
|
||||
@@ -71,22 +80,24 @@ public:
|
||||
if (mLib) {
|
||||
// Loading from a libosmesa.os
|
||||
fOSMesaGetProcAddress = (GetProcAddressFunc) dlsym(mLib, "OSMesaGetProcAddress");
|
||||
} else {
|
||||
}
|
||||
#if defined(__linux__)
|
||||
else {
|
||||
// Filament is built into a .so
|
||||
fOSMesaGetProcAddress = (GetProcAddressFunc) dlsym(RTLD_LOCAL, "OSMesaGetProcAddress");
|
||||
}
|
||||
|
||||
if (!fOSMesaGetProcAddress) {
|
||||
// Statically linking osmesa
|
||||
fOSMesaGetProcAddress = OSMesaGetProcAddress;
|
||||
}
|
||||
#endif // __linux__
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(fOSMesaGetProcAddress)
|
||||
<< "Unable to link against libOSMesa to create a software GL context";
|
||||
|
||||
fOSMesaCreateContext = (CreateContextFunc) fOSMesaGetProcAddress("OSMesaCreateContext");
|
||||
fOSMesaDestroyContext =
|
||||
(DestroyContextFunc) fOSMesaGetProcAddress("OSMesaDestroyContext");
|
||||
fOSMesaCreateContextAttribs =
|
||||
(CreateContextAttribsFunc) fOSMesaGetProcAddress("OSMesaCreateContextAttribs");
|
||||
fOSMesaDestroyContext = (DestroyContextFunc) fOSMesaGetProcAddress("OSMesaDestroyContext");
|
||||
fOSMesaMakeCurrent = (MakeCurrentFunc) fOSMesaGetProcAddress("OSMesaMakeCurrent");
|
||||
}
|
||||
|
||||
@@ -101,14 +112,24 @@ private:
|
||||
|
||||
}// anonymous namespace
|
||||
|
||||
Driver* PlatformOSMesa::createDriver(void* const sharedGLContext,
|
||||
Driver* PlatformOSMesa::createDriver(void* sharedGLContext,
|
||||
const DriverConfig& driverConfig) noexcept {
|
||||
|
||||
OSMesaAPI* api = new OSMesaAPI();
|
||||
mOsMesaApi = api;
|
||||
|
||||
static constexpr int attribs[] = {
|
||||
OSMESA_FORMAT, GL_RGBA,
|
||||
OSMESA_DEPTH_BITS, 24,
|
||||
OSMESA_STENCIL_BITS, 8,
|
||||
OSMESA_ACCUM_BITS, 0,
|
||||
OSMESA_PROFILE, OSMESA_CORE_PROFILE,
|
||||
0,
|
||||
};
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(sharedGLContext == nullptr)
|
||||
<< "shared GL context is not supported with PlatformOSMesa";
|
||||
mContext = api->fOSMesaCreateContext(GL_RGBA, NULL);
|
||||
mContext = api->fOSMesaCreateContextAttribs(attribs, NULL);
|
||||
|
||||
// We need to do a no-op makecurrent here so that the context will be in a correct state before
|
||||
// any GL calls.
|
||||
|
||||
@@ -75,7 +75,7 @@ struct WGLSwapChain {
|
||||
|
||||
static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr;
|
||||
|
||||
Driver* PlatformWGL::createDriver(void* const sharedGLContext,
|
||||
Driver* PlatformWGL::createDriver(void* sharedGLContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
int result = 0;
|
||||
int pixelFormat = 0;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace filament::backend {
|
||||
|
||||
using namespace backend;
|
||||
|
||||
Driver* PlatformWebGL::createDriver(void* const sharedGLContext,
|
||||
Driver* PlatformWebGL::createDriver(void* sharedGLContext,
|
||||
const Platform::DriverConfig& driverConfig) noexcept {
|
||||
return OpenGLPlatform::createDefaultDriver(this, sharedGLContext, driverConfig);
|
||||
}
|
||||
|
||||
+18
-120
@@ -14,35 +14,25 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "VulkanDescriptorSetManager.h"
|
||||
#include "VulkanDescriptorSetCache.h"
|
||||
|
||||
#include "vulkan/VulkanCommands.h"
|
||||
#include "vulkan/VulkanHandles.h"
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "VulkanCommands.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanConstants.h"
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
|
||||
using DescriptorCount = VulkanDescriptorSetLayout::Count;
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetManager::DescriptorSetLayoutArray;
|
||||
using BitmaskGroupHashFn = utils::hash::MurmurHashFn<BitmaskGroup>;
|
||||
struct BitmaskGroupEqual {
|
||||
bool operator()(BitmaskGroup const& k1, BitmaskGroup const& k2) const {
|
||||
return k1 == k2;
|
||||
}
|
||||
};
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetCache::DescriptorSetLayoutArray;
|
||||
|
||||
// We create a pool for each layout as defined by the number of descriptors of each type. For
|
||||
// example, a layout of
|
||||
@@ -199,72 +189,12 @@ struct Equal {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Bitmask>
|
||||
uint32_t createBindings(VkDescriptorSetLayoutBinding* toBind, uint32_t count, VkDescriptorType type,
|
||||
Bitmask const& mask) {
|
||||
Bitmask alreadySeen;
|
||||
mask.forEachSetBit([&](size_t index) {
|
||||
VkShaderStageFlags stages = 0;
|
||||
uint32_t binding = 0;
|
||||
if (index < fvkutils::getFragmentStageShift<Bitmask>()) {
|
||||
binding = (uint32_t) index;
|
||||
stages |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||
auto fragIndex = index + fvkutils::getFragmentStageShift<Bitmask>();
|
||||
if (mask.test(fragIndex)) {
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
alreadySeen.set(fragIndex);
|
||||
}
|
||||
} else if (!alreadySeen.test(index)) {
|
||||
// We are in fragment stage bits
|
||||
binding = (uint32_t) (index - fvkutils::getFragmentStageShift<Bitmask>());
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
}
|
||||
|
||||
if (stages) {
|
||||
toBind[count++] = {
|
||||
.binding = binding,
|
||||
.descriptorType = type,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = stages,
|
||||
};
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
inline VkDescriptorSetLayout createLayout(VkDevice device, BitmaskGroup const& bitmaskGroup) {
|
||||
// Note that the following *needs* to be static so that VkDescriptorSetLayoutCreateInfo will not
|
||||
// refer to stack memory.
|
||||
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
|
||||
uint32_t count = 0;
|
||||
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
||||
bitmaskGroup.dynamicUbo);
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmaskGroup.ubo);
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
bitmaskGroup.sampler);
|
||||
count = createBindings(toBind, count, VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
|
||||
bitmaskGroup.inputAttachment);
|
||||
|
||||
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
|
||||
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.bindingCount = count,
|
||||
.pBindings = toBind,
|
||||
};
|
||||
|
||||
VkDescriptorSetLayout layout;
|
||||
vkCreateDescriptorSetLayout(device, &dlinfo, VKALLOC, &layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// This is an ever-expanding pool of sets where it
|
||||
// 1. Keeps a list of smaller pools of different layout-dimensions.
|
||||
// 2. Will add a pool if existing pool are not compatible with the requested layout o runs out.
|
||||
class VulkanDescriptorSetManager::DescriptorInfinitePool {
|
||||
class VulkanDescriptorSetCache::DescriptorInfinitePool {
|
||||
private:
|
||||
static constexpr uint16_t EXPECTED_SET_COUNT = 10;
|
||||
static constexpr float SET_COUNT_GROWTH_FACTOR = 1.5;
|
||||
@@ -319,61 +249,34 @@ private:
|
||||
std::vector<std::unique_ptr<DescriptorPool>> mPools;
|
||||
};
|
||||
|
||||
class VulkanDescriptorSetManager::DescriptorSetLayoutManager {
|
||||
public:
|
||||
DescriptorSetLayoutManager(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks) {
|
||||
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
auto vklayout = createLayout(mDevice, bitmasks);
|
||||
mVkLayouts[bitmasks] = vklayout;
|
||||
return vklayout;
|
||||
}
|
||||
|
||||
~DescriptorSetLayoutManager() {
|
||||
for (auto& itr: mVkLayouts) {
|
||||
vkDestroyDescriptorSetLayout(mDevice, itr.second, VKALLOC);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
tsl::robin_map<BitmaskGroup, VkDescriptorSetLayout, BitmaskGroupHashFn, BitmaskGroupEqual>
|
||||
mVkLayouts;
|
||||
};
|
||||
|
||||
VulkanDescriptorSetManager::VulkanDescriptorSetManager(VkDevice device,
|
||||
VulkanDescriptorSetCache::VulkanDescriptorSetCache(VkDevice device,
|
||||
fvkmemory::ResourceManager* resourceManager)
|
||||
: mDevice(device),
|
||||
mResourceManager(resourceManager),
|
||||
mLayoutManager(std::make_unique<DescriptorSetLayoutManager>(device)),
|
||||
mDescriptorPool(std::make_unique<DescriptorInfinitePool>(device)) {}
|
||||
|
||||
VulkanDescriptorSetManager::~VulkanDescriptorSetManager() = default;
|
||||
VulkanDescriptorSetCache::~VulkanDescriptorSetCache() = default;
|
||||
|
||||
void VulkanDescriptorSetManager::terminate() noexcept{
|
||||
mLayoutManager.reset();
|
||||
void VulkanDescriptorSetCache::terminate() noexcept{
|
||||
mDescriptorPool.reset();
|
||||
clearHistory();
|
||||
}
|
||||
|
||||
// bind() is not really binding the set but just stashing until we have all the info
|
||||
// (pipelinelayout).
|
||||
void VulkanDescriptorSetManager::bind(uint8_t setIndex,
|
||||
void VulkanDescriptorSetCache::bind(uint8_t setIndex,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
backend::DescriptorSetOffsetArray&& offsets) {
|
||||
set->setOffsets(std::move(offsets));
|
||||
mStashedSets[setIndex] = set;
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::unbind(uint8_t setIndex) {
|
||||
void VulkanDescriptorSetCache::unbind(uint8_t setIndex) {
|
||||
mStashedSets[setIndex] = {};
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands,
|
||||
void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
||||
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask const& setMask) {
|
||||
// setMask indicates the set of descriptor sets the driver wants to bind, curMask is the
|
||||
// actual set of sets that *needs* to be bound.
|
||||
@@ -412,7 +315,7 @@ void VulkanDescriptorSetManager::commit(VulkanCommandBuffer* commands,
|
||||
};
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateBuffer(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanBufferObject> bufferObject,
|
||||
VkDeviceSize offset, VkDeviceSize size) noexcept {
|
||||
VkDescriptorBufferInfo const info = {
|
||||
@@ -438,7 +341,7 @@ void VulkanDescriptorSetManager::updateBuffer(fvkmemory::resource_ptr<VulkanDesc
|
||||
set->acquire(bufferObject);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
||||
VkSampler sampler) noexcept {
|
||||
VkDescriptorImageInfo info{
|
||||
@@ -470,13 +373,13 @@ void VulkanDescriptorSetManager::updateSampler(fvkmemory::resource_ptr<VulkanDes
|
||||
set->acquire(texture);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::updateInputAttachment(
|
||||
void VulkanDescriptorSetCache::updateInputAttachment(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
VulkanAttachment const& attachment) noexcept {
|
||||
// TOOD: fill-in this region
|
||||
// TOOD: fill this in.
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetManager::createSet(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet(
|
||||
Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
auto const vkSet = mDescriptorPool->obtainSet(layout);
|
||||
auto const& count = layout->count;
|
||||
@@ -492,12 +395,7 @@ fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetManager::createS
|
||||
});
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::initVkLayout(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
layout->setVkLayout(mLayoutManager->getVkLayout(layout->bitmask));
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetManager::clearHistory() {
|
||||
void VulkanDescriptorSetCache::clearHistory() {
|
||||
mStashedSets = {};
|
||||
}
|
||||
|
||||
+8
-16
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
|
||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
|
||||
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETCACHE_H
|
||||
|
||||
#include "vulkan/VulkanHandles.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "vulkan/utils/Definitions.h" // For DescriptorSetMask
|
||||
|
||||
@@ -34,20 +34,16 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
// [GDSR]: Great-Descriptor-Set-Refactor: As of 03/20/24, the Filament frontend is planning to
|
||||
// introduce descriptor set. This PR will arrive before that change is complete. As such, some of
|
||||
// the methods introduced here will be obsolete, and certain logic will be generalized.
|
||||
|
||||
// Abstraction over the pool and the layout cache.
|
||||
class VulkanDescriptorSetManager {
|
||||
// Abstraction over the descriptor set pool.
|
||||
class VulkanDescriptorSetCache {
|
||||
public:
|
||||
static constexpr uint8_t UNIQUE_DESCRIPTOR_SET_COUNT =
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT;
|
||||
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||
|
||||
VulkanDescriptorSetManager(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
||||
~VulkanDescriptorSetManager();
|
||||
VulkanDescriptorSetCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
||||
~VulkanDescriptorSetCache();
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
@@ -72,12 +68,9 @@ public:
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
void initVkLayout(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
void clearHistory();
|
||||
|
||||
private:
|
||||
class DescriptorSetLayoutManager;
|
||||
class DescriptorInfinitePool;
|
||||
|
||||
using DescriptorSetArray =
|
||||
@@ -85,7 +78,6 @@ private:
|
||||
|
||||
VkDevice mDevice;
|
||||
fvkmemory::ResourceManager* mResourceManager;
|
||||
std::unique_ptr<DescriptorSetLayoutManager> mLayoutManager;
|
||||
std::unique_ptr<DescriptorInfinitePool> mDescriptorPool;
|
||||
std::pair<VulkanAttachment, VkDescriptorImageInfo> mInputAttachment;
|
||||
DescriptorSetArray mStashedSets = {};
|
||||
@@ -99,4 +91,4 @@ private:
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETMANAGER_H
|
||||
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETCACHE_H
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "VulkanDescriptorSetLayoutCache.h"
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
|
||||
|
||||
template<typename Bitmask>
|
||||
uint32_t appendBindings(VkDescriptorSetLayoutBinding* toBind, VkDescriptorType type,
|
||||
Bitmask const& mask) {
|
||||
uint32_t count = 0;
|
||||
Bitmask alreadySeen;
|
||||
mask.forEachSetBit([&](size_t index) {
|
||||
VkShaderStageFlags stages = 0;
|
||||
uint32_t binding = 0;
|
||||
if (index < fvkutils::getFragmentStageShift<Bitmask>()) {
|
||||
binding = (uint32_t) index;
|
||||
stages |= VK_SHADER_STAGE_VERTEX_BIT;
|
||||
auto fragIndex = index + fvkutils::getFragmentStageShift<Bitmask>();
|
||||
if (mask.test(fragIndex)) {
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
alreadySeen.set(fragIndex);
|
||||
}
|
||||
} else if (!alreadySeen.test(index)) {
|
||||
// We are in fragment stage bits
|
||||
binding = (uint32_t) (index - fvkutils::getFragmentStageShift<Bitmask>());
|
||||
stages |= VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
}
|
||||
|
||||
if (stages) {
|
||||
toBind[count++] = {
|
||||
.binding = binding,
|
||||
.descriptorType = type,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = stages,
|
||||
};
|
||||
}
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
VulkanDescriptorSetLayoutCache::VulkanDescriptorSetLayoutCache(VkDevice device,
|
||||
fvkmemory::ResourceManager* resourceManager)
|
||||
: mDevice(device),
|
||||
mResourceManager(resourceManager) {}
|
||||
|
||||
VulkanDescriptorSetLayoutCache::~VulkanDescriptorSetLayoutCache() = default;
|
||||
|
||||
void VulkanDescriptorSetLayoutCache::terminate() noexcept {
|
||||
for (auto& itr: mVkLayouts) {
|
||||
vkDestroyDescriptorSetLayout(mDevice, itr.second, VKALLOC);
|
||||
}
|
||||
}
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> VulkanDescriptorSetLayoutCache::createLayout(
|
||||
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info) {
|
||||
auto layout = fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::make(mResourceManager, handle,
|
||||
info);
|
||||
VkDescriptorSetLayout vklayout = VK_NULL_HANDLE;
|
||||
auto const& bitmasks = layout->bitmask;
|
||||
if (auto itr = mVkLayouts.find(bitmasks); itr != mVkLayouts.end()) {
|
||||
vklayout = itr->second;
|
||||
} else {
|
||||
VkDescriptorSetLayoutBinding toBind[VulkanDescriptorSetLayout::MAX_BINDINGS];
|
||||
uint32_t count = 0;
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
||||
bitmasks.dynamicUbo);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
bitmasks.sampler);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
|
||||
bitmasks.inputAttachment);
|
||||
|
||||
assert_invariant(count != 0 && "Need at least one binding for descriptor set layout.");
|
||||
VkDescriptorSetLayoutCreateInfo dlinfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.bindingCount = count,
|
||||
.pBindings = toBind,
|
||||
};
|
||||
|
||||
vkCreateDescriptorSetLayout(mDevice, &dlinfo, VKALLOC, &vklayout);
|
||||
mVkLayouts[bitmasks] = vklayout;
|
||||
}
|
||||
layout->setVkLayout(vklayout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETLAYOUTCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETLAYOUTCACHE_H
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Program.h>
|
||||
#include <backend/TargetBufferInfo.h>
|
||||
|
||||
#include <utils/bitset.h>
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanDescriptorSetLayoutCache {
|
||||
public:
|
||||
VulkanDescriptorSetLayoutCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
||||
~VulkanDescriptorSetLayoutCache();
|
||||
|
||||
void terminate() noexcept;
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> createLayout(
|
||||
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info);
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
fvkmemory::ResourceManager* mResourceManager;
|
||||
|
||||
using BitmaskGroup = VulkanDescriptorSetLayout::Bitmask;
|
||||
using BitmaskGroupHashFn = utils::hash::MurmurHashFn<BitmaskGroup>;
|
||||
struct BitmaskGroupEqual {
|
||||
bool operator()(BitmaskGroup const& k1, BitmaskGroup const& k2) const { return k1 == k2; }
|
||||
};
|
||||
|
||||
tsl::robin_map<BitmaskGroup, VkDescriptorSetLayout, BitmaskGroupHashFn, BitmaskGroupEqual>
|
||||
mVkLayouts;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif// TNT_FILAMENT_BACKEND_CACHING_VULKANDESCRIPTORSETLAYOUTCACHE_H
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "VulkanDriver.h"
|
||||
|
||||
#include "CommandStreamDispatcher.h"
|
||||
#include "DataReshaper.h"
|
||||
#include "SystraceProfile.h"
|
||||
#include "VulkanAsyncHandles.h"
|
||||
#include "VulkanBuffer.h"
|
||||
@@ -35,7 +34,6 @@
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -44,8 +42,6 @@
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
using utils::FixedCapacityVector;
|
||||
|
||||
#if defined(__clang__)
|
||||
// Vulkan functions often immediately dereference pointers, so it's fine to pass in a pointer
|
||||
// to a stack-allocated variable.
|
||||
@@ -219,7 +215,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mSamplerCache(mPlatform->getDevice()),
|
||||
mBlitter(mPlatform->getPhysicalDevice(), &mCommands),
|
||||
mReadPixels(mPlatform->getDevice()),
|
||||
mDescriptorSetManager(mPlatform->getDevice(), &mResourceManager),
|
||||
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mQueryManager(mPlatform->getDevice()),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
||||
mStereoscopicType(driverConfig.stereoscopicType) {
|
||||
@@ -329,7 +326,8 @@ void VulkanDriver::terminate() {
|
||||
mPipelineCache.terminate();
|
||||
mFramebufferCache.reset();
|
||||
mSamplerCache.terminate();
|
||||
mDescriptorSetManager.terminate();
|
||||
mDescriptorSetLayoutCache.terminate();
|
||||
mDescriptorSetCache.terminate();
|
||||
mPipelineLayoutCache.terminate();
|
||||
|
||||
// Before terminating ResourceManager, we must make sure all of the resource_ptrs have been unset.
|
||||
@@ -365,7 +363,7 @@ void VulkanDriver::collectGarbage() {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
// Command buffers need to be submitted and completed before other resources can be gc'd.
|
||||
mCommands.gc();
|
||||
mDescriptorSetManager.clearHistory();
|
||||
mDescriptorSetCache.clearHistory();
|
||||
mStagePool.gc();
|
||||
mFramebufferCache.gc();
|
||||
mPipelineCache.gc();
|
||||
@@ -408,7 +406,7 @@ void VulkanDriver::updateDescriptorSetBuffer(
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||
auto buffer = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
|
||||
mDescriptorSetManager.updateBuffer(set, binding, buffer, offset, size);
|
||||
mDescriptorSetCache.updateBuffer(set, binding, buffer, offset, size);
|
||||
}
|
||||
|
||||
void VulkanDriver::updateDescriptorSetTexture(
|
||||
@@ -421,7 +419,7 @@ void VulkanDriver::updateDescriptorSetTexture(
|
||||
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
||||
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(params);
|
||||
mDescriptorSetManager.updateSampler(set, binding, texture, vksampler);
|
||||
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
||||
}
|
||||
|
||||
void VulkanDriver::flush(int) {
|
||||
@@ -778,8 +776,7 @@ void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||
|
||||
void VulkanDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
|
||||
backend::DescriptorSetLayout&& info) {
|
||||
auto layout = resource_ptr<VulkanDescriptorSetLayout>::make(&mResourceManager, dslh, info);
|
||||
mDescriptorSetManager.initVkLayout(layout);
|
||||
auto layout = mDescriptorSetLayoutCache.createLayout(dslh, std::move(info));
|
||||
layout.inc();
|
||||
}
|
||||
|
||||
@@ -788,7 +785,7 @@ void VulkanDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout =
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
|
||||
auto set = mDescriptorSetManager.createSet(dsh, layout);
|
||||
auto set = mDescriptorSetCache.createSet(dsh, layout);
|
||||
set.inc();
|
||||
}
|
||||
|
||||
@@ -1060,7 +1057,7 @@ bool VulkanDriver::isDepthStencilBlitSupported(TextureFormat format) {
|
||||
formats.end();
|
||||
}
|
||||
|
||||
bool VulkanDriver::isProtectedTexturesSupported() { return false; }
|
||||
bool VulkanDriver::isProtectedTexturesSupported() { return isProtectedContentSupported(); }
|
||||
|
||||
bool VulkanDriver::isDepthClampSupported() {
|
||||
return mContext.isDepthClampSupported();
|
||||
@@ -1493,7 +1490,7 @@ void VulkanDriver::nextSubpass(int) {
|
||||
|
||||
if (mCurrentRenderPass.params.subpassMask & 0x1) {
|
||||
VulkanAttachment& subpassInput = renderTarget->getColor0();
|
||||
mDescriptorSetManager.updateInputAttachment({}, subpassInput);
|
||||
mDescriptorSetCache.updateInputAttachment({}, subpassInput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1820,9 +1817,9 @@ void VulkanDriver::bindDescriptorSet(
|
||||
backend::DescriptorSetOffsetArray&& offsets) {
|
||||
if (dsh) {
|
||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||
mDescriptorSetManager.bind(setIndex, set, std::move(offsets));
|
||||
mDescriptorSetCache.bind(setIndex, set, std::move(offsets));
|
||||
} else {
|
||||
mDescriptorSetManager.unbind(setIndex);
|
||||
mDescriptorSetCache.unbind(setIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1830,7 +1827,7 @@ void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t ins
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
VkCommandBuffer cmdbuffer = mCurrentRenderPass.commandBuffer->buffer();
|
||||
|
||||
mDescriptorSetManager.commit(mCurrentRenderPass.commandBuffer,
|
||||
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer,
|
||||
mBoundPipeline.pipelineLayout,
|
||||
mBoundPipeline.descriptorSetMask);
|
||||
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanStagePool.h"
|
||||
#include "VulkanQueryManager.h"
|
||||
#include "vulkan/caching/VulkanDescriptorSetManager.h"
|
||||
#include "vulkan/caching/VulkanPipelineLayoutCache.h"
|
||||
#include "vulkan/VulkanDescriptorSetCache.h"
|
||||
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
|
||||
#include "vulkan/VulkanPipelineLayoutCache.h"
|
||||
#include "vulkan/memory/ResourceManager.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "vulkan/utils/Definitions.h"
|
||||
@@ -137,7 +138,8 @@ private:
|
||||
VulkanSamplerCache mSamplerCache;
|
||||
VulkanBlitter mBlitter;
|
||||
VulkanReadPixels mReadPixels;
|
||||
VulkanDescriptorSetManager mDescriptorSetManager;
|
||||
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
|
||||
VulkanDescriptorSetCache mDescriptorSetCache;
|
||||
VulkanQueryManager mQueryManager;
|
||||
|
||||
// This is necessary for us to write to push constants after binding a pipeline.
|
||||
|
||||
@@ -15,15 +15,12 @@
|
||||
*/
|
||||
|
||||
#include "VulkanPipelineCache.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "caching/VulkanDescriptorSetManager.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanHandles.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
|
||||
#if defined(__clang__)
|
||||
|
||||
+2
-1
@@ -17,7 +17,8 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANPIPELINELAYOUTCACHE_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANPIPELINELAYOUTCACHE_H
|
||||
|
||||
#include <vulkan/VulkanHandles.h>
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
#include <utils/Hash.h>
|
||||
@@ -736,10 +736,6 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
chainStruct(&context.mPhysicalDeviceFeatures, &queryProtectedMemoryFeatures);
|
||||
chainStruct(&context.mPhysicalDeviceProperties, &protectedMemoryProperties);
|
||||
|
||||
// We know we need to allocate the protected version of the VK objects
|
||||
context.mProtectedMemorySupported =
|
||||
static_cast<bool>(queryProtectedMemoryFeatures.protectedMemory);
|
||||
|
||||
// Initialize the following fields: physicalDeviceProperties, memoryProperties,
|
||||
// physicalDeviceFeatures, graphicsQueueFamilyIndex.
|
||||
vkGetPhysicalDeviceProperties2(mImpl->mPhysicalDevice, &context.mPhysicalDeviceProperties);
|
||||
@@ -759,6 +755,10 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
|
||||
mImpl->mGraphicsQueueIndex = 0;
|
||||
}
|
||||
|
||||
// We know we need to allocate the protected version of the VK objects
|
||||
context.mProtectedMemorySupported =
|
||||
static_cast<bool>(queryProtectedMemoryFeatures.protectedMemory);
|
||||
|
||||
if (context.mProtectedMemorySupported) {
|
||||
mImpl->mProtectedGraphicsQueueFamilyIndex = identifyGraphicsQueueFamilyIndex(
|
||||
mImpl->mPhysicalDevice, (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_PROTECTED_BIT));
|
||||
|
||||
@@ -223,6 +223,19 @@ Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
||||
return Platform::ExternalImageHandle{};
|
||||
}
|
||||
|
||||
VulkanPlatformAndroid::ExternalImageDescAndroid VulkanPlatformAndroid::getExternalImageDesc(
|
||||
ExternalImageHandleRef externalImage) const noexcept {
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
|
||||
return {
|
||||
.width = fvkExternalImage->width,
|
||||
.height = fvkExternalImage->height,
|
||||
.format = fvkExternalImage->format,
|
||||
.usage = fvkExternalImage->usage,
|
||||
};
|
||||
}
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMetadata(
|
||||
ExternalImageHandleRef externalImage) {
|
||||
auto const* fvkExternalImage =
|
||||
|
||||
@@ -143,7 +143,7 @@ wgpu::Device WebGPUPlatform::requestDevice(wgpu::Adapter const& adapter) {
|
||||
return device;
|
||||
}
|
||||
|
||||
Driver* WebGPUPlatform::createDriver(void* const sharedContext,
|
||||
Driver* WebGPUPlatform::createDriver(void* sharedContext,
|
||||
const Platform::DriverConfig& /*driverConfig*/) noexcept {
|
||||
if (sharedContext) {
|
||||
FWGPU_LOGW << "sharedContext is ignored/unused in the WebGPU backend. A non-null "
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
@@ -118,7 +120,9 @@ wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t flags)
|
||||
if (useXcb) {
|
||||
wgpu::SurfaceSourceXCBWindow surfaceSourceXcb{};
|
||||
surfaceSourceXcb.connection = g_x11.connection;
|
||||
surfaceSourceXcb.window = reinterpret_cast<uint32_t>(nativeWindow);
|
||||
|
||||
// TODO: this looks really wrong, please fix!!
|
||||
surfaceSourceXcb.window = *((uint32_t*) nativeWindow);
|
||||
wgpu::SurfaceDescriptor surfaceDescriptor{
|
||||
.nextInChain = &surfaceSourceXcb,
|
||||
.label = "linux_xcb_surface"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -88,7 +88,12 @@ void minLeftMaxRight(inout vec4 a, inout vec4 b, inout vec4 c, inout vec4 d, ino
|
||||
|
||||
// - we can't use a function because offset must be a constant expression
|
||||
// - we can't use line continuation because that's not supported on desktop
|
||||
#ifdef TARGET_WEBGPU_ENVIRONMENT
|
||||
//WebGPU can not handle texelFetchOffset without an error about "ConstOffset is only permitted...".
|
||||
#define makeSample(ij, o) vec4(texelFetch(materialParams_dof, ij+o, 0).rgb, texelFetch(materialParams_alpha, ij+o, 0).r)
|
||||
#else
|
||||
#define makeSample(ij, o) vec4(texelFetchOffset(materialParams_dof, ij, 0, o).rgb, texelFetchOffset(materialParams_alpha, ij, 0, o).r)
|
||||
#endif
|
||||
|
||||
vec4 median(ivec2 ij) {
|
||||
vec4 v[9];
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.58.2"
|
||||
spec.version = "1.58.1"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.58.2/filament-v1.58.2-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.58.1/filament-v1.58.1-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -31,13 +31,17 @@ if (WIN32)
|
||||
set(SRCS ${SRCS} src/BlueGLCoreWindowsImpl.S)
|
||||
endif()
|
||||
elseif (APPLE AND NOT IOS)
|
||||
set(SRCS ${SRCS} src/BlueGLDarwin.cpp)
|
||||
if (FILAMENT_SUPPORTS_OSMESA)
|
||||
set(SRCS ${SRCS} src/BlueGLOSMesa.cpp)
|
||||
else()
|
||||
set(SRCS ${SRCS} src/BlueGLDarwin.cpp)
|
||||
endif()
|
||||
set(SRCS ${SRCS} src/BlueGLCoreDarwinUniversalImpl.S)
|
||||
elseif(LINUX)
|
||||
if(FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
if (FILAMENT_SUPPORTS_EGL_ON_LINUX)
|
||||
set(SRCS ${SRCS} src/BlueGLLinuxEGL.cpp)
|
||||
elseif(FILAMENT_SUPPORTS_OSMESA)
|
||||
set(SRCS ${SRCS} src/BlueGLLinuxOSMesa.cpp)
|
||||
elseif (FILAMENT_SUPPORTS_OSMESA)
|
||||
set(SRCS ${SRCS} src/BlueGLOSMesa.cpp)
|
||||
else()
|
||||
set(SRCS ${SRCS} src/BlueGLLinux.cpp)
|
||||
endif()
|
||||
@@ -53,7 +57,11 @@ include_directories(${PUBLIC_HDR_DIR})
|
||||
add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRCS})
|
||||
|
||||
if(FILAMENT_SUPPORTS_OSMESA)
|
||||
target_compile_options(${TARGET} PRIVATE -I${FILAMENT_OSMESA_PATH}/include/GL)
|
||||
if (APPLE)
|
||||
target_compile_options(${TARGET} PRIVATE -I${FILAMENT_OSMESA_PATH}/include)
|
||||
else()
|
||||
target_compile_options(${TARGET} PRIVATE -I${FILAMENT_OSMESA_PATH}/include/GL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# specify where the public headers of this library are
|
||||
|
||||
@@ -17,42 +17,64 @@
|
||||
#include <dlfcn.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
#include <osmesa.h>
|
||||
|
||||
// This is to ensure that linking during compilation will not fail even if
|
||||
// OSMesaGetProcAddress is not linked.
|
||||
__attribute__((weak)) OSMESAproc OSMesaGetProcAddress(char const*);
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <GL/osmesa.h>
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
#if defined(__linux__)
|
||||
#endif
|
||||
|
||||
namespace bluegl {
|
||||
|
||||
namespace {
|
||||
using ProcAddressFunc = void*(*)(char const* funcName);
|
||||
}
|
||||
|
||||
// This is to ensure that linking during compilation will not fail even if
|
||||
// OSMesaGetProcAddress is not linked.
|
||||
__attribute__((weak)) OSMESAproc OSMesaGetProcAddress(char const*);
|
||||
|
||||
struct Driver {
|
||||
ProcAddressFunc OSMesaGetProcAddress;
|
||||
void* library;
|
||||
} g_driver = {nullptr, nullptr};
|
||||
|
||||
bool initBinder() {
|
||||
constexpr char const* libraryNames[] = {"libOSMesa.so", "libosmesa.so"};
|
||||
static constexpr char const* libraryNames[] = {
|
||||
#if defined(__linux__)
|
||||
"libOSMesa.so",
|
||||
"libosmesa.so",
|
||||
#elif defined(__APPLE__)
|
||||
"libOSMesa.dylib",
|
||||
#endif
|
||||
};
|
||||
for (char const* name: libraryNames) {
|
||||
g_driver.library = dlopen(name, RTLD_GLOBAL | RTLD_NOW);
|
||||
if (g_driver.library) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!g_driver.library) {
|
||||
// The library has been linked explicitly during compile.
|
||||
g_driver.OSMesaGetProcAddress = (ProcAddressFunc) dlsym(RTLD_LOCAL, "OSMesaGetProcAddress");
|
||||
} else {
|
||||
if (g_driver.library) {
|
||||
// Linking against a libosmesa.so.
|
||||
g_driver.OSMesaGetProcAddress =
|
||||
(ProcAddressFunc) dlsym(g_driver.library, "OSMesaGetProcAddress");
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
else {
|
||||
// If Filament was built as a dynamic library.
|
||||
g_driver.OSMesaGetProcAddress = (ProcAddressFunc) dlsym(RTLD_LOCAL, "OSMesaGetProcAddress");
|
||||
}
|
||||
if (!g_driver.OSMesaGetProcAddress) {
|
||||
// If statically linking OSMesa.
|
||||
g_driver.OSMesaGetProcAddress = (ProcAddressFunc) OSMesaGetProcAddress;
|
||||
}
|
||||
#endif
|
||||
|
||||
return g_driver.OSMesaGetProcAddress;
|
||||
}
|
||||
@@ -203,7 +203,7 @@ enum class ReflectionMode : uint8_t {
|
||||
// can't really use std::underlying_type<AttributeIndex>::type because the driver takes a uint32_t
|
||||
using AttributeBitset = utils::bitset32;
|
||||
|
||||
static constexpr size_t MATERIAL_PROPERTIES_COUNT = 29;
|
||||
static constexpr size_t MATERIAL_PROPERTIES_COUNT = 30;
|
||||
enum class Property : uint8_t {
|
||||
BASE_COLOR, //!< float4, all shading models
|
||||
ROUGHNESS, //!< float, lit shading models only
|
||||
@@ -234,6 +234,7 @@ enum class Property : uint8_t {
|
||||
BENT_NORMAL, //!< float3, all shading models only, except unlit
|
||||
SPECULAR_FACTOR, //!< float, lit shading models only, except subsurface and cloth
|
||||
SPECULAR_COLOR_FACTOR, //!< float3, lit shading models only, except subsurface and cloth
|
||||
SHADOW_STRENGTH, //!< float, [0, 1] strength of shadows received by this material
|
||||
|
||||
// when adding new Properties, make sure to update MATERIAL_PROPERTIES_COUNT
|
||||
};
|
||||
|
||||
@@ -217,12 +217,13 @@ public:
|
||||
MaterialBuilder(MaterialBuilder&& rhs) noexcept = default;
|
||||
MaterialBuilder& operator=(MaterialBuilder&& rhs) noexcept = default;
|
||||
|
||||
static constexpr size_t MATERIAL_VARIABLES_COUNT = 4;
|
||||
static constexpr size_t MATERIAL_VARIABLES_COUNT = 5;
|
||||
enum class Variable : uint8_t {
|
||||
CUSTOM0,
|
||||
CUSTOM1,
|
||||
CUSTOM2,
|
||||
CUSTOM3
|
||||
CUSTOM3,
|
||||
CUSTOM4, // CUSTOM4 is only available if the vertex attribute `color` is not required.
|
||||
// when adding more variables, make sure to update MATERIAL_VARIABLES_COUNT
|
||||
};
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ std::unordered_map<std::string, Property> Enums::mStringToProperty = {
|
||||
{ "microThickness", Property::MICRO_THICKNESS },
|
||||
{ "bentNormal", Property::BENT_NORMAL },
|
||||
{ "specularFactor", Property::SPECULAR_FACTOR },
|
||||
{ "specularColorFactor", Property::SPECULAR_COLOR_FACTOR }
|
||||
{ "specularColorFactor", Property::SPECULAR_COLOR_FACTOR },
|
||||
{ "shadowStrength", Property::SHADOW_STRENGTH }
|
||||
};
|
||||
|
||||
template <>
|
||||
|
||||
@@ -517,29 +517,104 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob* spirv, std::string* outMsl,
|
||||
MetalArgumentBuffer::destroy(&argBuffer);
|
||||
}
|
||||
}
|
||||
void GLSLPostProcessor::rebindImageSamplerForWGSL(std::vector<uint32_t>& spirv) {
|
||||
constexpr size_t HEADER_SIZE = 5;
|
||||
size_t const dataSize = spirv.size();
|
||||
uint32_t* data = spirv.data();
|
||||
|
||||
struct Descriptor {
|
||||
uint32_t binding = 0xFFFFFFFF;
|
||||
bool fixed = false;
|
||||
};
|
||||
|
||||
std::unordered_map<uint32_t, Descriptor> descriptors;
|
||||
std::set<uint32_t> unidentifiedLoadTargets;
|
||||
std::vector<uint32_t> bindingTargets;
|
||||
|
||||
auto getDesc = [&](uint32_t var) -> Descriptor& {
|
||||
using itertype = std::unordered_map<uint32_t, Descriptor>::iterator;
|
||||
itertype itr;
|
||||
if (itr = descriptors.find(var); itr == descriptors.end()) {
|
||||
itr = descriptors.insert({var, {}}).first;
|
||||
}
|
||||
return itr->second;
|
||||
};
|
||||
|
||||
auto pass = [&](uint32_t targetOp, std::function<void(uint32_t)> f) {
|
||||
for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) {
|
||||
uint32_t const firstWord = data[cursor];
|
||||
uint32_t const wordCount = firstWord >> 16;
|
||||
uint32_t const op = firstWord & 0x0000FFFF;
|
||||
if (targetOp == op) {
|
||||
f(cursor+1);
|
||||
}
|
||||
cursor += wordCount;
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t samplerTypeId = 0;
|
||||
pass(spv::Op::OpName, [&](uint32_t pos) {
|
||||
auto target = data[pos];
|
||||
char* name = (char*)&data[pos+1];
|
||||
std::string_view view(name);
|
||||
if (view.find("_sampler") != std::string_view::npos) {
|
||||
unidentifiedLoadTargets.insert(target);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Write out the offsetted bindings
|
||||
pass(spv::Op::OpDecorate, [&](uint32_t pos) {
|
||||
uint32_t const type = data[pos + 1];
|
||||
if (type == spv::Decoration::DecorationBinding) {
|
||||
uint32_t const targetVar = data[pos];
|
||||
if (unidentifiedLoadTargets.find(targetVar) != unidentifiedLoadTargets.end()) {
|
||||
data[pos + 2] = data[pos + 2]*2 + 1;
|
||||
}
|
||||
else {
|
||||
data[pos + 2] = data[pos + 2] *2;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool GLSLPostProcessor::spirvToWgsl(SpirvBlob *spirv, std::string *outWsl) {
|
||||
#if FILAMENT_SUPPORTS_WEBGPU
|
||||
// We need to remove dead code for our variant-filter workaround for WebGPU to work
|
||||
// This is especially relevant for removing the push constants that morphing uses when not disabled
|
||||
spv::spirvbin_t remapper(0);
|
||||
remapper.remap(*spirv, spv::spirvbin_base_t::DCE_ALL);
|
||||
//We need to run some opt-passes at all times to transpile to WGSL
|
||||
auto optimizer = createEmptyOptimizer();
|
||||
optimizer->RegisterPass(CreateSplitCombinedImageSamplerPass());
|
||||
//optimizer->RegisterPass(CreateResolveBindingConflictsPass());
|
||||
|
||||
//Currently no options we want to use
|
||||
const tint::spirv::reader::Options readerOpts{};
|
||||
optimizeSpirv(optimizer, *spirv);
|
||||
|
||||
//After splitting the image samplers, we need to remap the bindings to separate them.
|
||||
//TODO: This has issues, so use CreateResolveBindingConflictsPass instead. Mapping is less predictable but short term
|
||||
rebindImageSamplerForWGSL(*spirv);
|
||||
|
||||
//Allow non-uniform derivitives due to our nested shaders. See https://github.com/gpuweb/gpuweb/issues/3479
|
||||
const tint::spirv::reader::Options readerOpts{true};
|
||||
tint::wgsl::writer::Options writerOpts{};
|
||||
|
||||
tint::Program tintRead = tint::spirv::reader::Read(*spirv, readerOpts);
|
||||
|
||||
if (tintRead.Diagnostics().ContainsErrors()) {
|
||||
//TODO remove this if block once combined image sampler conversion works.
|
||||
if (tintRead.Diagnostics().Str().rfind("error: WGSL does not support combined image-samplers") != std::string::npos) {
|
||||
slog.w << "This tint reader error is currently ignored during WebGPU bringup: " << tintRead.Diagnostics().Str() << io::endl;
|
||||
}
|
||||
else {
|
||||
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
|
||||
return false;
|
||||
}
|
||||
//We know errors can potentially crop up, and want the ability to ignore them if needed for sample bringup
|
||||
#ifndef FILAMENT_WEBGPU_IGNORE_TNT_READ_ERRORS
|
||||
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
|
||||
spv_context context = spvContextCreate(SPV_ENV_VULKAN_1_1_SPIRV_1_4);
|
||||
spv_text text = nullptr;
|
||||
spv_diagnostic diagnostic = nullptr;
|
||||
spv_result_t result = spvBinaryToText(
|
||||
context,
|
||||
spirv->data(),
|
||||
spirv->size(),
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | SPV_BINARY_TO_TEXT_OPTION_COLOR,
|
||||
&text,
|
||||
&diagnostic);
|
||||
slog.e << "Beginning SpirV-output dump\n\n" << text->str << "\n\nEndSPIRV\n" << io::endl;
|
||||
spvTextDestroy(text);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
tint::Result<tint::wgsl::writer::Output> wgslOut = tint::wgsl::writer::Generate(tintRead,writerOpts);
|
||||
@@ -887,10 +962,8 @@ bool GLSLPostProcessor::fullOptimization(const TShader& tShader,
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
||||
MaterialBuilder::Optimization optimization, Config const& config) {
|
||||
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createEmptyOptimizer() {
|
||||
auto optimizer = std::make_shared<spvtools::Optimizer>(SPV_ENV_UNIVERSAL_1_3);
|
||||
|
||||
optimizer->SetMessageConsumer([](spv_message_level_t level,
|
||||
const char* source, const spv_position_t& position, const char* message) {
|
||||
if (!filterSpvOptimizerMessage(level)) {
|
||||
@@ -899,6 +972,12 @@ std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
||||
slog.e << stringifySpvOptimizerMessage(level, source, position, message)
|
||||
<< io::endl;
|
||||
});
|
||||
return optimizer;
|
||||
}
|
||||
|
||||
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
||||
MaterialBuilder::Optimization optimization, Config const& config) {
|
||||
auto optimizer = createEmptyOptimizer();
|
||||
|
||||
if (optimization == MaterialBuilder::Optimization::SIZE) {
|
||||
// When optimizing for size, we don't run the SPIR-V through any size optimization passes
|
||||
@@ -923,7 +1002,7 @@ std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
|
||||
return optimizer;
|
||||
}
|
||||
|
||||
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const {
|
||||
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) {
|
||||
if (!optimizer->Run(spirv.data(), spirv.size(), &spirv)) {
|
||||
slog.e << "SPIR-V optimizer pass failed" << io::endl;
|
||||
return;
|
||||
@@ -977,7 +1056,7 @@ void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config c
|
||||
|
||||
RegisterPass(CreateWrapOpKillPass());
|
||||
RegisterPass(CreateDeadBranchElimPass());
|
||||
RegisterPass(CreateMergeReturnPass(), MaterialBuilder::TargetApi::METAL);
|
||||
RegisterPass(CreateMergeReturnPass(), MaterialBuilder::TargetApi::METAL | MaterialBuilder::TargetApi::WEBGPU);
|
||||
RegisterPass(CreateInlineExhaustivePass());
|
||||
RegisterPass(CreateAggressiveDCEPass());
|
||||
RegisterPass(CreatePrivateToLocalPass());
|
||||
|
||||
@@ -114,14 +114,20 @@ private:
|
||||
* Retrieve an optimizer instance tuned for the given optimization level and shader configuration.
|
||||
*/
|
||||
using OptimizerPtr = std::shared_ptr<spvtools::Optimizer>;
|
||||
|
||||
static OptimizerPtr createOptimizer(
|
||||
MaterialBuilder::Optimization optimization,
|
||||
Config const& config);
|
||||
|
||||
static OptimizerPtr createEmptyOptimizer();
|
||||
|
||||
static void registerSizePasses(spvtools::Optimizer& optimizer, Config const& config);
|
||||
|
||||
static void registerPerformancePasses(spvtools::Optimizer& optimizer, Config const& config);
|
||||
|
||||
void optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const;
|
||||
static void optimizeSpirv(OptimizerPtr optimizer, SpirvBlob &spirv);
|
||||
|
||||
static void rebindImageSamplerForWGSL(std::vector<uint32_t>& spirv);
|
||||
|
||||
void fixupClipDistance(SpirvBlob& spirv, GLSLPostProcessor::Config const& config) const;
|
||||
|
||||
|
||||
@@ -252,6 +252,7 @@ MaterialBuilder& MaterialBuilder::variable(Variable v, const char* name) noexcep
|
||||
case Variable::CUSTOM1:
|
||||
case Variable::CUSTOM2:
|
||||
case Variable::CUSTOM3:
|
||||
case Variable::CUSTOM4:
|
||||
assert(size_t(v) < MATERIAL_VARIABLES_COUNT);
|
||||
mVariables[size_t(v)] = { CString(name), Precision::DEFAULT, false };
|
||||
break;
|
||||
@@ -266,6 +267,7 @@ MaterialBuilder& MaterialBuilder::variable(Variable v,
|
||||
case Variable::CUSTOM1:
|
||||
case Variable::CUSTOM2:
|
||||
case Variable::CUSTOM3:
|
||||
case Variable::CUSTOM4:
|
||||
assert(size_t(v) < MATERIAL_VARIABLES_COUNT);
|
||||
mVariables[size_t(v)] = { CString(name), precision, true };
|
||||
break;
|
||||
@@ -1254,6 +1256,15 @@ error:
|
||||
OutputTarget::COLOR, Precision::DEFAULT, OutputType::FLOAT4, "color");
|
||||
}
|
||||
|
||||
if (mMaterialDomain == MaterialDomain::SURFACE) {
|
||||
if (mRequiredAttributes[VertexAttribute::COLOR] &&
|
||||
!mVariables[int(Variable::CUSTOM4)].name.empty()) {
|
||||
// both the color attribute and the custom4 variable are present, that's not supported
|
||||
slog.e << "Error: when the 'color' attribute is required 'Variable::CUSTOM4' is not supported." << io::endl;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe check MaterialDomain::COMPUTE has outputs
|
||||
|
||||
// Resolve all the #include directives within user code.
|
||||
|
||||
@@ -937,6 +937,15 @@ utils::io::sstream& CodeGenerator::generateSpecializationConstant(utils::io::sst
|
||||
std::string const constantString = std::visit(SpecializationConstantFormatter(), value);
|
||||
|
||||
static const char* types[] = { "int", "float", "bool" };
|
||||
|
||||
// Spec constants aren't fully supported in Tint,
|
||||
// workaround until https://issues.chromium.org/issues/42250586 is resolved
|
||||
if (mTargetApi == TargetApi::WEBGPU) {
|
||||
out << "layout (constant_id = " << id << ") const "
|
||||
<< types[value.index()] << " " << name << "_hack = " << constantString << ";\n"
|
||||
<< types[value.index()] << " " << name << " = " << name << "_hack;\n";
|
||||
return out;
|
||||
}
|
||||
if (mTargetLanguage == MaterialBuilderBase::TargetLanguage::SPIRV) {
|
||||
out << "layout (constant_id = " << id << ") const "
|
||||
<< types[value.index()] << " " << name << " = " << constantString << ";\n";
|
||||
@@ -1218,6 +1227,7 @@ char const* CodeGenerator::getConstantName(MaterialBuilder::Property property) n
|
||||
case Property::BENT_NORMAL: return "BENT_NORMAL";
|
||||
case Property::SPECULAR_FACTOR: return "SPECULAR_FACTOR";
|
||||
case Property::SPECULAR_COLOR_FACTOR: return "SPECULAR_COLOR_FACTOR";
|
||||
case Property::SHADOW_STRENGTH: return "SHADOW_STRENGTH";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -696,6 +696,25 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerBentNormal) {
|
||||
EXPECT_TRUE(PropertyListsMatch(expected, properties));
|
||||
}
|
||||
|
||||
TEST_F(MaterialCompiler, StaticCodeAnalyzerShadowStrength) {
|
||||
std::string fragmentCode(R"(
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
material.shadowStrength = 0.1;
|
||||
}
|
||||
)");
|
||||
|
||||
std::string shaderCode = shaderWithAllProperties(*jobSystem, ShaderStage::FRAGMENT,
|
||||
fragmentCode);
|
||||
|
||||
GLSLTools glslTools;
|
||||
MaterialBuilder::PropertyList properties{ false };
|
||||
glslTools.findProperties(ShaderStage::FRAGMENT, shaderCode, properties);
|
||||
MaterialBuilder::PropertyList expected{ false };
|
||||
expected[size_t(filamat::MaterialBuilder::Property::SHADOW_STRENGTH)] = true;
|
||||
EXPECT_TRUE(PropertyListsMatch(expected, properties));
|
||||
}
|
||||
|
||||
TEST_F(MaterialCompiler, StaticCodeAnalyzerOutputFactor) {
|
||||
std::string fragmentCode(R"(
|
||||
void material(inout MaterialInputs material) {
|
||||
@@ -759,6 +778,48 @@ TEST_F(MaterialCompiler, Uv0AndUv1) {
|
||||
EXPECT_TRUE(result.isValid());
|
||||
}
|
||||
|
||||
TEST_F(MaterialCompiler, FiveCustomVariables) {
|
||||
filamat::MaterialBuilder builder;
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM0, "custom0");
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM1, "custom1");
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM2, "custom2");
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM3, "custom3");
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM4, "custom4");
|
||||
filamat::Package result = builder.build(*jobSystem);
|
||||
EXPECT_TRUE(result.isValid());
|
||||
}
|
||||
|
||||
TEST_F(MaterialCompiler, FourCustomVariablesAndColorAttribute) {
|
||||
filamat::MaterialBuilder builder;
|
||||
builder.require(filament::VertexAttribute::COLOR);
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM0, "custom0");
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM1, "custom1");
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM2, "custom2");
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM3, "custom3");
|
||||
filamat::Package result = builder.build(*jobSystem);
|
||||
EXPECT_TRUE(result.isValid());
|
||||
}
|
||||
|
||||
TEST_F(MaterialCompiler, FiveCustomVariablesAndColorAttributeFails) {
|
||||
filamat::MaterialBuilder builder;
|
||||
builder.require(filament::VertexAttribute::COLOR);
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM0, "custom0");
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM1, "custom1");
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM2, "custom2");
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM3, "custom3");
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM4, "custom4");
|
||||
filamat::Package result = builder.build(*jobSystem);
|
||||
EXPECT_FALSE(result.isValid());
|
||||
}
|
||||
|
||||
TEST_F(MaterialCompiler, CustomVariable4AndColorAttributeFails) {
|
||||
filamat::MaterialBuilder builder;
|
||||
builder.require(filament::VertexAttribute::COLOR);
|
||||
builder.variable(MaterialBuilder::Variable::CUSTOM4, "custom4");
|
||||
filamat::Package result = builder.build(*jobSystem);
|
||||
EXPECT_FALSE(result.isValid());
|
||||
}
|
||||
|
||||
TEST_F(MaterialCompiler, Arrays) {
|
||||
filamat::MaterialBuilder builder;
|
||||
|
||||
|
||||
@@ -20,6 +20,9 @@ struct PostProcessVertexInputs {
|
||||
#ifdef VARIABLE_CUSTOM3
|
||||
vec4 VARIABLE_CUSTOM3;
|
||||
#endif
|
||||
#ifdef VARIABLE_CUSTOM4
|
||||
vec4 VARIABLE_CUSTOM4;
|
||||
#endif
|
||||
};
|
||||
|
||||
void initPostProcessMaterialVertex(out PostProcessVertexInputs inputs) {
|
||||
@@ -35,4 +38,7 @@ void initPostProcessMaterialVertex(out PostProcessVertexInputs inputs) {
|
||||
#ifdef VARIABLE_CUSTOM3
|
||||
inputs.VARIABLE_CUSTOM3 = vec4(0.0);
|
||||
#endif
|
||||
#ifdef VARIABLE_CUSTOM4
|
||||
inputs.VARIABLE_CUSTOM4 = vec4(0.0);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ void main() {
|
||||
#if defined(VARIABLE_CUSTOM3)
|
||||
VARIABLE_CUSTOM_AT3 = inputs.VARIABLE_CUSTOM3;
|
||||
#endif
|
||||
#if defined(VARIABLE_CUSTOM4)
|
||||
VARIABLE_CUSTOM_AT4 = inputs.VARIABLE_CUSTOM4;
|
||||
#endif
|
||||
|
||||
// some PowerVR drivers crash when gl_Position is written more than once
|
||||
gl_Position = inputs.position;
|
||||
|
||||
@@ -129,14 +129,14 @@ bool isDoubleSided() {
|
||||
/**
|
||||
* Returns the cascade index for this fragment (between 0 and CONFIG_MAX_SHADOW_CASCADES - 1).
|
||||
*/
|
||||
int getShadowCascade() {
|
||||
uint getShadowCascade() {
|
||||
highp float z = mulMat4x4Float3(getViewFromWorldMatrix(), getWorldPosition()).z;
|
||||
ivec4 greaterZ = ivec4(greaterThan(frameUniforms.cascadeSplits, vec4(z)));
|
||||
int cascadeCount = frameUniforms.cascades & 0xF;
|
||||
uint cascadeCount = frameUniforms.cascades & 0xF;
|
||||
return clamp(greaterZ.x + greaterZ.y + greaterZ.z + greaterZ.w, 0, cascadeCount - 1);
|
||||
}
|
||||
|
||||
highp vec4 getCascadeLightSpacePosition(int cascade) {
|
||||
highp vec4 getCascadeLightSpacePosition(uint cascade) {
|
||||
// For the first cascade, return the interpolated light space position.
|
||||
// This branch will be coherent (mostly) for neighboring fragments, and it's worth avoiding
|
||||
// the matrix multiply inside computeLightSpacePosition.
|
||||
|
||||
@@ -52,12 +52,15 @@ void evaluateDirectionalLight(const MaterialInputs material,
|
||||
if (light.NoL > 0.0) {
|
||||
float ssContactShadowOcclusion = 0.0;
|
||||
|
||||
int cascade = getShadowCascade();
|
||||
uint cascade = getShadowCascade();
|
||||
bool cascadeHasVisibleShadows = bool(frameUniforms.cascades & ((1 << cascade) << 8));
|
||||
bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1);
|
||||
if (hasDirectionalShadows && cascadeHasVisibleShadows) {
|
||||
highp vec4 shadowPosition = getShadowPosition(cascade);
|
||||
visibility = shadow(true, sampler0_shadowMap, cascade, shadowPosition, 0.0);
|
||||
visibility = shadow(true, sampler0_shadowMap, int(cascade), shadowPosition, 0.0);
|
||||
#if defined(MATERIAL_HAS_SHADOW_STRENGTH)
|
||||
applyShadowStrength(visibility, material.shadowStrength);
|
||||
#endif
|
||||
// shadow far attenuation
|
||||
highp vec3 v = getWorldPosition() - getWorldCameraPosition();
|
||||
// (viewFromWorld * v).z == dot(transpose(viewFromWorld), v)
|
||||
|
||||
@@ -222,6 +222,9 @@ void evaluatePunctualLights(const MaterialInputs material,
|
||||
highp vec4 shadowPosition = getShadowPosition(shadowIndex, light.direction, light.zLight);
|
||||
visibility = shadow(false, sampler0_shadowMap, shadowIndex,
|
||||
shadowPosition, light.zLight);
|
||||
#if defined(MATERIAL_HAS_SHADOW_STRENGTH)
|
||||
applyShadowStrength(visibility, material.shadowStrength);
|
||||
#endif
|
||||
}
|
||||
if (light.contactShadows && visibility > 0.0) {
|
||||
if ((object_uniforms_flagsChannels & FILAMENT_OBJECT_CONTACT_SHADOWS_BIT) != 0) {
|
||||
|
||||
@@ -151,6 +151,9 @@ void main() {
|
||||
#if defined(VARIABLE_CUSTOM3)
|
||||
VARIABLE_CUSTOM_AT3 = material.VARIABLE_CUSTOM3;
|
||||
#endif
|
||||
#if defined(VARIABLE_CUSTOM4) && !defined(HAS_ATTRIBUTE_COLOR)
|
||||
VARIABLE_CUSTOM_AT4 = material.VARIABLE_CUSTOM4;
|
||||
#endif
|
||||
|
||||
// The world position can be changed by the user in materialVertex()
|
||||
vertex_worldPosition.xyz = material.worldPosition.xyz;
|
||||
|
||||
@@ -98,6 +98,10 @@ struct MaterialInputs {
|
||||
vec3 specularColorFactor;
|
||||
#endif
|
||||
|
||||
#if defined(MATERIAL_HAS_SHADOW_STRENGTH)
|
||||
float shadowStrength;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
void initMaterial(out MaterialInputs material) {
|
||||
@@ -195,6 +199,9 @@ void initMaterial(out MaterialInputs material) {
|
||||
material.specularColorFactor = vec3(1.0);
|
||||
#endif
|
||||
|
||||
#if defined(MATERIAL_HAS_SHADOW_STRENGTH)
|
||||
material.shadowStrength = 0.0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(MATERIAL_HAS_CUSTOM_SURFACE_SHADING)
|
||||
|
||||
@@ -20,6 +20,9 @@ struct MaterialVertexInputs {
|
||||
#ifdef VARIABLE_CUSTOM3
|
||||
vec4 VARIABLE_CUSTOM3;
|
||||
#endif
|
||||
#if defined(VARIABLE_CUSTOM4) && !defined(HAS_ATTRIBUTE_COLOR)
|
||||
vec4 VARIABLE_CUSTOM4;
|
||||
#endif
|
||||
#ifdef HAS_ATTRIBUTE_TANGENTS
|
||||
vec3 worldNormal;
|
||||
#endif
|
||||
@@ -74,6 +77,9 @@ void initMaterialVertex(out MaterialVertexInputs material) {
|
||||
#endif
|
||||
#ifdef VARIABLE_CUSTOM3
|
||||
material.VARIABLE_CUSTOM3 = vec4(0.0);
|
||||
#endif
|
||||
#if defined(VARIABLE_CUSTOM4) && !defined(HAS_ATTRIBUTE_COLOR)
|
||||
material.VARIABLE_CUSTOM4 = vec4(0.0);
|
||||
#endif
|
||||
material.worldPosition = computeWorldPosition();
|
||||
#ifdef VERTEX_DOMAIN_DEVICE
|
||||
|
||||
@@ -41,12 +41,15 @@ vec4 evaluateMaterial(const MaterialInputs material) {
|
||||
#if defined(VARIANT_HAS_DIRECTIONAL_LIGHTING)
|
||||
#if defined(VARIANT_HAS_SHADOWING)
|
||||
float visibility = 1.0;
|
||||
int cascade = getShadowCascade();
|
||||
uint cascade = getShadowCascade();
|
||||
bool cascadeHasVisibleShadows = bool(frameUniforms.cascades & ((1 << cascade) << 8));
|
||||
bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1);
|
||||
if (hasDirectionalShadows && cascadeHasVisibleShadows) {
|
||||
highp vec4 shadowPosition = getShadowPosition(cascade);
|
||||
visibility = shadow(true, sampler0_shadowMap, cascade, shadowPosition, 0.0);
|
||||
#if defined(MATERIAL_HAS_SHADOW_STRENGTH)
|
||||
applyShadowStrength(visibility, material.shadowStrength);
|
||||
#endif
|
||||
// shadow far attenuation
|
||||
highp vec3 v = getWorldPosition() - getWorldCameraPosition();
|
||||
// (viewFromWorld * v).z == dot(transpose(viewFromWorld), v)
|
||||
|
||||
@@ -485,7 +485,7 @@ float ShadowSample_VSM(const bool ELVSM, const highp sampler2DArray shadowMap,
|
||||
|
||||
// get texture coordinate for directional and spot shadow maps
|
||||
#if defined(VARIANT_HAS_DIRECTIONAL_LIGHTING)
|
||||
highp vec4 getShadowPosition(const int cascade) {
|
||||
highp vec4 getShadowPosition(const uint cascade) {
|
||||
return getCascadeLightSpacePosition(cascade);
|
||||
}
|
||||
#endif
|
||||
@@ -511,6 +511,12 @@ int getPointLightFace(const highp vec3 r) {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(MATERIAL_HAS_SHADOW_STRENGTH)
|
||||
void applyShadowStrength(inout float visibility, float strength) {
|
||||
visibility = 1.0 - (1.0 - visibility) * strength;
|
||||
}
|
||||
#endif
|
||||
|
||||
// PCF sampling
|
||||
float shadow(const bool DIRECTIONAL,
|
||||
const mediump sampler2DArrayShadow shadowMap,
|
||||
|
||||
@@ -2,30 +2,30 @@
|
||||
// Varyings
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
LAYOUT_LOCATION(4) VARYING highp vec4 vertex_worldPosition;
|
||||
|
||||
#if defined(HAS_ATTRIBUTE_TANGENTS)
|
||||
LAYOUT_LOCATION(5) SHADING_INTERPOLATION VARYING mediump vec3 vertex_worldNormal;
|
||||
#if defined(MATERIAL_NEEDS_TBN)
|
||||
LAYOUT_LOCATION(6) SHADING_INTERPOLATION VARYING mediump vec4 vertex_worldTangent;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
LAYOUT_LOCATION(7) VARYING highp vec4 vertex_position;
|
||||
|
||||
#if defined(FILAMENT_HAS_FEATURE_INSTANCING)
|
||||
LAYOUT_LOCATION(8) flat VARYING highp int instance_index;
|
||||
highp int logical_instance_index;
|
||||
#endif
|
||||
|
||||
#if defined(HAS_ATTRIBUTE_COLOR)
|
||||
LAYOUT_LOCATION(9) VARYING mediump vec4 vertex_color;
|
||||
LAYOUT_LOCATION(4) VARYING mediump vec4 vertex_color;
|
||||
#endif
|
||||
|
||||
#if defined(HAS_ATTRIBUTE_UV0) && !defined(HAS_ATTRIBUTE_UV1)
|
||||
LAYOUT_LOCATION(10) VARYING highp vec2 vertex_uv01;
|
||||
LAYOUT_LOCATION(5) VARYING highp vec2 vertex_uv01;
|
||||
#elif defined(HAS_ATTRIBUTE_UV1)
|
||||
LAYOUT_LOCATION(10) VARYING highp vec4 vertex_uv01;
|
||||
LAYOUT_LOCATION(5) VARYING highp vec4 vertex_uv01;
|
||||
#endif
|
||||
|
||||
LAYOUT_LOCATION(6) VARYING highp vec4 vertex_worldPosition;
|
||||
|
||||
#if defined(HAS_ATTRIBUTE_TANGENTS)
|
||||
LAYOUT_LOCATION(7) SHADING_INTERPOLATION VARYING mediump vec3 vertex_worldNormal;
|
||||
#if defined(MATERIAL_NEEDS_TBN)
|
||||
LAYOUT_LOCATION(8) SHADING_INTERPOLATION VARYING mediump vec4 vertex_worldTangent;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
LAYOUT_LOCATION(9) VARYING highp vec4 vertex_position;
|
||||
|
||||
#if defined(FILAMENT_HAS_FEATURE_INSTANCING)
|
||||
LAYOUT_LOCATION(10) flat VARYING highp int instance_index;
|
||||
highp int logical_instance_index;
|
||||
#endif
|
||||
|
||||
#if defined(VARIANT_HAS_SHADOWING) && defined(VARIANT_HAS_DIRECTIONAL_LIGHTING)
|
||||
|
||||
@@ -46,7 +46,12 @@ def render_test(gltf_viewer, test_config, output_dir,
|
||||
for backend in test_config.backends:
|
||||
env = None
|
||||
if backend == 'opengl' and opengl_lib and os.path.isdir(opengl_lib):
|
||||
env = {'LD_LIBRARY_PATH': opengl_lib}
|
||||
env = {
|
||||
'LD_LIBRARY_PATH': opengl_lib,
|
||||
|
||||
# for macOS
|
||||
'DYLD_LIBRARY_PATH': opengl_lib,
|
||||
}
|
||||
|
||||
for model in test.models:
|
||||
model_path = test_config.models[model]
|
||||
|
||||
+10
-2
@@ -18,11 +18,19 @@ OUTPUT_DIR="$(pwd)/out/renderdiff_tests"
|
||||
RENDERDIFF_TEST_DIR="$(pwd)/test/renderdiff"
|
||||
TEST_UTILS_DIR="$(pwd)/test/utils"
|
||||
MESA_DIR="$(pwd)/mesa/out/"
|
||||
MESA_LIB_DIR="${MESA_DIR}/lib/x86_64-linux-gnu"
|
||||
|
||||
os_name=$(uname -s)
|
||||
if [[ "$os_name" == "Linux" ]]; then
|
||||
MESA_LIB_DIR="${MESA_DIR}lib/x86_64-linux-gnu"
|
||||
elif [[ "$os_name" == "Darwin" ]]; then
|
||||
MESA_LIB_DIR="${MESA_DIR}lib"
|
||||
else
|
||||
echo "Unsupported platform for renderdiff tests"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function prepare_mesa() {
|
||||
if [ ! -d ${MESA_LIB_DIR} ]; then
|
||||
rm -rf mesa
|
||||
bash ${TEST_UTILS_DIR}/get_mesa.sh
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"models": ["lucy", "DamagedHelmet"],
|
||||
"rendering": {
|
||||
"viewer.cameraFocusDistance": 0,
|
||||
"view.postProcessingEnabled": true
|
||||
"view.postProcessingEnabled": true,
|
||||
"view.dithering": "NONE"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
+88
-34
@@ -17,55 +17,109 @@
|
||||
set -xe
|
||||
|
||||
# GITHUB_CLANG_VERSION is set in build/linux/ci-common.sh
|
||||
os_name=$(uname -s)
|
||||
LLVM_VERSION=16
|
||||
MESA_DIR=$(pwd)/mesa
|
||||
|
||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
# We only want to do this if it is a CI machine.
|
||||
sudo apt-get -y remove llvm-*
|
||||
if [[ "$os_name" == "Linux" ]]; then
|
||||
if [[ "$GITHUB_WORKFLOW" ]]; then
|
||||
# We only want to do this if it is a CI machine.
|
||||
sudo apt-get -y remove llvm-*
|
||||
|
||||
# We do a manual install of dependencies instead of `apt-get -y build-dep mesa`
|
||||
# because this allows us to compile an older mesa, and also because build-deps
|
||||
# is constantly being updated and sometimes not compatible with the current
|
||||
# linux platform.
|
||||
# Note that we assume this platform is compatible with ubuntu-22.04 x86_64
|
||||
sudo apt-get -y install \
|
||||
autoconf automake autopoint autotools-dev bindgen bison build-essential bzip2 cpp cpp-11 debhelper debugedit dh-autoreconf dh-strip-nondeterminism diffstat directx-headers-dev dpkg-dev dwz flex g++ g++-11 gcc gcc-11 gcc-11-base:amd64 gettext glslang-tools icu-devtools intltool-debian lib32gcc-s1 lib32stdc++6 libarchive-zip-perl libasan6:amd64 libatomic1:amd64 libc-dev-bin libc6-dbg:amd64 libc6-dev:amd64 libc6-i386 libcc1-0:amd64 libclang-${GITHUB_CLANG_VERSION}-dev libclang-common-${GITHUB_CLANG_VERSION}-dev libclang-cpp${GITHUB_CLANG_VERSION} libclang-cpp${GITHUB_CLANG_VERSION}-dev libclang1-14 libclang1-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION}-dev libcrypt-dev:amd64 libdebhelper-perl libdpkg-perl libdrm-amdgpu1:amd64 libdrm-dev:amd64 libdrm-intel1:amd64 libdrm-nouveau2:amd64 libdrm-radeon1:amd64 libelf-dev:amd64 libexpat1-dev:amd64 libffi-dev:amd64 libfile-stripnondeterminism-perl libgc1:amd64 libgcc-11-dev:amd64 libgl1:amd64 libgl1-mesa-dri:amd64 libglapi-mesa:amd64 libglvnd-core-dev:amd64 libglvnd0:amd64 libglx-mesa0:amd64 libglx0:amd64 libgomp1:amd64 libicu-dev:amd64 libisl23:amd64 libitm1:amd64 libllvm14:amd64 libllvm${GITHUB_CLANG_VERSION}:amd64 libllvmspirvlib-${GITHUB_CLANG_VERSION}-dev:amd64 libllvmspirvlib${GITHUB_CLANG_VERSION}:amd64 liblsan0:amd64 libmpc3:amd64 libncurses-dev:amd64 libnsl-dev:amd64 libobjc-11-dev:amd64 libobjc4:amd64 libpciaccess-dev:amd64 libpciaccess0:amd64 libpfm4:amd64 libpthread-stubs0-dev:amd64 libquadmath0:amd64 libsensors-config libsensors-dev:amd64 libsensors5:amd64 libset-scalar-perl libstd-rust-1.75:amd64 libstd-rust-dev:amd64 libstdc++-11-dev:amd64 libsub-override-perl libtinfo-dev:amd64 libtirpc-dev:amd64 libtool libtsan0:amd64 libubsan1:amd64 libva-dev:amd64 libva-drm2:amd64 libva-glx2:amd64 libva-wayland2:amd64 libva-x11-2:amd64 libva2:amd64 libvdpau-dev:amd64 libvdpau1:amd64 libvulkan-dev:amd64 libvulkan1:amd64 libwayland-bin libwayland-client0:amd64 libwayland-cursor0:amd64 libwayland-dev:amd64 libwayland-egl-backend-dev:amd64 libwayland-egl1:amd64 libwayland-server0:amd64 libx11-dev:amd64 libx11-xcb-dev:amd64 libx11-xcb1:amd64 libxau-dev:amd64 libxcb-dri2-0:amd64 libxcb-dri2-0-dev:amd64 libxcb-dri3-0:amd64 libxcb-dri3-dev:amd64 libxcb-glx0:amd64 libxcb-glx0-dev:amd64 libxcb-present-dev:amd64 libxcb-present0:amd64 libxcb-randr0:amd64 libxcb-randr0-dev:amd64 libxcb-render0:amd64 libxcb-render0-dev:amd64 libxcb-shape0:amd64 libxcb-shape0-dev:amd64 libxcb-shm0:amd64 libxcb-shm0-dev:amd64 libxcb-sync-dev:amd64 libxcb-sync1:amd64 libxcb-xfixes0:amd64 libxcb-xfixes0-dev:amd64 libxcb1-dev:amd64 libxdmcp-dev:amd64 libxext-dev:amd64 libxfixes-dev:amd64 libxfixes3:amd64 libxml2-dev:amd64 libxrandr-dev:amd64 libxrandr2:amd64 libxrender-dev:amd64 libxrender1:amd64 libxshmfence-dev:amd64 libxshmfence1:amd64 libxxf86vm-dev:amd64 libxxf86vm1:amd64 libz3-4:amd64 libz3-dev:amd64 libzstd-dev:amd64 linux-libc-dev:amd64 llvm-${GITHUB_CLANG_VERSION} llvm-${GITHUB_CLANG_VERSION}-dev llvm-${GITHUB_CLANG_VERSION}-linker-tools llvm-${GITHUB_CLANG_VERSION}-runtime llvm-${GITHUB_CLANG_VERSION}-tools llvm-spirv-${GITHUB_CLANG_VERSION} lto-disabled-list m4 make meson ninja-build pkg-config po-debconf python3-mako python3-ply python3-pygments quilt rpcsvc-proto rustc spirv-tools valgrind wayland-protocols x11proto-dev xorg-sgml-doctools xtrans-dev zlib1g-dev:amd64 \
|
||||
clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
|
||||
# We do a manual install of dependencies instead of `apt-get -y build-dep mesa`
|
||||
# because this allows us to compile an older mesa, and also because build-deps
|
||||
# is constantly being updated and sometimes not compatible with the current
|
||||
# linux platform.
|
||||
# Note that we assume this platform is compatible with ubuntu-22.04 x86_64
|
||||
sudo apt-get -y install \
|
||||
autoconf automake autopoint autotools-dev bindgen bison build-essential bzip2 cpp cpp-11 debhelper debugedit dh-autoreconf dh-strip-nondeterminism diffstat directx-headers-dev dpkg-dev dwz flex g++ g++-11 gcc gcc-11 gcc-11-base:amd64 gettext glslang-tools icu-devtools intltool-debian lib32gcc-s1 lib32stdc++6 libarchive-zip-perl libasan6:amd64 libatomic1:amd64 libc-dev-bin libc6-dbg:amd64 libc6-dev:amd64 libc6-i386 libcc1-0:amd64 libclang-${GITHUB_CLANG_VERSION}-dev libclang-common-${GITHUB_CLANG_VERSION}-dev libclang-cpp${GITHUB_CLANG_VERSION} libclang-cpp${GITHUB_CLANG_VERSION}-dev libclang1-14 libclang1-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION} libclc-${GITHUB_CLANG_VERSION}-dev libcrypt-dev:amd64 libdebhelper-perl libdpkg-perl libdrm-amdgpu1:amd64 libdrm-dev:amd64 libdrm-intel1:amd64 libdrm-nouveau2:amd64 libdrm-radeon1:amd64 libelf-dev:amd64 libexpat1-dev:amd64 libffi-dev:amd64 libfile-stripnondeterminism-perl libgc1:amd64 libgcc-11-dev:amd64 libgl1:amd64 libgl1-mesa-dri:amd64 libglapi-mesa:amd64 libglvnd-core-dev:amd64 libglvnd0:amd64 libglx-mesa0:amd64 libglx0:amd64 libgomp1:amd64 libicu-dev:amd64 libisl23:amd64 libitm1:amd64 libllvm14:amd64 libllvm${GITHUB_CLANG_VERSION}:amd64 libllvmspirvlib-${GITHUB_CLANG_VERSION}-dev:amd64 libllvmspirvlib${GITHUB_CLANG_VERSION}:amd64 liblsan0:amd64 libmpc3:amd64 libncurses-dev:amd64 libnsl-dev:amd64 libobjc-11-dev:amd64 libobjc4:amd64 libpciaccess-dev:amd64 libpciaccess0f:amd64 libpfm4:amd64 libpthread-stubs0-dev:amd64 libquadmath0:amd64 libsensors-config libsensors-dev:amd64 libsensors5:amd64 libset-scalar-perl libstd-rust-1.75:amd64 libstd-rust-dev:amd64 libstdc++-11-dev:amd64 libsub-override-perl libtinfo-dev:amd64 libtirpc-dev:amd64 libtool libtsan0:amd64 libubsan1:amd64 libva-dev:amd64 libva-drm2:amd64 libva-glx2:amd64 libva-wayland2:amd64 libva-x11-2:amd64 libva2:amd64 libvdpau-dev:amd64 libvdpau1:amd64 libvulkan-dev:amd64 libvulkan1:amd64 libwayland-bin libwayland-client0:amd64 libwayland-cursor0:amd64 libwayland-dev:amd64 libwayland-egl-backend-dev:amd64 libwayland-egl1:amd64 libwayland-server0:amd64 libx11-dev:amd64 libx11-xcb-dev:amd64 libx11-xcb1:amd64 libxau-dev:amd64 libxcb-dri2-0:amd64 libxcb-dri2-0-dev:amd64 libxcb-dri3-0:amd64 libxcb-dri3-dev:amd64 libxcb-glx0:amd64 libxcb-glx0-dev:amd64 libxcb-present-dev:amd64 libxcb-present0:amd64 libxcb-randr0:amd64 libxcb-randr0-dev:amd64 libxcb-render0:amd64 libxcb-render0-dev:amd64 libxcb-shape0:amd64 libxcb-shape0-dev:amd64 libxcb-shm0:amd64 libxcb-shm0-dev:amd64 libxcb-sync-dev:amd64 libxcb-sync1:amd64 libxcb-xfixes0:amd64 libxcb-xfixes0-dev:amd64 libxcb1-dev:amd64 libxdmcp-dev:amd64 libxext-dev:amd64 libxfixes-dev:amd64 libxfixes3:amd64 libxml2-dev:amd64 libxrandr-dev:amd64 libxrandr2:amd64 libxrender-dev:amd64 libxrender1:amd64 libxshmfence-dev:amd64 libxshmfence1:amd64 libxxf86vm-dev:amd64 libxxf86vm1:amd64 libz3-4:amd64 libz3-dev:amd64 libzstd-dev:amd64 linux-libc-dev:amd64 llvm-${LLVM_VERSION} llvm-${LLVM_VERSION}-dev llvm-${LLVM_VERSION}-linker-tools llvm-${LLVM_VERSION}-runtime llvm-${LLVM_VERSION}-tools llvm-spirv-${LLVM_VERSION} lto-disabled-list m4 make meson ninja-build pkg-config po-debconf python3-mako python3-ply python3-pygments quilt rpcsvc-proto rustc spirv-tools valgrind wayland-protocols x11proto-dev xorg-sgml-doctools xtrans-dev zlib1g-dev:amd64 \
|
||||
clang-$GITHUB_CLANG_VERSION libc++-$GITHUB_CLANG_VERSION-dev libc++abi-$GITHUB_CLANG_VERSION-dev
|
||||
|
||||
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${GITHUB_CLANG_VERSION} 100
|
||||
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-${GITHUB_CLANG_VERSION} 100
|
||||
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang-${GITHUB_CLANG_VERSION} 100
|
||||
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++-${GITHUB_CLANG_VERSION} 100
|
||||
else
|
||||
set +e
|
||||
sudo apt-get -y build-dep mesa
|
||||
sudo apt -y remove llvm-18 llvm-18-* llvm-19 llvm-19-*
|
||||
set -e
|
||||
CURRENT_CLANG_VERSION=$(clang --version | head -n 1 | awk '{ print $4 }' | awk 'BEGIN { FS="\\." } { print $1 }')
|
||||
GITHUB_CLANG_VERSION=${GITHUB_CLANG_VERSION:-${CURRENT_CLANG_VERSION}}
|
||||
sudo apt-get -y install clang-${GITHUB_CLANG_VERSION} \
|
||||
libc++-${GITHUB_CLANG_VERSION}-dev \
|
||||
libc++abi-${GITHUB_CLANG_VERSION}-dev \
|
||||
llvm-${LLVM_VERSION} \
|
||||
llvm-${LLVM_VERSION}-{dev,tools,runtime}
|
||||
fi # [[ "$GITHUB_WORKFLOW" ]]
|
||||
elif [[ "$os_name" == "Darwin" ]]; then
|
||||
if [[ ! "$GITHUB_WORKFLOW" ]]; then
|
||||
if [ ! command -v brew > /dev/null 2>&1 ]; then
|
||||
echo "Error: need to install homebrew to continue"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
brew install autoconf automake libx11 libxext libxrandr llvm@${LLVM_VERSION} ninja meson pkg-config libxshmfence
|
||||
|
||||
NEEDED_PYTHON_DEPS=("mako" "setuptools")
|
||||
for cmd in "${NEEDED_PYTHON_DEPS[@]}"; do
|
||||
if ! pip3 show "${cmd}" >/dev/null 2>&1; then
|
||||
sudo pip3 install ${cmd}
|
||||
fi
|
||||
done
|
||||
fi # [[ "$os_name" == x ]]
|
||||
|
||||
LOCAL_LDFLAGS=${LDFLAGS}
|
||||
LOCAL_CPPFLAGS=${CPPFLAGS}
|
||||
LOCAL_PATH=${PATH}
|
||||
LOCAL_CXX=$(which clang++)
|
||||
LOCAL_CC=$(which clang)
|
||||
|
||||
CHECKOUT_MESA=false
|
||||
if [[ -d "${MESA_DIR}" ]]; then
|
||||
cd ${MESA_DIR}
|
||||
if ! git fsck --connectivity-only > /dev/null 2>&1; then
|
||||
echo "git fsck failed for mesa; try redownloading"
|
||||
CHECKOUT_MESA=true;
|
||||
fi
|
||||
cd ..;
|
||||
else
|
||||
set +e
|
||||
sudo apt-get -y build-dep mesa
|
||||
sudo apt -y remove llvm-18 llvm-18-* llvm-19 llvm-19-*
|
||||
set -e
|
||||
|
||||
CURRENT_CLANG_VERSION=$(clang --version | head -n 1 | awk '{ print $4 }' | awk 'BEGIN { FS="\\." } { print $1 }')
|
||||
GITHUB_CLANG_VERSION=${GITHUB_CLANG_VERSION:-${CURRENT_CLANG_VERSION}}
|
||||
|
||||
sudo apt-get -y install clang-${GITHUB_CLANG_VERSION} \
|
||||
libc++-${GITHUB_CLANG_VERSION}-dev \
|
||||
libc++abi-${GITHUB_CLANG_VERSION}-dev \
|
||||
llvm-${GITHUB_CLANG_VERSION} \
|
||||
llvm-${GITHUB_CLANG_VERSION}-{dev,tools,runtime}
|
||||
CHECKOUT_MESA=true
|
||||
fi
|
||||
|
||||
if [[ "$CHECKOUT_MESA" = "true" ]]; then
|
||||
rm -rf ${MESA_DIR}
|
||||
|
||||
export CXX=`which clang++` && export CC=`which clang`
|
||||
|
||||
git clone https://gitlab.freedesktop.org/mesa/mesa.git
|
||||
#git clone https://gitlab.freedesktop.org/mesa/mesa.git
|
||||
git clone git://anongit.freedesktop.org/mesa/mesa
|
||||
# Due to gitlab mesa outage.
|
||||
if [[ "${MESA_DIR}" != "$(pwd)/mesa" ]]; then
|
||||
mv mesa ${MESA_DIR}
|
||||
fi
|
||||
fi
|
||||
|
||||
pushd .
|
||||
cd ${MESA_DIR}
|
||||
|
||||
cd mesa
|
||||
|
||||
git checkout mesa-23.2.1
|
||||
# Need >= 24 to have llvmpipe for swrast. llvmpipe is needed for GL >= 4.1.
|
||||
git checkout mesa-24.2.1
|
||||
|
||||
mkdir -p out
|
||||
|
||||
if [[ "$os_name" == "Darwin" ]]; then
|
||||
LOCAL_LDFLAGS="-L/opt/homebrew/opt/llvm@${LLVM_VERSION}/lib"
|
||||
LOCAL_CPPFLAGS="-I/opt/homebrew/opt/llvm@${LLVM_VERSION}/include -I/opt/homebrew/include"
|
||||
LOCAL_PATH=${PATH}:/opt/homebrew/opt/llvm@${LLVM_VERSION}/bin
|
||||
|
||||
# This is necessary to be able to build vk (lavapipe) on macOS. Doesn't seem like a real dependency.
|
||||
sed -I '' "s/error('Vulkan drivers require dri3 for X11 support')//g" meson.build
|
||||
fi
|
||||
|
||||
# -Dosmesa=true => builds OSMesa, which is an offscreen GL context
|
||||
# -Dgallium-drivers=swrast => builds GL software rasterizer
|
||||
# -Dvulkan-drivers=swrast => builds VK software rasterizer
|
||||
meson setup builddir/ -Dprefix="$(pwd)/out" -Dosmesa=true -Dglx=xlib -Dgallium-drivers=swrast -Dvulkan-drivers=swrast
|
||||
meson install -C builddir/
|
||||
# -Dgallium-drivers=llvmpipe is needed for GL >= 4.1 pipe-screen (see src/gallium/auxiliary/target-helpers/inline_sw_helper.h)
|
||||
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
|
||||
meson setup --wipe builddir/ -Dprefix="$(pwd)/out" -Dglx=xlib -Dosmesa=true -Dgallium-drivers=llvmpipe,swrast -Dvulkan-drivers=swrast
|
||||
CXX=${LOCAL_CXX} CC=${LOCAL_CC} PATH=${LOCAL_PATH} LDFLAGS=${LOCAL_LDFLAGS} CPPFLAGS=${LOCAL_CPPFLAGS} \
|
||||
meson install -C builddir/
|
||||
|
||||
popd
|
||||
|
||||
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': '9f5fc9d1e934571a1f713b5a00cd5f15db8c71ed',
|
||||
|
||||
'effcee_revision': '12241cbc30f20730b656db7fd5a3fa36cd420843',
|
||||
'effcee_revision': '874b47102c57a8979c0f154cf8e0eab53c0a0502',
|
||||
|
||||
'googletest_revision': 'e235eb34c6c4fed790ccdad4b16394301360dcd4',
|
||||
'googletest_revision': '3af834740f58ef56058e6f8a1be51f62bc3a6515',
|
||||
|
||||
# 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': '0e710677989b4326ac974fd80c5308191ed80965',
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ uint32_t InstructionFolder::UnaryOperate(spv::Op opcode,
|
||||
if (s_operand == std::numeric_limits<int32_t>::min()) {
|
||||
return s_operand;
|
||||
}
|
||||
return -s_operand;
|
||||
return static_cast<uint32_t>(-s_operand);
|
||||
}
|
||||
case spv::Op::OpNot:
|
||||
return ~operand;
|
||||
|
||||
+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,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user