Compare commits
34 Commits
v1.59.4
...
ebridgewat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c6b470650 | ||
|
|
68a99ef54c | ||
|
|
39fabdbc3b | ||
|
|
83967fdb1b | ||
|
|
b88c2d80a4 | ||
|
|
4a36193f04 | ||
|
|
8df19cb6b2 | ||
|
|
783c1c9c83 | ||
|
|
a92771c5ef | ||
|
|
c98a76135b | ||
|
|
7f2836bb43 | ||
|
|
b60d02919b | ||
|
|
156dde6872 | ||
|
|
dd2fbf01aa | ||
|
|
beba1f63f1 | ||
|
|
e20dd9ec93 | ||
|
|
7f80d956b6 | ||
|
|
3c941863dc | ||
|
|
57e2007aaf | ||
|
|
f88f0607c7 | ||
|
|
91b98964cc | ||
|
|
d92a90e22b | ||
|
|
79a548ecae | ||
|
|
3c4c8940b2 | ||
|
|
75d2252d33 | ||
|
|
9d3a55291d | ||
|
|
170dec0945 | ||
|
|
7967157fbb | ||
|
|
597ced13e1 | ||
|
|
c3542b135e | ||
|
|
ca3ff7e08e | ||
|
|
42c760a92f | ||
|
|
327a537bcc | ||
|
|
cfc4f34c18 |
29
.github/workflows/presubmit.yml
vendored
@@ -108,16 +108,27 @@ jobs:
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install python prereqs
|
||||
run: pip install mako setuptools pyyaml
|
||||
- name: Run script
|
||||
- name: Cache Mesa and deps
|
||||
id: mesa-cache
|
||||
uses: actions/cache@v4 # Use a specific version
|
||||
with:
|
||||
path: |
|
||||
$HOME/Library/Caches/Homebrew
|
||||
mesa
|
||||
key: ${{ runner.os }}-mesa-deps-${{ vars.MESA_VERSION }}
|
||||
- name: Get Mesa
|
||||
id: mesa-prereq
|
||||
env:
|
||||
MESA_VERSION: ${{ vars.MESA_VERSION }}
|
||||
run: |
|
||||
echo "disabled test"
|
||||
# bash test/renderdiff/test.sh
|
||||
# - uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: presubmit-renderdiff-result
|
||||
# path: ./out/renderdiff_tests
|
||||
bash test/utils/get_mesa.sh
|
||||
- name: Run Test
|
||||
run: |
|
||||
bash test/renderdiff/test.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: presubmit-renderdiff-result
|
||||
path: ./out/renderdiff_tests
|
||||
|
||||
validate-wgsl-webgpu:
|
||||
name: validate-wgsl-webgpu
|
||||
|
||||
@@ -801,6 +801,7 @@ add_subdirectory(${EXTERNAL}/draco/tnt)
|
||||
add_subdirectory(${EXTERNAL}/jsmn/tnt)
|
||||
add_subdirectory(${EXTERNAL}/stb/tnt)
|
||||
add_subdirectory(${EXTERNAL}/getopt)
|
||||
add_subdirectory(${EXTERNAL}/perfetto/tnt)
|
||||
|
||||
# Note that this has to be placed after mikktspace in order for combine_static_libs to work.
|
||||
add_subdirectory(${LIBRARIES}/geometry)
|
||||
|
||||
@@ -26,6 +26,10 @@ add_library(utils STATIC IMPORTED)
|
||||
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
|
||||
|
||||
add_library(perfetto STATIC IMPORTED)
|
||||
set_target_properties(perfetto PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libperfetto.a)
|
||||
|
||||
add_library(filabridge STATIC IMPORTED)
|
||||
set_target_properties(filabridge PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libfilabridge.a)
|
||||
@@ -40,6 +44,7 @@ set_target_properties(shaders PROPERTIES IMPORTED_LOCATION
|
||||
|
||||
set(FILAMAT_INCLUDE_DIRS
|
||||
../../libs/utils/include
|
||||
../../third_party/perfetto
|
||||
)
|
||||
|
||||
include_directories(${FILAMENT_DIR}/include)
|
||||
@@ -55,6 +60,7 @@ target_link_libraries(filamat-jni
|
||||
filabridge
|
||||
shaders
|
||||
utils
|
||||
perfetto
|
||||
log
|
||||
smol-v
|
||||
$<$<STREQUAL:${FILAMENT_SUPPORTS_WEBGPU},ON>:tint>
|
||||
|
||||
@@ -21,6 +21,10 @@ add_library(utils STATIC IMPORTED)
|
||||
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
|
||||
|
||||
add_library(perfetto STATIC IMPORTED)
|
||||
set_target_properties(perfetto PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libperfetto.a)
|
||||
|
||||
add_library(ibl-lite STATIC IMPORTED)
|
||||
set_target_properties(ibl-lite PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libibl-lite.a)
|
||||
@@ -123,6 +127,7 @@ target_link_libraries(filament-jni
|
||||
PRIVATE android
|
||||
PRIVATE jnigraphics
|
||||
PRIVATE utils
|
||||
PRIVATE perfetto
|
||||
|
||||
# libgeometry is PUBLIC because gltfio uses it.
|
||||
PUBLIC geometry
|
||||
@@ -141,6 +146,7 @@ target_include_directories(filament-jni PRIVATE
|
||||
${FILAMENT_DIR}/include
|
||||
../../filament/backend/include
|
||||
../../third_party/robin-map
|
||||
../../third_party/perfetto
|
||||
../../libs/utils/include)
|
||||
|
||||
# Force a relink when the version script is changed:
|
||||
|
||||
@@ -35,6 +35,10 @@ add_library(utils STATIC IMPORTED)
|
||||
set_target_properties(utils PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libutils.a)
|
||||
|
||||
add_library(perfetto STATIC IMPORTED)
|
||||
set_target_properties(perfetto PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libperfetto.a)
|
||||
|
||||
add_library(uberzlib STATIC IMPORTED)
|
||||
set_target_properties(uberzlib PROPERTIES IMPORTED_LOCATION
|
||||
${FILAMENT_DIR}/lib/${ANDROID_ABI}/libuberzlib.a)
|
||||
@@ -121,6 +125,7 @@ set(GLTFIO_INCLUDE_DIRS
|
||||
../../third_party/meshoptimizer/src
|
||||
../../third_party/robin-map
|
||||
../../third_party/stb
|
||||
../../third_party/perfetto
|
||||
../../libs/utils/include
|
||||
../../libs/ktxreader/include
|
||||
)
|
||||
@@ -129,7 +134,7 @@ add_library(gltfio-jni SHARED ${GLTFIO_SRCS})
|
||||
target_include_directories(gltfio-jni PRIVATE ${GLTFIO_INCLUDE_DIRS})
|
||||
set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.symbols)
|
||||
set_target_properties(gltfio-jni PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/libgltfio-jni.map)
|
||||
target_link_libraries(gltfio-jni filament-jni utils uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
|
||||
target_link_libraries(gltfio-jni filament-jni utils perfetto uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
|
||||
target_link_libraries(gltfio-jni dracodec meshoptimizer)
|
||||
target_compile_definitions(gltfio-jni PUBLIC GLTFIO_DRACO_SUPPORTED=1)
|
||||
target_include_directories(gltfio-jni PRIVATE ${DRACO_DIR}/src)
|
||||
|
||||
@@ -56,10 +56,16 @@ popd >/dev/null
|
||||
rm -rf out/check-headers
|
||||
mkdir -p out/check-headers
|
||||
|
||||
TMP_FILE=out/check-headers/temp.cpp
|
||||
echo "Checking that public headers compile independently..."
|
||||
for include in "${includes[@]}"; do
|
||||
rm -f ${TMP_FILE}
|
||||
echo "Checking ${include}"
|
||||
echo "#include <${include}>" >> out/check-headers/temp.cpp
|
||||
clang -std=c++17 -I "${FILAMENT_HEADERS}" out/check-headers/temp.cpp -c -o /dev/null
|
||||
if [[ "${include}" == "utils/Systrace.h" ]]; then
|
||||
# A necessary define before we can include utils/Systrace.h
|
||||
echo "#define SYSTRACE_TAG SYSTRACE_TAG_DISABLED" >> ${TMP_FILE}
|
||||
fi
|
||||
echo "#include <${include}>" >> ${TMP_FILE}
|
||||
clang -std=c++17 -I "${FILAMENT_HEADERS}" ${TMP_FILE} -c -o /dev/null
|
||||
done
|
||||
echo "Done!"
|
||||
|
||||
@@ -194,6 +194,8 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/VulkanDriver.cpp
|
||||
src/vulkan/VulkanDriver.h
|
||||
src/vulkan/VulkanDriverFactory.h
|
||||
src/vulkan/VulkanExternalImageManager.cpp
|
||||
src/vulkan/VulkanExternalImageManager.h
|
||||
src/vulkan/VulkanFboCache.cpp
|
||||
src/vulkan/VulkanFboCache.h
|
||||
src/vulkan/VulkanHandles.cpp
|
||||
@@ -259,6 +261,7 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
src/webgpu/WebGPUHandles.h
|
||||
src/webgpu/WebGPUSwapChain.cpp
|
||||
src/webgpu/WebGPUSwapChain.h
|
||||
src/webgpu/WGPUProgram.cpp
|
||||
)
|
||||
if (WIN32)
|
||||
list(APPEND SRCS src/webgpu/platform/WebGPUPlatformWindows.cpp)
|
||||
|
||||
@@ -294,6 +294,16 @@ public:
|
||||
VkQueue getProtectedGraphicsQueue() const noexcept;
|
||||
|
||||
struct ExternalImageMetadata {
|
||||
/**
|
||||
* The Filament texture format.
|
||||
*/
|
||||
TextureFormat filamentFormat;
|
||||
|
||||
/**
|
||||
* The Filament texture usage.
|
||||
*/
|
||||
TextureUsage filamentUsage;
|
||||
|
||||
/**
|
||||
* The width of the external image
|
||||
*/
|
||||
@@ -304,11 +314,6 @@ public:
|
||||
*/
|
||||
uint32_t height;
|
||||
|
||||
/**
|
||||
* The layerCount of the external image
|
||||
*/
|
||||
uint32_t layerCount;
|
||||
|
||||
/**
|
||||
* The layer count of the external image
|
||||
*/
|
||||
@@ -324,11 +329,6 @@ public:
|
||||
*/
|
||||
VkFormat format;
|
||||
|
||||
/**
|
||||
* An external buffer can be protected. This tells you if it is.
|
||||
*/
|
||||
bool isProtected;
|
||||
|
||||
/**
|
||||
* The type of external format (opaque int) if used.
|
||||
*/
|
||||
@@ -348,20 +348,61 @@ public:
|
||||
* Heap information
|
||||
*/
|
||||
uint32_t memoryTypeBits;
|
||||
|
||||
/**
|
||||
* Ycbcr conversion components
|
||||
*/
|
||||
VkComponentMapping ycbcrConversionComponents;
|
||||
|
||||
/**
|
||||
* Ycbcr model
|
||||
*/
|
||||
VkSamplerYcbcrModelConversion ycbcrModel;
|
||||
|
||||
/**
|
||||
* Ycbcr range
|
||||
*/
|
||||
VkSamplerYcbcrRange ycbcrRange;
|
||||
|
||||
/**
|
||||
* Ycbcr x chroma offset
|
||||
*/
|
||||
VkChromaLocation xChromaOffset;
|
||||
|
||||
/**
|
||||
* Ycbcr y chroma offset
|
||||
*/
|
||||
VkChromaLocation yChromaOffset;
|
||||
};
|
||||
virtual ExternalImageMetadata getExternalImageMetadata(ExternalImageHandleRef externalImage);
|
||||
|
||||
using ImageData = std::pair<VkImage, VkDeviceMemory>;
|
||||
virtual ImageData createExternalImageData(ExternalImageHandleRef externalImage,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
||||
VkImageUsageFlags usage);
|
||||
|
||||
virtual VkSampler createExternalSampler(SamplerYcbcrConversion chroma,
|
||||
SamplerParams sampler, uint32_t internalFormat);
|
||||
// Note that the image metadata might change per-frame, hence we need a method for extracting
|
||||
// it.
|
||||
virtual ExternalImageMetadata extractExternalImageMetadata(ExternalImageHandleRef image) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual VkImageView createExternalImageView(SamplerYcbcrConversion chroma,
|
||||
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
|
||||
VkImageViewType viewType, VkComponentMapping swizzle);
|
||||
struct ImageData {
|
||||
struct Bundle {
|
||||
VkImage image = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
|
||||
inline bool valid() const noexcept {
|
||||
return image != VK_NULL_HANDLE;
|
||||
}
|
||||
};
|
||||
// It's possible for the external image to also have a known VK format. We need to create an
|
||||
// image for that in case we are not looking to use an external "sampler" with this image.
|
||||
Bundle internal;
|
||||
|
||||
// If we get a externalFormat in the metadata, then we should create an image with
|
||||
// VK_FORMAT_UNDEFINED
|
||||
Bundle external;
|
||||
};
|
||||
|
||||
virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
@@ -374,20 +415,6 @@ private:
|
||||
// Platform dependent helper methods
|
||||
static ExtensionSet getSwapchainInstanceExtensionsImpl();
|
||||
|
||||
static ExternalImageMetadata getExternalImageMetadataImpl(ExternalImageHandleRef externalImage,
|
||||
VkDevice device);
|
||||
|
||||
static ImageData createExternalImageDataImpl(ExternalImageHandleRef externalImage,
|
||||
VkDevice device, const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
||||
VkImageUsageFlags usage);
|
||||
static VkSampler createExternalSamplerImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma, SamplerParams sampler,
|
||||
uint32_t internalFormat);
|
||||
static VkImageView createExternalImageViewImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
|
||||
VkImageSubresourceRange range, VkImageViewType viewType,
|
||||
VkComponentMapping swizzle);
|
||||
|
||||
// Platform dependent helper methods
|
||||
static SurfaceBundle createVkSurfaceKHRImpl(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) noexcept;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace filament::backend {
|
||||
|
||||
class VulkanPlatformAndroid : public VulkanPlatform {
|
||||
public:
|
||||
Platform::ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer,
|
||||
ExternalImageHandle UTILS_PUBLIC createExternalImage(AHardwareBuffer const* buffer,
|
||||
bool sRGB) noexcept;
|
||||
|
||||
struct UTILS_PUBLIC ExternalImageDescAndroid {
|
||||
@@ -39,31 +39,26 @@ public:
|
||||
ExternalImageDescAndroid UTILS_PUBLIC getExternalImageDesc(
|
||||
ExternalImageHandleRef externalImage) const noexcept;
|
||||
|
||||
virtual ExternalImageMetadata extractExternalImageMetadata(
|
||||
ExternalImageHandleRef image) const override;
|
||||
|
||||
virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const override;
|
||||
|
||||
protected:
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const override;
|
||||
|
||||
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept override;
|
||||
|
||||
private:
|
||||
struct ExternalImageVulkanAndroid : public Platform::ExternalImage {
|
||||
AHardwareBuffer* aHardwareBuffer = nullptr;
|
||||
bool sRGB = false;
|
||||
unsigned int width; // Texture width
|
||||
unsigned int height; // Texture height
|
||||
TextureFormat format;// Texture format
|
||||
TextureUsage usage; // Texture usage flags
|
||||
|
||||
protected:
|
||||
~ExternalImageVulkanAndroid() override;
|
||||
};
|
||||
|
||||
virtual ExternalImageMetadata getExternalImageMetadata(ExternalImageHandleRef externalImage);
|
||||
|
||||
using ImageData = VulkanPlatform::ImageData;
|
||||
virtual ImageData createExternalImageData(ExternalImageHandleRef externalImage,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex,
|
||||
VkImageUsageFlags usage);
|
||||
|
||||
virtual ExtensionSet getSwapchainInstanceExtensions() const;
|
||||
|
||||
using SurfaceBundle = VulkanPlatform::SurfaceBundle;
|
||||
virtual SurfaceBundle createVkSurfaceKHR(void* nativeWindow, VkInstance instance,
|
||||
uint64_t flags) const noexcept;
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -84,7 +84,7 @@ void CommandStream::execute(void* buffer) {
|
||||
|
||||
Profiler profiler;
|
||||
|
||||
if (SYSTRACE_TAG) {
|
||||
if constexpr (SYSTRACE_TAG) {
|
||||
if (UTILS_UNLIKELY(mUsePerformanceCounter)) {
|
||||
// we want to remove all this when tracing is completely disabled
|
||||
profiler.resetEvents(Profiler::EV_CPU_CYCLES | Profiler::EV_BPU_MISSES);
|
||||
@@ -100,7 +100,7 @@ void CommandStream::execute(void* buffer) {
|
||||
}
|
||||
});
|
||||
|
||||
if (SYSTRACE_TAG) {
|
||||
if constexpr (SYSTRACE_TAG) {
|
||||
if (UTILS_UNLIKELY(mUsePerformanceCounter)) {
|
||||
// we want to remove all this when tracing is completely disabled
|
||||
profiler.stop();
|
||||
|
||||
@@ -1408,8 +1408,8 @@ void MetalDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, Primit
|
||||
auto primitive = handle_cast<MetalRenderPrimitive>(rph);
|
||||
auto vertexBuffer = handle_cast<MetalVertexBuffer>(vbh);
|
||||
auto indexBuffer = handle_cast<MetalIndexBuffer>(ibh);
|
||||
MetalVertexBufferInfo const* const vbi = handle_cast<MetalVertexBufferInfo>(vertexBuffer->vbih);
|
||||
primitive->setBuffers(vbi, vertexBuffer, indexBuffer);
|
||||
primitive->vertexBuffer = vertexBuffer;
|
||||
primitive->indexBuffer = indexBuffer;
|
||||
primitive->type = pt;
|
||||
}
|
||||
|
||||
|
||||
@@ -194,12 +194,8 @@ struct MetalIndexBuffer : public HwIndexBuffer {
|
||||
};
|
||||
|
||||
struct MetalRenderPrimitive : public HwRenderPrimitive {
|
||||
MetalRenderPrimitive();
|
||||
void setBuffers(MetalVertexBufferInfo const* const vbi,
|
||||
MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer);
|
||||
// The pointers to MetalVertexBuffer and MetalIndexBuffer are "weak".
|
||||
// The MetalVertexBuffer and MetalIndexBuffer must outlive the MetalRenderPrimitive.
|
||||
|
||||
MetalVertexBuffer* vertexBuffer = nullptr;
|
||||
MetalIndexBuffer* indexBuffer = nullptr;
|
||||
};
|
||||
@@ -380,7 +376,6 @@ public:
|
||||
|
||||
math::uint2 getAttachmentSize() noexcept;
|
||||
|
||||
bool isDefaultRenderTarget() const { return defaultRenderTarget; }
|
||||
uint8_t getSamples() const { return samples; }
|
||||
|
||||
Attachment getDrawColorAttachment(size_t index);
|
||||
|
||||
@@ -536,15 +536,6 @@ MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uin
|
||||
uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount),
|
||||
buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { }
|
||||
|
||||
MetalRenderPrimitive::MetalRenderPrimitive() {
|
||||
}
|
||||
|
||||
void MetalRenderPrimitive::setBuffers(MetalVertexBufferInfo const* const vbi,
|
||||
MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer) {
|
||||
this->vertexBuffer = vertexBuffer;
|
||||
this->indexBuffer = indexBuffer;
|
||||
}
|
||||
|
||||
MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept
|
||||
: HwProgram(program.getName()), mContext(context) {
|
||||
mToken = context.shaderCompiler->createProgram(program.getName(), std::move(program));
|
||||
|
||||
@@ -16,9 +16,15 @@
|
||||
|
||||
#include "GLUtils.h"
|
||||
|
||||
#include "private/backend/Driver.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/trap.h>
|
||||
|
||||
#include "private/backend/Driver.h"
|
||||
#include <string_view>
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -28,38 +34,31 @@ using namespace utils;
|
||||
namespace GLUtils {
|
||||
|
||||
UTILS_NOINLINE
|
||||
const char* getGLError(GLenum error) noexcept {
|
||||
const char* string = "unknown";
|
||||
std::string_view getGLErrorString(GLenum error) noexcept {
|
||||
switch (error) {
|
||||
case GL_NO_ERROR:
|
||||
string = "GL_NO_ERROR";
|
||||
break;
|
||||
return "GL_NO_ERROR";
|
||||
case GL_INVALID_ENUM:
|
||||
string = "GL_INVALID_ENUM";
|
||||
break;
|
||||
return "GL_INVALID_ENUM";
|
||||
case GL_INVALID_VALUE:
|
||||
string = "GL_INVALID_VALUE";
|
||||
break;
|
||||
return "GL_INVALID_VALUE";
|
||||
case GL_INVALID_OPERATION:
|
||||
string = "GL_INVALID_OPERATION";
|
||||
break;
|
||||
return "GL_INVALID_OPERATION";
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
string = "GL_INVALID_FRAMEBUFFER_OPERATION";
|
||||
break;
|
||||
return "GL_INVALID_FRAMEBUFFER_OPERATION";
|
||||
case GL_OUT_OF_MEMORY:
|
||||
string = "GL_OUT_OF_MEMORY";
|
||||
break;
|
||||
return "GL_OUT_OF_MEMORY";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return string;
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
GLenum checkGLError(io::ostream& out, const char* function, size_t line) noexcept {
|
||||
GLenum const error = glGetError();
|
||||
if (error != GL_NO_ERROR) {
|
||||
const char* string = getGLError(error);
|
||||
if (UTILS_VERY_UNLIKELY(error != GL_NO_ERROR)) {
|
||||
auto const string = getGLErrorString(error);
|
||||
out << "OpenGL error " << io::hex << error << " (" << string << ") in \""
|
||||
<< function << "\" at line " << io::dec << line << io::endl;
|
||||
}
|
||||
@@ -69,46 +68,39 @@ GLenum checkGLError(io::ostream& out, const char* function, size_t line) noexcep
|
||||
UTILS_NOINLINE
|
||||
void assertGLError(io::ostream& out, const char* function, size_t line) noexcept {
|
||||
GLenum const err = checkGLError(out, function, line);
|
||||
if (err != GL_NO_ERROR) {
|
||||
if (UTILS_VERY_UNLIKELY(err != GL_NO_ERROR)) {
|
||||
debug_trap();
|
||||
}
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
const char* getFramebufferStatus(GLenum status) noexcept {
|
||||
const char* string = "unknown";
|
||||
std::string_view getFramebufferStatusString(GLenum status) noexcept {
|
||||
switch (status) {
|
||||
case GL_FRAMEBUFFER_COMPLETE:
|
||||
string = "GL_FRAMEBUFFER_COMPLETE";
|
||||
break;
|
||||
return "GL_FRAMEBUFFER_COMPLETE";
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
|
||||
string = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
|
||||
break;
|
||||
return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
|
||||
string = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
|
||||
break;
|
||||
return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
|
||||
case GL_FRAMEBUFFER_UNSUPPORTED:
|
||||
string = "GL_FRAMEBUFFER_UNSUPPORTED";
|
||||
break;
|
||||
return "GL_FRAMEBUFFER_UNSUPPORTED";
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
case GL_FRAMEBUFFER_UNDEFINED:
|
||||
string = "GL_FRAMEBUFFER_UNDEFINED";
|
||||
break;
|
||||
return "GL_FRAMEBUFFER_UNDEFINED";
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
|
||||
string = "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE";
|
||||
break;
|
||||
return "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE";
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return string;
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
GLenum checkFramebufferStatus(io::ostream& out, GLenum target, const char* function, size_t line) noexcept {
|
||||
GLenum const status = glCheckFramebufferStatus(target);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
const char* string = getFramebufferStatus(status);
|
||||
if (UTILS_VERY_UNLIKELY(status != GL_FRAMEBUFFER_COMPLETE)) {
|
||||
auto const string = getFramebufferStatusString(status);
|
||||
out << "OpenGL framebuffer error " << io::hex << status << " (" << string << ") in \""
|
||||
<< function << "\" at line " << io::dec << line << io::endl;
|
||||
}
|
||||
@@ -118,7 +110,7 @@ GLenum checkFramebufferStatus(io::ostream& out, GLenum target, const char* funct
|
||||
UTILS_NOINLINE
|
||||
void assertFramebufferStatus(io::ostream& out, GLenum target, const char* function, size_t line) noexcept {
|
||||
GLenum const status = checkFramebufferStatus(out, target, function, line);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
if (UTILS_VERY_UNLIKELY(status != GL_FRAMEBUFFER_COMPLETE)) {
|
||||
debug_trap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,29 +17,26 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "gl_headers.h"
|
||||
|
||||
namespace filament::backend {
|
||||
namespace GLUtils {
|
||||
namespace filament::backend::GLUtils {
|
||||
|
||||
const char* getGLError(GLenum error) noexcept;
|
||||
std::string_view getGLErrorString(GLenum error) noexcept;
|
||||
GLenum checkGLError(utils::io::ostream& out, const char* function, size_t line) noexcept;
|
||||
void assertGLError(utils::io::ostream& out, const char* function, size_t line) noexcept;
|
||||
|
||||
const char* getFramebufferStatus(GLenum err) noexcept;
|
||||
std::string_view getFramebufferStatusString(GLenum err) noexcept;
|
||||
GLenum checkFramebufferStatus(utils::io::ostream& out, GLenum target, const char* function, size_t line) noexcept;
|
||||
void assertFramebufferStatus(utils::io::ostream& out, GLenum target, const char* function, size_t line) noexcept;
|
||||
|
||||
@@ -53,7 +50,7 @@ void assertFramebufferStatus(utils::io::ostream& out, GLenum target, const char*
|
||||
# define CHECK_GL_FRAMEBUFFER_STATUS(out, target) { GLUtils::checkFramebufferStatus(out, target, __func__, __LINE__); }
|
||||
#endif
|
||||
|
||||
constexpr GLuint getComponentCount(ElementType type) noexcept {
|
||||
constexpr GLuint getComponentCount(ElementType const type) noexcept {
|
||||
using ElementType = ElementType;
|
||||
switch (type) {
|
||||
case ElementType::BYTE:
|
||||
@@ -87,27 +84,29 @@ constexpr GLuint getComponentCount(ElementType type) noexcept {
|
||||
case ElementType::USHORT4:
|
||||
return 4;
|
||||
}
|
||||
// should never happen
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Our enums to GLenum conversions
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
constexpr GLbitfield getAttachmentBitfield(TargetBufferFlags flags) noexcept {
|
||||
constexpr GLbitfield getAttachmentBitfield(TargetBufferFlags const flags) noexcept {
|
||||
GLbitfield mask = 0;
|
||||
if (any(flags & TargetBufferFlags::COLOR_ALL)) {
|
||||
mask |= (GLbitfield)GL_COLOR_BUFFER_BIT;
|
||||
mask |= GLbitfield(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
if (any(flags & TargetBufferFlags::DEPTH)) {
|
||||
mask |= (GLbitfield)GL_DEPTH_BUFFER_BIT;
|
||||
mask |= GLbitfield(GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
if (any(flags & TargetBufferFlags::STENCIL)) {
|
||||
mask |= (GLbitfield)GL_STENCIL_BUFFER_BIT;
|
||||
mask |= GLbitfield(GL_STENCIL_BUFFER_BIT);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
constexpr GLenum getBufferUsage(BufferUsage usage) noexcept {
|
||||
constexpr GLenum getBufferUsage(BufferUsage const usage) noexcept {
|
||||
switch (usage) {
|
||||
case BufferUsage::STATIC:
|
||||
return GL_STATIC_DRAW;
|
||||
@@ -116,7 +115,7 @@ constexpr GLenum getBufferUsage(BufferUsage usage) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr GLenum getBufferBindingType(BufferObjectBinding bindingType) noexcept {
|
||||
constexpr GLenum getBufferBindingType(BufferObjectBinding const bindingType) noexcept {
|
||||
switch (bindingType) {
|
||||
case BufferObjectBinding::VERTEX:
|
||||
return GL_ARRAY_BUFFER;
|
||||
@@ -135,13 +134,15 @@ constexpr GLenum getBufferBindingType(BufferObjectBinding bindingType) noexcept
|
||||
return 0x90D2; // just to return something
|
||||
#endif
|
||||
}
|
||||
// should never happen
|
||||
return GL_ARRAY_BUFFER;
|
||||
}
|
||||
|
||||
constexpr GLboolean getNormalization(bool normalized) noexcept {
|
||||
constexpr GLboolean getNormalization(bool const normalized) noexcept {
|
||||
return GLboolean(normalized ? GL_TRUE : GL_FALSE);
|
||||
}
|
||||
|
||||
constexpr GLenum getComponentType(ElementType type) noexcept {
|
||||
constexpr GLenum getComponentType(ElementType const type) noexcept {
|
||||
using ElementType = ElementType;
|
||||
switch (type) {
|
||||
case ElementType::BYTE:
|
||||
@@ -184,9 +185,11 @@ constexpr GLenum getComponentType(ElementType type) noexcept {
|
||||
return GL_HALF_FLOAT_OES;
|
||||
#endif
|
||||
}
|
||||
// should never happen
|
||||
return GL_INT;
|
||||
}
|
||||
|
||||
constexpr GLenum getTextureTargetNotExternal(SamplerType target) noexcept {
|
||||
constexpr GLenum getTextureTargetNotExternal(SamplerType const target) noexcept {
|
||||
switch (target) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
return GL_TEXTURE_2D;
|
||||
@@ -202,14 +205,16 @@ constexpr GLenum getTextureTargetNotExternal(SamplerType target) noexcept {
|
||||
// we should never be here
|
||||
return GL_TEXTURE_2D;
|
||||
}
|
||||
// should never happen
|
||||
return GL_TEXTURE_2D;
|
||||
}
|
||||
|
||||
constexpr GLenum getCubemapTarget(uint16_t layer) noexcept {
|
||||
constexpr GLenum getCubemapTarget(uint16_t const layer) noexcept {
|
||||
assert_invariant(layer <= 5);
|
||||
return GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer;
|
||||
}
|
||||
|
||||
constexpr GLenum getWrapMode(SamplerWrapMode mode) noexcept {
|
||||
constexpr GLenum getWrapMode(SamplerWrapMode const mode) noexcept {
|
||||
using SamplerWrapMode = SamplerWrapMode;
|
||||
switch (mode) {
|
||||
case SamplerWrapMode::REPEAT:
|
||||
@@ -219,6 +224,8 @@ constexpr GLenum getWrapMode(SamplerWrapMode mode) noexcept {
|
||||
case SamplerWrapMode::MIRRORED_REPEAT:
|
||||
return GL_MIRRORED_REPEAT;
|
||||
}
|
||||
// should never happen
|
||||
return GL_CLAMP_TO_EDGE;
|
||||
}
|
||||
|
||||
constexpr GLenum getTextureFilter(SamplerMinFilter filter) noexcept {
|
||||
@@ -234,6 +241,8 @@ constexpr GLenum getTextureFilter(SamplerMinFilter filter) noexcept {
|
||||
return GL_NEAREST_MIPMAP_NEAREST
|
||||
- GLenum(SamplerMinFilter::NEAREST_MIPMAP_NEAREST) + GLenum(filter);
|
||||
}
|
||||
// should never happen
|
||||
return GL_NEAREST;
|
||||
}
|
||||
|
||||
constexpr GLenum getTextureFilter(SamplerMagFilter filter) noexcept {
|
||||
@@ -241,7 +250,7 @@ constexpr GLenum getTextureFilter(SamplerMagFilter filter) noexcept {
|
||||
}
|
||||
|
||||
|
||||
constexpr GLenum getBlendEquationMode(BlendEquation mode) noexcept {
|
||||
constexpr GLenum getBlendEquationMode(BlendEquation const mode) noexcept {
|
||||
using BlendEquation = BlendEquation;
|
||||
switch (mode) {
|
||||
case BlendEquation::ADD: return GL_FUNC_ADD;
|
||||
@@ -250,9 +259,11 @@ constexpr GLenum getBlendEquationMode(BlendEquation mode) noexcept {
|
||||
case BlendEquation::MIN: return GL_MIN;
|
||||
case BlendEquation::MAX: return GL_MAX;
|
||||
}
|
||||
// should never happen
|
||||
return GL_FUNC_ADD;
|
||||
}
|
||||
|
||||
constexpr GLenum getBlendFunctionMode(BlendFunction mode) noexcept {
|
||||
constexpr GLenum getBlendFunctionMode(BlendFunction const mode) noexcept {
|
||||
using BlendFunction = BlendFunction;
|
||||
switch (mode) {
|
||||
case BlendFunction::ZERO: return GL_ZERO;
|
||||
@@ -267,9 +278,11 @@ constexpr GLenum getBlendFunctionMode(BlendFunction mode) noexcept {
|
||||
case BlendFunction::ONE_MINUS_DST_ALPHA: return GL_ONE_MINUS_DST_ALPHA;
|
||||
case BlendFunction::SRC_ALPHA_SATURATE: return GL_SRC_ALPHA_SATURATE;
|
||||
}
|
||||
// should never happen
|
||||
return GL_ONE;
|
||||
}
|
||||
|
||||
constexpr GLenum getCompareFunc(SamplerCompareFunc func) noexcept {
|
||||
constexpr GLenum getCompareFunc(SamplerCompareFunc const func) noexcept {
|
||||
switch (func) {
|
||||
case SamplerCompareFunc::LE: return GL_LEQUAL;
|
||||
case SamplerCompareFunc::GE: return GL_GEQUAL;
|
||||
@@ -280,28 +293,30 @@ constexpr GLenum getCompareFunc(SamplerCompareFunc func) noexcept {
|
||||
case SamplerCompareFunc::A: return GL_ALWAYS;
|
||||
case SamplerCompareFunc::N: return GL_NEVER;
|
||||
}
|
||||
// should never happen
|
||||
return GL_LEQUAL;
|
||||
}
|
||||
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
constexpr GLenum getTextureCompareMode(SamplerCompareMode mode) noexcept {
|
||||
constexpr GLenum getTextureCompareMode(SamplerCompareMode const mode) noexcept {
|
||||
return mode == SamplerCompareMode::NONE ?
|
||||
GL_NONE : GL_COMPARE_REF_TO_TEXTURE;
|
||||
}
|
||||
|
||||
constexpr GLenum getTextureCompareFunc(SamplerCompareFunc func) noexcept {
|
||||
constexpr GLenum getTextureCompareFunc(SamplerCompareFunc const func) noexcept {
|
||||
return getCompareFunc(func);
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr GLenum getDepthFunc(SamplerCompareFunc func) noexcept {
|
||||
constexpr GLenum getDepthFunc(SamplerCompareFunc const func) noexcept {
|
||||
return getCompareFunc(func);
|
||||
}
|
||||
|
||||
constexpr GLenum getStencilFunc(SamplerCompareFunc func) noexcept {
|
||||
constexpr GLenum getStencilFunc(SamplerCompareFunc const func) noexcept {
|
||||
return getCompareFunc(func);
|
||||
}
|
||||
|
||||
constexpr GLenum getStencilOp(StencilOperation op) noexcept {
|
||||
constexpr GLenum getStencilOp(StencilOperation const op) noexcept {
|
||||
switch (op) {
|
||||
case StencilOperation::KEEP: return GL_KEEP;
|
||||
case StencilOperation::ZERO: return GL_ZERO;
|
||||
@@ -312,9 +327,11 @@ constexpr GLenum getStencilOp(StencilOperation op) noexcept {
|
||||
case StencilOperation::DECR_WRAP: return GL_DECR_WRAP;
|
||||
case StencilOperation::INVERT: return GL_INVERT;
|
||||
}
|
||||
// should never happen
|
||||
return GL_KEEP;
|
||||
}
|
||||
|
||||
constexpr GLenum getFormat(PixelDataFormat format) noexcept {
|
||||
constexpr GLenum getFormat(PixelDataFormat const format) noexcept {
|
||||
using PixelDataFormat = PixelDataFormat;
|
||||
switch (format) {
|
||||
case PixelDataFormat::RGB: return GL_RGB;
|
||||
@@ -336,9 +353,11 @@ constexpr GLenum getFormat(PixelDataFormat format) noexcept {
|
||||
default: return GL_NONE;
|
||||
#endif
|
||||
}
|
||||
// should never happen
|
||||
return GL_RGBA;
|
||||
}
|
||||
|
||||
constexpr GLenum getType(PixelDataType type) noexcept {
|
||||
constexpr GLenum getType(PixelDataType const type) noexcept {
|
||||
using PixelDataType = PixelDataType;
|
||||
switch (type) {
|
||||
case PixelDataType::UBYTE: return GL_UNSIGNED_BYTE;
|
||||
@@ -360,10 +379,12 @@ constexpr GLenum getType(PixelDataType type) noexcept {
|
||||
default: return GL_NONE;
|
||||
#endif
|
||||
}
|
||||
// should never happen
|
||||
return GL_UNSIGNED_INT;
|
||||
}
|
||||
|
||||
#if !defined(__EMSCRIPTEN__) && !defined(FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2)
|
||||
constexpr GLenum getSwizzleChannel(TextureSwizzle c) noexcept {
|
||||
constexpr GLenum getSwizzleChannel(TextureSwizzle const c) noexcept {
|
||||
using TextureSwizzle = TextureSwizzle;
|
||||
switch (c) {
|
||||
case TextureSwizzle::SUBSTITUTE_ZERO: return GL_ZERO;
|
||||
@@ -373,10 +394,12 @@ constexpr GLenum getSwizzleChannel(TextureSwizzle c) noexcept {
|
||||
case TextureSwizzle::CHANNEL_2: return GL_BLUE;
|
||||
case TextureSwizzle::CHANNEL_3: return GL_ALPHA;
|
||||
}
|
||||
// should never happen
|
||||
return GL_RED;
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr GLenum getCullingMode(CullingMode mode) noexcept {
|
||||
constexpr GLenum getCullingMode(CullingMode const mode) noexcept {
|
||||
switch (mode) {
|
||||
case CullingMode::NONE:
|
||||
// should never happen
|
||||
@@ -388,11 +411,13 @@ constexpr GLenum getCullingMode(CullingMode mode) noexcept {
|
||||
case CullingMode::FRONT_AND_BACK:
|
||||
return GL_FRONT_AND_BACK;
|
||||
}
|
||||
// should never happen
|
||||
return GL_FRONT_AND_BACK;
|
||||
}
|
||||
|
||||
// ES2 supported internal formats for texturing and how they map to a format/type
|
||||
constexpr std::pair<GLenum, GLenum> textureFormatToFormatAndType(
|
||||
TextureFormat format) noexcept {
|
||||
TextureFormat const format) noexcept {
|
||||
switch (format) {
|
||||
case TextureFormat::R8: return { 0x1909 /*GL_LUMINANCE*/, GL_UNSIGNED_BYTE };
|
||||
case TextureFormat::RGB8: return { GL_RGB, GL_UNSIGNED_BYTE };
|
||||
@@ -413,7 +438,7 @@ constexpr std::pair<GLenum, GLenum> textureFormatToFormatAndType(
|
||||
// clang loses it on this one, and generates a huge jump table when
|
||||
// inlined. So we don't mark it as inline (only constexpr) which solves the problem,
|
||||
// strangely, when not inlined, clang simply generates an array lookup.
|
||||
constexpr /* inline */ GLenum getInternalFormat(TextureFormat format) noexcept {
|
||||
constexpr /* inline */ GLenum getInternalFormat(TextureFormat const format) noexcept {
|
||||
switch (format) {
|
||||
|
||||
/* Formats supported by our ES2 implementations */
|
||||
@@ -661,7 +686,7 @@ public:
|
||||
|
||||
unordered_string_set split(const char* extensions) noexcept;
|
||||
|
||||
} // namespace GLUtils
|
||||
} // namespace filament::backend
|
||||
} // namespace filament::backend::GLUtils
|
||||
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H
|
||||
|
||||
@@ -2098,6 +2098,7 @@ void OpenGLDriver::setAcquiredImage(Handle<HwStream> sh, void* hwbuffer, const m
|
||||
|
||||
glstream->user_thread.pending = mPlatform.transformAcquiredImage({
|
||||
hwbuffer, cb, userData, handler });
|
||||
glstream->user_thread.transform = transform;
|
||||
|
||||
if (glstream->user_thread.pending.image != nullptr) {
|
||||
// If there's no pending image, do nothing. Note that GL_OES_EGL_image does not let you pass
|
||||
|
||||
@@ -85,6 +85,7 @@ OpenGLProgram::~OpenGLProgram() noexcept {
|
||||
delete lazyInitializationData;
|
||||
|
||||
ShaderCompilerService::terminate(mToken);
|
||||
assert_invariant(!mToken);
|
||||
}
|
||||
|
||||
delete [] mUniformsRecords;
|
||||
|
||||
@@ -17,11 +17,15 @@
|
||||
#include "ShaderCompilerService.h"
|
||||
|
||||
#include "BlobCacheKey.h"
|
||||
#include "CallbackManager.h"
|
||||
#include "CompilerThreadPool.h"
|
||||
#include "OpenGLBlobCache.h"
|
||||
#include "OpenGLDriver.h"
|
||||
|
||||
#include <iterator>
|
||||
#include <private/backend/BackendUtils.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
@@ -34,9 +38,9 @@
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -47,6 +51,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -54,17 +59,17 @@ using namespace utils;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
static inline std::string to_string(bool b) noexcept { return b ? "true" : "false"; }
|
||||
static inline std::string to_string(int i) noexcept { return std::to_string(i); }
|
||||
static inline std::string to_string(float f) noexcept { return "float(" + std::to_string(f) + ")"; }
|
||||
static std::string to_string(bool const b) { return b ? "true" : "false"; }
|
||||
static std::string to_string(int const i) { return std::to_string(i); }
|
||||
static std::string to_string(float const f) { return "float(" + std::to_string(f) + ")"; }
|
||||
|
||||
static void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
|
||||
GLuint shaderId, CString const& sourceCode) noexcept;
|
||||
static void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept;
|
||||
|
||||
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source,
|
||||
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context, char* source,
|
||||
size_t len) noexcept;
|
||||
static void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source,
|
||||
static void process_OVR_multiview2(OpenGLContext const& context, int32_t eyeCount, char* source,
|
||||
size_t len) noexcept;
|
||||
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
|
||||
static std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept;
|
||||
@@ -72,20 +77,15 @@ static std::array<std::string_view, 3> splitShaderSource(std::string_view source
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
struct ProgramData {
|
||||
GLuint program{};
|
||||
shaders_t shaders{};
|
||||
};
|
||||
|
||||
~OpenGLProgramToken() override;
|
||||
|
||||
OpenGLProgramToken(ShaderCompilerService& compiler, utils::CString const& name) noexcept
|
||||
: compiler(compiler), name(name) {
|
||||
OpenGLProgramToken(ShaderCompilerService& compiler, CString const& name) noexcept
|
||||
: compiler(compiler), name(name), handle(compiler.issueCallbackHandle()) {
|
||||
}
|
||||
|
||||
ShaderCompilerService& compiler;
|
||||
utils::CString const& name;
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
|
||||
CString const& name;
|
||||
FixedCapacityVector<std::pair<CString, uint8_t>> attributes;
|
||||
shaders_source_t shaderSourceCode;
|
||||
void* user = nullptr;
|
||||
struct {
|
||||
@@ -93,48 +93,34 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
GLuint program = 0;
|
||||
} gl; // 12 bytes
|
||||
|
||||
|
||||
// Sets the programData, typically from the compiler thread, and signal the main thread.
|
||||
// This is similar to std::promise::set_value.
|
||||
void set(ProgramData const& data) noexcept {
|
||||
// Used in THREAD_POOL mode. The job from ThreadPool should call this when the token is ready to
|
||||
// be used. It sends a signal to the engine thread being blocked upon the `wait` call, so that
|
||||
// the engine thread resumes its processing with the token.
|
||||
void signal() noexcept {
|
||||
std::unique_lock const l(lock);
|
||||
programData = data;
|
||||
signaled = true;
|
||||
cond.notify_one();
|
||||
}
|
||||
|
||||
// Get the programBinary, wait if necessary.
|
||||
// This is similar to std::future::get
|
||||
ProgramData const& get() const noexcept {
|
||||
std::unique_lock l(lock);
|
||||
cond.wait(l, [this](){ return signaled; });
|
||||
return programData;
|
||||
}
|
||||
|
||||
// Used in THREAD_POOL mode. The engine thread should call this before accessing token's fields.
|
||||
// This may block until the token is ready to be used.
|
||||
void wait() const noexcept {
|
||||
std::unique_lock l(lock);
|
||||
cond.wait(l, [this](){ return signaled; });
|
||||
}
|
||||
|
||||
// Checks if the programBinary is ready.
|
||||
// This is similar to std::future::wait_for(0s)
|
||||
bool isReady() const noexcept {
|
||||
std::unique_lock l(lock);
|
||||
using namespace std::chrono_literals;
|
||||
return cond.wait_for(l, 0s, [this](){ return signaled; });
|
||||
cond.wait(l, [this] { return signaled; });
|
||||
}
|
||||
|
||||
CallbackManager::Handle handle{};
|
||||
BlobCacheKey key;
|
||||
mutable utils::Mutex lock;
|
||||
mutable utils::Condition cond;
|
||||
ProgramData programData;
|
||||
bool signaled = false;
|
||||
|
||||
bool canceled = false; // not part of the signaling
|
||||
// Used for the `THREAD_POOL` mode.
|
||||
mutable Mutex lock;
|
||||
mutable Condition cond;
|
||||
bool signaled = false;
|
||||
};
|
||||
|
||||
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() = default;
|
||||
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() {
|
||||
compiler.submitCallbackHandle(handle);
|
||||
}
|
||||
|
||||
/* static */ void ShaderCompilerService::setUserData(const program_token_t& token,
|
||||
void* user) noexcept {
|
||||
@@ -189,9 +175,9 @@ void ShaderCompilerService::init() noexcept {
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// - on Adreno there is a single compiler object. We can't use a pool > 1
|
||||
// also glProgramBinary blocks if other threads are compiling.
|
||||
// - on Mali shader compilation can be multi-threaded, but program linking happens on
|
||||
// - on Mali shader compilation can be multithreaded, but program linking happens on
|
||||
// a single service thread, so we don't bother using more than one thread either.
|
||||
// - on PowerVR shader compilation and linking can be multi-threaded.
|
||||
// - on PowerVR shader compilation and linking can be multithreaded.
|
||||
// How many threads should we use?
|
||||
// - on macOS (M1 MacBook Pro/Ventura) there is global lock around all GL APIs when using
|
||||
// a shared context, so parallel shader compilation yields no benefit.
|
||||
@@ -220,7 +206,7 @@ void ShaderCompilerService::init() noexcept {
|
||||
|
||||
mShaderCompilerThreadCount = poolSize;
|
||||
mCompilerThreadPool.init(mShaderCompilerThreadCount,
|
||||
[&platform = mDriver.mPlatform, priority]() {
|
||||
[&platform = mDriver.mPlatform, priority] {
|
||||
// give the thread a name
|
||||
JobSystem::setThreadName("CompilerThreadPool");
|
||||
// run at a slightly lower priority than other filament threads
|
||||
@@ -228,7 +214,7 @@ void ShaderCompilerService::init() noexcept {
|
||||
// create a gl context current to this thread
|
||||
platform.createContext(true);
|
||||
},
|
||||
[&platform = mDriver.mPlatform]() {
|
||||
[&platform = mDriver.mPlatform] {
|
||||
// release context and thread state
|
||||
platform.releaseContext();
|
||||
});
|
||||
@@ -249,119 +235,65 @@ void ShaderCompilerService::terminate() noexcept {
|
||||
}
|
||||
|
||||
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
utils::CString const& name, Program&& program) {
|
||||
CString const& name, Program&& program) {
|
||||
auto& gl = mDriver.getContext();
|
||||
|
||||
// Create a token. A callback condition (handle) is internally created upon token creation.
|
||||
auto token = std::make_shared<OpenGLProgramToken>(*this, name);
|
||||
if (UTILS_UNLIKELY(gl.isES2())) {
|
||||
token->attributes = std::move(program.getAttributes());
|
||||
}
|
||||
|
||||
// Try retrieving the cached program blob if available.
|
||||
token->gl.program = mBlobCache.retrieve(&token->key, mDriver.mPlatform, program);
|
||||
if (token->gl.program) {
|
||||
return token;
|
||||
}
|
||||
|
||||
token->handle = mCallbackManager.get();
|
||||
|
||||
// Initiate program compilation.
|
||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||
switch (mMode) {
|
||||
case Mode::THREAD_POOL: {
|
||||
// queue a compile job
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, &gl, program = std::move(program), token]() mutable {
|
||||
// compile the shaders
|
||||
shaders_t shaders{};
|
||||
compileShaders(gl,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
program.isMultiview(),
|
||||
shaders,
|
||||
token->shaderSourceCode);
|
||||
|
||||
// link the program
|
||||
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
|
||||
|
||||
OpenGLProgramToken::ProgramData programData;
|
||||
programData.shaders = shaders;
|
||||
|
||||
// We need to query the link status here to guarantee that the
|
||||
// program is compiled and linked now (we don't want this to be
|
||||
// deferred to later). We don't care about the result at this point.
|
||||
GLint status = GL_FALSE;
|
||||
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
|
||||
programData.program = glProgram;
|
||||
|
||||
// we don't need to check for success here, it'll be done on the
|
||||
// main thread side.
|
||||
token->set(programData);
|
||||
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
// caching must be the last thing we do
|
||||
if (token->key && status == GL_TRUE) {
|
||||
// Attempt to cache. This calls glGetProgramBinary.
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, glProgram);
|
||||
}
|
||||
compileShaders(gl, std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(), program.isMultiview(), token);
|
||||
linkProgram(gl, token);
|
||||
// Now `token->gl.program` must be populated, so we signal the completion
|
||||
// of the linking. We don't need to check the result of the program here
|
||||
// because it'll be done in the engine thread.
|
||||
token->signal();
|
||||
// We try caching the program blob after sending the signal. This allows us
|
||||
// to unblock the engine thread as soon as the token is ready while
|
||||
// performing an expensive caching operation still in the pool.
|
||||
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::SYNCHRONOUS:
|
||||
case Mode::ASYNCHRONOUS: {
|
||||
// this cannot fail because we check compilation status after linking the program
|
||||
// shaders[] is filled with id of shader stages present.
|
||||
compileShaders(gl,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
program.isMultiview(),
|
||||
token->gl.shaders,
|
||||
token->shaderSourceCode);
|
||||
compileShaders(gl, std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(), program.isMultiview(), token);
|
||||
|
||||
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
||||
assert_invariant(mMode != Mode::THREAD_POOL);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// don't attempt to link this program if all shaders are not done compiling
|
||||
GLint status;
|
||||
// Check link completion if link was initiated.
|
||||
if (token->gl.program) {
|
||||
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (auto shader: token->gl.shaders) {
|
||||
if (shader) {
|
||||
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return isLinkCompleted(token);
|
||||
}
|
||||
}
|
||||
|
||||
if (!token->gl.program) {
|
||||
// link the program, this also cannot fail because status is checked later.
|
||||
token->gl.program = linkProgram(mDriver.getContext(),
|
||||
token->gl.shaders, token->attributes);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// wait until the link finishes...
|
||||
// Link hasn't been initiated, then check compile completion.
|
||||
if (!isCompileCompleted(token)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
if (token->key) {
|
||||
// TODO: technically we don't have to cache right now. Is it advantageous to
|
||||
// do this later, maybe depending on CPU usage?
|
||||
// attempt to cache if we don't have a thread pool (otherwise it's done
|
||||
// by the pool).
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
if (!token->gl.program) {
|
||||
linkProgram(mDriver.getContext(), token);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
return false;// Wait until the link finishes.
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
break;
|
||||
@@ -375,7 +307,7 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
return token;
|
||||
}
|
||||
|
||||
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) {
|
||||
GLuint ShaderCompilerService::getProgram(program_token_t& token) {
|
||||
GLuint const program = initialize(token);
|
||||
assert_invariant(token == nullptr);
|
||||
#if !FILAMENT_ENABLE_MATDBG
|
||||
@@ -384,43 +316,29 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
||||
return program;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cancel program compilation. This function is responsible for cleaning up the ongoing
|
||||
* compilation & link process. If the process is already completed by calling `initialize(token)`,
|
||||
* this function is not called.
|
||||
*/
|
||||
/* static */ void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
token->canceled = true;
|
||||
|
||||
bool const isTickOpCanceled = token->compiler.cancelTickOp(token);
|
||||
assert_invariant(token);// This function should be called when the token is still alive.
|
||||
|
||||
if (token->compiler.mMode == Mode::THREAD_POOL) {
|
||||
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||
auto const job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||
if (!job) {
|
||||
// The job is being executed right now. We need to wait for it to finish to avoid a
|
||||
// race.
|
||||
// It's likely that the job was already completed. But it may be still being
|
||||
// executed at this moment. Just try waiting for it to avoid a race.
|
||||
token->wait();
|
||||
} else {
|
||||
// The job has not been executed, but we still need to inform the callback manager in
|
||||
// order for future callbacks to be successfully called.
|
||||
token->compiler.mCallbackManager.put(token->handle);
|
||||
}
|
||||
} else if (isTickOpCanceled) {
|
||||
// Since the tick op was canceled, we need to .put the token here.
|
||||
token->compiler.mCallbackManager.put(token->handle);
|
||||
}
|
||||
|
||||
for (GLuint& shader: token->gl.shaders) {
|
||||
if (shader) {
|
||||
if (token->gl.program) {
|
||||
glDetachShader(token->gl.program, shader);
|
||||
}
|
||||
glDeleteShader(shader);
|
||||
shader = 0;
|
||||
}
|
||||
}
|
||||
if (token->gl.program) {
|
||||
glDeleteProgram(token->gl.program);
|
||||
}
|
||||
|
||||
token.reset();
|
||||
cleanupProgramAndShaders(token);
|
||||
|
||||
// Cleanup the token.
|
||||
token->compiler.cancelTickOp(token);
|
||||
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
|
||||
}
|
||||
|
||||
void ShaderCompilerService::tick() {
|
||||
@@ -430,8 +348,16 @@ void ShaderCompilerService::tick() {
|
||||
}
|
||||
}
|
||||
|
||||
CallbackManager::Handle ShaderCompilerService::issueCallbackHandle() const noexcept {
|
||||
return mCallbackManager.get();
|
||||
}
|
||||
|
||||
void ShaderCompilerService::submitCallbackHandle(CallbackManager::Handle handle) noexcept {
|
||||
mCallbackManager.put(handle);
|
||||
}
|
||||
|
||||
void ShaderCompilerService::notifyWhenAllProgramsAreReady(
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
CallbackHandler* handler, CallbackHandler::Callback const callback, void* user) {
|
||||
if (callback) {
|
||||
mCallbackManager.setCallback(handler, callback, user);
|
||||
}
|
||||
@@ -439,117 +365,137 @@ void ShaderCompilerService::notifyWhenAllProgramsAreReady(
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
/* static */ void ShaderCompilerService::getProgramFromCompilerPool(
|
||||
program_token_t& token) noexcept {
|
||||
OpenGLProgramToken::ProgramData const& programData{ token->get() };
|
||||
if (!token->canceled) {
|
||||
token->gl.shaders = programData.shaders;
|
||||
token->gl.program = programData.program;
|
||||
}
|
||||
}
|
||||
GLuint ShaderCompilerService::initialize(program_token_t& token) {
|
||||
|
||||
GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
if (!token->gl.program) {
|
||||
switch (mMode) {
|
||||
case Mode::THREAD_POOL: {
|
||||
// we need this program right now, remove it from the queue
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
// if we were able to remove it, we execute the job now, otherwise it means
|
||||
// it's being executed right now.
|
||||
job();
|
||||
}
|
||||
|
||||
if (!token->canceled) {
|
||||
token->compiler.cancelTickOp(token);
|
||||
}
|
||||
assert_invariant(token);// This function should be called when the token is still alive.
|
||||
|
||||
// Block until we get the program from the pool. Generally this wouldn't block
|
||||
// because we just compiled the program above, when executing job.
|
||||
ShaderCompilerService::getProgramFromCompilerPool(token);
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::ASYNCHRONOUS: {
|
||||
// we force the program link -- which might stall, either here or below in
|
||||
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
|
||||
token->compiler.cancelTickOp(token);
|
||||
|
||||
token->gl.program =
|
||||
linkProgram(mDriver.getContext(), token->gl.shaders, token->attributes);
|
||||
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
if (token->key) {
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::SYNCHRONOUS: {
|
||||
// if we don't have a program yet, block until we get it.
|
||||
tick();
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::UNDEFINED: {
|
||||
assert_invariant(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// by this point we must have a GL program
|
||||
ensureTokenIsReady(token);
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
GLuint program = 0;
|
||||
// Check status of program linking. If it failed, errors will be logged.
|
||||
bool const linked = checkLinkStatusAndCleanupShaders(token);
|
||||
|
||||
// check status of program linking and shader compilation, logs error and free all resources
|
||||
// in case of error.
|
||||
bool const success = checkProgramStatus(token);
|
||||
|
||||
// Unless we have matdbg, we panic if a program is invalid. Otherwise, we'd get a UB.
|
||||
// The compilation error has been logged to log.e by this point.
|
||||
FILAMENT_CHECK_POSTCONDITION(FILAMENT_ENABLE_MATDBG || success)
|
||||
// We panic if it failed to create the program.
|
||||
FILAMENT_CHECK_POSTCONDITION(linked)
|
||||
<< "OpenGL program " << token->name.c_str_safe() << " failed to link or compile";
|
||||
|
||||
if (UTILS_LIKELY(success)) {
|
||||
program = token->gl.program;
|
||||
// no need to keep the shaders around
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint& shader: token->gl.shaders) {
|
||||
if (shader) {
|
||||
glDetachShader(program, shader);
|
||||
glDeleteShader(shader);
|
||||
shader = 0;
|
||||
}
|
||||
}
|
||||
// The program is successfully created. Try caching the program blob. In the THREAD_POOL mode,
|
||||
// caching is performed in the pool.
|
||||
if (mMode != Mode::THREAD_POOL) {
|
||||
tryCachingProgram(mBlobCache, mDriver.mPlatform, token);
|
||||
}
|
||||
|
||||
// and destroy all temporary init data
|
||||
token = nullptr;
|
||||
GLuint const program = token->gl.program;
|
||||
|
||||
// Cleanup the token.
|
||||
token->compiler.cancelTickOp(token);
|
||||
token = nullptr;// This will submit a callback condition (handle) to the callback manager.
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
void ShaderCompilerService::ensureTokenIsReady(program_token_t const& token) {
|
||||
if (token->gl.program) {
|
||||
return;// It's ready.
|
||||
}
|
||||
|
||||
switch (mMode) {
|
||||
case Mode::THREAD_POOL: {
|
||||
// We need this program right now, make sure the job is finished.
|
||||
if (auto job = mCompilerThreadPool.dequeue(token)) {
|
||||
job();// The job hasn't started yet, so execute it now.
|
||||
}
|
||||
|
||||
// This may block if the job was already taken by a thread ahead of the `dequeue`
|
||||
// above and currently being executed. Otherwise, the job must have already been
|
||||
// completed by this point from either the code above or the other thread.
|
||||
token->wait();
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::ASYNCHRONOUS: {
|
||||
// Technically the shader compilation may not have finished yet. To deal with the case,
|
||||
// ideally, we should wait here until the compilation is finished. However, for now, we
|
||||
// just log warnings here instead of repeatedly checking compile status. If this turns
|
||||
// out to be a real issue later, we would need to consider doing the canonical way.
|
||||
if (!isCompileCompleted(token)) {
|
||||
slog.w << "Shader compilation for OpenGL program " << token->name.c_str_safe()
|
||||
<< " is not completed yet. The following program link may not succeed.";
|
||||
}
|
||||
|
||||
linkProgram(mDriver.getContext(), token);
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::SYNCHRONOUS: {
|
||||
// We must not have called the TickOp yet until now. Call now to have
|
||||
// `token->gl.program` ready to use.
|
||||
tick();
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::UNDEFINED: {
|
||||
assert_invariant(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
|
||||
program_token_t const& token, Job job) noexcept {
|
||||
// insert items in order of priority and at the end of the range
|
||||
auto& ops = mRunAtNextTickOps;
|
||||
auto const pos = std::lower_bound(ops.begin(), ops.end(), priority,
|
||||
[](ContainerType const& lhs, CompilerPriorityQueue const priorityQueue) {
|
||||
return std::get<0>(lhs) < priorityQueue;
|
||||
});
|
||||
ops.emplace(pos, priority, token, std::move(job));
|
||||
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", mRunAtNextTickOps.size());
|
||||
}
|
||||
|
||||
bool ShaderCompilerService::cancelTickOp(program_token_t const& token) noexcept {
|
||||
// We do a linear search here, but this is rare, and we know the list is pretty small.
|
||||
auto& ops = mRunAtNextTickOps;
|
||||
auto const pos = std::find_if(ops.begin(), ops.end(), [&](const auto& item) {
|
||||
return std::get<1>(item) == token;
|
||||
});
|
||||
if (pos != ops.end()) {
|
||||
ops.erase(pos);
|
||||
return true;
|
||||
}
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShaderCompilerService::executeTickOps() noexcept {
|
||||
auto& ops = mRunAtNextTickOps;
|
||||
auto it = ops.begin();
|
||||
while (it != ops.end()) {
|
||||
Job const& job = std::get<2>(*it);
|
||||
bool const remove = job.fn(job);
|
||||
if (remove) {
|
||||
it = ops.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
|
||||
}
|
||||
|
||||
/*
|
||||
* Compile shaders in the ShaderSource. This cannot fail because compilation failures are not
|
||||
* checked until after the program is linked.
|
||||
* This always returns the GL shader IDs or zero a shader stage is not present.
|
||||
*/
|
||||
/* static */ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
bool multiview,
|
||||
shaders_t& outShaders,
|
||||
UTILS_UNUSED_IN_RELEASE shaders_source_t& outShaderSourceCode) noexcept {
|
||||
|
||||
FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
bool multiview, program_token_t const& token) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
|
||||
auto appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
|
||||
auto const appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
|
||||
s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
|
||||
s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value);
|
||||
s += '\n';
|
||||
@@ -558,7 +504,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
|
||||
std::string specializationConstantString;
|
||||
int32_t numViews = 2;
|
||||
for (auto const& sc : specializationConstants) {
|
||||
for (auto const& sc: specializationConstants) {
|
||||
appendSpecConstantString(specializationConstantString, sc);
|
||||
if (sc.id == 8) {
|
||||
// This constant must match
|
||||
@@ -596,7 +542,7 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
if (UTILS_LIKELY(!shadersSource[i].empty())) {
|
||||
Program::ShaderBlob& shader = shadersSource[i];
|
||||
char* shader_src = reinterpret_cast<char*>(shader.data());
|
||||
size_t shader_len = shader.size();
|
||||
size_t const shader_len = shader.size();
|
||||
|
||||
// remove GOOGLE_cpp_style_line_directive
|
||||
process_GOOGLE_cpp_style_line_directive(context, shader_src, shader_len);
|
||||
@@ -619,178 +565,191 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
}
|
||||
|
||||
std::array<std::string_view, 5> sources = {
|
||||
version,
|
||||
prolog,
|
||||
specializationConstantString,
|
||||
packingFunctions,
|
||||
{ body.data(), body.size() - 1 } // null-terminated
|
||||
version, prolog, specializationConstantString, packingFunctions,
|
||||
{ body.data(), body.size() - 1 }// null-terminated
|
||||
};
|
||||
|
||||
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of
|
||||
// zero to glShaderSource(). glShaderSource should work with lengths of zero, but some
|
||||
// drivers instead interpret zero as a sentinel for a null-terminated string.
|
||||
auto partitionPoint = std::stable_partition(
|
||||
sources.begin(), sources.end(), [](std::string_view s) { return !s.empty(); });
|
||||
size_t count = std::distance(sources.begin(), partitionPoint);
|
||||
auto const partitionPoint = std::stable_partition(sources.begin(), sources.end(),
|
||||
[](std::string_view s) { return !s.empty(); });
|
||||
size_t const count = std::distance(sources.begin(), partitionPoint);
|
||||
|
||||
std::array<const char*, 5> shaderStrings;
|
||||
std::array<GLint, 5> lengths;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
shaderStrings[i] = sources[i].data();
|
||||
lengths[i] = sources[i].size();
|
||||
for (size_t j = 0; j < count; j++) {
|
||||
shaderStrings[j] = sources[j].data();
|
||||
lengths[j] = GLint(sources[j].size());
|
||||
}
|
||||
|
||||
GLuint const shaderId = glCreateShader(glShaderType);
|
||||
glShaderSource(shaderId, count, shaderStrings.data(), lengths.data());
|
||||
|
||||
glShaderSource(shaderId, GLsizei(count), shaderStrings.data(), lengths.data());
|
||||
glCompileShader(shaderId);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// for debugging we return the original shader source (without the modifications we
|
||||
// made here), otherwise the line numbers wouldn't match.
|
||||
outShaderSourceCode[i] = { shader_src, shader_len };
|
||||
token->shaderSourceCode[i] = { shader_src, shader_len };
|
||||
#endif
|
||||
|
||||
outShaders[i] = shaderId;
|
||||
token->gl.shaders[i] = shaderId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a program from the given shader IDs and links it. This cannot fail because errors
|
||||
* are checked later. This always returns a valid GL program ID (which doesn't mean the
|
||||
* program itself is valid).
|
||||
*/
|
||||
/* static */ GLuint ShaderCompilerService::linkProgram(OpenGLContext& context,
|
||||
shaders_t const& shaders,
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept {
|
||||
/* static */ bool ShaderCompilerService::isCompileCompleted(program_token_t const& token) noexcept {
|
||||
GLenum param = GL_COMPLETION_STATUS;
|
||||
if (UTILS_UNLIKELY(token->compiler.mMode != Mode::ASYNCHRONOUS)) {
|
||||
param = GL_COMPILE_STATUS;
|
||||
}
|
||||
|
||||
for (auto shader: token->gl.shaders) {
|
||||
if (!shader) {
|
||||
continue;
|
||||
}
|
||||
GLint status;
|
||||
glGetShaderiv(shader, param, &status);
|
||||
if (status == GL_FALSE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ void ShaderCompilerService::checkCompileStatus(program_token_t const& token) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
const GLuint shader = token->gl.shaders[i];
|
||||
if (!shader) {
|
||||
continue;// We're not using this shader stage.
|
||||
}
|
||||
// GL_COMPILE_STATUS may block until the compilation is completed.
|
||||
GLint status;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (UTILS_LIKELY(status == GL_TRUE)) {
|
||||
continue;// Succeeded in compilation.
|
||||
}
|
||||
// Something went wrong. Log the error message.
|
||||
const ShaderStage type = static_cast<ShaderStage>(i);
|
||||
logCompilationError(slog.e, type, token->name.c_str_safe(), shader,
|
||||
token->shaderSourceCode[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void ShaderCompilerService::linkProgram(OpenGLContext const& context,
|
||||
program_token_t const& token) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
|
||||
// Shader compilation should be completed by now. Check the status and log errors on failure.
|
||||
checkCompileStatus(token);
|
||||
|
||||
// Link program
|
||||
GLuint const program = glCreateProgram();
|
||||
for (auto shader : shaders) {
|
||||
for (auto const shader: token->gl.shaders) {
|
||||
if (shader) {
|
||||
glAttachShader(program, shader);
|
||||
}
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(context.isES2())) {
|
||||
for (auto const& [ name, loc ] : attributes) {
|
||||
for (auto const& [name, loc]: token->attributes) {
|
||||
glBindAttribLocation(program, loc, name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
glLinkProgram(program);
|
||||
|
||||
return program;
|
||||
token->gl.program = program;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void ShaderCompilerService::runAtNextTick(CompilerPriorityQueue priority,
|
||||
const program_token_t& token, Job job) noexcept {
|
||||
// insert items in order of priority and at the end of the range
|
||||
auto& ops = mRunAtNextTickOps;
|
||||
auto const pos = std::lower_bound(ops.begin(), ops.end(), priority,
|
||||
[](ContainerType const& lhs, CompilerPriorityQueue priorityQueue) {
|
||||
return std::get<0>(lhs) < priorityQueue;
|
||||
});
|
||||
ops.emplace(pos, priority, token, std::move(job));
|
||||
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", mRunAtNextTickOps.size());
|
||||
}
|
||||
|
||||
bool ShaderCompilerService::cancelTickOp(program_token_t token) noexcept {
|
||||
// We do a linear search here, but this is rare, and we know the list is pretty small.
|
||||
auto& ops = mRunAtNextTickOps;
|
||||
auto pos = std::find_if(ops.begin(), ops.end(), [&](const auto& item) {
|
||||
return std::get<1>(item) == token;
|
||||
});
|
||||
if (pos != ops.end()) {
|
||||
ops.erase(pos);
|
||||
return true;
|
||||
}
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
void ShaderCompilerService::executeTickOps() noexcept {
|
||||
auto& ops = mRunAtNextTickOps;
|
||||
auto it = ops.begin();
|
||||
while (it != ops.end()) {
|
||||
Job const& job = std::get<2>(*it);
|
||||
bool const remove = job.fn(job);
|
||||
if (remove) {
|
||||
it = ops.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
SYSTRACE_CONTEXT();
|
||||
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* Checks a program link status and logs errors and frees resources on failure.
|
||||
* Returns true on success.
|
||||
*/
|
||||
/* static */ bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noexcept {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
|
||||
/* static */ bool ShaderCompilerService::isLinkCompleted(program_token_t const& token) noexcept {
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
GLint status;
|
||||
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
|
||||
if (UTILS_LIKELY(status == GL_TRUE)) {
|
||||
return true;
|
||||
GLenum param = GL_COMPLETION_STATUS;
|
||||
if (UTILS_UNLIKELY(token->compiler.mMode != Mode::ASYNCHRONOUS)) {
|
||||
param = GL_LINK_STATUS;
|
||||
}
|
||||
|
||||
// only if the link fails, we check the compilation status
|
||||
GLint status;
|
||||
glGetProgramiv(token->gl.program, param, &status);
|
||||
return (status == GL_TRUE);
|
||||
}
|
||||
|
||||
/* static */ bool ShaderCompilerService::checkLinkStatusAndCleanupShaders(
|
||||
program_token_t const& token) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
bool linked = true;
|
||||
GLint status;
|
||||
// GL_LINK_STATUS may block until the link is completed.
|
||||
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
|
||||
if (UTILS_UNLIKELY(status != GL_TRUE)) {
|
||||
// Something went wrong. Log the error message.
|
||||
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program);
|
||||
linked = false;
|
||||
}
|
||||
// No need to keep the shaders around regardless of the result of the program linking.
|
||||
UTILS_NOUNROLL
|
||||
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
|
||||
const ShaderStage type = static_cast<ShaderStage>(i);
|
||||
const GLuint shader = token->gl.shaders[i];
|
||||
for (GLuint& shader: token->gl.shaders) {
|
||||
if (shader) {
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (status != GL_TRUE) {
|
||||
logCompilationError(slog.e, type,
|
||||
token->name.c_str_safe(), shader, token->shaderSourceCode[i]);
|
||||
}
|
||||
glDetachShader(token->gl.program, shader);
|
||||
glDeleteShader(shader);
|
||||
token->gl.shaders[i] = 0;
|
||||
shader = 0;
|
||||
}
|
||||
}
|
||||
// log the link error as well
|
||||
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program);
|
||||
glDeleteProgram(token->gl.program);
|
||||
token->gl.program = 0;
|
||||
return false;
|
||||
return linked;
|
||||
}
|
||||
|
||||
/* static */ void ShaderCompilerService::tryCachingProgram(OpenGLBlobCache& cache,
|
||||
OpenGLPlatform& platform, program_token_t const& token) noexcept {
|
||||
if (!token->key || !token->gl.program) {
|
||||
return; // Invalid params
|
||||
}
|
||||
GLint status = GL_FALSE;
|
||||
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
return;// Link failure
|
||||
}
|
||||
|
||||
cache.insert(platform, token->key, token->gl.program);
|
||||
}
|
||||
|
||||
/* static */ void ShaderCompilerService::cleanupProgramAndShaders(
|
||||
program_token_t const& token) noexcept {
|
||||
for (GLuint& shader: token->gl.shaders) {
|
||||
if (!shader) {
|
||||
continue;
|
||||
}
|
||||
if (token->gl.program) {
|
||||
glDetachShader(token->gl.program, shader);
|
||||
}
|
||||
glDeleteShader(shader);
|
||||
shader = 0;
|
||||
}
|
||||
if (token->gl.program) {
|
||||
glDeleteProgram(token->gl.program);
|
||||
token->gl.program = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
UTILS_NOINLINE
|
||||
/* static */ void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
|
||||
GLuint shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
||||
GLuint const shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
||||
|
||||
auto to_string = [](ShaderStage type) -> const char* {
|
||||
switch (type) {
|
||||
case ShaderStage::VERTEX:
|
||||
return "vertex";
|
||||
case ShaderStage::FRAGMENT:
|
||||
return "fragment";
|
||||
case ShaderStage::COMPUTE:
|
||||
return "compute";
|
||||
}
|
||||
};
|
||||
{ // scope for the temporary string storage
|
||||
auto to_string = [](ShaderStage type) -> const char* {
|
||||
switch (type) {
|
||||
case ShaderStage::VERTEX:
|
||||
return "vertex";
|
||||
case ShaderStage::FRAGMENT:
|
||||
return "fragment";
|
||||
case ShaderStage::COMPUTE:
|
||||
return "compute";
|
||||
}
|
||||
return "unknown";
|
||||
};
|
||||
|
||||
{// scope for the temporary string storage
|
||||
GLint length = 0;
|
||||
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
|
||||
|
||||
@@ -837,8 +796,8 @@ UTILS_NOINLINE
|
||||
|
||||
// If usages of the Google-style line directive are present, remove them, as some
|
||||
// drivers don't allow the quotation marks. This source modification happens in-place.
|
||||
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source,
|
||||
size_t len) noexcept {
|
||||
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext const& context,
|
||||
char* source, size_t len) noexcept {
|
||||
if (!context.ext.GOOGLE_cpp_style_line_directive) {
|
||||
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
|
||||
removeGoogleLineDirectives(source, len);// length is unaffected
|
||||
@@ -850,13 +809,13 @@ UTILS_NOINLINE
|
||||
// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine
|
||||
// the number of views, which is assumed as a single digit, for multiview.
|
||||
// This source modification happens in-place.
|
||||
/* static */ void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source,
|
||||
size_t len) noexcept {
|
||||
/* static */ void process_OVR_multiview2(OpenGLContext const& context, int32_t const eyeCount,
|
||||
char* source, size_t const len) noexcept {
|
||||
// We don't use regular expression in favor of performance.
|
||||
if (context.ext.OVR_multiview2) {
|
||||
const std::string_view shader{ source, len };
|
||||
const std::string_view layout = "layout";
|
||||
const std::string_view num_views = "num_views";
|
||||
constexpr std::string_view layout = "layout";
|
||||
constexpr std::string_view num_views = "num_views";
|
||||
size_t found = 0;
|
||||
while (true) {
|
||||
found = shader.find(layout, found);
|
||||
@@ -1011,20 +970,20 @@ mediump vec4 unpackSnorm4x8(highp uint v) {
|
||||
// - extensions
|
||||
// - everything else
|
||||
/* static */ std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept {
|
||||
auto version_start = source.find("#version");
|
||||
auto const version_start = source.find("#version");
|
||||
assert_invariant(version_start != std::string_view::npos);
|
||||
|
||||
auto version_eol = source.find('\n', version_start) + 1;
|
||||
auto const version_eol = source.find('\n', version_start) + 1;
|
||||
assert_invariant(version_eol != std::string_view::npos);
|
||||
|
||||
auto prolog_start = version_eol;
|
||||
auto const prolog_start = version_eol;
|
||||
auto prolog_eol = source.rfind("\n#extension");// last #extension line
|
||||
if (prolog_eol == std::string_view::npos) {
|
||||
prolog_eol = prolog_start;
|
||||
} else {
|
||||
prolog_eol = source.find('\n', prolog_eol + 1) + 1;
|
||||
}
|
||||
auto body_start = prolog_eol;
|
||||
auto const body_start = prolog_eol;
|
||||
|
||||
std::string_view const version = source.substr(version_start, version_eol - version_start);
|
||||
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);
|
||||
|
||||
@@ -24,23 +24,23 @@
|
||||
#include "OpenGLBlobCache.h"
|
||||
|
||||
#include <backend/CallbackHandler.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/CString.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Invocable.h>
|
||||
#include <utils/JobSystem.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class OpenGLDriver;
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
void tick();
|
||||
|
||||
// Destroys a valid token and all associated resources. Used to "cancel" a program compilation.
|
||||
// This function is not called if `initialize(token)` is already invoked.
|
||||
static void terminate(program_token_t& token);
|
||||
|
||||
// stores a user data pointer in the token
|
||||
@@ -92,6 +93,12 @@ public:
|
||||
// retrieves the user data pointer stored in the token
|
||||
static void* getUserData(const program_token_t& token) noexcept;
|
||||
|
||||
// Issue one callback handle.
|
||||
CallbackManager::Handle issueCallbackHandle() const noexcept;
|
||||
|
||||
// Return a callback handle to the callback manager.
|
||||
void submitCallbackHandle(CallbackManager::Handle handle) noexcept;
|
||||
|
||||
// call the callback when all active programs are ready
|
||||
void notifyWhenAllProgramsAreReady(
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
||||
@@ -99,7 +106,7 @@ public:
|
||||
private:
|
||||
struct Job {
|
||||
template<typename FUNC>
|
||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
|
||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {} // NOLINT(*-explicit-constructor)
|
||||
Job(std::function<bool(Job const& job)> fn,
|
||||
CallbackHandler* handler, void* user, CallbackHandler::Callback callback)
|
||||
: fn(std::move(fn)), handler(handler), user(user), callback(callback) {
|
||||
@@ -128,26 +135,49 @@ private:
|
||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||
std::vector<ContainerType> mRunAtNextTickOps;
|
||||
|
||||
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
|
||||
GLuint initialize(program_token_t& token);
|
||||
void ensureTokenIsReady(program_token_t const& token);
|
||||
|
||||
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
|
||||
|
||||
static void compileShaders(
|
||||
OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
bool multiview, shaders_t& outShaders, shaders_source_t& outShaderSourceCode) noexcept;
|
||||
|
||||
static GLuint linkProgram(OpenGLContext& context, shaders_t const& shaders,
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept;
|
||||
|
||||
static bool checkProgramStatus(program_token_t const& token) noexcept;
|
||||
|
||||
void runAtNextTick(CompilerPriorityQueue priority,
|
||||
const program_token_t& token, Job job) noexcept;
|
||||
void runAtNextTick(CompilerPriorityQueue priority, program_token_t const& token,
|
||||
Job job) noexcept;
|
||||
void executeTickOps() noexcept;
|
||||
bool cancelTickOp(program_token_t token) noexcept;
|
||||
// order of insertion is important
|
||||
bool cancelTickOp(program_token_t const& token) noexcept;
|
||||
|
||||
// Compile shaders with the given `shaderSource`. `gl.shaders` is always populated with valid
|
||||
// shader IDs after this method. But this doesn't necessarily mean the shaders are successfully
|
||||
// compiled. Errors can be checked by calling `checkCompileStatus` later.
|
||||
static void compileShaders(OpenGLContext& context, Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const&
|
||||
specializationConstants,
|
||||
bool multiview, program_token_t const& token) noexcept;
|
||||
|
||||
// Check if the shader compilation is completed. You may want to call this when the extension
|
||||
// `KHR_parallel_shader_compile` is enabled.
|
||||
static bool isCompileCompleted(program_token_t const& token) noexcept;
|
||||
|
||||
// Check compilation status of the shaders and log errors on failure.
|
||||
static void checkCompileStatus(program_token_t const& token) noexcept;
|
||||
|
||||
// Create a program by linking the compiled shaders. `gl.program` is always populated with a
|
||||
// valid program ID after this method. But this doesn't necessarily mean the program is
|
||||
// successfully linked. Errors can be checked by calling `checkLinkStatusAndCleanupShaders`
|
||||
// later.
|
||||
static void linkProgram(OpenGLContext const& context, program_token_t const& token) noexcept;
|
||||
|
||||
// Check if the program link is completed. You may want to call this when the extension
|
||||
// `KHR_parallel_shader_compile` is enabled.
|
||||
static bool isLinkCompleted(program_token_t const& token) noexcept;
|
||||
|
||||
// Check link status of the program and log errors on failure. Return the result of the link.
|
||||
// Also cleanup shaders regardless of the result.
|
||||
static bool checkLinkStatusAndCleanupShaders(program_token_t const& token) noexcept;
|
||||
|
||||
// Try caching the program if we haven't done it yet. Cache it only when the program is valid.
|
||||
static void tryCachingProgram(OpenGLBlobCache& cache, OpenGLPlatform& platform,
|
||||
program_token_t const& token) noexcept;
|
||||
|
||||
// Cleanup GL resources.
|
||||
static void cleanupProgramAndShaders(program_token_t const& token) noexcept;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
#define COREVIDEO_SILENCE_GL_DEPRECATION
|
||||
|
||||
#include "CocoaExternalImage.h"
|
||||
#include <utils/Panic.h>
|
||||
#include "../GLUtils.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
static const char *s_vertex = R"SHADER(#version 410 core
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -295,6 +295,8 @@ VulkanCommandBuffer& CommandBufferPool::getRecording() {
|
||||
}
|
||||
|
||||
void CommandBufferPool::gc() {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("CommandBufferPool::gc");
|
||||
ActiveBuffers reclaimed;
|
||||
mSubmitted.forEachSetBit([this,&reclaimed] (size_t index) {
|
||||
auto& buffer = mBuffers[index];
|
||||
@@ -304,6 +306,7 @@ void CommandBufferPool::gc() {
|
||||
}
|
||||
});
|
||||
mSubmitted &= ~reclaimed;
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void CommandBufferPool::update() {
|
||||
@@ -333,7 +336,9 @@ void CommandBufferPool::wait() {
|
||||
mSubmitted.forEachSetBit([this, &count, &fences] (size_t index) {
|
||||
fences[count++] = mBuffers[index]->getVkFence();
|
||||
});
|
||||
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
|
||||
if (count) {
|
||||
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
@@ -152,12 +152,12 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
|
||||
#elif FVK_ENABLED(FVK_DEBUG_SYSTRACE)
|
||||
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
|
||||
#define FVK_SYSTRACE_CONTEXT() SYSTRACE_CONTEXT()
|
||||
#define FVK_SYSTRACE_START(marker) SYSTRACE_NAME_BEGIN(marker)
|
||||
#define FVK_SYSTRACE_END() SYSTRACE_NAME_END()
|
||||
#define FVK_SYSTRACE_SCOPE() SYSTRACE_NAME(__func__)
|
||||
#define FVK_PROFILE_MARKER(marker) FVK_SYSTRACE_SCOPE()
|
||||
#define FVK_SYSTRACE_SCOPE() SYSTRACE_CALL()
|
||||
#define FVK_PROFILE_MARKER(marker) SYSTRACE_CALL()
|
||||
|
||||
#else
|
||||
#define FVK_SYSTRACE_CONTEXT()
|
||||
|
||||
@@ -31,8 +31,8 @@ namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
using DescriptorCount = VulkanDescriptorSetLayout::Count;
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetCache::DescriptorSetLayoutArray;
|
||||
using DescriptorCount = VulkanDescriptorSetCache::DescriptorCount;
|
||||
|
||||
// We create a pool for each layout as defined by the number of descriptors of each type. For
|
||||
// example, a layout of
|
||||
@@ -203,11 +203,10 @@ public:
|
||||
DescriptorInfinitePool(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VkDescriptorSet obtainSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
auto const vklayout = layout->getVkLayout();
|
||||
VkDescriptorSet obtainSet(DescriptorCount const& count, VkDescriptorSetLayout vklayout) {
|
||||
DescriptorPool* sameTypePool = nullptr;
|
||||
for (auto& pool: mPools) {
|
||||
if (!pool->canAllocate(layout->count)) {
|
||||
if (!pool->canAllocate(count)) {
|
||||
continue;
|
||||
}
|
||||
if (auto set = pool->obtainSet(vklayout); set != VK_NULL_HANDLE) {
|
||||
@@ -225,8 +224,7 @@ public:
|
||||
}
|
||||
|
||||
// We need to increase the set of pools by one.
|
||||
mPools.push_back(std::make_unique<DescriptorPool>(mDevice,
|
||||
DescriptorCount::fromLayoutBitmask(layout->bitmask), capacity));
|
||||
mPools.push_back(std::make_unique<DescriptorPool>(mDevice, count, capacity));
|
||||
auto& pool = mPools.back();
|
||||
auto ret = pool->obtainSet(vklayout);
|
||||
assert_invariant(ret != VK_NULL_HANDLE && "failed to obtain a set?");
|
||||
@@ -276,39 +274,36 @@ void VulkanDescriptorSetCache::unbind(uint8_t setIndex) {
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
||||
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask const& setMask) {
|
||||
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask const& useExternalSamplers,
|
||||
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.
|
||||
fvkutils::DescriptorSetMask curMask = setMask;
|
||||
|
||||
auto& updateSets = mStashedSets;
|
||||
bool const pipelineLayoutIsSame = mLastBoundInfo.pipelineLayout == pipelineLayout;
|
||||
|
||||
if (pipelineLayoutIsSame) {
|
||||
auto& lastBoundSets = mLastBoundInfo.boundSets;
|
||||
setMask.forEachSetBit([&](size_t index) {
|
||||
if (!updateSets[index] || updateSets[index] == lastBoundSets[index]) {
|
||||
curMask.unset(index);
|
||||
}
|
||||
});
|
||||
if (curMask.none() &&
|
||||
mLastBoundInfo.setMask == setMask && mLastBoundInfo.boundSets == updateSets) {
|
||||
return;
|
||||
auto const& updateSets = mStashedSets;
|
||||
curMask.forEachSetBit([&](size_t index) {
|
||||
if (!updateSets[index]) {
|
||||
curMask.unset(index);
|
||||
}
|
||||
} else {
|
||||
setMask.forEachSetBit([&](size_t index) {
|
||||
if (!updateSets[index]) {
|
||||
});
|
||||
|
||||
if (mLastBoundInfo.pipelineLayout == pipelineLayout) {
|
||||
auto& lastBoundSets = mLastBoundInfo.boundSets;
|
||||
curMask.forEachSetBit([&](size_t index) {
|
||||
if (updateSets[index] == lastBoundSets[index] && !useExternalSamplers[index]) {
|
||||
curMask.unset(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](size_t index) {
|
||||
curMask.forEachSetBit([&](size_t index) {
|
||||
// This code actually binds the descriptor sets.
|
||||
auto set = updateSets[index];
|
||||
VkCommandBuffer const cmdbuffer = commands->buffer();
|
||||
VkDescriptorSet vkset = useExternalSamplers[index] ? set->getExternalSamplerVkSet() :
|
||||
set->getVkSet();
|
||||
vkCmdBindDescriptorSets(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, index,
|
||||
1, &set->getVkSet(), set->uniqueDynamicUboCount, set->getOffsets()->data());
|
||||
1, &vkset, set->uniqueDynamicUboCount, set->getOffsets()->data());
|
||||
commands->acquire(set);
|
||||
});
|
||||
|
||||
@@ -334,7 +329,7 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
||||
if (set->dynamicUboMask.test(binding)) {
|
||||
type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
}
|
||||
VkWriteDescriptorSet const descriptorWrite = {
|
||||
VkWriteDescriptorSet descriptorWrite = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = set->getVkSet(),
|
||||
.dstBinding = binding,
|
||||
@@ -343,12 +338,17 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
||||
.pBufferInfo = &info,
|
||||
};
|
||||
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
|
||||
|
||||
if (auto externalSamplerSet = set->getExternalSamplerVkSet();
|
||||
externalSamplerSet != VK_NULL_HANDLE) {
|
||||
descriptorWrite.dstSet = externalSamplerSet;
|
||||
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
|
||||
}
|
||||
set->acquire(bufferObject);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
||||
VkSampler sampler) noexcept {
|
||||
void VulkanDescriptorSetCache::updateSamplerImpl(VkDescriptorSet vkset, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept {
|
||||
VkImageSubresourceRange range = texture->getPrimaryViewRange();
|
||||
VkImageViewType const expectedType = texture->getViewType();
|
||||
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
|
||||
@@ -364,16 +364,29 @@ void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescr
|
||||
.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout()),
|
||||
};
|
||||
|
||||
VkWriteDescriptorSet const descriptorWrite = {
|
||||
VkWriteDescriptorSet descriptorWrite = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = set->getVkSet(),
|
||||
.dstSet = vkset,
|
||||
.dstBinding = binding,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
|
||||
.pImageInfo = &info,
|
||||
};
|
||||
vkUpdateDescriptorSets(mDevice, 1, &descriptorWrite, 0, nullptr);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t binding, fvkmemory::resource_ptr<VulkanTexture> texture,
|
||||
VkSampler sampler) noexcept {
|
||||
updateSamplerImpl(set->getVkSet(), binding, texture, sampler);
|
||||
set->acquire(texture);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::updateSamplerForExternalSamplerSet(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture) noexcept {
|
||||
updateSamplerImpl(set->getExternalSamplerVkSet(), binding, texture, VK_NULL_HANDLE);
|
||||
set->acquire(texture);
|
||||
}
|
||||
|
||||
@@ -383,32 +396,32 @@ void VulkanDescriptorSetCache::updateInputAttachment(
|
||||
// TOOD: fill this in.
|
||||
}
|
||||
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> VulkanDescriptorSetCache::createSet(
|
||||
Handle<HwDescriptorSet> handle, fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
auto const vkSet = getVkSet(layout);
|
||||
auto const vkSet = mDescriptorPool->obtainSet(layout->count, layout->getVkLayout());
|
||||
auto const& count = layout->count;
|
||||
auto const vklayout = layout->getVkLayout();
|
||||
auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle,
|
||||
layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
|
||||
[vkSet, count, vklayout, this](VulkanDescriptorSet*) {
|
||||
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts
|
||||
// down).
|
||||
if (mDescriptorPool) {
|
||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||
}
|
||||
});
|
||||
set->setVkSet(vkSet);
|
||||
auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(
|
||||
mResourceManager, handle, layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
|
||||
[vkSet, count, vklayout, this](
|
||||
VulkanDescriptorSet*) { this->manualRecycle(count, vklayout, vkSet); },
|
||||
vkSet);
|
||||
return set;
|
||||
}
|
||||
|
||||
VkDescriptorSet VulkanDescriptorSetCache::getVkSet(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
return mDescriptorPool->obtainSet(layout);
|
||||
VkDescriptorSet VulkanDescriptorSetCache::getVkSet(DescriptorCount const& count,
|
||||
VkDescriptorSetLayout vklayout) {
|
||||
return mDescriptorPool->obtainSet(count, vklayout);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::manualRecyle(VulkanDescriptorSetLayout::Count const& count,
|
||||
void VulkanDescriptorSetCache::manualRecycle(VulkanDescriptorSetLayout::Count const& count,
|
||||
VkDescriptorSetLayout vklayout, VkDescriptorSet vkSet) {
|
||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts
|
||||
// down).
|
||||
if (mDescriptorPool) {
|
||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
|
||||
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
using DescriptorSetLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||
using DescriptorSetArray =
|
||||
std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>, UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
using DescriptorCount = VulkanDescriptorSetLayout::Count;
|
||||
|
||||
VulkanDescriptorSetCache(VkDevice device, fvkmemory::ResourceManager* resourceManager);
|
||||
~VulkanDescriptorSetCache();
|
||||
@@ -56,6 +57,10 @@ public:
|
||||
void updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
|
||||
|
||||
void updateSamplerForExternalSamplerSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture) noexcept;
|
||||
|
||||
|
||||
void updateInputAttachment(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
VulkanAttachment const& attachment) noexcept;
|
||||
|
||||
@@ -65,17 +70,17 @@ public:
|
||||
void unbind(uint8_t setIndex);
|
||||
|
||||
void commit(VulkanCommandBuffer* commands, VkPipelineLayout pipelineLayout,
|
||||
fvkutils::DescriptorSetMask const& useExternalSamplerMask,
|
||||
fvkutils::DescriptorSetMask const& setMask);
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> createSet(Handle<HwDescriptorSet> handle,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
// This method is only meant to be used with external samplers (or internally within this
|
||||
// class).
|
||||
VkDescriptorSet getVkSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
// This method is meant to be used with external samplers
|
||||
VkDescriptorSet getVkSet(DescriptorCount const& count, VkDescriptorSetLayout vklayout);
|
||||
|
||||
// This method is only meant to be used with external samplers.
|
||||
void manualRecyle(VulkanDescriptorSetLayout::Count const& count, VkDescriptorSetLayout vklayout,
|
||||
// This method is meant to be used with external samplers
|
||||
void manualRecycle(VulkanDescriptorSetLayout::Count const& count, VkDescriptorSetLayout vklayout,
|
||||
VkDescriptorSet vkSet);
|
||||
|
||||
DescriptorSetArray const& getBoundSets() const { return mStashedSets; }
|
||||
@@ -83,6 +88,9 @@ public:
|
||||
void gc();
|
||||
|
||||
private:
|
||||
void updateSamplerImpl(VkDescriptorSet set, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
|
||||
|
||||
class DescriptorInfinitePool;
|
||||
|
||||
VkDevice mDevice;
|
||||
|
||||
@@ -127,6 +127,7 @@ void VulkanDescriptorSetLayoutCache::terminate() noexcept {
|
||||
|
||||
VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
|
||||
VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
||||
fvkutils::SamplerBitmask externalSamplers,
|
||||
utils::FixedCapacityVector<VkSampler> immutableSamplers) {
|
||||
LayoutKey key = {
|
||||
.bitmask = bitmasks,
|
||||
@@ -141,7 +142,7 @@ VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
|
||||
bitmasks.dynamicUbo);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, bitmasks.ubo);
|
||||
count += appendSamplerBindings(&toBind[count], bitmasks.sampler, bitmasks.externalSampler,
|
||||
count += appendSamplerBindings(&toBind[count], bitmasks.sampler, externalSamplers,
|
||||
immutableSamplers);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
|
||||
bitmasks.inputAttachment);
|
||||
@@ -160,9 +161,9 @@ VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
|
||||
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> VulkanDescriptorSetLayoutCache::createLayout(
|
||||
Handle<HwDescriptorSetLayout> handle, backend::DescriptorSetLayout&& info) {
|
||||
BitmaskGroup maskGroup = VulkanDescriptorSetLayout::Bitmask::fromLayoutDescription(info);
|
||||
auto layout = fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::make(mResourceManager, handle,
|
||||
info);
|
||||
layout->setVkLayout(getVkLayout(layout->bitmask));
|
||||
std::move(info), getVkLayout(maskGroup, maskGroup.externalSampler));
|
||||
return layout;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
|
||||
// This method is meant to be used with external samplers
|
||||
VkDescriptorSetLayout getVkLayout(VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
||||
fvkutils::SamplerBitmask externalSamplers,
|
||||
utils::FixedCapacityVector<VkSampler> immutableSamplers = {});
|
||||
|
||||
private:
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
#include "vulkan/utils/Definitions.h"
|
||||
#include "vulkan/vulkan_core.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
@@ -198,11 +197,9 @@ Dispatcher VulkanDriver::getDispatcher() const noexcept {
|
||||
}
|
||||
|
||||
VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig) noexcept
|
||||
Platform::DriverConfig const& driverConfig)
|
||||
: mPlatform(platform),
|
||||
mResourceManager(
|
||||
driverConfig.handleArenaSize,
|
||||
driverConfig.disableHandleUseAfterFreeCheck,
|
||||
mResourceManager(driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck,
|
||||
driverConfig.disableHeapHandleTags),
|
||||
mAllocator(createAllocator(mPlatform->getInstance(), mPlatform->getPhysicalDevice(),
|
||||
mPlatform->getDevice())),
|
||||
@@ -221,6 +218,8 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
|
||||
mDescriptorSetLayoutCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mDescriptorSetCache(mPlatform->getDevice(), &mResourceManager),
|
||||
mQueryManager(mPlatform->getDevice()),
|
||||
mExternalImageManager(platform, &mSamplerCache, &mYcbcrConversionCache, &mDescriptorSetCache,
|
||||
&mDescriptorSetLayoutCache),
|
||||
mIsSRGBSwapChainSupported(mPlatform->getCustomization().isSRGBSwapChainSupported),
|
||||
mStereoscopicType(driverConfig.stereoscopicType) {
|
||||
|
||||
@@ -251,7 +250,7 @@ VulkanDriver::~VulkanDriver() noexcept = default;
|
||||
|
||||
UTILS_NOINLINE
|
||||
Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig) noexcept {
|
||||
Platform::DriverConfig const& driverConfig) {
|
||||
#if 0
|
||||
// this is useful for development, but too verbose even for debug builds
|
||||
// For reference on a 64-bits machine in Release mode:
|
||||
@@ -313,7 +312,7 @@ void VulkanDriver::terminate() {
|
||||
|
||||
mCurrentSwapChain = {};
|
||||
mDefaultRenderTarget = {};
|
||||
mBoundPipeline = {};
|
||||
mPipelineState = {};
|
||||
|
||||
mQueryManager.terminate();
|
||||
|
||||
@@ -325,6 +324,10 @@ void VulkanDriver::terminate() {
|
||||
|
||||
mCommands.terminate();
|
||||
|
||||
// Must come before samplerCache, ycbcrConversionCache, descriptorSetCache,
|
||||
// descriptorSetLayoutCache
|
||||
mExternalImageManager.terminate();
|
||||
|
||||
mStagePool.terminate();
|
||||
mPipelineCache.terminate();
|
||||
mFramebufferCache.terminate();
|
||||
@@ -381,6 +384,10 @@ void VulkanDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
int64_t refreshIntervalNs, uint32_t frameId) {
|
||||
FVK_PROFILE_MARKER(PROFILE_NAME_BEGINFRAME);
|
||||
// Do nothing.
|
||||
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
mExternalImageManager.onBeginFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::setFrameScheduledCallback(Handle<HwSwapChain> sch, CallbackHandler* handler,
|
||||
@@ -421,12 +428,17 @@ void VulkanDriver::updateDescriptorSetTexture(
|
||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
||||
|
||||
// TODO: YcbcrConversion?
|
||||
VulkanSamplerCache::Params cacheParams = {
|
||||
.sampler = params,
|
||||
};
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
|
||||
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
||||
if (mExternalImageManager.isExternallySampledTexture(texture)) {
|
||||
mExternalImageManager.bindExternallySampledTexture(set, binding, texture, params);
|
||||
mAppState.hasBoundExternalImages = true;
|
||||
} else {
|
||||
VulkanSamplerCache::Params cacheParams = {
|
||||
.sampler = params,
|
||||
};
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
|
||||
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
||||
mExternalImageManager.clearTextureBinding(set, binding);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::flush(int) {
|
||||
@@ -446,6 +458,7 @@ void VulkanDriver::finish(int dummy) {
|
||||
void VulkanDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
|
||||
PrimitiveType pt) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh);
|
||||
auto ib = resource_ptr<VulkanIndexBuffer>::cast(&mResourceManager, ibh);
|
||||
auto ptr = resource_ptr<VulkanRenderPrimitive>::make(&mResourceManager, rph, pt, vb, ib);
|
||||
@@ -456,12 +469,14 @@ void VulkanDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
if (!rph) {
|
||||
return;
|
||||
}
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto ptr = resource_ptr<VulkanRenderPrimitive>::cast(&mResourceManager, rph);
|
||||
ptr.dec();
|
||||
}
|
||||
|
||||
void VulkanDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount,
|
||||
uint8_t attributeCount, AttributeArray attributes) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto vbi = resource_ptr<VulkanVertexBufferInfo>::make(&mResourceManager, vbih, bufferCount,
|
||||
attributeCount, attributes);
|
||||
vbi.inc();
|
||||
@@ -471,12 +486,14 @@ void VulkanDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
|
||||
if (!vbih) {
|
||||
return;
|
||||
}
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager, vbih);
|
||||
vbi.dec();
|
||||
}
|
||||
|
||||
void VulkanDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount,
|
||||
Handle<HwVertexBufferInfo> vbih) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager, vbih);
|
||||
auto vb = resource_ptr<VulkanVertexBuffer>::make(&mResourceManager, vbh, mContext, mStagePool,
|
||||
vertexCount, vbi);
|
||||
@@ -487,12 +504,14 @@ void VulkanDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
|
||||
if (!vbh) {
|
||||
return;
|
||||
}
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto vb = resource_ptr<VulkanVertexBuffer>::cast(&mResourceManager, vbh);
|
||||
vb.dec();
|
||||
}
|
||||
|
||||
void VulkanDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
|
||||
uint32_t indexCount, BufferUsage usage) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto elementSize = (uint8_t) getElementTypeSize(elementType);
|
||||
auto ib = resource_ptr<VulkanIndexBuffer>::make(&mResourceManager, ibh, mAllocator, mStagePool,
|
||||
elementSize, indexCount);
|
||||
@@ -503,12 +522,14 @@ void VulkanDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
|
||||
if (!ibh) {
|
||||
return;
|
||||
}
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto ib = resource_ptr<VulkanIndexBuffer>::cast(&mResourceManager, ibh);
|
||||
ib.dec();
|
||||
}
|
||||
|
||||
void VulkanDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType, BufferUsage usage) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto bo = resource_ptr<VulkanBufferObject>::make(&mResourceManager, boh, mAllocator, mStagePool,
|
||||
byteCount, bindingType);
|
||||
bo.inc();
|
||||
@@ -518,6 +539,7 @@ void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
|
||||
if (!boh) {
|
||||
return;
|
||||
}
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
|
||||
bo.dec();
|
||||
}
|
||||
@@ -563,41 +585,48 @@ void VulkanDriver::createTextureExternalImage2R(Handle<HwTexture> th, backend::S
|
||||
backend::TextureFormat format, uint32_t width, uint32_t height, backend::TextureUsage usage,
|
||||
Platform::ExternalImageHandleRef externalImage) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto const& metadata = mPlatform->getExternalImageMetadata(externalImage);
|
||||
if (metadata.isProtected) {
|
||||
usage |= backend::TextureUsage::PROTECTED;
|
||||
}
|
||||
|
||||
VkImageUsageFlags vkUsage = metadata.usage;
|
||||
if (any(usage & TextureUsage::BLIT_SRC)) {
|
||||
vkUsage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
|
||||
if (any(usage & (TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE))) {
|
||||
vkUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
auto metadata = mPlatform->extractExternalImageMetadata(externalImage);
|
||||
|
||||
assert_invariant(width == metadata.width);
|
||||
assert_invariant(height == metadata.height);
|
||||
assert_invariant(fvkutils::getVkFormat(format) == metadata.format);
|
||||
|
||||
VkMemoryPropertyFlags const requiredMemoryFlags = any(usage & TextureUsage::UPLOADABLE)
|
||||
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
||||
: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
uint32_t const memoryTypeIndex =
|
||||
mContext.selectMemoryType(metadata.memoryTypeBits, requiredMemoryFlags);
|
||||
FILAMENT_CHECK_POSTCONDITION(memoryTypeIndex != VK_MAX_MEMORY_TYPES)
|
||||
<< "failed to find a valid memory type for external image memory.";
|
||||
// We do not check the format since AHB could return both a known format and an external format.
|
||||
// In which case, we choose one or the other, but this choice is not known to the client.
|
||||
// Therefore the following lines are commented out.
|
||||
// assert_invariant(format == metadata.filamentFormat);
|
||||
// assert_invariant(fvkutils::getVkFormat(format) == metadata.format);
|
||||
|
||||
VkImage vkimg;
|
||||
VkDeviceMemory deviceMemory;
|
||||
std::tie(vkimg, deviceMemory) =
|
||||
mPlatform->createExternalImageData(externalImage, metadata, memoryTypeIndex, vkUsage);
|
||||
auto imgData = mPlatform->createVkImageFromExternal(externalImage);
|
||||
|
||||
assert_invariant(imgData.internal.valid() || imgData.external.valid());
|
||||
|
||||
VkFormat vkformat = metadata.format;
|
||||
VkImage vkimage = VK_NULL_HANDLE;
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
if (imgData.internal.valid()) {
|
||||
metadata.externalFormat = 0;
|
||||
vkimage = imgData.internal.image;
|
||||
memory = imgData.internal.memory;
|
||||
} else { // imgData.external.valid()
|
||||
vkformat = VK_FORMAT_UNDEFINED;
|
||||
vkimage = imgData.external.image;
|
||||
memory = imgData.external.memory;
|
||||
}
|
||||
|
||||
VkSamplerYcbcrConversion const conversion =
|
||||
mExternalImageManager.getVkSamplerYcbcrConversion(metadata);
|
||||
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mContext,
|
||||
mPlatform->getDevice(), mAllocator, &mResourceManager, &mCommands, vkimg, deviceMemory,
|
||||
metadata.format, VK_NULL_HANDLE, metadata.samples, metadata.width, metadata.height,
|
||||
metadata.layerCount, usage, mStagePool);
|
||||
mPlatform->getDevice(), mAllocator, &mResourceManager, &mCommands, vkimage, memory,
|
||||
vkformat, conversion, metadata.samples, metadata.width, metadata.height,
|
||||
metadata.layers, usage, mStagePool);
|
||||
auto& commands = mCommands.get();
|
||||
// Unlike uploaded textures or swapchains, we need to explicit transition this
|
||||
// texture into the read layout.
|
||||
texture->transitionLayout(&commands, texture->getPrimaryViewRange(), VulkanLayout::READ_ONLY);
|
||||
|
||||
if (imgData.external.valid()) {
|
||||
mExternalImageManager.addExternallySampledTexture(texture, externalImage);
|
||||
}
|
||||
|
||||
texture.inc();
|
||||
}
|
||||
@@ -630,6 +659,8 @@ void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
}
|
||||
auto texture = resource_ptr<VulkanTexture>::cast(&mResourceManager, th);
|
||||
texture.dec();
|
||||
|
||||
mExternalImageManager.removeExternallySampledTexture(texture);
|
||||
}
|
||||
|
||||
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
@@ -746,6 +777,7 @@ void VulkanDriver::createFenceR(Handle<HwFence> fh, int) {
|
||||
}
|
||||
|
||||
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
// Running gc() to guard against an edge case where the old swapchains need to have been
|
||||
// destroyed before the new swapchain can be created. Otherwise, we would fail
|
||||
// vkCreateSwapchainKHR with VK_ERROR_NATIVE_WINDOW_IN_USE_KHR.
|
||||
@@ -787,6 +819,7 @@ void VulkanDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {
|
||||
|
||||
void VulkanDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
|
||||
backend::DescriptorSetLayout&& info) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto layout = mDescriptorSetLayoutCache.createLayout(dslh, std::move(info));
|
||||
layout.inc();
|
||||
}
|
||||
@@ -798,6 +831,10 @@ void VulkanDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
|
||||
auto set = mDescriptorSetCache.createSet(dsh, layout);
|
||||
set.inc();
|
||||
|
||||
if (layout->hasExternalSamplers()) {
|
||||
mAppState.hasExternalSamplerLayouts = true;
|
||||
}
|
||||
}
|
||||
|
||||
Handle<HwVertexBufferInfo> VulkanDriver::createVertexBufferInfoS() noexcept {
|
||||
@@ -922,6 +959,10 @@ void VulkanDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh
|
||||
void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
|
||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||
set.dec();
|
||||
|
||||
if (mAppState.hasExternalSamplers() && set->getExternalSamplerVkSet() != VK_NULL_HANDLE) {
|
||||
mExternalImageManager.removeDescriptorSet(set);
|
||||
}
|
||||
}
|
||||
|
||||
Handle<HwStream> VulkanDriver::createStreamNative(void* nativeStream) {
|
||||
@@ -1482,7 +1523,6 @@ void VulkanDriver::endRenderPass(int) {
|
||||
// pipeline barrier between framebuffer writes and shader reads.
|
||||
rt->emitBarriersEndRenderPass(*mCurrentRenderPass.commandBuffer);
|
||||
|
||||
mRenderPassFboInfo = {};
|
||||
mCurrentRenderPass.renderTarget = {};
|
||||
mCurrentRenderPass.renderPass = VK_NULL_HANDLE;
|
||||
|
||||
@@ -1542,10 +1582,10 @@ void VulkanDriver::commit(Handle<HwSwapChain> sch) {
|
||||
|
||||
void VulkanDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
backend::PushConstantVariant value) {
|
||||
assert_invariant(mBoundPipeline.program && "Expect a program when writing to push constants");
|
||||
assert_invariant(mPipelineState.program && "Expect a program when writing to push constants");
|
||||
assert_invariant(mCurrentRenderPass.commandBuffer && "Should be called within a renderpass");
|
||||
mBoundPipeline.program->writePushConstant(mCurrentRenderPass.commandBuffer->buffer(),
|
||||
mBoundPipeline.pipelineLayout, stage, index, value);
|
||||
mPipelineState.program->writePushConstant(mCurrentRenderPass.commandBuffer->buffer(),
|
||||
mPipelineState.pipelineLayout, stage, index, value);
|
||||
}
|
||||
|
||||
void VulkanDriver::insertEventMarker(char const* string) {
|
||||
@@ -1724,16 +1764,69 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
}
|
||||
|
||||
void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
// This resets all of the pipeline states; the most relevant (needing reset) is .bindInDraw.
|
||||
mPipelineState = {};
|
||||
|
||||
auto& setLayouts = pipelineState.pipelineLayout.setLayout;
|
||||
DescriptorSetLayoutHandleList layoutHandles;
|
||||
uint8_t layoutCount = 0;
|
||||
std::transform(setLayouts.begin(), setLayouts.end(), layoutHandles.begin(),
|
||||
[&](auto const& handle) -> resource_ptr<VulkanDescriptorSetLayout> {
|
||||
if (!handle) {
|
||||
return {};
|
||||
}
|
||||
layoutCount++;
|
||||
return resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, handle);
|
||||
});
|
||||
|
||||
constexpr uint8_t descriptorSetMaskTable[4] = {0x1, 0x3, 0x7, 0xF};
|
||||
fvkutils::DescriptorSetMask const descriptorSetMask =
|
||||
fvkutils::DescriptorSetMask(descriptorSetMaskTable[layoutCount]);
|
||||
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
auto const haveExternalSamplers = [&](auto layoutHandle) {
|
||||
if (!layoutHandle) {
|
||||
return false;
|
||||
}
|
||||
return layoutHandle->hasExternalSamplers();
|
||||
};
|
||||
if (std::any_of(layoutHandles.begin(), layoutHandles.end(), haveExternalSamplers)) {
|
||||
BindInDrawBundle bundle = {
|
||||
.pipelineState = pipelineState,
|
||||
.dsLayoutHandles = layoutHandles,
|
||||
.descriptorSetMask = descriptorSetMask,
|
||||
};
|
||||
mPipelineState.bindInDraw = { true, bundle };
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The normal, non-external sampler path
|
||||
using VkDescriptorSetLayoutArray = VulkanPipelineLayoutCache::DescriptorSetLayoutArray;
|
||||
VkDescriptorSetLayoutArray vkLayouts;
|
||||
std::transform(layoutHandles.begin(), layoutHandles.end(), vkLayouts.begin(),
|
||||
[](auto const& layout) -> VkDescriptorSetLayout {
|
||||
if (!layout) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
return layout->getVkLayout();
|
||||
});
|
||||
auto program = resource_ptr<VulkanProgram>::cast(&mResourceManager, pipelineState.program);
|
||||
auto pipelineLayout = mPipelineLayoutCache.getLayout(vkLayouts, program);
|
||||
bindPipelineImpl(pipelineState, pipelineLayout, descriptorSetMask);
|
||||
}
|
||||
|
||||
void VulkanDriver::bindPipelineImpl(PipelineState const& pipelineState,
|
||||
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask descriptorSetMask) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto commands = mCurrentRenderPass.commandBuffer;
|
||||
auto vbi = resource_ptr<VulkanVertexBufferInfo>::cast(&mResourceManager,
|
||||
pipelineState.vertexBufferInfo);
|
||||
|
||||
Handle<HwProgram> programHandle = pipelineState.program;
|
||||
RasterState const& rasterState = pipelineState.rasterState;
|
||||
PolygonOffset const& depthOffset = pipelineState.polygonOffset;
|
||||
|
||||
auto program = resource_ptr<VulkanProgram>::cast(&mResourceManager, programHandle);
|
||||
auto program = resource_ptr<VulkanProgram>::cast(&mResourceManager, pipelineState.program);
|
||||
commands->acquire(program);
|
||||
|
||||
// Update the VK raster state.
|
||||
@@ -1775,28 +1868,11 @@ void VulkanDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
mPipelineCache.bindPrimitiveTopology(topology);
|
||||
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi->getAttributeCount());
|
||||
|
||||
auto& setLayouts = pipelineState.pipelineLayout.setLayout;
|
||||
VulkanDescriptorSetLayout::DescriptorSetLayoutArray layoutList;
|
||||
uint8_t layoutCount = 0;
|
||||
std::transform(setLayouts.begin(), setLayouts.end(), layoutList.begin(),
|
||||
[&](Handle<HwDescriptorSetLayout> handle) -> VkDescriptorSetLayout {
|
||||
if (!handle) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
auto layout =
|
||||
resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, handle);
|
||||
layoutCount++;
|
||||
return layout->getVkLayout();
|
||||
});
|
||||
auto pipelineLayout = mPipelineLayoutCache.getLayout(layoutList, program);
|
||||
|
||||
constexpr uint8_t descriptorSetMaskTable[4] = {0x1, 0x3, 0x7, 0xF};
|
||||
|
||||
mBoundPipeline = {
|
||||
.program = program,
|
||||
.pipelineLayout = pipelineLayout,
|
||||
.descriptorSetMask = fvkutils::DescriptorSetMask(descriptorSetMaskTable[layoutCount]),
|
||||
};
|
||||
// Note that we cannot reinit mPipeline because the .bindInDraw metadata that needs to carry
|
||||
// over even on bind.
|
||||
mPipelineState.program = program;
|
||||
mPipelineState.pipelineLayout = pipelineLayout;
|
||||
mPipelineState.descriptorSetMask = descriptorSetMask;
|
||||
|
||||
mPipelineCache.bindLayout(pipelineLayout);
|
||||
mPipelineCache.bindPipeline(mCurrentRenderPass.commandBuffer);
|
||||
@@ -1841,15 +1917,41 @@ void VulkanDriver::bindDescriptorSet(
|
||||
void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
VkCommandBuffer cmdbuffer = mCurrentRenderPass.commandBuffer->buffer();
|
||||
auto const& [doBindInDraw, bundle] = mPipelineState.bindInDraw;
|
||||
|
||||
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer,
|
||||
mBoundPipeline.pipelineLayout,
|
||||
mBoundPipeline.descriptorSetMask);
|
||||
fvkutils::DescriptorSetMask setsWithExternalSamplers = {};
|
||||
if (doBindInDraw) {
|
||||
auto& layoutHandles = bundle.dsLayoutHandles;
|
||||
setsWithExternalSamplers = mExternalImageManager.prepareBindSets(layoutHandles,
|
||||
mDescriptorSetCache.getBoundSets());
|
||||
|
||||
VulkanDescriptorSetLayout::DescriptorSetLayoutArray vklayouts;
|
||||
for (size_t i = 0; i < layoutHandles.size(); i++) {
|
||||
if (!layoutHandles[i]) {
|
||||
vklayouts[i] = VK_NULL_HANDLE;
|
||||
continue;
|
||||
}
|
||||
if (setsWithExternalSamplers[i]) {
|
||||
vklayouts[i] = layoutHandles[i]->getExternalSamplerVkLayout();
|
||||
} else {
|
||||
vklayouts[i] = layoutHandles[i]->getVkLayout();
|
||||
}
|
||||
}
|
||||
auto program =
|
||||
resource_ptr<VulkanProgram>::cast(&mResourceManager, bundle.pipelineState.program);
|
||||
VkPipelineLayout const pipelineLayout = mPipelineLayoutCache.getLayout(vklayouts, program);
|
||||
if (pipelineLayout != mPipelineState.pipelineLayout) {
|
||||
bindPipelineImpl(bundle.pipelineState, pipelineLayout, bundle.descriptorSetMask);
|
||||
}
|
||||
mPipelineState.bindInDraw.first = false;
|
||||
}
|
||||
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer, mPipelineState.pipelineLayout,
|
||||
setsWithExternalSamplers, mPipelineState.descriptorSetMask);
|
||||
|
||||
// Finally, make the actual draw call. TODO: support subranges
|
||||
const uint32_t firstIndex = indexOffset;
|
||||
const int32_t vertexOffset = 0;
|
||||
const uint32_t firstInstId = 0;
|
||||
uint32_t const firstIndex = indexOffset;
|
||||
constexpr int32_t vertexOffset = 0;
|
||||
constexpr uint32_t firstInstId = 0;
|
||||
|
||||
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "VulkanYcbcrConversionCache.h"
|
||||
#include "vulkan/VulkanDescriptorSetCache.h"
|
||||
#include "vulkan/VulkanDescriptorSetLayoutCache.h"
|
||||
#include "vulkan/VulkanExternalImageManager.h"
|
||||
#include "vulkan/VulkanPipelineLayoutCache.h"
|
||||
#include "vulkan/memory/ResourceManager.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
@@ -54,7 +55,7 @@ constexpr uint8_t MAX_RENDERTARGET_ATTACHMENT_TEXTURES =
|
||||
class VulkanDriver final : public DriverBase {
|
||||
public:
|
||||
static Driver* create(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig) noexcept;
|
||||
Platform::DriverConfig const& driverConfig);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
// Encapsulates the VK_EXT_debug_utils extension. In particular, we use
|
||||
@@ -89,8 +90,8 @@ private:
|
||||
void debugCommandBegin(CommandStream* cmds, bool synchronous,
|
||||
const char* methodName) noexcept override;
|
||||
|
||||
inline VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig) noexcept;
|
||||
VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig);
|
||||
|
||||
~VulkanDriver() noexcept override;
|
||||
|
||||
@@ -119,6 +120,8 @@ private:
|
||||
|
||||
private:
|
||||
void collectGarbage();
|
||||
void bindPipelineImpl(PipelineState const& pipelineState, VkPipelineLayout pipelineLayout,
|
||||
fvkutils::DescriptorSetMask descriptorSetMask);
|
||||
|
||||
VulkanPlatform* mPlatform = nullptr;
|
||||
fvkmemory::ResourceManager mResourceManager;
|
||||
@@ -143,21 +146,39 @@ private:
|
||||
VulkanDescriptorSetLayoutCache mDescriptorSetLayoutCache;
|
||||
VulkanDescriptorSetCache mDescriptorSetCache;
|
||||
VulkanQueryManager mQueryManager;
|
||||
VulkanExternalImageManager mExternalImageManager;
|
||||
|
||||
// This is necessary for us to write to push constants after binding a pipeline.
|
||||
struct {
|
||||
resource_ptr<VulkanProgram> program;
|
||||
VkPipelineLayout pipelineLayout;
|
||||
fvkutils::DescriptorSetMask descriptorSetMask;
|
||||
} mBoundPipeline = {};
|
||||
using DescriptorSetLayoutHandleList = std::array<resource_ptr<VulkanDescriptorSetLayout>,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
struct BindInDrawBundle {
|
||||
PipelineState pipelineState = {};
|
||||
DescriptorSetLayoutHandleList dsLayoutHandles = {};
|
||||
fvkutils::DescriptorSetMask descriptorSetMask = {};
|
||||
resource_ptr<VulkanProgram> program = {};
|
||||
};
|
||||
|
||||
// We need to store information about a render pass to enable better barriers at the end of a
|
||||
// renderpass.
|
||||
struct {
|
||||
using AttachmentArray =
|
||||
fvkutils::StaticVector<VulkanAttachment, MAX_RENDERTARGET_ATTACHMENT_TEXTURES>;
|
||||
AttachmentArray attachments;
|
||||
} mRenderPassFboInfo = {};
|
||||
// For push constant
|
||||
resource_ptr<VulkanProgram> program = {};
|
||||
// For push commiting dynamic ubos in draw()
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
fvkutils::DescriptorSetMask descriptorSetMask = {};
|
||||
|
||||
std::pair<bool, BindInDrawBundle> bindInDraw = {false, {}};
|
||||
} mPipelineState = {};
|
||||
|
||||
struct {
|
||||
// This tracks whether the app has seen external samplers bound to a the descriptor set.
|
||||
// This will force bindPipeline to take a slow path.
|
||||
bool hasExternalSamplerLayouts = false;
|
||||
bool hasBoundExternalImages = false;
|
||||
|
||||
bool hasExternalSamplers() const noexcept {
|
||||
return hasExternalSamplerLayouts && hasBoundExternalImages;
|
||||
}
|
||||
} mAppState;
|
||||
|
||||
bool const mIsSRGBSwapChainSupported;
|
||||
backend::StereoscopicType const mStereoscopicType;
|
||||
|
||||
294
filament/backend/src/vulkan/VulkanExternalImageManager.cpp
Normal file
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "VulkanExternalImageManager.h"
|
||||
|
||||
#include "VulkanDescriptorSetCache.h"
|
||||
#include "VulkanDescriptorSetLayoutCache.h"
|
||||
#include "VulkanSamplerCache.h"
|
||||
#include "VulkanYcbcrConversionCache.h"
|
||||
#include "vulkan/memory/ResourcePointer.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
using Bitmask = fvkutils::UniformBufferBitmask;
|
||||
static_assert(sizeof(Bitmask) * 8 == fvkutils::MAX_DESCRIPTOR_SET_BITMASK_BITS);
|
||||
|
||||
template<typename T>
|
||||
void erasep(std::vector<T>& v, std::function<bool(T const&)> f) {
|
||||
auto newEnd = std::remove_if(v.begin(), v.end(), f);
|
||||
v.erase(newEnd, v.end());
|
||||
}
|
||||
|
||||
using ImageData = VulkanExternalImageManager::VulkanExternalImageManager::ImageData;
|
||||
ImageData& findImage(std::vector<ImageData>& images,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture) {
|
||||
auto itr = std::find_if(images.begin(), images.end(), [&](ImageData const& data) {
|
||||
return data.image == texture;
|
||||
});
|
||||
assert_invariant(itr != images.end());
|
||||
return *itr;
|
||||
}
|
||||
|
||||
void copySet(VkDevice device, VkDescriptorSet srcSet, VkDescriptorSet dstSet, Bitmask bindings) {
|
||||
// TODO: fix the size for better memory management
|
||||
std::vector<VkCopyDescriptorSet> copies;
|
||||
bindings.forEachSetBit([&](size_t index) {
|
||||
copies.push_back({
|
||||
.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
|
||||
.srcSet = srcSet,
|
||||
.srcBinding = (uint32_t) index,
|
||||
.dstSet = dstSet,
|
||||
.dstBinding = (uint32_t) index,
|
||||
.descriptorCount = 1,
|
||||
});
|
||||
});
|
||||
vkUpdateDescriptorSets(device, 0, nullptr, copies.size(), copies.data());
|
||||
}
|
||||
|
||||
Bitmask foldBitsInHalf(Bitmask bitset) {
|
||||
Bitmask outBitset;
|
||||
bitset.forEachSetBit([&](size_t index) {
|
||||
constexpr size_t BITMASK_LOWER_BITS_LEN = sizeof(outBitset) * 4;
|
||||
outBitset.set(index % BITMASK_LOWER_BITS_LEN);
|
||||
});
|
||||
return outBitset;
|
||||
}
|
||||
|
||||
}// namespace
|
||||
|
||||
VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform,
|
||||
VulkanSamplerCache* samplerCache, VulkanYcbcrConversionCache* ycbcrConversionCache,
|
||||
VulkanDescriptorSetCache* setCache, VulkanDescriptorSetLayoutCache* layoutCache)
|
||||
: mPlatform(platform),
|
||||
mSamplerCache(samplerCache),
|
||||
mYcbcrConversionCache(ycbcrConversionCache),
|
||||
mDescriptorSetCache(setCache),
|
||||
mDescriptorSetLayoutCache(layoutCache) {
|
||||
}
|
||||
|
||||
VulkanExternalImageManager::~VulkanExternalImageManager() = default;
|
||||
|
||||
void VulkanExternalImageManager::terminate() {
|
||||
mSetBindings.clear();
|
||||
mImages.clear();
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::onBeginFrame() {
|
||||
std::for_each(mImages.begin(), mImages.end(), [](ImageData& image) {
|
||||
image.hasBeenValidated = false;
|
||||
});
|
||||
std::for_each(mSetBindings.begin(), mSetBindings.end(), [](SetBindingInfo& info) {
|
||||
info.bound = false;
|
||||
});
|
||||
}
|
||||
|
||||
fvkutils::DescriptorSetMask VulkanExternalImageManager::prepareBindSets(LayoutArray const& layouts,
|
||||
SetArray const& sets) {
|
||||
fvkutils::DescriptorSetMask shouldUseExternalSampler{};
|
||||
for (uint8_t i = 0; i < sets.size(); i++) {
|
||||
auto set = sets[i];
|
||||
auto layout = layouts[i];
|
||||
if (!set || !layout) {
|
||||
continue;
|
||||
}
|
||||
if (hasExternalSampler(set)) {
|
||||
updateSetAndLayout(set, layout);
|
||||
shouldUseExternalSampler.set(i);
|
||||
}
|
||||
}
|
||||
return shouldUseExternalSampler;
|
||||
}
|
||||
|
||||
bool VulkanExternalImageManager::hasExternalSampler(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set) {
|
||||
auto itr = std::find_if(mSetBindings.begin(), mSetBindings.end(),
|
||||
[&](SetBindingInfo const& info) { return info.set == set; });
|
||||
return itr != mSetBindings.end();
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::updateSetAndLayout(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
utils::FixedCapacityVector<
|
||||
std::tuple<uint8_t, VkSampler, fvkmemory::resource_ptr<VulkanTexture>>>
|
||||
samplerAndBindings;
|
||||
samplerAndBindings.reserve(MAX_SAMPLER_COUNT);
|
||||
|
||||
fvkutils::SamplerBitmask actualExternalSamplers;
|
||||
for (auto& bindingInfo : mSetBindings) {
|
||||
if (bindingInfo.set != set || bindingInfo.bound) {
|
||||
continue;
|
||||
}
|
||||
auto& imageData = findImage(mImages, bindingInfo.image);
|
||||
updateImage(&imageData);
|
||||
|
||||
auto samplerParams = bindingInfo.samplerParams;
|
||||
// according to spec, these must match chromaFilter
|
||||
// https://registry.khronos.org/vulkan/specs/latest/man/html/VkSamplerCreateInfo.html#VUID-VkSamplerCreateInfo-minFilter-01645
|
||||
samplerParams.filterMag = SamplerMagFilter::NEAREST;
|
||||
samplerParams.filterMin = SamplerMinFilter::NEAREST;
|
||||
|
||||
auto sampler = mSamplerCache->getSampler({
|
||||
.sampler = samplerParams,
|
||||
.conversion = imageData.conversion,
|
||||
});
|
||||
actualExternalSamplers.set(bindingInfo.binding);
|
||||
samplerAndBindings.push_back({ bindingInfo.binding, sampler, bindingInfo.image });
|
||||
bindingInfo.bound = true;
|
||||
}
|
||||
|
||||
if (samplerAndBindings.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by binding number
|
||||
std::sort(samplerAndBindings.begin(), samplerAndBindings.end(), [](auto const& a, auto const& b) {
|
||||
return std::get<0>(a) < std::get<0>(b);
|
||||
});
|
||||
|
||||
utils::FixedCapacityVector<VkSampler> outSamplers;
|
||||
outSamplers.reserve(MAX_SAMPLER_COUNT);
|
||||
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
|
||||
[&](auto const& b) { outSamplers.push_back(std::get<1>(b)); });
|
||||
|
||||
VkDescriptorSetLayout const oldLayout = layout->getExternalSamplerVkLayout();
|
||||
VkDescriptorSetLayout const newLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
|
||||
actualExternalSamplers, outSamplers);
|
||||
|
||||
// Need to copy the set
|
||||
VkDescriptorSet const oldSet = set->getExternalSamplerVkSet();
|
||||
if (oldLayout != newLayout || oldSet == VK_NULL_HANDLE) {
|
||||
// Build a new descriptor set from the new layout
|
||||
VkDescriptorSet const newSet = mDescriptorSetCache->getVkSet(layout->count, newLayout);
|
||||
auto const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
||||
auto const samplers = layout->bitmask.sampler & (~actualExternalSamplers);
|
||||
|
||||
// Each bitmask denotes a binding index, and separated into two stages - vertex and buffer
|
||||
// We fold the two stages into just the lower half of the bits to denote a combined set of
|
||||
// bindings.
|
||||
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
|
||||
VkDescriptorSet const srcSet = oldSet != VK_NULL_HANDLE ? oldSet : set->getVkSet();
|
||||
copySet(mPlatform->getDevice(), srcSet, newSet, copyBindings);
|
||||
|
||||
set->setExternalSamplerVkSet(newSet, [&](VulkanDescriptorSet*) {
|
||||
mDescriptorSetCache->manualRecycle(layout->count, newLayout, newSet);
|
||||
});
|
||||
if (oldLayout != newLayout) {
|
||||
layout->setExternalSamplerVkLayout(newLayout);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the external samplers in the set
|
||||
for (auto& [binding, sampler, image]: samplerAndBindings) {
|
||||
mDescriptorSetCache->updateSamplerForExternalSamplerSet(set, binding, image);
|
||||
}
|
||||
}
|
||||
|
||||
VkSamplerYcbcrConversion VulkanExternalImageManager::getVkSamplerYcbcrConversion(
|
||||
VulkanPlatform::ExternalImageMetadata const& metadata) {
|
||||
// This external image does not require external sampler (YUV conversion).
|
||||
if (metadata.externalFormat == 0 && !fvkutils::isVKYcbcrConversionFormat(metadata.format)) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
VulkanYcbcrConversionCache::Params ycbcrParams = {
|
||||
.conversion = {
|
||||
.ycbcrModel = fvkutils::getYcbcrModelConversionFilament(metadata.ycbcrModel),
|
||||
.r = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.r, 0),
|
||||
.g = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.g, 1),
|
||||
.b = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.b, 2),
|
||||
.a = fvkutils::getSwizzleFilament(metadata.ycbcrConversionComponents.a, 3),
|
||||
.ycbcrRange = fvkutils::getYcbcrRangeFilament(metadata.ycbcrRange),
|
||||
.xChromaOffset = fvkutils::getChromaLocationFilament(metadata.xChromaOffset),
|
||||
.yChromaOffset = fvkutils::getChromaLocationFilament(metadata.yChromaOffset),
|
||||
|
||||
// Unclear where to get the chromaFilter, we just assume it's nearest.
|
||||
.chromaFilter = SamplerMagFilter::NEAREST,
|
||||
},
|
||||
.format = metadata.format,
|
||||
.externalFormat = metadata.externalFormat,
|
||||
};
|
||||
return mYcbcrConversionCache->getConversion(ycbcrParams);
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::updateImage(ImageData* image) {
|
||||
if (image->hasBeenValidated) {
|
||||
return;
|
||||
}
|
||||
image->hasBeenValidated = true;
|
||||
|
||||
auto metadata = mPlatform->extractExternalImageMetadata(image->platformHandle);
|
||||
auto vkYcbcr = getVkSamplerYcbcrConversion(metadata);
|
||||
if (vkYcbcr == image->conversion) {
|
||||
return;
|
||||
}
|
||||
|
||||
image->image->setYcbcrConversion(vkYcbcr);
|
||||
image->conversion = vkYcbcr;
|
||||
return;
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::removeDescriptorSet(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> inSet) {
|
||||
erasep<SetBindingInfo>(mSetBindings,
|
||||
[&](auto const& bindingInfo) { return (bindingInfo.set == inSet); });
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::bindExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t bindingPoint,
|
||||
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
|
||||
// Should we do duplicate validation here?
|
||||
auto& imageData = findImage(mImages, image);
|
||||
mSetBindings.push_back({ bindingPoint, imageData.image, set, samplerParams });
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::addExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
Platform::ExternalImageHandleRef platformHandleRef) {
|
||||
mImages.push_back({ image, platformHandleRef, false });
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::removeExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanTexture> image) {
|
||||
erasep<SetBindingInfo>(mSetBindings,
|
||||
[&](auto const& bindingInfo) { return (bindingInfo.image == image); });
|
||||
erasep<ImageData>(mImages, [&](auto const& imageData) {
|
||||
return imageData.image == image;
|
||||
});
|
||||
}
|
||||
|
||||
bool VulkanExternalImageManager::isExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanTexture> image) const {
|
||||
return std::find_if(mImages.begin(), mImages.end(), [&](auto const& imageData) {
|
||||
return imageData.image == image;
|
||||
}) != mImages.end();
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::clearTextureBinding(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t bindingPoint) {
|
||||
erasep<SetBindingInfo>(mSetBindings, [&](auto const& bindingInfo) {
|
||||
return (bindingInfo.set == set && bindingInfo.binding == bindingPoint);
|
||||
});
|
||||
}
|
||||
|
||||
} // namesapce filament::backend
|
||||
121
filament/backend/src/vulkan/VulkanExternalImageManager.h
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_CACHING_VULKANEXTERNALIMAGEMANAGER_H
|
||||
#define TNT_FILAMENT_BACKEND_CACHING_VULKANEXTERNALIMAGEMANAGER_H
|
||||
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class VulkanYcbcrConversionCache;
|
||||
class VulkanSamplerCache;
|
||||
class VulkanDescriptorSetLayoutCache;
|
||||
class VulkanDescriptorSetCache;
|
||||
|
||||
// Manages the logic of external images and their quirks wrt Vulikan.
|
||||
class VulkanExternalImageManager {
|
||||
public:
|
||||
|
||||
VulkanExternalImageManager(
|
||||
VulkanPlatform* platform,
|
||||
VulkanSamplerCache* samplerCache,
|
||||
VulkanYcbcrConversionCache* ycbcrConversionCache,
|
||||
VulkanDescriptorSetCache* setCache,
|
||||
VulkanDescriptorSetLayoutCache* layoutCache);
|
||||
|
||||
~VulkanExternalImageManager();
|
||||
|
||||
void terminate();
|
||||
|
||||
void onBeginFrame();
|
||||
|
||||
using SetArray = std::array<fvkmemory::resource_ptr<VulkanDescriptorSet>,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
using LayoutArray = std::array<fvkmemory::resource_ptr<VulkanDescriptorSetLayout>,
|
||||
VulkanDescriptorSetLayout::UNIQUE_DESCRIPTOR_SET_COUNT>;
|
||||
|
||||
using VkLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||
|
||||
// Returns bitmask to indicate whether or not to use the external sampler version of each
|
||||
// descriptor set.
|
||||
fvkutils::DescriptorSetMask prepareBindSets(LayoutArray const& layouts, SetArray const& sets);
|
||||
|
||||
void removeDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||
|
||||
void bindExternallySampledTexture(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t bindingPoint, fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
SamplerParams samplerParams);
|
||||
|
||||
void clearTextureBinding(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
uint8_t bindingPoint);
|
||||
|
||||
void addExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> external,
|
||||
Platform::ExternalImageHandleRef platformHandleRef);
|
||||
|
||||
void removeExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image);
|
||||
|
||||
bool isExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image) const;
|
||||
|
||||
VkSamplerYcbcrConversion getVkSamplerYcbcrConversion(
|
||||
VulkanPlatform::ExternalImageMetadata const& metadata);
|
||||
|
||||
struct ImageData {
|
||||
fvkmemory::resource_ptr<VulkanTexture> image;
|
||||
Platform::ExternalImageHandle platformHandle;
|
||||
bool hasBeenValidated = false; // indicates whether the image has been validated *this frame*
|
||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
private:
|
||||
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||
|
||||
void updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
void updateImage(ImageData* imageData);
|
||||
|
||||
VulkanPlatform* mPlatform;
|
||||
VulkanSamplerCache* mSamplerCache;
|
||||
VulkanYcbcrConversionCache* mYcbcrConversionCache;
|
||||
VulkanDescriptorSetCache* mDescriptorSetCache;
|
||||
VulkanDescriptorSetLayoutCache* mDescriptorSetLayoutCache;
|
||||
|
||||
using SetAndLayout = std::pair<fvkmemory::resource_ptr<VulkanDescriptorSet>,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout>>;
|
||||
|
||||
struct SetBindingInfo {
|
||||
uint8_t binding = 0;
|
||||
fvkmemory::resource_ptr<VulkanTexture> image;
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set;
|
||||
SamplerParams samplerParams;
|
||||
bool bound = false;
|
||||
};
|
||||
|
||||
// Use vectors instead of hash maps because we only expect small number of entries.
|
||||
std::vector<SetBindingInfo> mSetBindings;
|
||||
std::vector<ImageData> mImages;
|
||||
};
|
||||
|
||||
} // filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_CACHING_VULKANEXTERNALIMAGEMANAGER_H
|
||||
@@ -330,12 +330,20 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
|
||||
mRenderPassCache[config] = {renderPass, mCurrentTime};
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_FBO_CACHE)
|
||||
FVK_LOGD << "Created render pass " << renderPass << " with "
|
||||
<< "samples = " << int(config.samples) << ", "
|
||||
<< "depth = " << (hasDepth ? 1 : 0) << ", "
|
||||
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
FVK_LOGD << "Created render pass " << renderPass << " with ";
|
||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; ++i) {
|
||||
FVK_LOGD << (int) config.colorFormat[i] << " ";
|
||||
}
|
||||
FVK_LOGD << ", "
|
||||
<< "depth = " << config.depthFormat << ", "
|
||||
<< "initialDepthLayout = " << (int) config.initialDepthLayout << ", "
|
||||
<< "samples = " << int(config.samples) << ", "
|
||||
<< "needsResolveMask = " << int(config.needsResolveMask) << ", "
|
||||
<< "usesLazilyAllocatedMemory = " << int(config.usesLazilyAllocatedMemory) << ", "
|
||||
<< "viewCount = " << int(config.viewCount) << ", "
|
||||
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
|
||||
return renderPass;
|
||||
}
|
||||
|
||||
@@ -149,11 +149,18 @@ void VulkanDescriptorSet::acquire(fvkmemory::resource_ptr<VulkanBufferObject> ob
|
||||
mResources.push_back(obj);
|
||||
}
|
||||
|
||||
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout const& layout)
|
||||
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout&& layout,
|
||||
VkDescriptorSetLayout vkLayout)
|
||||
: bitmask(fromBackendLayout(layout)),
|
||||
count(Count::fromLayoutBitmask(bitmask)) {}
|
||||
count(Count::fromLayoutBitmask(bitmask)),
|
||||
mVkLayout(vkLayout) {}
|
||||
|
||||
PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept {
|
||||
VulkanDescriptorSetLayout::Bitmask VulkanDescriptorSetLayout::Bitmask::fromLayoutDescription(
|
||||
DescriptorSetLayout const& layout) {
|
||||
return fromBackendLayout(layout);
|
||||
}
|
||||
|
||||
PushConstantDescription::PushConstantDescription(backend::Program const& program) {
|
||||
mRangeCount = 0;
|
||||
for (auto stage : { ShaderStage::VERTEX, ShaderStage::FRAGMENT, ShaderStage::COMPUTE }) {
|
||||
auto const& constants = program.getPushConstants(stage);
|
||||
|
||||
@@ -71,7 +71,7 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
||||
fvkutils::SamplerBitmask sampler; // 8 bytes
|
||||
fvkutils::InputAttachmentBitmask inputAttachment; // 8 bytes
|
||||
|
||||
// This is a subset of the bitmask.sampler field.
|
||||
// This is a subset of the sampler field.
|
||||
fvkutils::SamplerBitmask externalSampler; // 8 bytes
|
||||
|
||||
bool operator==(Bitmask const& right) const {
|
||||
@@ -79,6 +79,8 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
||||
inputAttachment == right.inputAttachment &&
|
||||
externalSampler == right.externalSampler;
|
||||
}
|
||||
|
||||
static Bitmask fromLayoutDescription(DescriptorSetLayout const& layout);
|
||||
};
|
||||
static_assert(sizeof(Bitmask) == 40);
|
||||
|
||||
@@ -120,16 +122,20 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
||||
}
|
||||
};
|
||||
|
||||
VulkanDescriptorSetLayout(DescriptorSetLayout const& layout);
|
||||
VulkanDescriptorSetLayout(DescriptorSetLayout&& layout, VkDescriptorSetLayout vkLayout);
|
||||
|
||||
// Note that we don't destroy the vklayout. This is done by the layout cache.
|
||||
~VulkanDescriptorSetLayout() = default;
|
||||
|
||||
VkDescriptorSetLayout const& getVkLayout() const noexcept { return mVkLayout; }
|
||||
VkDescriptorSetLayout getVkLayout() const noexcept { return mVkLayout; }
|
||||
|
||||
// It is possible to have the layout switch out due to AHardwarebuffer (external image) format
|
||||
// changes.
|
||||
void setVkLayout(VkDescriptorSetLayout vklayout) noexcept { mVkLayout = vklayout; }
|
||||
VkDescriptorSetLayout getExternalSamplerVkLayout() const noexcept {
|
||||
return mExternalSamplerVkLayout;
|
||||
}
|
||||
|
||||
void setExternalSamplerVkLayout(VkDescriptorSetLayout vklayout) noexcept {
|
||||
mExternalSamplerVkLayout = vklayout;
|
||||
}
|
||||
|
||||
bool hasExternalSamplers() const noexcept { return bitmask.externalSampler.count() > 0; }
|
||||
|
||||
@@ -137,7 +143,11 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
||||
Count const count;
|
||||
|
||||
private:
|
||||
VkDescriptorSetLayout mVkLayout = VK_NULL_HANDLE;
|
||||
// This is the layout without any immutable samplers.
|
||||
VkDescriptorSetLayout const mVkLayout = VK_NULL_HANDLE;
|
||||
|
||||
// This is the layout with immutable samplers, and can be updated.
|
||||
VkDescriptorSetLayout mExternalSamplerVkLayout = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
struct VulkanDescriptorSet : public HwDescriptorSet, fvkmemory::Resource {
|
||||
@@ -149,23 +159,37 @@ public:
|
||||
VulkanDescriptorSet(
|
||||
fvkutils::UniformBufferBitmask const& dynamicUboMask,
|
||||
uint8_t uniqueDynamicUboCount,
|
||||
OnRecycle&& onRecycleFn)
|
||||
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet)
|
||||
: dynamicUboMask(dynamicUboMask),
|
||||
uniqueDynamicUboCount(uniqueDynamicUboCount),
|
||||
mVkSet(vkSet),
|
||||
mOnRecycleFn(std::move(onRecycleFn)) {}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-exception-escape)
|
||||
~VulkanDescriptorSet() {
|
||||
if (mOnRecycleFn) {
|
||||
mOnRecycleFn(this);
|
||||
}
|
||||
if (mOnRecycleExternalSamplerFn) {
|
||||
mOnRecycleExternalSamplerFn(this);
|
||||
}
|
||||
}
|
||||
|
||||
VkDescriptorSet const& getVkSet() const noexcept {
|
||||
VkDescriptorSet getVkSet() const noexcept {
|
||||
return mVkSet;
|
||||
}
|
||||
|
||||
// Note that the only case where you'd set it more than once is with external images/samplers.
|
||||
void setVkSet(VkDescriptorSet vkset) noexcept { mVkSet = vkset; }
|
||||
VkDescriptorSet getExternalSamplerVkSet() const noexcept {
|
||||
return mExternalSamplerVkSet;
|
||||
}
|
||||
|
||||
void setExternalSamplerVkSet(VkDescriptorSet vkset, OnRecycle onRecycle) {
|
||||
mExternalSamplerVkSet = vkset;
|
||||
if (mOnRecycleExternalSamplerFn) {
|
||||
mOnRecycleExternalSamplerFn(this);
|
||||
}
|
||||
mOnRecycleExternalSamplerFn = onRecycle;
|
||||
}
|
||||
|
||||
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
|
||||
mOffsets = std::move(offsets);
|
||||
@@ -182,17 +206,20 @@ public:
|
||||
uint8_t const uniqueDynamicUboCount;
|
||||
|
||||
private:
|
||||
VkDescriptorSet mVkSet = VK_NULL_HANDLE;
|
||||
VkDescriptorSet const mVkSet;
|
||||
VkDescriptorSet mExternalSamplerVkSet = VK_NULL_HANDLE;
|
||||
|
||||
backend::DescriptorSetOffsetArray mOffsets;
|
||||
std::vector<fvkmemory::resource_ptr<fvkmemory::Resource>> mResources;
|
||||
OnRecycle mOnRecycleFn;
|
||||
OnRecycle mOnRecycleExternalSamplerFn;
|
||||
};
|
||||
|
||||
using PushConstantNameArray = utils::FixedCapacityVector<char const*>;
|
||||
using PushConstantNameByStage = std::array<PushConstantNameArray, Program::SHADER_TYPE_COUNT>;
|
||||
|
||||
struct PushConstantDescription {
|
||||
explicit PushConstantDescription(backend::Program const& program) noexcept;
|
||||
explicit PushConstantDescription(backend::Program const& program);
|
||||
|
||||
VkPushConstantRange const* getVkRanges() const noexcept { return mRanges; }
|
||||
uint32_t getVkRangeCount() const noexcept { return mRangeCount; }
|
||||
|
||||
@@ -66,8 +66,12 @@ void VulkanPipelineCache::bindPipeline(VulkanCommandBuffer* commands) {
|
||||
// If an error occurred, allow higher levels to handle it gracefully.
|
||||
assert_invariant(cacheEntry != nullptr && "Failed to create/find pipeline");
|
||||
|
||||
mBoundPipeline = mPipelineRequirements;
|
||||
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle);
|
||||
|
||||
static PipelineEqual equal;
|
||||
if (!equal(mBoundPipeline, mPipelineRequirements)) {
|
||||
mBoundPipeline = mPipelineRequirements;
|
||||
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle);
|
||||
}
|
||||
}
|
||||
|
||||
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {
|
||||
|
||||
@@ -335,7 +335,7 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
|
||||
mTaskHandler->post(std::move(waitFenceFunc), std::move(cleanPbdFunc));
|
||||
}
|
||||
|
||||
void VulkanReadPixels::runUntilComplete() noexcept {
|
||||
void VulkanReadPixels::runUntilComplete() {
|
||||
if (!mTaskHandler) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public:
|
||||
OnReadCompleteFunction const& readCompleteFunc);
|
||||
|
||||
// This method will block until all of the in-flight requests are complete.
|
||||
void runUntilComplete() noexcept;
|
||||
void runUntilComplete();
|
||||
|
||||
private:
|
||||
VkDevice mDevice = VK_NULL_HANDLE;
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace filament::backend {
|
||||
VulkanSamplerCache::VulkanSamplerCache(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VkSampler VulkanSamplerCache::getSampler(Params params) noexcept {
|
||||
VkSampler VulkanSamplerCache::getSampler(Params params) {
|
||||
auto iter = mCache.find(params);
|
||||
if (UTILS_LIKELY(iter != mCache.end())) {
|
||||
return iter->second;
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
static_assert(sizeof(Params) == 16);
|
||||
|
||||
explicit VulkanSamplerCache(VkDevice device);
|
||||
VkSampler getSampler(Params params) noexcept;
|
||||
VkSampler getSampler(Params params);
|
||||
void terminate() noexcept;
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
static constexpr uint32_t TIME_BEFORE_EVICTION = FVK_MAX_COMMAND_BUFFERS;
|
||||
static constexpr uint32_t TIME_BEFORE_EVICTION = 3;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -39,7 +39,7 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
|
||||
auto stage = iter->second;
|
||||
mFreeStages.erase(iter);
|
||||
stage->lastAccessed = mCurrentFrame;
|
||||
mUsedStages.insert(stage);
|
||||
mUsedStages.push_back(stage);
|
||||
return stage;
|
||||
}
|
||||
// We were not able to find a sufficiently large stage, so create a new one.
|
||||
@@ -51,7 +51,7 @@ VulkanStage const* VulkanStagePool::acquireStage(uint32_t numBytes) {
|
||||
});
|
||||
|
||||
// Create the VkBuffer.
|
||||
mUsedStages.insert(stage);
|
||||
mUsedStages.push_back(stage);
|
||||
VkBufferCreateInfo bufferInfo {
|
||||
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
||||
.size = numBytes,
|
||||
@@ -77,7 +77,7 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
|
||||
if (image->format == vkformat && image->width == width && image->height == height) {
|
||||
mFreeImages.erase(image);
|
||||
image->lastAccessed = mCurrentFrame;
|
||||
mUsedImages.insert(image);
|
||||
mUsedImages.push_back(image);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
|
||||
.lastAccessed = mCurrentFrame,
|
||||
});
|
||||
|
||||
mUsedImages.insert(image);
|
||||
mUsedImages.push_back(image);
|
||||
|
||||
const VkImageCreateInfo imageInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
@@ -161,7 +161,7 @@ void VulkanStagePool::gc() noexcept {
|
||||
stage->lastAccessed = mCurrentFrame;
|
||||
mFreeStages.insert(std::make_pair(stage->capacity, stage));
|
||||
} else {
|
||||
mUsedStages.insert(stage);
|
||||
mUsedStages.push_back(stage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ void VulkanStagePool::gc() noexcept {
|
||||
image->lastAccessed = mCurrentFrame;
|
||||
mFreeImages.insert(image);
|
||||
} else {
|
||||
mUsedImages.insert(image);
|
||||
mUsedImages.push_back(image);
|
||||
}
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -73,10 +74,10 @@ private:
|
||||
std::multimap<uint32_t, VulkanStage const*> mFreeStages;
|
||||
|
||||
// Simple unordered set for stashing a list of in-use stages that can be reclaimed later.
|
||||
std::unordered_set<VulkanStage const*> mUsedStages;
|
||||
std::vector<VulkanStage const*> mUsedStages;
|
||||
|
||||
std::unordered_set<VulkanStageImage const*> mFreeImages;
|
||||
std::unordered_set<VulkanStageImage const*> mUsedImages;
|
||||
std::vector<VulkanStageImage const*> mUsedImages;
|
||||
|
||||
// Store the current "time" (really just a frame count) and LRU eviction parameters.
|
||||
uint64_t mCurrentFrame = 0;
|
||||
|
||||
@@ -223,8 +223,7 @@ VkImageUsageFlags getUsage(VulkanContext const& context, uint8_t samples,
|
||||
VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands,
|
||||
VmaAllocator allocator, VkDevice device, VkImage image, VkDeviceMemory deviceMemory,
|
||||
VkFormat format, VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
|
||||
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat, VkImageUsageFlags usage,
|
||||
bool isProtected)
|
||||
VkSamplerYcbcrConversion ycbcrConversion, VkImageUsageFlags usage, bool isProtected)
|
||||
: mStagePool(stagePool),
|
||||
mCommands(commands),
|
||||
mAllocator(allocator),
|
||||
@@ -234,17 +233,17 @@ VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommand
|
||||
mVkFormat(format),
|
||||
mViewType(viewType),
|
||||
mFullViewRange{ fvkutils::getImageAspect(format), 0, levels, 0, layerCount },
|
||||
mYcbcr{ ycbcrConversion, isExternalFormat },
|
||||
mYcbcr{ ycbcrConversion },
|
||||
mDefaultLayout(getDefaultLayoutImpl(usage)),
|
||||
mUsage(usage),
|
||||
mIsProtected(isProtected) {}
|
||||
|
||||
VulkanTextureState::~VulkanTextureState() {
|
||||
clearCachedImageViews();
|
||||
if (mTextureImageMemory != VK_NULL_HANDLE) {
|
||||
vkDestroyImage(mDevice, mTextureImage, VKALLOC);
|
||||
vkFreeMemory(mDevice, mTextureImageMemory, VKALLOC);
|
||||
}
|
||||
clearCachedImageViews();
|
||||
}
|
||||
|
||||
void VulkanTextureState::clearCachedImageViews() noexcept {
|
||||
@@ -272,7 +271,7 @@ VkImageView VulkanTextureState::getImageView(VkImageSubresourceRange range, VkIm
|
||||
.flags = 0,
|
||||
.image = mTextureImage,
|
||||
.viewType = viewType,
|
||||
.format = mYcbcr.isExternalFormat ? VK_FORMAT_UNDEFINED : mVkFormat,
|
||||
.format = mYcbcr.conversion != VK_NULL_HANDLE ? VK_FORMAT_UNDEFINED : mVkFormat,
|
||||
.components = swizzle,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
@@ -294,7 +293,6 @@ VulkanTexture::VulkanTexture(VulkanContext const& context, VkDevice device, VmaA
|
||||
commands, allocator, device, image, memory, format,
|
||||
fvkutils::getViewType(SamplerType::SAMPLER_2D),
|
||||
/*mipLevels=*/1, getLayerCountFromDepth(depth), conversion,
|
||||
/*isExternalFormat=*/false,
|
||||
getUsage(context, samples, VK_NULL_HANDLE, format, tusage),
|
||||
any(usage & TextureUsage::PROTECTED))) {
|
||||
mPrimaryViewRange = mState->mFullViewRange;
|
||||
@@ -424,8 +422,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
mState = fvkmemory::resource_ptr<VulkanTextureState>::construct(resourceManager, stagePool,
|
||||
commands, allocator, device, textureImage, textureImageMemory, vkFormat,
|
||||
fvkutils::getViewType(target), levels, getLayerCount(target, depth),
|
||||
VK_NULL_HANDLE /* ycbcrConversion */, false /*isExternalFormat*/, imageInfo.usage,
|
||||
isProtected);
|
||||
VK_NULL_HANDLE /* ycbcrConversion */, imageInfo.usage, isProtected);
|
||||
|
||||
// Spec out the "primary" VkImageView that shaders use to sample from the image.
|
||||
mPrimaryViewRange = mState->mFullViewRange;
|
||||
@@ -764,15 +761,13 @@ void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanTexture::setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternalFormat) {
|
||||
void VulkanTexture::setYcbcrConversion(VkSamplerYcbcrConversion conversion) {
|
||||
// Note that this comparison is valid because we only ever create VkSamplerYcbcrConversion from
|
||||
// a cache. So for each set of parameters, there is exactly one conversion (similar to
|
||||
// samplers).
|
||||
VulkanTextureState::Ycbcr ycbcr = {
|
||||
.conversion = conversion,
|
||||
.isExternalFormat = isExternalFormat,
|
||||
};
|
||||
|
||||
if (mState->mYcbcr != ycbcr) {
|
||||
mState->mYcbcr = ycbcr;
|
||||
mState->clearCachedImageViews();
|
||||
|
||||
@@ -40,8 +40,7 @@ struct VulkanTextureState : public fvkmemory::Resource {
|
||||
VulkanTextureState(VulkanStagePool& stagePool, VulkanCommands* commands, VmaAllocator allocator,
|
||||
VkDevice device, VkImage image, VkDeviceMemory deviceMemory, VkFormat format,
|
||||
VkImageViewType viewType, uint8_t levels, uint8_t layerCount,
|
||||
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat,
|
||||
VkImageUsageFlags usage, bool isProtected);
|
||||
VkSamplerYcbcrConversion ycbcrConversion, VkImageUsageFlags usage, bool isProtected);
|
||||
|
||||
~VulkanTextureState();
|
||||
|
||||
@@ -86,10 +85,9 @@ private:
|
||||
// conversion matrix per-frame.
|
||||
struct Ycbcr {
|
||||
VkSamplerYcbcrConversion conversion;
|
||||
bool isExternalFormat;
|
||||
|
||||
bool operator==(Ycbcr const& other) const {
|
||||
return conversion == other.conversion && isExternalFormat == other.isExternalFormat;
|
||||
return conversion == other.conversion;
|
||||
}
|
||||
|
||||
bool operator!=(Ycbcr const& other) const {
|
||||
@@ -209,7 +207,7 @@ struct VulkanTexture : public HwTexture, fvkmemory::Resource {
|
||||
// This is used in the case of external images and external samplers. AHB might update the
|
||||
// conversion per-frame. This implies that we need to invalidate the view cache when that
|
||||
// happens.
|
||||
void setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternal);
|
||||
void setYcbcrConversion(VkSamplerYcbcrConversion conversion);
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
void print() const;
|
||||
|
||||
@@ -33,7 +33,7 @@ VulkanYcbcrConversionCache::VulkanYcbcrConversionCache(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
|
||||
VulkanYcbcrConversionCache::Params params) noexcept {
|
||||
VulkanYcbcrConversionCache::Params params) {
|
||||
auto iter = mCache.find(params);
|
||||
if (UTILS_LIKELY(iter != mCache.end())) {
|
||||
return iter->second;
|
||||
@@ -43,7 +43,7 @@ VkSamplerYcbcrConversion VulkanYcbcrConversionCache::getConversion(
|
||||
TextureSwizzle const swizzleArray[] = { chroma.r, chroma.g, chroma.b, chroma.a };
|
||||
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
|
||||
.format = fvkutils::getVkFormat(params.format),
|
||||
.format = params.format,
|
||||
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
|
||||
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
|
||||
.components = fvkutils::getSwizzleMap(swizzleArray),
|
||||
|
||||
@@ -30,15 +30,14 @@ namespace filament::backend {
|
||||
class VulkanYcbcrConversionCache {
|
||||
public:
|
||||
struct Params {
|
||||
SamplerYcbcrConversion conversion = {};
|
||||
TextureFormat format = {};
|
||||
uint16_t padding = 0;
|
||||
uint64_t externalFormat = 0;
|
||||
SamplerYcbcrConversion conversion = {}; // 4
|
||||
VkFormat format; // 4
|
||||
uint64_t externalFormat = 0; // 8
|
||||
};
|
||||
static_assert(sizeof(Params) == 16);
|
||||
|
||||
explicit VulkanYcbcrConversionCache(VkDevice device);
|
||||
VkSamplerYcbcrConversion getConversion(Params params) noexcept;
|
||||
VkSamplerYcbcrConversion getConversion(Params params);
|
||||
void terminate() noexcept;
|
||||
|
||||
private:
|
||||
@@ -48,7 +47,8 @@ private:
|
||||
bool operator()(Params lhs, Params rhs) const noexcept {
|
||||
SamplerYcbcrConversion::EqualTo equal;
|
||||
return equal(lhs.conversion, rhs.conversion) &&
|
||||
lhs.externalFormat == rhs.externalFormat;
|
||||
lhs.externalFormat == rhs.externalFormat &&
|
||||
lhs.format == rhs.format;
|
||||
}
|
||||
};
|
||||
using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
|
||||
|
||||
@@ -31,6 +31,8 @@ ResourceManager::ResourceManager(size_t arenaSize, bool disableUseAfterFreeCheck
|
||||
: mHandleAllocatorImpl("Handles", arenaSize, disableUseAfterFreeCheck, disablePoolHandleTags) {}
|
||||
|
||||
void ResourceManager::gc() noexcept {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("ResourceManager::gc");
|
||||
auto destroyAll = [this](GcList& list) {
|
||||
for (auto const& [type, id]: list) {
|
||||
destroyWithType(type, id);
|
||||
@@ -49,6 +51,7 @@ void ResourceManager::gc() noexcept {
|
||||
GcList gcs;
|
||||
std::swap(gcs, mGcList);
|
||||
destroyAll(gcs);
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void ResourceManager::terminate() noexcept {
|
||||
|
||||
@@ -132,6 +132,10 @@ public:
|
||||
return id() == other.id() && type() == other.type();
|
||||
}
|
||||
|
||||
inline bool operator!=(resource_ptr<D> const& other) const {
|
||||
return !((*this) == other);
|
||||
}
|
||||
|
||||
inline explicit operator bool() const {
|
||||
return bool(mRef);
|
||||
}
|
||||
|
||||
@@ -212,6 +212,8 @@ ExtensionSet getDeviceExtensions(VkPhysicalDevice device) {
|
||||
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
|
||||
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
|
||||
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
|
||||
// This is needed for external images. See VulkanPlatformAndroid
|
||||
VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME,
|
||||
#endif
|
||||
// MoltenVk is the only non-conformant implementation we're interested in.
|
||||
#if defined(__APPLE__)
|
||||
@@ -993,30 +995,6 @@ VkQueue VulkanPlatform::getProtectedGraphicsQueue() const noexcept {
|
||||
return mImpl->mProtectedGraphicsQueue;
|
||||
}
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadata(
|
||||
ExternalImageHandleRef externalImage) {
|
||||
return getExternalImageMetadataImpl(externalImage, mImpl->mDevice);
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageData(
|
||||
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata,
|
||||
uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
return createExternalImageDataImpl(externalImage, mImpl->mDevice, metadata, memoryTypeIndex,
|
||||
usage);
|
||||
}
|
||||
|
||||
VkSampler VulkanPlatform::createExternalSampler(SamplerYcbcrConversion chroma,
|
||||
SamplerParams sampler, uint32_t internalFormat) {
|
||||
return createExternalSamplerImpl(mImpl->mDevice, chroma, sampler, internalFormat);
|
||||
}
|
||||
|
||||
VkImageView VulkanPlatform::createExternalImageView(SamplerYcbcrConversion chroma,
|
||||
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
|
||||
VkImageViewType viewType, VkComponentMapping swizzle) {
|
||||
return createExternalImageViewImpl(mImpl->mDevice, chroma, internalFormat, image, range,
|
||||
viewType, swizzle);
|
||||
}
|
||||
|
||||
ExtensionSet VulkanPlatform::getSwapchainInstanceExtensions() const {
|
||||
return getSwapchainInstanceExtensionsImpl();
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
#include <backend/platforms/VulkanPlatformAndroid.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "vulkan/VulkanContext.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <private/backend/BackendUtilsAndroid.h>
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include "vulkan/utils/Image.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
@@ -39,6 +39,14 @@ namespace {
|
||||
|
||||
VkFormat transformVkFormat(VkFormat format, bool sRGB) {
|
||||
if (!sRGB) {
|
||||
switch (format) {
|
||||
case VK_FORMAT_R8G8B8A8_SRGB:
|
||||
return VK_FORMAT_R8G8B8A8_UNORM;
|
||||
case VK_FORMAT_R8G8B8_SRGB:
|
||||
return VK_FORMAT_R8G8B8_UNORM;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
@@ -57,7 +65,7 @@ VkFormat transformVkFormat(VkFormat format, bool sRGB) {
|
||||
}
|
||||
|
||||
bool isProtectedFromUsage(uint64_t usage) {
|
||||
return (usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT) ? true : false;
|
||||
return usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
|
||||
}
|
||||
|
||||
std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer_Desc& desc,
|
||||
@@ -120,7 +128,9 @@ std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer
|
||||
usage = 0;
|
||||
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) {
|
||||
usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||
|
||||
// We shouldn't be using external samplers as input attachments
|
||||
// usage |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
|
||||
}
|
||||
if (desc.usage & AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER) {
|
||||
if (isDepthFormat) {
|
||||
@@ -136,87 +146,37 @@ std::pair<VkFormat, VkImageUsageFlags> getVKFormatAndUsage(const AHardwareBuffer
|
||||
return { format, usage };
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData allocateExternalImage(AHardwareBuffer* buffer, VkDevice device,
|
||||
VulkanPlatform::ExternalImageMetadata const& metadata, uint32_t memoryTypeIndex,
|
||||
VkImageUsageFlags usage) {
|
||||
VulkanPlatform::ImageData data;
|
||||
|
||||
// if external format we need to specifiy it in the allocation
|
||||
const bool useExternalFormat = metadata.format == VK_FORMAT_UNDEFINED;
|
||||
|
||||
const VkExternalFormatANDROID externalFormat = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
||||
.pNext = nullptr,
|
||||
// pass down the format (external means we don't have it VK defined)
|
||||
.externalFormat = metadata.externalFormat,
|
||||
std::pair<TextureFormat, TextureUsage> getFilamentFormatAndUsage(const AHardwareBuffer_Desc& desc,
|
||||
bool sRGB) {
|
||||
auto const format = mapToFilamentFormat(desc.format, sRGB);
|
||||
return {
|
||||
format,
|
||||
mapToFilamentUsage(desc.usage, format),
|
||||
};
|
||||
const VkExternalMemoryImageCreateInfo externalCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
|
||||
.pNext = useExternalFormat ? &externalFormat : nullptr,
|
||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
|
||||
};
|
||||
|
||||
VkImageCreateInfo imageInfo{ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
|
||||
imageInfo.pNext = &externalCreateInfo;
|
||||
imageInfo.format = metadata.format;
|
||||
imageInfo.imageType = VK_IMAGE_TYPE_2D;
|
||||
imageInfo.extent = {
|
||||
metadata.width,
|
||||
metadata.height,
|
||||
1u,
|
||||
};
|
||||
imageInfo.mipLevels = 1;
|
||||
imageInfo.arrayLayers = metadata.layers;
|
||||
imageInfo.samples = metadata.samples;
|
||||
imageInfo.usage = usage;
|
||||
|
||||
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &data.first);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateImage failed with error=" << static_cast<int32_t>(result);
|
||||
|
||||
// Allocate the memory
|
||||
VkImportAndroidHardwareBufferInfoANDROID androidHardwareBufferInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
|
||||
.pNext = nullptr,
|
||||
.buffer = buffer,
|
||||
};
|
||||
VkMemoryDedicatedAllocateInfo memoryDedicatedAllocateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
|
||||
.pNext = &androidHardwareBufferInfo,
|
||||
.image = data.first,
|
||||
.buffer = VK_NULL_HANDLE,
|
||||
};
|
||||
VkMemoryAllocateInfo allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.pNext = &memoryDedicatedAllocateInfo,
|
||||
.allocationSize = metadata.allocationSize,
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
};
|
||||
result = vkAllocateMemory(device, &allocInfo, VKALLOC, &data.second);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkAllocateMemory failed with error=" << static_cast<int32_t>(result);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}// namespace
|
||||
|
||||
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() = default;
|
||||
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
if (aHardwareBuffer) {
|
||||
AHardwareBuffer_release(aHardwareBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
||||
AHardwareBuffer const* buffer, bool sRGB) noexcept {
|
||||
if (__builtin_available(android 26, *)) {
|
||||
auto bufferImpl = const_cast<AHardwareBuffer*>(buffer);
|
||||
AHardwareBuffer_acquire(bufferImpl);
|
||||
|
||||
AHardwareBuffer_Desc hardwareBufferDescription = {};
|
||||
AHardwareBuffer_describe(buffer, &hardwareBufferDescription);
|
||||
|
||||
auto* const p = new (std::nothrow) ExternalImageVulkanAndroid;
|
||||
p->aHardwareBuffer = const_cast<AHardwareBuffer*>(buffer);
|
||||
p->sRGB = sRGB;
|
||||
p->height = hardwareBufferDescription.height;
|
||||
p->width = hardwareBufferDescription.width;
|
||||
TextureFormat textureFormat = mapToFilamentFormat(hardwareBufferDescription.format, sRGB);
|
||||
p->format = textureFormat;
|
||||
p->usage = mapToFilamentUsage(hardwareBufferDescription.usage, textureFormat);
|
||||
return Platform::ExternalImageHandle{ p };
|
||||
}
|
||||
|
||||
@@ -225,23 +185,20 @@ Platform::ExternalImageHandle VulkanPlatformAndroid::createExternalImage(
|
||||
|
||||
VulkanPlatformAndroid::ExternalImageDescAndroid VulkanPlatformAndroid::getExternalImageDesc(
|
||||
ExternalImageHandleRef externalImage) const noexcept {
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
|
||||
auto metadata = extractExternalImageMetadata(externalImage);
|
||||
return {
|
||||
.width = fvkExternalImage->width,
|
||||
.height = fvkExternalImage->height,
|
||||
.format = fvkExternalImage->format,
|
||||
.usage = fvkExternalImage->usage,
|
||||
.width = metadata.width,
|
||||
.height = metadata.height,
|
||||
.format = metadata.filamentFormat,
|
||||
.usage = metadata.filamentUsage,
|
||||
};
|
||||
}
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMetadata(
|
||||
ExternalImageHandleRef externalImage) {
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::extractExternalImageMetadata(
|
||||
ExternalImageHandleRef image) const {
|
||||
auto const* fvkExternalImage = static_cast<ExternalImageVulkanAndroid const*>(image.get());
|
||||
|
||||
ExternalImageMetadata metadata;
|
||||
ExternalImageMetadata metadata = {};
|
||||
AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
|
||||
if (__builtin_available(android 26, *)) {
|
||||
AHardwareBuffer_Desc bufferDesc;
|
||||
@@ -249,16 +206,29 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMet
|
||||
metadata.width = bufferDesc.width;
|
||||
metadata.height = bufferDesc.height;
|
||||
metadata.layers = bufferDesc.layers;
|
||||
metadata.isProtected = isProtectedFromUsage(bufferDesc.usage);
|
||||
std::tie(metadata.format, metadata.usage) =
|
||||
getVKFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
|
||||
}
|
||||
std::tie(metadata.filamentFormat, metadata.filamentUsage) =
|
||||
getFilamentFormatAndUsage(bufferDesc, fvkExternalImage->sRGB);
|
||||
|
||||
if (isProtectedFromUsage(bufferDesc.usage)) {
|
||||
metadata.filamentUsage |= TextureUsage::PROTECTED;
|
||||
}
|
||||
|
||||
// TODO: The following seems unnecessary. we should be able to discern directly from the
|
||||
// bufferDesc.
|
||||
if (any(metadata.filamentUsage & TextureUsage::BLIT_SRC)) {
|
||||
metadata.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||||
}
|
||||
|
||||
if (any(metadata.filamentUsage & (TextureUsage::BLIT_DST | TextureUsage::UPLOADABLE))) {
|
||||
metadata.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||||
}
|
||||
}
|
||||
metadata.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
VkAndroidHardwareBufferFormatPropertiesANDROID formatInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID,
|
||||
.pNext = nullptr,
|
||||
};
|
||||
VkAndroidHardwareBufferPropertiesANDROID properties = {
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
|
||||
@@ -268,138 +238,155 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::getExternalImageMet
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkGetAndroidHardwareBufferProperties failed with error="
|
||||
<< static_cast<int32_t>(result);
|
||||
|
||||
VkFormat bufferPropertiesFormat = transformVkFormat(formatInfo.format, fvkExternalImage->sRGB);
|
||||
FILAMENT_CHECK_POSTCONDITION(metadata.format == bufferPropertiesFormat)
|
||||
<< "mismatched image format( " << metadata.format << ") and queried format("
|
||||
<< bufferPropertiesFormat << ") for external image (AHB)";
|
||||
metadata.externalFormat = formatInfo.externalFormat;
|
||||
|
||||
bool const requiresConversion =
|
||||
metadata.format == VK_FORMAT_UNDEFINED ||
|
||||
fvkutils::isVKYcbcrConversionFormat(metadata.format);
|
||||
if (requiresConversion) {
|
||||
metadata.format = VK_FORMAT_UNDEFINED;
|
||||
metadata.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
metadata.externalFormat = formatInfo.externalFormat;
|
||||
} else {
|
||||
metadata.externalFormat = 0;
|
||||
}
|
||||
|
||||
metadata.allocationSize = properties.allocationSize;
|
||||
metadata.memoryTypeBits = properties.memoryTypeBits;
|
||||
|
||||
metadata.ycbcrConversionComponents = formatInfo.samplerYcbcrConversionComponents;
|
||||
metadata.ycbcrModel = formatInfo.suggestedYcbcrModel;
|
||||
metadata.ycbcrRange = formatInfo.suggestedYcbcrRange;
|
||||
metadata.xChromaOffset = formatInfo.suggestedXChromaOffset;
|
||||
metadata.yChromaOffset = formatInfo.suggestedYChromaOffset;
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
VulkanPlatformAndroid::ImageData VulkanPlatformAndroid::createExternalImageData(
|
||||
ExternalImageHandleRef externalImage, const ExternalImageMetadata& metadata,
|
||||
uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
|
||||
ExternalImageHandleRef externalImage) const {
|
||||
auto metadata = extractExternalImageMetadata(externalImage);
|
||||
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
ImageData data = allocateExternalImage(fvkExternalImage->aHardwareBuffer, getDevice(), metadata,
|
||||
memoryTypeIndex, usage);
|
||||
VkResult result = vkBindImageMemory(getDevice(), data.first, data.second, 0);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
|
||||
return data;
|
||||
}
|
||||
AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
|
||||
|
||||
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device, SamplerYcbcrConversion chroma,
|
||||
uint32_t internalFormat, VkImage image, VkImageSubresourceRange range,
|
||||
VkImageViewType viewType, VkComponentMapping swizzle){
|
||||
VkExternalFormatANDROID externalFormat = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
||||
.externalFormat = internalFormat,
|
||||
VkDevice const device = getDevice();
|
||||
VkPhysicalDevice const physicalDevice = getPhysicalDevice();
|
||||
auto buildImage = [&](ExternalImageMetadata const& metadata) {
|
||||
bool const isExternal = metadata.externalFormat != 0;
|
||||
VkExternalFormatANDROID const externalFormat = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
||||
.pNext = nullptr,
|
||||
.externalFormat = metadata.externalFormat,
|
||||
};
|
||||
VkExternalMemoryImageCreateInfo externalCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
|
||||
.pNext = isExternal ? &externalFormat : nullptr,
|
||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
|
||||
};
|
||||
|
||||
VkFormat formats[2] = {};
|
||||
VkImageFormatListCreateInfo imageFormatListInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
|
||||
.pNext = nullptr,
|
||||
.viewFormatCount = 2,
|
||||
.pViewFormats = formats,
|
||||
};
|
||||
|
||||
if (fvkExternalImage->sRGB) {
|
||||
formats[0] = metadata.format;
|
||||
formats[1] = transformVkFormat(metadata.format, /*sRGB=*/false);
|
||||
imageFormatListInfo.pNext = externalCreateInfo.pNext;
|
||||
externalCreateInfo.pNext = &imageFormatListInfo;
|
||||
}
|
||||
|
||||
VkImageCreateInfo const imageInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = &externalCreateInfo,
|
||||
.flags = fvkExternalImage->sRGB ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0u,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
// For non external images, use the same format as the AHB, which isn't in SRGB
|
||||
// Fix VUID-VkMemoryAllocateInfo-pNext-02387
|
||||
.format = transformVkFormat(metadata.format, /*sRGB=*/false),
|
||||
.extent = {
|
||||
metadata.width,
|
||||
metadata.height,
|
||||
1u,
|
||||
},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = metadata.layers,
|
||||
.samples = metadata.samples,
|
||||
.usage = metadata.usage,
|
||||
};
|
||||
VkImage image;
|
||||
VkResult result = vkCreateImage(device, &imageInfo, VKALLOC, &image);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateImage failed with error=" << static_cast<int32_t>(result);
|
||||
return image;
|
||||
};
|
||||
|
||||
TextureSwizzle const swizzleArray[] = {chroma.r, chroma.g, chroma.b, chroma.a};
|
||||
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
|
||||
.pNext = &externalFormat,
|
||||
.format = VK_FORMAT_UNDEFINED,
|
||||
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
|
||||
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
|
||||
.components = fvkutils::getSwizzleMap(swizzleArray),
|
||||
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
|
||||
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
|
||||
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
|
||||
};
|
||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||
VkResult result = vkCreateSamplerYcbcrConversion(device, &conversionInfo,
|
||||
nullptr, &conversion);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create Ycbcr Conversion."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
auto allocMem = [&](VkImage image, ExternalImageMetadata const& metadata) {
|
||||
bool const isExternal = metadata.externalFormat != 0;
|
||||
// Allocate the memory
|
||||
VkImportAndroidHardwareBufferInfoANDROID const androidHardwareBufferInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
|
||||
.buffer = buffer,
|
||||
};
|
||||
VkMemoryDedicatedAllocateInfo const memoryDedicatedAllocateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
|
||||
.pNext = &androidHardwareBufferInfo,
|
||||
.image = image,
|
||||
.buffer = VK_NULL_HANDLE,
|
||||
};
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
|
||||
VkMemoryPropertyFlags requiredMemoryFlags =
|
||||
!isExternal && any(metadata.filamentUsage & TextureUsage::UPLOADABLE)
|
||||
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
||||
: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
|
||||
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
|
||||
.conversion = conversion,
|
||||
if (any(metadata.filamentUsage & TextureUsage::PROTECTED)) {
|
||||
requiredMemoryFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
|
||||
}
|
||||
|
||||
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
|
||||
metadata.memoryTypeBits, requiredMemoryFlags);
|
||||
|
||||
VkMemoryAllocateInfo const allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.pNext = &memoryDedicatedAllocateInfo,
|
||||
.allocationSize = metadata.allocationSize,
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
};
|
||||
VkDeviceMemory memory;
|
||||
VkResult result = vkAllocateMemory(device, &allocInfo, VKALLOC, &memory);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkAllocateMemory failed with error=" << static_cast<int32_t>(result);
|
||||
result = vkBindImageMemory(getDevice(), image, memory, 0);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
|
||||
return memory;
|
||||
};
|
||||
|
||||
VkImageViewCreateInfo viewInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.pNext = &samplerYcbcrConversionInfo,
|
||||
.flags = 0,
|
||||
.image = image,
|
||||
.viewType = viewType,
|
||||
.format = VK_FORMAT_UNDEFINED,
|
||||
.components = swizzle,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
VkImageView imageView;
|
||||
result = vkCreateImageView(device, &viewInfo, VKALLOC, &imageView);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create VkImageView."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
VulkanPlatform::ImageData::Bundle internal = {}, external = {};
|
||||
auto img = buildImage(metadata);
|
||||
auto mem = allocMem(img, metadata);
|
||||
|
||||
return imageView;
|
||||
}
|
||||
// Note that we're always choosing a non-externally sampled format if it exists.
|
||||
if (metadata.externalFormat == 0) {
|
||||
internal = { img, mem };
|
||||
} else {
|
||||
external = { img, mem };
|
||||
}
|
||||
|
||||
VkSampler VulkanPlatform::createExternalSamplerImpl(
|
||||
VkDevice device, SamplerYcbcrConversion chroma, SamplerParams params,
|
||||
uint32_t internalFormat) {
|
||||
VkExternalFormatANDROID externalFormat = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
||||
.externalFormat = internalFormat,
|
||||
return {
|
||||
.internal = internal,
|
||||
.external = external,
|
||||
};
|
||||
|
||||
TextureSwizzle const swizzleArray[] = {chroma.r, chroma.g, chroma.b, chroma.a};
|
||||
VkSamplerYcbcrConversionCreateInfo conversionInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_CREATE_INFO,
|
||||
.pNext = &externalFormat,
|
||||
.format = VK_FORMAT_UNDEFINED,
|
||||
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
|
||||
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
|
||||
.components = fvkutils::getSwizzleMap(swizzleArray),
|
||||
.xChromaOffset = fvkutils::getChromaLocation(chroma.xChromaOffset),
|
||||
.yChromaOffset = fvkutils::getChromaLocation(chroma.yChromaOffset),
|
||||
.chromaFilter = fvkutils::getFilter(chroma.chromaFilter),
|
||||
};
|
||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||
VkResult result = vkCreateSamplerYcbcrConversion(device, &conversionInfo,
|
||||
nullptr, &conversion);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create Ycbcr Conversion."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
|
||||
VkSamplerYcbcrConversionInfo samplerYcbcrConversionInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_YCBCR_CONVERSION_INFO,
|
||||
.pNext = nullptr,
|
||||
.conversion = conversion,
|
||||
};
|
||||
|
||||
VkSamplerCreateInfo samplerInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
|
||||
.pNext = &samplerYcbcrConversionInfo,
|
||||
.magFilter = fvkutils::getFilter(params.filterMag),
|
||||
.minFilter = fvkutils::getFilter(params.filterMin),
|
||||
.mipmapMode = fvkutils::getMipmapMode(params.filterMin),
|
||||
.addressModeU = fvkutils::getWrapMode(params.wrapS),
|
||||
.addressModeV = fvkutils::getWrapMode(params.wrapT),
|
||||
.addressModeW = fvkutils::getWrapMode(params.wrapR),
|
||||
.anisotropyEnable = params.anisotropyLog2 == 0 ? VK_FALSE : VK_TRUE,
|
||||
.maxAnisotropy = (float)(1u << params.anisotropyLog2),
|
||||
.compareEnable = fvkutils::getCompareEnable(params.compareMode),
|
||||
.compareOp = fvkutils::getCompareOp(params.compareFunc),
|
||||
.minLod = 0.0f,
|
||||
.maxLod = fvkutils::getMaxLod(params.filterMin),
|
||||
.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK,
|
||||
.unnormalizedCoordinates = VK_FALSE,
|
||||
};
|
||||
VkSampler sampler;
|
||||
result = vkCreateSampler(device, &samplerInfo, VKALLOC, &sampler);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "Unable to create sampler."
|
||||
<< " error=" << static_cast<int32_t>(result);
|
||||
return sampler;
|
||||
}
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const {
|
||||
@@ -413,7 +400,7 @@ VulkanPlatform::SurfaceBundle VulkanPlatformAndroid::createVkSurfaceKHR(void* na
|
||||
VkSurfaceKHR surface;
|
||||
VkExtent2D extent;
|
||||
|
||||
VkAndroidSurfaceCreateInfoKHR const createInfo{
|
||||
VkAndroidSurfaceCreateInfoKHR const createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR,
|
||||
.window = (ANativeWindow*) nativeWindow,
|
||||
};
|
||||
@@ -427,20 +414,9 @@ VulkanPlatform::SurfaceBundle VulkanPlatformAndroid::createVkSurfaceKHR(void* na
|
||||
// Deprecated platform dependent helper methods
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() { return {}; }
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
||||
return ExternalImageMetadata{};
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
return ImageData{};
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
return SurfaceBundle{};
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -43,30 +43,6 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
|
||||
return ret;
|
||||
}
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
||||
return {};
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
return {};
|
||||
}
|
||||
|
||||
VkSampler VulkanPlatform::createExternalSamplerImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma,
|
||||
SamplerParams sampler,
|
||||
uint32_t internalFormat) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
|
||||
VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
|
||||
@@ -84,30 +84,6 @@ using namespace bluevk;
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExternalImageMetadata VulkanPlatform::getExternalImageMetadataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device) {
|
||||
return {};
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData VulkanPlatform::createExternalImageDataImpl(
|
||||
ExternalImageHandleRef externalImage, VkDevice device,
|
||||
const ExternalImageMetadata& metadata, uint32_t memoryTypeIndex, VkImageUsageFlags usage) {
|
||||
return {};
|
||||
}
|
||||
|
||||
VkSampler VulkanPlatform::createExternalSamplerImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma,
|
||||
SamplerParams sampler,
|
||||
uint32_t internalFormat) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
|
||||
SamplerYcbcrConversion chroma, uint32_t internalFormat, VkImage image,
|
||||
VkImageSubresourceRange range, VkImageViewType viewType, VkComponentMapping swizzle) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||
VulkanPlatform::ExtensionSet const ret = {
|
||||
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
|
||||
@@ -51,6 +51,8 @@ VkFormat getVkFormat(ElementType type, bool normalized, bool integer) {
|
||||
return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
// Non-normalized case
|
||||
switch (type) {
|
||||
// Single Component Types
|
||||
case ElementType::BYTE: return integer ? VK_FORMAT_R8_SINT : VK_FORMAT_R8_SSCALED;
|
||||
|
||||
@@ -184,6 +184,46 @@ bool isVkStencilFormat(VkFormat format) {
|
||||
return (getImageAspect(format) & VK_IMAGE_ASPECT_STENCIL_BIT) != 0;
|
||||
}
|
||||
|
||||
bool isVKYcbcrConversionFormat(VkFormat format) {
|
||||
switch (format) {
|
||||
case VK_FORMAT_G8B8G8R8_422_UNORM:
|
||||
case VK_FORMAT_B8G8R8G8_422_UNORM:
|
||||
case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM:
|
||||
case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM:
|
||||
case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM:
|
||||
case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM:
|
||||
case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM:
|
||||
case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G16B16G16R16_422_UNORM:
|
||||
case VK_FORMAT_B16G16R16G16_422_UNORM:
|
||||
case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM:
|
||||
case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM:
|
||||
case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM:
|
||||
case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM:
|
||||
case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM:
|
||||
case VK_FORMAT_G8_B8R8_2PLANE_444_UNORM:
|
||||
case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16:
|
||||
case VK_FORMAT_G16_B16R16_2PLANE_444_UNORM:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t mostSignificantBit(uint32_t x) { return 1ul << (31ul - utils::clz(x)); }
|
||||
|
||||
uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask) {
|
||||
|
||||
@@ -98,6 +98,8 @@ bool isVkDepthFormat(VkFormat format);
|
||||
|
||||
bool isVkStencilFormat(VkFormat format);
|
||||
|
||||
bool isVKYcbcrConversionFormat(VkFormat format);
|
||||
|
||||
VkImageAspectFlags getImageAspect(VkFormat format);
|
||||
|
||||
uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask);
|
||||
|
||||
9448
filament/backend/src/webgpu/TEMPORARY_webgpu_cpp_copy.h
Normal file
150
filament/backend/src/webgpu/WGPUProgram.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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 "WebGPUHandles.h"
|
||||
|
||||
#include "WebGPUConstants.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] constexpr std::string_view toString(ShaderStage stage) {
|
||||
switch (stage) {
|
||||
case ShaderStage::VERTEX:
|
||||
return "vertex";
|
||||
case ShaderStage::FRAGMENT:
|
||||
return "fragment";
|
||||
case ShaderStage::COMPUTE:
|
||||
return "compute";
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] wgpu::ShaderModule createShaderModule(wgpu::Device& device, const char* programName,
|
||||
std::array<utils::FixedCapacityVector<uint8_t>, Program::SHADER_TYPE_COUNT> const&
|
||||
shaderSource,
|
||||
ShaderStage stage) {
|
||||
utils::FixedCapacityVector<uint8_t> const& sourceBytes =
|
||||
shaderSource[static_cast<size_t>(stage)];
|
||||
if (sourceBytes.empty()) {
|
||||
return nullptr;// nothing to compile, the shader was not provided
|
||||
}
|
||||
wgpu::ShaderModuleWGSLDescriptor wgslDescriptor{};
|
||||
wgslDescriptor.code = wgpu::StringView(reinterpret_cast<const char*>(sourceBytes.data()));
|
||||
std::stringstream labelStream;
|
||||
labelStream << programName << " " << toString(stage) << " shader";
|
||||
auto label = labelStream.str();
|
||||
wgpu::ShaderModuleDescriptor descriptor{
|
||||
.nextInChain = &wgslDescriptor,
|
||||
.label = label.data()
|
||||
};
|
||||
wgpu::ShaderModule module = device.CreateShaderModule(&descriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(module != nullptr) << "Failed to create " << descriptor.label;
|
||||
module.GetCompilationInfo(wgpu::CallbackMode::AllowSpontaneous,
|
||||
[&descriptor](auto const& status, wgpu::CompilationInfo const* info) {
|
||||
switch (status) {
|
||||
case wgpu::CompilationInfoRequestStatus::CallbackCancelled:
|
||||
FWGPU_LOGW << "Shader compilation info callback cancelled for "
|
||||
<< descriptor.label << "?" << utils::io::endl;
|
||||
return;
|
||||
case wgpu::CompilationInfoRequestStatus::Success:
|
||||
break;
|
||||
}
|
||||
if (info != nullptr) {
|
||||
std::stringstream errorStream;
|
||||
int errorCount = 0;
|
||||
for (size_t msgIndex = 0; msgIndex < info->messageCount; msgIndex++) {
|
||||
wgpu::CompilationMessage const& message = info->messages[msgIndex];
|
||||
switch (message.type) {
|
||||
case wgpu::CompilationMessageType::Info:
|
||||
FWGPU_LOGI << descriptor.label << ": " << message.message
|
||||
<< " line#:" << message.lineNum
|
||||
<< " linePos:" << message.linePos
|
||||
<< " offset:" << message.offset
|
||||
<< " length:" << message.length << utils::io::endl;
|
||||
break;
|
||||
case wgpu::CompilationMessageType::Warning:
|
||||
FWGPU_LOGW << "Warning compiling " << descriptor.label << ": "
|
||||
<< message.message << " line#:" << message.lineNum
|
||||
<< " linePos:" << message.linePos
|
||||
<< " offset:" << message.offset
|
||||
<< " length:" << message.length << utils::io::endl;
|
||||
break;
|
||||
case wgpu::CompilationMessageType::Error:
|
||||
errorCount++;
|
||||
errorStream << "Error " << errorCount << " : "
|
||||
<< std::string_view(message.message)
|
||||
<< " line#:" << message.lineNum
|
||||
<< " linePos:" << message.linePos
|
||||
<< " offset:" << message.offset
|
||||
<< " length:" << message.length << "\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
FILAMENT_CHECK_POSTCONDITION(errorCount < 1)
|
||||
<< errorCount << " error(s) compiling " << descriptor.label << ":\n"
|
||||
<< errorStream.str();
|
||||
}
|
||||
FWGPU_LOGD << descriptor.label << " compiled successfully" << utils::io::endl;
|
||||
});
|
||||
return module;
|
||||
}
|
||||
|
||||
std::vector<wgpu::ConstantEntry> convertConstants(
|
||||
utils::FixedCapacityVector<filament::backend::Program::SpecializationConstant> const&
|
||||
constantsInfo) {
|
||||
std::vector<wgpu::ConstantEntry> constants(constantsInfo.size());
|
||||
for (size_t i = 0; i < constantsInfo.size(); i++) {
|
||||
filament::backend::Program::SpecializationConstant const& specConstant = constantsInfo[i];
|
||||
wgpu::ConstantEntry& constantEntry = constants[i];
|
||||
constantEntry.key = wgpu::StringView(std::to_string(specConstant.id));
|
||||
if (auto* v = std::get_if<int32_t>(&specConstant.value)) {
|
||||
constantEntry.value = static_cast<double>(*v);
|
||||
} else if (auto* f = std::get_if<float>(&specConstant.value)) {
|
||||
constantEntry.value = static_cast<double>(*f);
|
||||
} else if (auto* b = std::get_if<bool>(&specConstant.value)) {
|
||||
constantEntry.value = *b ? 0.0 : 1.0;
|
||||
}
|
||||
}
|
||||
return constants;
|
||||
}
|
||||
|
||||
}// namespace
|
||||
|
||||
WGPUProgram::WGPUProgram(wgpu::Device& device, Program& program)
|
||||
: HwProgram(program.getName()),
|
||||
vertexShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
|
||||
ShaderStage::VERTEX)),
|
||||
fragmentShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
|
||||
ShaderStage::FRAGMENT)),
|
||||
computeShaderModule(createShaderModule(device, name.c_str_safe(), program.getShadersSource(),
|
||||
ShaderStage::COMPUTE)),
|
||||
constants(convertConstants(program.getSpecializationConstants())) {}
|
||||
|
||||
}// namespace filament::backend
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "webgpu/WebGPUDriver.h"
|
||||
|
||||
#include "WebGPUSwapChain.h"
|
||||
#include "webgpu/WebGPUConstants.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include "CommandStreamDispatcher.h"
|
||||
@@ -184,8 +183,6 @@ void printAdapterDetails(wgpu::Adapter const& adapter) {
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
void printDeviceDetails(wgpu::Device const& device) {
|
||||
wgpu::SupportedFeatures supportedFeatures{};
|
||||
@@ -237,6 +234,7 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfi
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
printDeviceDetails(mDevice);
|
||||
#endif
|
||||
mQueue = mDevice.GetQueue();
|
||||
}
|
||||
|
||||
WebGPUDriver::~WebGPUDriver() noexcept = default;
|
||||
@@ -319,12 +317,18 @@ void WebGPUDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
|
||||
if (boh) {
|
||||
destructHandle<WGPUBufferObject>(boh);
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyProgram(Handle<HwProgram> ph) {
|
||||
if (ph) {
|
||||
destructHandle<WGPUProgram>(ph);
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyRenderTarget(Handle<HwRenderTarget> rth) {
|
||||
@@ -350,6 +354,9 @@ void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh)
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
|
||||
if (tqh) {
|
||||
destructHandle<WebGPUDescriptorSet>(tqh);
|
||||
}
|
||||
}
|
||||
|
||||
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
|
||||
@@ -369,7 +376,7 @@ Handle<HwTexture> WebGPUDriver::importTextureS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
|
||||
return Handle<HwProgram>((Handle<HwProgram>::HandleId) mNextFakeHandle++);
|
||||
return allocHandle<WGPUProgram>();
|
||||
}
|
||||
|
||||
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
|
||||
@@ -401,7 +408,7 @@ Handle<HwVertexBuffer> WebGPUDriver::createVertexBufferS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwDescriptorSet> WebGPUDriver::createDescriptorSetS() noexcept {
|
||||
return Handle<HwDescriptorSet>((Handle<HwDescriptorSet>::HandleId) mNextFakeHandle++);
|
||||
return allocHandle<WebGPUDescriptorSet>();
|
||||
}
|
||||
|
||||
Handle<HwRenderPrimitive> WebGPUDriver::createRenderPrimitiveS() noexcept {
|
||||
@@ -441,7 +448,6 @@ void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
||||
assert_invariant(!mSwapChain);
|
||||
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
|
||||
|
||||
mQueue = mDevice.GetQueue();
|
||||
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
|
||||
mSwapChain = constructHandle<WebGPUSwapChain>(sch, std::move(surface), surfaceSize, mAdapter,
|
||||
mDevice, flags);
|
||||
@@ -464,16 +470,27 @@ void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t wi
|
||||
uint32_t height, uint64_t flags) {}
|
||||
|
||||
void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount,
|
||||
uint8_t attributeCount, AttributeArray attributes) {}
|
||||
uint8_t attributeCount, AttributeArray attributes) {
|
||||
constructHandle<WGPUVertexBufferInfo>(vbih, bufferCount, attributeCount, attributes);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount,
|
||||
Handle<HwVertexBufferInfo> vbih) {}
|
||||
Handle<HwVertexBufferInfo> vbih) {
|
||||
auto* vertexBufferInfo = handleCast<WGPUVertexBufferInfo>(vbih);
|
||||
constructHandle<WGPUVertexBuffer>(vbh, mDevice, vertexCount, vertexBufferInfo->bufferCount,
|
||||
vbih);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
|
||||
uint32_t indexCount, BufferUsage usage) {}
|
||||
uint32_t indexCount, BufferUsage usage) {
|
||||
auto elementSize = static_cast<uint8_t>(getElementTypeSize(elementType));
|
||||
constructHandle<WGPUIndexBuffer>(ibh, mDevice, elementSize, indexCount);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType, BufferUsage usage) {}
|
||||
BufferObjectBinding bindingType, BufferUsage usage) {
|
||||
constructHandle<WGPUBufferObject>(boh, mDevice, bindingType, byteCount);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createTextureR(Handle<HwTexture> th, SamplerType target, uint8_t levels,
|
||||
TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth,
|
||||
@@ -503,9 +520,20 @@ void WebGPUDriver::importTextureR(Handle<HwTexture> th, intptr_t id, SamplerType
|
||||
uint32_t depth, TextureUsage usage) {}
|
||||
|
||||
void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, Handle<HwVertexBuffer> vbh,
|
||||
Handle<HwIndexBuffer> ibh, PrimitiveType pt) {}
|
||||
Handle<HwIndexBuffer> ibh, PrimitiveType pt) {
|
||||
assert_invariant(mDevice);
|
||||
|
||||
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {}
|
||||
auto* renderPrimitive = constructHandle<WGPURenderPrimitive>(rph);
|
||||
auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh);
|
||||
auto* indexBuffer = handleCast<WGPUIndexBuffer>(ibh);
|
||||
renderPrimitive->vertexBuffer = vertexBuffer;
|
||||
renderPrimitive->indexBuffer = indexBuffer;
|
||||
renderPrimitive->type = pt;
|
||||
}
|
||||
|
||||
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
constructHandle<WGPUProgram>(ph, mDevice, program);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
|
||||
assert_invariant(!mDefaultRenderTarget);
|
||||
@@ -527,7 +555,10 @@ void WebGPUDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh
|
||||
}
|
||||
|
||||
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||
Handle<HwDescriptorSetLayout> dslh) {}
|
||||
Handle<HwDescriptorSetLayout> dslh) {
|
||||
auto layout = handleCast<WebGPUDescriptorSetLayout>(dslh);
|
||||
constructHandle<WebGPUDescriptorSet>(dsh, layout->getLayout(), layout->getLayoutSize());
|
||||
}
|
||||
|
||||
Handle<HwStream> WebGPUDriver::createStreamNative(void* nativeStream) {
|
||||
return {};
|
||||
@@ -567,7 +598,7 @@ bool WebGPUDriver::isTextureFormatSupported(TextureFormat format) {
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isTextureSwizzleSupported() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isTextureFormatMipmappable(TextureFormat format) {
|
||||
@@ -656,20 +687,21 @@ size_t WebGPUDriver::getMaxArrayTextureLayers() {
|
||||
|
||||
void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
|
||||
uint32_t byteOffset) {
|
||||
scheduleDestroy(std::move(p));
|
||||
updateGPUBuffer(handleCast<WGPUIndexBuffer>(ibh), std::move(p), byteOffset);
|
||||
}
|
||||
|
||||
void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> ibh, BufferDescriptor&& p,
|
||||
uint32_t byteOffset) {
|
||||
scheduleDestroy(std::move(p));
|
||||
updateGPUBuffer(handleCast<WGPUBufferObject>(ibh), std::move(p), byteOffset);
|
||||
}
|
||||
|
||||
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> ibh, BufferDescriptor&& p,
|
||||
uint32_t byteOffset) {
|
||||
scheduleDestroy(std::move(p));
|
||||
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> ibh,
|
||||
BufferDescriptor&& p, uint32_t byteOffset) {
|
||||
updateGPUBuffer(handleCast<WGPUBufferObject>(ibh), std::move(p), byteOffset);
|
||||
}
|
||||
|
||||
void WebGPUDriver::resetBufferObject(Handle<HwBufferObject> boh) {
|
||||
// Is there something that needs to be done here? Vulkan has left it unimplemented.
|
||||
}
|
||||
|
||||
void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t index,
|
||||
@@ -677,7 +709,8 @@ void WebGPUDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t in
|
||||
auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh);
|
||||
auto* bufferObject = handleCast<WGPUBufferObject>(boh);
|
||||
assert_invariant(index < vertexBuffer->buffers.size());
|
||||
vertexBuffer->setBuffer(bufferObject, index);
|
||||
assert_invariant(bufferObject->buffer.GetUsage() & wgpu::BufferUsage::Vertex);
|
||||
vertexBuffer->buffers[index] = bufferObject->buffer;
|
||||
}
|
||||
|
||||
void WebGPUDriver::update3DImage(Handle<HwTexture> th,
|
||||
@@ -715,6 +748,7 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
};
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
assert_invariant(mCommandEncoder);
|
||||
|
||||
// TODO: Remove this code once WebGPU pipeline is implemented
|
||||
static float red = 1.0f;
|
||||
if (red - 0.01 > 0) {
|
||||
@@ -828,6 +862,19 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
auto* renderPrimitive = handleCast<WGPURenderPrimitive>(rph);
|
||||
|
||||
// This *must* match the WGPUVertexBufferInfo that was bound in bindPipeline(). But we want
|
||||
// to allow to call this before bindPipeline(), so the validation can only happen in draw()
|
||||
auto vbi = handleCast<WGPUVertexBufferInfo>(renderPrimitive->vertexBuffer->vbih);
|
||||
assert_invariant(
|
||||
vbi->getVertexBufferLayoutSize() == renderPrimitive->vertexBuffer->buffers.size());
|
||||
for (uint32_t i = 0; i < vbi->getVertexBufferLayoutSize(); i++) {
|
||||
mRenderPassEncoder.SetVertexBuffer(i, renderPrimitive->vertexBuffer->buffers[i]);
|
||||
}
|
||||
|
||||
mRenderPassEncoder.SetIndexBuffer(renderPrimitive->indexBuffer->buffer,
|
||||
renderPrimitive->indexBuffer->indexFormat);
|
||||
}
|
||||
|
||||
void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
@@ -853,25 +900,48 @@ void WebGPUDriver::endTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
void WebGPUDriver::resetState(int) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::updateDescriptorSetBuffer(
|
||||
Handle<HwDescriptorSet> dsh,
|
||||
backend::descriptor_binding_t binding,
|
||||
Handle<HwBufferObject> boh,
|
||||
uint32_t offset,
|
||||
void WebGPUDriver::updateDescriptorSetBuffer(Handle<HwDescriptorSet> dsh,
|
||||
backend::descriptor_binding_t binding, Handle<HwBufferObject> boh, uint32_t offset,
|
||||
uint32_t size) {
|
||||
auto bindGroup = handleCast<WebGPUDescriptorSet>(dsh);
|
||||
auto buffer = handleCast<WGPUBufferObject>(boh);
|
||||
if (!bindGroup->getIsLocked()) {
|
||||
// TODO making assumptions that size and offset mean the same thing here.
|
||||
wgpu::BindGroupEntry entry{ .binding = static_cast<uint32_t>(binding * 2),
|
||||
.buffer = buffer->buffer,
|
||||
.offset = offset,
|
||||
.size = size };
|
||||
bindGroup->addEntry(entry.binding, std::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUDriver::updateDescriptorSetTexture(
|
||||
Handle<HwDescriptorSet> dsh,
|
||||
backend::descriptor_binding_t binding,
|
||||
Handle<HwTexture> th,
|
||||
SamplerParams params) {
|
||||
void WebGPUDriver::updateDescriptorSetTexture(Handle<HwDescriptorSet> dsh,
|
||||
backend::descriptor_binding_t binding, Handle<HwTexture> th, SamplerParams params) {
|
||||
/*
|
||||
auto bindGroup = handleCast<WebGPUDescriptorSet>(dsh);
|
||||
auto texture = handleCast<WGPUTexture>(th);
|
||||
|
||||
// TODO very high odds badd assumptions are in here about handling HwTexture. Revisit with more
|
||||
// understanding. Right now assuming there is a wgpu::TextureView filled in
|
||||
if (!bindGroup->getIsLocked()) {
|
||||
// TODO making assumptions that size and offset mean the same thing here.
|
||||
wgpu::BindGroupEntry tEntry{ .binding = static_cast<uint32_t>(binding * 2),
|
||||
.textureView = texture->texView };
|
||||
bindGroup->addEntry(tEntry.binding, std::move(tEntry));
|
||||
|
||||
wgpu::BindGroupEntry sEntry{ .binding = static_cast<uint32_t>(binding * 2 + 1),
|
||||
.sampler = texture->sampler };
|
||||
bindGroup->addEntry(sEntry.binding, std::move(sEntry));
|
||||
}
|
||||
//TODO Just the setup, this function stilll needs the rest of logic implemented
|
||||
*/
|
||||
}
|
||||
|
||||
void WebGPUDriver::bindDescriptorSet(
|
||||
Handle<HwDescriptorSet> dsh,
|
||||
backend::descriptor_set_t set,
|
||||
void WebGPUDriver::bindDescriptorSet(Handle<HwDescriptorSet> dsh, backend::descriptor_set_t set,
|
||||
backend::DescriptorSetOffsetArray&& offsets) {
|
||||
auto bindGroup = handleCast<WebGPUDescriptorSet>(dsh);
|
||||
// TODO: presume we need this, use it. Probably Encoder::SetBindGroup
|
||||
auto wbg = bindGroup->lockAndReturn(mDevice);
|
||||
}
|
||||
|
||||
void WebGPUDriver::setDebugTag(HandleBase::HandleId handleId, utils::CString tag) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
|
||||
|
||||
#include "WebGPUHandles.h"
|
||||
#include "webgpu/WebGPUConstants.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
@@ -34,7 +35,7 @@
|
||||
#include <memory>
|
||||
|
||||
#ifndef FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB
|
||||
# define FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB 8
|
||||
#define FILAMENT_WEBGPU_HANDLE_ARENA_SIZE_IN_MB 8
|
||||
#endif
|
||||
|
||||
namespace filament::backend {
|
||||
@@ -56,6 +57,27 @@ private:
|
||||
[[nodiscard]] ShaderModel getShaderModel() const noexcept final;
|
||||
[[nodiscard]] ShaderLanguage getShaderLanguage() const noexcept final;
|
||||
|
||||
template<typename GPUBufferObject>
|
||||
void updateGPUBuffer(GPUBufferObject* gpuBufferObject, BufferDescriptor&& bufferDescriptor,
|
||||
uint32_t byteOffset) {
|
||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
|
||||
<< "copyIntoBuffer called with a null buffer";
|
||||
FILAMENT_CHECK_PRECONDITION(
|
||||
bufferDescriptor.size + byteOffset <= gpuBufferObject->buffer.GetSize())
|
||||
<< "Attempting to copy " << bufferDescriptor.size << " bytes into a buffer of size "
|
||||
<< gpuBufferObject->buffer.GetSize() << " at offset " << byteOffset;
|
||||
|
||||
// TODO: All buffer objects are created with CopyDst usage.
|
||||
// This may have some performance implications. That should be investigated later.
|
||||
assert_invariant(gpuBufferObject->buffer.GetUsage() & wgpu::BufferUsage::CopyDst);
|
||||
|
||||
// WriteBuffer is an async call. But cpu buffer data is already written to the staging
|
||||
// buffer on return from the WriteBuffer.
|
||||
mQueue.WriteBuffer(gpuBufferObject->buffer, byteOffset, bufferDescriptor.buffer,
|
||||
bufferDescriptor.size);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
}
|
||||
|
||||
// the platform (e.g. OS) specific aspects of the WebGPU backend are strictly only
|
||||
// handled in the WebGPUPlatform
|
||||
WebGPUPlatform& mPlatform;
|
||||
@@ -99,8 +121,8 @@ private:
|
||||
return mHandleAllocator.allocate<D>();
|
||||
}
|
||||
|
||||
template<typename D, typename B, typename ... ARGS>
|
||||
D* constructHandle(Handle<B>& handle, ARGS&& ... args) noexcept {
|
||||
template<typename D, typename B, typename... ARGS>
|
||||
D* constructHandle(Handle<B>& handle, ARGS&&... args) noexcept {
|
||||
return mHandleAllocator.construct<D>(handle, std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
@@ -112,7 +134,7 @@ private:
|
||||
template<typename D, typename B>
|
||||
void destructHandle(Handle<B>& handle) noexcept {
|
||||
auto* p = mHandleAllocator.handle_cast<D*>(handle);
|
||||
return mHandleAllocator.deallocate(handle, p);
|
||||
mHandleAllocator.deallocate(handle, p);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
* 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.
|
||||
@@ -19,46 +19,159 @@
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
wgpu::Buffer createIndexBuffer(wgpu::Device const& device, uint8_t elementSize, uint32_t indexCount) {
|
||||
wgpu::BufferDescriptor descriptor{ .label = "index_buffer",
|
||||
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index,
|
||||
.size = elementSize * indexCount,
|
||||
.mappedAtCreation = false };
|
||||
return device.CreateBuffer(&descriptor);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
WGPUIndexBuffer::WGPUIndexBuffer(wgpu::Device const& device, uint8_t elementSize,
|
||||
uint32_t indexCount)
|
||||
: buffer(createIndexBuffer(device, elementSize, indexCount)) {}
|
||||
|
||||
|
||||
WGPUVertexBuffer::WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
|
||||
Handle<WGPUVertexBufferInfo> vbih)
|
||||
: HwVertexBuffer(vextexCount),
|
||||
vbih(vbih),
|
||||
buffers(bufferCount) {
|
||||
wgpu::BufferDescriptor descriptor {
|
||||
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex,
|
||||
.size = vextexCount * bufferCount,
|
||||
.mappedAtCreation = false };
|
||||
|
||||
for (uint32_t i = 0; i < bufferCount; ++i) {
|
||||
descriptor.label = ("vertex_buffer_" + std::to_string(i)).c_str();
|
||||
buffers[i] = device.CreateBuffer(&descriptor);
|
||||
constexpr wgpu::BufferUsage getBufferObjectUsage(
|
||||
filament::backend::BufferObjectBinding bindingType) noexcept {
|
||||
switch (bindingType) {
|
||||
case filament::backend::BufferObjectBinding::VERTEX:
|
||||
return wgpu::BufferUsage::Vertex;
|
||||
case filament::backend::BufferObjectBinding::UNIFORM:
|
||||
return wgpu::BufferUsage::Uniform;
|
||||
case filament::backend::BufferObjectBinding::SHADER_STORAGE:
|
||||
return wgpu::BufferUsage::Storage;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Empty function is a place holder for verxtex buffer updates and should be
|
||||
// updated for that purpose.
|
||||
void WGPUVertexBuffer::setBuffer(WGPUBufferObject* bufferObject, uint32_t index) {}
|
||||
wgpu::Buffer createBuffer(wgpu::Device const& device, wgpu::BufferUsage usage, uint32_t size,
|
||||
char const* label) {
|
||||
wgpu::BufferDescriptor descriptor{ .label = label,
|
||||
.usage = usage,
|
||||
.size = size,
|
||||
.mappedAtCreation = false };
|
||||
return device.CreateBuffer(&descriptor);
|
||||
}
|
||||
|
||||
WGPUBufferObject::WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount)
|
||||
wgpu::VertexFormat getVertexFormat(filament::backend::ElementType type, bool normalized, bool integer) {
|
||||
using ElementType = filament::backend::ElementType;
|
||||
using VertexFormat = wgpu::VertexFormat;
|
||||
if (normalized) {
|
||||
switch (type) {
|
||||
// Single Component Types
|
||||
case ElementType::BYTE: return VertexFormat::Snorm8;
|
||||
case ElementType::UBYTE: return VertexFormat::Unorm8;
|
||||
case ElementType::SHORT: return VertexFormat::Snorm16;
|
||||
case ElementType::USHORT: return VertexFormat::Unorm16;
|
||||
// Two Component Types
|
||||
case ElementType::BYTE2: return VertexFormat::Snorm8x2;
|
||||
case ElementType::UBYTE2: return VertexFormat::Unorm8x2;
|
||||
case ElementType::SHORT2: return VertexFormat::Snorm16x2;
|
||||
case ElementType::USHORT2: return VertexFormat::Unorm16x2;
|
||||
// Three Component Types
|
||||
// There is no vertex format type for 3 byte data in webgpu. Use
|
||||
// 4 byte signed normalized type and ignore the last byte.
|
||||
// TODO: This is to be verified.
|
||||
case ElementType::BYTE3: return VertexFormat::Snorm8x4; // NOT MINSPEC
|
||||
case ElementType::UBYTE3: return VertexFormat::Unorm8x4; // NOT MINSPEC
|
||||
case ElementType::SHORT3: return VertexFormat::Snorm16x4; // NOT MINSPEC
|
||||
case ElementType::USHORT3: return VertexFormat::Unorm16x4; // NOT MINSPEC
|
||||
// Four Component Types
|
||||
case ElementType::BYTE4: return VertexFormat::Snorm8x4;
|
||||
case ElementType::UBYTE4: return VertexFormat::Unorm8x4;
|
||||
case ElementType::SHORT4: return VertexFormat::Snorm16x4;
|
||||
case ElementType::USHORT4: return VertexFormat::Unorm8x4;
|
||||
default:
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Normalized format does not exist.";
|
||||
return VertexFormat::Float32x3;
|
||||
}
|
||||
}
|
||||
switch (type) {
|
||||
// Single Component Types
|
||||
// There is no direct alternative for SSCALED in webgpu. Convert them to Float32 directly.
|
||||
// This will result in increased memory on the cpu side.
|
||||
// TODO: Is Float16 acceptable instead with some potential accuracy errors?
|
||||
case ElementType::BYTE: return integer ? VertexFormat::Sint8 : VertexFormat::Float32;
|
||||
case ElementType::UBYTE: return integer ? VertexFormat::Uint8 : VertexFormat::Float32;
|
||||
case ElementType::SHORT: return integer ? VertexFormat::Sint16 : VertexFormat::Float32;
|
||||
case ElementType::USHORT: return integer ? VertexFormat::Uint16 : VertexFormat::Float32;
|
||||
case ElementType::HALF: return VertexFormat::Float16;
|
||||
case ElementType::INT: return VertexFormat::Sint32;
|
||||
case ElementType::UINT: return VertexFormat::Uint32;
|
||||
case ElementType::FLOAT: return VertexFormat::Float32;
|
||||
// Two Component Types
|
||||
case ElementType::BYTE2: return integer ? VertexFormat::Sint8x2 : VertexFormat::Float32x2;
|
||||
case ElementType::UBYTE2: return integer ? VertexFormat::Uint8x2 : VertexFormat::Float32x2;
|
||||
case ElementType::SHORT2: return integer ? VertexFormat::Sint16x2 : VertexFormat::Float32x2;
|
||||
case ElementType::USHORT2: return integer ? VertexFormat::Uint16x2 : VertexFormat::Float32x2;
|
||||
case ElementType::HALF2: return VertexFormat::Float16x2;
|
||||
case ElementType::FLOAT2: return VertexFormat::Float32x2;
|
||||
// Three Component Types
|
||||
case ElementType::BYTE3: return VertexFormat::Sint8x4; // NOT MINSPEC
|
||||
case ElementType::UBYTE3: return VertexFormat::Uint8x4; // NOT MINSPEC
|
||||
case ElementType::SHORT3: return VertexFormat::Sint16x4; // NOT MINSPEC
|
||||
case ElementType::USHORT3: return VertexFormat::Uint16x4; // NOT MINSPEC
|
||||
case ElementType::HALF3: return VertexFormat::Float16x4; // NOT MINSPEC
|
||||
case ElementType::FLOAT3: return VertexFormat::Float32x3;
|
||||
// Four Component Types
|
||||
case ElementType::BYTE4: return integer ? VertexFormat::Sint8x4 : VertexFormat::Float32x4;
|
||||
case ElementType::UBYTE4: return integer ? VertexFormat::Uint8x4 : VertexFormat::Float32x4;
|
||||
case ElementType::SHORT4: return integer ? VertexFormat::Sint16x4 : VertexFormat::Float32x4;
|
||||
case ElementType::USHORT4: return integer ? VertexFormat::Uint16x4 : VertexFormat::Float32x4;
|
||||
case ElementType::HALF4: return VertexFormat::Float16x4;
|
||||
case ElementType::FLOAT4: return VertexFormat::Float32x4;
|
||||
}
|
||||
}
|
||||
|
||||
}// namespace
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
WGPUVertexBufferInfo::WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
||||
AttributeArray const& attributes)
|
||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||
mVertexBufferLayout(bufferCount),
|
||||
mAttributes(bufferCount) {
|
||||
assert_invariant(attributeCount > 0);
|
||||
assert_invariant(bufferCount > 0);
|
||||
for (uint32_t attribIndex = 0; attribIndex < attributes.size(); attribIndex++) {
|
||||
Attribute const& attrib = attributes[attribIndex];
|
||||
// Ignore the attributes which are not bind to vertex buffers.
|
||||
if (attrib.buffer == Attribute::BUFFER_UNUSED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert_invariant(attrib.buffer < bufferCount);
|
||||
bool const isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET;
|
||||
bool const isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED;
|
||||
wgpu::VertexFormat vertexFormat = getVertexFormat(attrib.type, isNormalized, isInteger);
|
||||
|
||||
// Attributes are sequential per buffer
|
||||
mAttributes[attrib.buffer].push_back({
|
||||
.format = vertexFormat,
|
||||
.offset = attrib.offset,
|
||||
.shaderLocation = static_cast<uint32_t>(mAttributes[attrib.buffer].size()),
|
||||
});
|
||||
|
||||
mVertexBufferLayout[attrib.buffer].stepMode = wgpu::VertexStepMode::Vertex;
|
||||
if (mVertexBufferLayout[attrib.buffer].arrayStride == 0) {
|
||||
mVertexBufferLayout[attrib.buffer].arrayStride = attrib.stride;
|
||||
} else {
|
||||
assert_invariant(mVertexBufferLayout[attrib.buffer].arrayStride == attrib.stride);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t bufferIndex = 0; bufferIndex < bufferCount; bufferIndex++) {
|
||||
mVertexBufferLayout[bufferIndex].attributeCount = mAttributes[bufferIndex].size();
|
||||
mVertexBufferLayout[bufferIndex].attributes = mAttributes[bufferIndex].data();
|
||||
}
|
||||
}
|
||||
|
||||
WGPUIndexBuffer::WGPUIndexBuffer(wgpu::Device const& device, uint8_t elementSize,
|
||||
uint32_t indexCount)
|
||||
: buffer(createBuffer(device, wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index,
|
||||
elementSize * indexCount, "index_buffer")),
|
||||
indexFormat(elementSize == 2 ? wgpu::IndexFormat::Uint16 : wgpu::IndexFormat::Uint32) {}
|
||||
|
||||
|
||||
WGPUVertexBuffer::WGPUVertexBuffer(wgpu::Device const& device, uint32_t vertexCount,
|
||||
uint32_t bufferCount, Handle<HwVertexBufferInfo> vbih)
|
||||
: HwVertexBuffer(vertexCount),
|
||||
vbih(vbih),
|
||||
buffers(bufferCount) {}
|
||||
|
||||
WGPUBufferObject::WGPUBufferObject(wgpu::Device const& device, BufferObjectBinding bindingType,
|
||||
uint32_t byteCount)
|
||||
: HwBufferObject(byteCount),
|
||||
buffer(createBuffer(device, wgpu::BufferUsage::CopyDst | getBufferObjectUsage(bindingType),
|
||||
byteCount, "buffer_object")),
|
||||
bufferObjectBinding(bindingType) {}
|
||||
|
||||
wgpu::ShaderStage WebGPUDescriptorSetLayout::filamentStageToWGPUStage(ShaderStageFlags fFlags) {
|
||||
@@ -146,7 +259,39 @@ WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const&
|
||||
.entries = wEntries.data()
|
||||
};
|
||||
// TODO Do we need to defer this until we have more info on textures and samplers??
|
||||
mLayoutSize = wEntries.size();
|
||||
mLayout = device.CreateBindGroupLayout(&layoutDescriptor);
|
||||
}
|
||||
|
||||
WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {}
|
||||
|
||||
WebGPUDescriptorSet::WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, uint layoutSize)
|
||||
: mLayout(layout),
|
||||
entries(layoutSize, wgpu::BindGroupEntry{}) {
|
||||
// Establish the size of entries based on the layout. This should be reliable and efficient.
|
||||
}
|
||||
WebGPUDescriptorSet::~WebGPUDescriptorSet() {}
|
||||
|
||||
wgpu::BindGroup WebGPUDescriptorSet::lockAndReturn(const wgpu::Device& device) {
|
||||
if (mBindGroup) {
|
||||
return mBindGroup;
|
||||
}
|
||||
// TODO label? Should we just copy layout label?
|
||||
wgpu::BindGroupDescriptor desc{ .layout = mLayout,
|
||||
.entryCount = entries.size(),
|
||||
.entries = entries.data() };
|
||||
mBindGroup = device.CreateBindGroup(&desc);
|
||||
return mBindGroup;
|
||||
}
|
||||
|
||||
void WebGPUDescriptorSet::addEntry(uint index, wgpu::BindGroupEntry&& entry) {
|
||||
if (mBindGroup) {
|
||||
// We will keep getting hits from future updates, but shouldn't do anything
|
||||
// Filament guarantees this won't change after things have locked.
|
||||
return;
|
||||
}
|
||||
// TODO: Putting some level of trust that Filament is not going to reuse indexes or go past the
|
||||
// layout index for efficiency. Add guards if wrong.
|
||||
entries[index] = std::move(entry);
|
||||
}
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -28,27 +28,59 @@
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WGPUProgram final : public HwProgram {
|
||||
public:
|
||||
WGPUProgram(wgpu::Device&, Program&);
|
||||
|
||||
wgpu::ShaderModule vertexShaderModule = nullptr;
|
||||
wgpu::ShaderModule fragmentShaderModule = nullptr;
|
||||
wgpu::ShaderModule computeShaderModule = nullptr;
|
||||
std::vector<wgpu::ConstantEntry> constants;
|
||||
};
|
||||
|
||||
struct WGPUBufferObject;
|
||||
// TODO: Currently WGPUVertexBufferInfo is not used by WebGPU for useful task.
|
||||
// Update the struct when used by WebGPU driver.
|
||||
struct WGPUVertexBufferInfo : public HwVertexBufferInfo {
|
||||
|
||||
// VertexBufferInfo contains layout info for Vertex Buffer based on WebGPU structs. In WebGPU each
|
||||
// VertexBufferLayout is associated with a single vertex buffer. So number of mVertexBufferLayout
|
||||
// is equal to bufferCount. Each VertexBufferLayout can contain multiple VertexAttribute. Bind index
|
||||
// of vertex buffer is implicitly calculated by the position of VertexBufferLayout in an array.
|
||||
class WGPUVertexBufferInfo : public HwVertexBufferInfo {
|
||||
public:
|
||||
WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
||||
AttributeArray const& attributes)
|
||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||
attributes(attributes) {}
|
||||
AttributeArray attributes;
|
||||
AttributeArray const& attributes);
|
||||
inline wgpu::VertexBufferLayout const* getVertexBufferLayout() const {
|
||||
return mVertexBufferLayout.data();
|
||||
}
|
||||
|
||||
inline uint32_t getVertexBufferLayoutSize() const {
|
||||
return mVertexBufferLayout.size();
|
||||
}
|
||||
|
||||
inline wgpu::VertexAttribute const* getVertexAttributeForIndex(uint32_t index) const {
|
||||
assert_invariant(index < mAttributes.size());
|
||||
return mAttributes[index].data();
|
||||
}
|
||||
|
||||
inline uint32_t getVertexAttributeSize(uint32_t index) const {
|
||||
assert_invariant(index < mAttributes.size());
|
||||
return mAttributes[index].size();
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO: can we do better in terms on heap management.
|
||||
std::vector<wgpu::VertexBufferLayout> mVertexBufferLayout {};
|
||||
std::vector<std::vector<wgpu::VertexAttribute>> mAttributes {};
|
||||
};
|
||||
|
||||
struct WGPUVertexBuffer : public HwVertexBuffer {
|
||||
WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
|
||||
Handle<WGPUVertexBufferInfo> vbih);
|
||||
WGPUVertexBuffer(wgpu::Device const &device, uint32_t vertexCount, uint32_t bufferCount,
|
||||
Handle<HwVertexBufferInfo> vbih);
|
||||
|
||||
void setBuffer(WGPUBufferObject *bufferObject, uint32_t index);
|
||||
|
||||
Handle<WGPUVertexBufferInfo> vbih;
|
||||
Handle<HwVertexBufferInfo> vbih;
|
||||
utils::FixedCapacityVector<wgpu::Buffer> buffers;
|
||||
};
|
||||
|
||||
@@ -57,29 +89,49 @@ struct WGPUIndexBuffer : public HwIndexBuffer {
|
||||
uint32_t indexCount);
|
||||
|
||||
wgpu::Buffer buffer;
|
||||
wgpu::IndexFormat indexFormat;
|
||||
};
|
||||
|
||||
// TODO: Currently WGPUVertexBufferInfo is not used by WebGPU for useful task.
|
||||
// Update the struct when used by WebGPU driver.
|
||||
struct WGPUBufferObject : HwBufferObject {
|
||||
WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount);
|
||||
WGPUBufferObject(wgpu::Device const &device, BufferObjectBinding bindingType, uint32_t byteCount);
|
||||
|
||||
wgpu::Buffer buffer;
|
||||
wgpu::Buffer buffer = nullptr;
|
||||
const BufferObjectBinding bufferObjectBinding;
|
||||
};
|
||||
class WebGPUDescriptorSetLayout : public HwDescriptorSetLayout {
|
||||
class WebGPUDescriptorSetLayout final : public HwDescriptorSetLayout {
|
||||
public:
|
||||
WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const& device);
|
||||
~WebGPUDescriptorSetLayout();
|
||||
[[nodiscard]] const wgpu::BindGroupLayout& getLayout() const { return mLayout; }
|
||||
[[nodiscard]] uint getLayoutSize() const { return mLayoutSize; }
|
||||
|
||||
private:
|
||||
// TODO: If this is useful elsewhere, remove it from this class
|
||||
// Convert Filament Shader Stage Flags bitmask to webgpu equivilant
|
||||
static wgpu::ShaderStage filamentStageToWGPUStage(ShaderStageFlags fFlags);
|
||||
|
||||
uint mLayoutSize;
|
||||
wgpu::BindGroupLayout mLayout;
|
||||
};
|
||||
|
||||
class WebGPUDescriptorSet final : public HwDescriptorSet {
|
||||
public:
|
||||
WebGPUDescriptorSet(const wgpu::BindGroupLayout& layout, uint layoutSize);
|
||||
~WebGPUDescriptorSet();
|
||||
|
||||
wgpu::BindGroup lockAndReturn(wgpu::Device const& device);
|
||||
void addEntry(uint index, wgpu::BindGroupEntry&& entry);
|
||||
[[nodiscard]] bool getIsLocked() const { return mBindGroup != nullptr; }
|
||||
|
||||
private:
|
||||
// TODO: Consider storing what we used to make the layout. However we need to essentially
|
||||
// Recreate some of the info (Sampler in slot X with the actual sampler) so letting Dawn confirm
|
||||
// there isn't a mismatch may be easiest.
|
||||
// Also storing the wgpu ObjectBase takes care of ownership challenges in theory
|
||||
wgpu::BindGroupLayout mLayout;
|
||||
std::vector<wgpu::BindGroupEntry> entries;
|
||||
wgpu::BindGroup mBindGroup;
|
||||
};
|
||||
|
||||
// TODO: Currently WGPUTexture is not used by WebGPU for useful task.
|
||||
// Update the struct when used by WebGPU driver.
|
||||
struct WGPUTexture : public HwTexture {
|
||||
@@ -90,10 +142,15 @@ struct WGPUTexture : public HwTexture {
|
||||
WGPUTexture(WGPUTexture const* src, uint8_t baseLevel, uint8_t levelCount) noexcept;
|
||||
|
||||
wgpu::Texture texture = nullptr;
|
||||
// TODO: Adding this but not yet setting it up. Filament "Textures" are combined image samplers,
|
||||
// rep both.
|
||||
wgpu::Sampler sampler = nullptr;
|
||||
//TODO: Not sure all the ways HwTexture is used. Overloading like this might be entirely wrong.
|
||||
wgpu::TextureView texView = nullptr;
|
||||
};
|
||||
|
||||
struct WGPURenderPrimitive : public HwRenderPrimitive {
|
||||
WGPURenderPrimitive();
|
||||
WGPURenderPrimitive() {}
|
||||
|
||||
void setBuffers(WGPUVertexBufferInfo const* const vbi,
|
||||
WGPUVertexBuffer* vertexBuffer, WGPUIndexBuffer* indexBuffer);
|
||||
|
||||
@@ -55,14 +55,17 @@ void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool is
|
||||
BackendTest::BackendTest() : commandBufferQueue(CONFIG_MIN_COMMAND_BUFFERS_SIZE,
|
||||
CONFIG_COMMAND_BUFFERS_SIZE, /*mPaused=*/false) {
|
||||
initializeDriver();
|
||||
mImageExpectations.emplace(getDriverApi());
|
||||
}
|
||||
|
||||
BackendTest::~BackendTest() {
|
||||
// Ensure all graphics commands and callbacks are finished.
|
||||
flushAndWait();
|
||||
mImageExpectations->evaluate();
|
||||
// Note: Don't terminate the driver for OpenGL, as it wipes away the context and removes the buffer from the screen.
|
||||
if (sBackend == Backend::OPENGL) {
|
||||
return;
|
||||
}
|
||||
flushAndWait();
|
||||
driver->terminate();
|
||||
delete driver;
|
||||
}
|
||||
@@ -156,51 +159,6 @@ void BackendTest::renderTriangle(
|
||||
api.endRenderPass();
|
||||
}
|
||||
|
||||
void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, size_t height,
|
||||
Handle<HwRenderTarget> rt, uint32_t expectedHash, bool exportScreenshot) {
|
||||
void* buffer = calloc(1, width * height * 4);
|
||||
|
||||
struct Capture {
|
||||
uint32_t expectedHash;
|
||||
char* name;
|
||||
bool exportScreenshot;
|
||||
size_t width, height;
|
||||
};
|
||||
auto* c = new Capture();
|
||||
c->expectedHash = expectedHash;
|
||||
c->name = strdup(testName);
|
||||
c->exportScreenshot = exportScreenshot;
|
||||
c->width = width;
|
||||
c->height = height;
|
||||
|
||||
PixelBufferDescriptor pbd(buffer, width * height * 4, PixelDataFormat::RGBA, PixelDataType::UBYTE,
|
||||
1, 0, 0, width, [](void* buffer, size_t size, void* user) {
|
||||
auto* c = (Capture*)user;
|
||||
|
||||
// Export a screenshot, if requested.
|
||||
if (c->exportScreenshot) {
|
||||
#ifndef FILAMENT_IOS
|
||||
LinearImage image(c->width, c->height, 4);
|
||||
image = toLinearWithAlpha<uint8_t>(c->width, c->height, c->width * 4,
|
||||
(uint8_t*) buffer);
|
||||
const std::string png = std::string(c->name) + ".png";
|
||||
std::ofstream outputStream(png.c_str(), std::ios::binary | std::ios::trunc);
|
||||
ImageEncoder::encode(outputStream, ImageEncoder::Format::PNG, image, "",
|
||||
png);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Hash the contents of the buffer and check that they match.
|
||||
uint32_t hash = utils::hash::murmur3((const uint32_t*) buffer, size / 4, 0);
|
||||
ASSERT_EQ(hash, c->expectedHash) << c->name << " failed: hashes do not match." << std::endl;
|
||||
|
||||
free(buffer);
|
||||
free(c->name);
|
||||
free(c);
|
||||
}, (void*)c);
|
||||
getDriverApi().readPixels(rt, 0, 0, width, height, std::move(pbd));
|
||||
}
|
||||
|
||||
bool BackendTest::matchesEnvironment(Backend backend) {
|
||||
return sBackend == backend;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "private/backend/DriverApi.h"
|
||||
|
||||
#include "PlatformRunner.h"
|
||||
#include "ImageExpectations.h"
|
||||
|
||||
namespace test {
|
||||
|
||||
@@ -65,17 +66,14 @@ protected:
|
||||
filament::backend::Handle<filament::backend::HwProgram> program,
|
||||
const filament::backend::RenderPassParams& params);
|
||||
|
||||
void readPixelsAndAssertHash(const char* testName, size_t width, size_t height,
|
||||
filament::backend::Handle<filament::backend::HwRenderTarget> rt, uint32_t expectedHash,
|
||||
bool exportScreenshot = false);
|
||||
|
||||
filament::backend::DriverApi& getDriverApi() { return *commandStream; }
|
||||
filament::backend::Driver& getDriver() { return *driver; }
|
||||
|
||||
ImageExpectations& getExpectations() { return *mImageExpectations; }
|
||||
|
||||
static bool matchesEnvironment(Backend backend);
|
||||
static bool matchesEnvironment(OperatingSystem operatingSystem);
|
||||
static bool matchesEnvironment(OperatingSystem operatingSystem, Backend backend);
|
||||
|
||||
private:
|
||||
|
||||
filament::backend::Driver* driver = nullptr;
|
||||
@@ -83,6 +81,10 @@ private:
|
||||
std::unique_ptr<filament::backend::DriverApi> commandStream;
|
||||
|
||||
filament::backend::Handle<filament::backend::HwBufferObject> uniform;
|
||||
|
||||
// This isn't truly optional, it just needs to delay construction until after the driver has
|
||||
// been initialized
|
||||
std::optional<ImageExpectations> mImageExpectations;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -97,16 +97,21 @@ void ImageExpectation::evaluate() {
|
||||
|
||||
void ImageExpectation::compareImage() const {
|
||||
bool bytesFilled = mResult.bytesFilled();
|
||||
// If this fails, it likely means that BackendTest::flushAndWait needs to be called before
|
||||
// ImageExpectations is evaluated or destroyed.
|
||||
EXPECT_THAT(bytesFilled, testing::IsTrue())
|
||||
<< "Render target wasn't copied to the buffer for " << mFileName;
|
||||
if (bytesFilled) {
|
||||
LoadedPng loadedImage(mParams.expectedFilePath());
|
||||
// Rather than directly compare the two images compare their hashes because comparing very
|
||||
// large arrays generates way too much debug output to be useful.
|
||||
uint32_t actualHash = mResult.hash();
|
||||
#ifndef FILAMENT_IOS
|
||||
LoadedPng loadedImage(mParams.expectedFilePath());
|
||||
uint32_t loadedImageHash = loadedImage.hash();
|
||||
EXPECT_THAT(actualHash, testing::Eq(loadedImageHash));
|
||||
EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash()));
|
||||
EXPECT_THAT(actualHash, testing::Eq(loadedImageHash)) << mParams.expectedFileName();
|
||||
#endif
|
||||
// For builds that can't load PNGs (currently iOS only) use the expected hash.
|
||||
EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash())) << mParams.expectedFileName();
|
||||
// TODO: Add better debug output, such as generating a diff image.
|
||||
}
|
||||
}
|
||||
@@ -121,12 +126,13 @@ ImageExpectations::~ImageExpectations() {
|
||||
|
||||
void ImageExpectations::addExpectation(const char* fileName, int lineNumber,
|
||||
filament::backend::RenderTargetHandle renderTarget, ScreenshotParams params) {
|
||||
mExpectations.emplace_back(fileName, lineNumber, mApi, std::move(params), renderTarget);
|
||||
mExpectations.emplace_back(std::make_unique<ImageExpectation>(fileName, lineNumber, mApi,
|
||||
std::move(params), renderTarget));
|
||||
}
|
||||
|
||||
void ImageExpectations::evaluate() {
|
||||
for (auto& expectation: mExpectations) {
|
||||
expectation.evaluate();
|
||||
expectation->evaluate();
|
||||
}
|
||||
mExpectations.clear();
|
||||
}
|
||||
@@ -134,16 +140,13 @@ void ImageExpectations::evaluate() {
|
||||
RenderTargetDump::RenderTargetDump(filament::backend::DriverApi& api,
|
||||
filament::backend::RenderTargetHandle renderTarget, const ScreenshotParams& params)
|
||||
: mInternal(std::make_unique<RenderTargetDump::Internal>(params)) {
|
||||
#ifdef FILAMENT_IOS
|
||||
bytesFilled_ = true;
|
||||
bytes_.resize(size);
|
||||
std::fill(bytes_.begin(), bytes_.end(), 0);
|
||||
#else
|
||||
const size_t size = mInternal->params.width() * mInternal->params.height() * 4;
|
||||
mInternal->bytes.resize(size);
|
||||
|
||||
auto cb = [](void* buffer, size_t size, void* user) {
|
||||
auto* internal = static_cast<RenderTargetDump::Internal*>(user);
|
||||
internal->bytesFilled = true;
|
||||
#ifndef FILAMENT_IOS
|
||||
image::LinearImage image(internal->params.width(), internal->params.width(), 4);
|
||||
image = image::toLinearWithAlpha<uint8_t>(internal->params.width(),
|
||||
internal->params.height(),
|
||||
@@ -152,14 +155,13 @@ RenderTargetDump::RenderTargetDump(filament::backend::DriverApi& api,
|
||||
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
|
||||
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG, image, "",
|
||||
filePath);
|
||||
internal->bytesFilled = true;
|
||||
#endif
|
||||
};
|
||||
filament::backend::PixelBufferDescriptor pb(mInternal->bytes.data(), size,
|
||||
filament::backend::PixelDataFormat::RGBA, filament::backend::PixelDataType::UBYTE, cb,
|
||||
(void*)mInternal.get());
|
||||
api.readPixels(renderTarget, 0, 0, mInternal->params.width(), mInternal->params.height(),
|
||||
std::move(pb));
|
||||
#endif
|
||||
}
|
||||
|
||||
RenderTargetDump::~RenderTargetDump() {
|
||||
@@ -183,9 +185,9 @@ bool RenderTargetDump::bytesFilled() const {
|
||||
|
||||
RenderTargetDump::Internal::Internal(const ScreenshotParams& params) : params(params) {}
|
||||
|
||||
LoadedPng::LoadedPng(std::string filePath) {
|
||||
LoadedPng::LoadedPng(std::string filePath) : mFilePath(std::move(filePath)) {
|
||||
#ifndef FILAMENT_IOS
|
||||
std::ifstream pngStream(filePath, std::ios::binary);
|
||||
std::ifstream pngStream(mFilePath, std::ios::binary);
|
||||
image::LinearImage loadedImage = image::ImageDecoder::decode(pngStream, filePath,
|
||||
image::ImageDecoder::ColorSpace::LINEAR);
|
||||
size_t valuesInImage = loadedImage.getWidth() * loadedImage.getHeight() *
|
||||
@@ -201,7 +203,8 @@ LoadedPng::LoadedPng(std::string filePath) {
|
||||
}
|
||||
|
||||
uint32_t LoadedPng::hash() const {
|
||||
EXPECT_THAT(mBytes, testing::Not(testing::IsEmpty()));
|
||||
EXPECT_THAT(mBytes, testing::Not(testing::IsEmpty()))
|
||||
<< "Failed to load expected test result: " << mFilePath;
|
||||
if (mBytes.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,7 @@ public:
|
||||
uint32_t hash() const;
|
||||
|
||||
private:
|
||||
std::string mFilePath;
|
||||
std::vector<unsigned char> mBytes;
|
||||
};
|
||||
|
||||
@@ -141,7 +142,8 @@ public:
|
||||
|
||||
private:
|
||||
filament::backend::DriverApi& mApi;
|
||||
std::vector<ImageExpectation> mExpectations;
|
||||
// Store expectations in unique pointers because they are self referential.
|
||||
std::vector<std::unique_ptr<ImageExpectation>> mExpectations;
|
||||
};
|
||||
|
||||
#endif //TNT_IMAGE_EXPECTATIONS_H
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
filament/backend/test/expected_images/DepthAndStencilBuffer.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
filament/backend/test/expected_images/FeedbackLoops.png
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
filament/backend/test/expected_images/RGB FLOAT to RGB16F.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/RGB FLOAT to RGB32F.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/RGBA FLOAT to RGBA16F.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/RGBA UBYTE to RGBA8.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/RenderExternalImage.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
BIN
filament/backend/test/expected_images/StencilBuffer.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
BIN
filament/backend/test/expected_images/UpdateImage3D.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/UpdateImageMipLevel.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
filament/backend/test/expected_images/UpdateImageSRGB.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
filament/backend/test/expected_images/scissor.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
@@ -197,16 +197,10 @@ TEST_F(BlitTest, ColorMagnify) {
|
||||
}
|
||||
|
||||
{
|
||||
ImageExpectations expectations(api);
|
||||
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
EXPECT_IMAGE(dstRenderTargets[0], expectations,
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
flushAndWait();
|
||||
RenderFrame frame(api);
|
||||
EXPECT_IMAGE(dstRenderTargets[0], getExpectations(),
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMagnify", 0x410bdd31));
|
||||
api.commit(swapChain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,14 +254,8 @@ TEST_F(BlitTest, ColorMinify) {
|
||||
{ 0, 0, kSrcTexWidth >> srcLevel, kSrcTexHeight >> srcLevel },
|
||||
SamplerMagFilter::LINEAR);
|
||||
|
||||
{
|
||||
ImageExpectations expectations(api);
|
||||
|
||||
EXPECT_IMAGE(dstRenderTargets[0], expectations,
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
|
||||
|
||||
flushAndWait();
|
||||
}
|
||||
EXPECT_IMAGE(dstRenderTargets[0], getExpectations(),
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorMinify", 0xf3d9c53f));
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, ColorResolve) {
|
||||
@@ -351,14 +339,8 @@ TEST_F(BlitTest, ColorResolve) {
|
||||
srcRenderTarget, { 0, 0, kSrcTexWidth, kSrcTexHeight },
|
||||
SamplerMagFilter::NEAREST);
|
||||
|
||||
{
|
||||
ImageExpectations expectations(api);
|
||||
|
||||
EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
|
||||
|
||||
flushAndWait();
|
||||
}
|
||||
EXPECT_IMAGE(dstRenderTarget, getExpectations(),
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "ColorResolve", 0xebfac2ef));
|
||||
}
|
||||
|
||||
TEST_F(BlitTest, Blit2DTextureArray) {
|
||||
@@ -423,17 +405,11 @@ TEST_F(BlitTest, Blit2DTextureArray) {
|
||||
}
|
||||
|
||||
{
|
||||
ImageExpectations expectations(api);
|
||||
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
|
||||
0x8de7d55b));
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
flushAndWait();
|
||||
RenderFrame frame(api);
|
||||
EXPECT_IMAGE(dstRenderTarget, getExpectations(),
|
||||
ScreenshotParams(kDstTexWidth, kDstTexHeight, "Blit2DTextureArray",
|
||||
0x8de7d55b));
|
||||
api.commit(swapChain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,18 +479,12 @@ TEST_F(BlitTest, BlitRegion) {
|
||||
}
|
||||
|
||||
{
|
||||
ImageExpectations expectations(api);
|
||||
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
// TODO: for some reason, this test has very, very slight (as in one pixel) differences
|
||||
// between OpenGL and Metal. So disable golden checking for now.
|
||||
// EXPECT_IMAGE(dstRenderTarget, expectations, ScreenshotParams(kDstTexWidth,
|
||||
// kDstTexHeight, "BlitRegion", 0x74fa34ed));
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
flushAndWait();
|
||||
RenderFrame frame(api);
|
||||
// TODO: for some reason, this test has very, very slight (as in one pixel) differences
|
||||
// between OpenGL and Metal. So disable golden checking for now.
|
||||
// EXPECT_IMAGE(dstRenderTarget, expectations, ScreenshotParams(kDstTexWidth,
|
||||
// kDstTexHeight, "BlitRegion", 0x74fa34ed));
|
||||
api.commit(swapChain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,25 +537,19 @@ TEST_F(BlitTest, BlitRegionToSwapChain) {
|
||||
};
|
||||
|
||||
{
|
||||
ImageExpectations expectations(api);
|
||||
{
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
RenderFrame frame(api);
|
||||
|
||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
|
||||
dstRect, srcRenderTargets[srcLevel],
|
||||
srcRect, SamplerMagFilter::LINEAR);
|
||||
api.blitDEPRECATED(TargetBufferFlags::COLOR0, dstRenderTarget,
|
||||
dstRect, srcRenderTargets[srcLevel],
|
||||
srcRect, SamplerMagFilter::LINEAR);
|
||||
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// TODO: for some reason, this test has been disabled. It needs to be tested on all
|
||||
// machines.
|
||||
// EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||
// ScreenshotParams(kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain", 0x0));
|
||||
}
|
||||
flushAndWait();
|
||||
api.commit(swapChain);
|
||||
}
|
||||
|
||||
// TODO: for some reason, this test has been disabled. It needs to be tested on all
|
||||
// machines.
|
||||
// EXPECT_IMAGE(dstRenderTarget, expectations,
|
||||
// ScreenshotParams(kDstTexWidth, kDstTexHeight, "BlitRegionToSwapChain", 0x0));
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "ImageExpectations.h"
|
||||
#include "Lifetimes.h"
|
||||
#include "Shader.h"
|
||||
#include "SharedShaders.h"
|
||||
@@ -220,9 +221,9 @@ TEST_F(BufferUpdatesTest, BufferObjectUpdateWithOffset) {
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||
renderTarget, swapChain, shader.getProgram(), params);
|
||||
|
||||
static const uint32_t expectedHash = 91322442;
|
||||
readPixelsAndAssertHash(
|
||||
"BufferObjectUpdateWithOffset", 512, 512, renderTarget, expectedHash, true);
|
||||
|
||||
EXPECT_IMAGE(renderTarget, getExpectations(),
|
||||
ScreenshotParams(512, 512, "BufferObjectUpdateWithOffset", 91322442));
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
|
||||
@@ -71,8 +71,6 @@ void main() {
|
||||
fragColor = textureLod(test_tex, uv, params.sourceLevel);
|
||||
})";
|
||||
|
||||
static uint32_t sPixelHashResult = 0;
|
||||
|
||||
// Selecting a NPOT texture size seems to exacerbate the bug seen with Intel GPU's.
|
||||
// Note that Filament uses a higher precision format (R11F_G11F_B10F) but this does not seem
|
||||
// necessary to trigger the bug.
|
||||
@@ -96,25 +94,6 @@ struct MaterialParams {
|
||||
float unused;
|
||||
};
|
||||
|
||||
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt) {
|
||||
const size_t size = kTexWidth * kTexHeight * 4;
|
||||
void* buffer = calloc(1, size);
|
||||
auto cb = [](void* buffer, size_t size, void* user) {
|
||||
int w = kTexWidth, h = kTexHeight;
|
||||
const uint32_t* texels = (uint32_t*) buffer;
|
||||
sPixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
|
||||
#ifndef FILAMENT_IOS
|
||||
LinearImage image(w, h, 4);
|
||||
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
|
||||
std::ofstream pngstrm("feedback.png", std::ios::binary | std::ios::trunc);
|
||||
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", "feedback.png");
|
||||
#endif
|
||||
free(buffer);
|
||||
};
|
||||
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
|
||||
dapi.readPixels(rt, 0, 0, kTexWidth, kTexHeight, std::move(pb));
|
||||
}
|
||||
|
||||
// TODO: This test needs work to get Metal and OpenGL to agree on results.
|
||||
// The problems are caused by both uploading and rendering into the same texture, since the OpenGL
|
||||
// backend's readPixels does not work correctly with textures that have image data uploaded.
|
||||
@@ -259,7 +238,8 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
// NOTE: Calling glReadPixels on any miplevel other than the base level
|
||||
// seems to be un-reliable on some GPU's.
|
||||
if (frame == kNumFrames - 1) {
|
||||
dumpScreenshot(api, renderTargets[0]);
|
||||
EXPECT_IMAGE(renderTargets[0], getExpectations(),
|
||||
ScreenshotParams(kTexWidth, kTexHeight, "FeedbackLoops", 0x70695aa1));
|
||||
}
|
||||
|
||||
api.flush();
|
||||
@@ -270,10 +250,6 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
getDriver().purge();
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t expected = 0x70695aa1;
|
||||
printf("Computed hash is 0x%8.8x, Expected 0x%8.8x\n", sPixelHashResult, expected);
|
||||
EXPECT_TRUE(sPixelHashResult == expected);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
|
||||
@@ -246,42 +246,42 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
std::vector<TestCase> testCases;
|
||||
|
||||
// Test basic upload.
|
||||
testCases.emplace_back("RGBA, UBYTE -> RGBA8", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8);
|
||||
testCases.emplace_back("RGBA UBYTE to RGBA8", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8);
|
||||
|
||||
// Test format conversion.
|
||||
// TODO: Vulkan crashes with `Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)'
|
||||
testCases.emplace_back("RGBA, FLOAT -> RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
|
||||
testCases.emplace_back("RGBA FLOAT to RGBA16F", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F);
|
||||
|
||||
// Test texture formats not all backends support natively.
|
||||
// TODO: Vulkan crashes with "VK_FORMAT_R32G32B32_SFLOAT is not supported"
|
||||
testCases.emplace_back("RGB, FLOAT -> RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
|
||||
testCases.emplace_back("RGB, FLOAT -> RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
|
||||
testCases.emplace_back("RGB FLOAT to RGB32F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F);
|
||||
testCases.emplace_back("RGB FLOAT to RGB16F", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB16F);
|
||||
|
||||
// Test packed format uploads.
|
||||
// TODO: Vulkan crashes with "Texture at colorAttachment[0] has usage (0x01) which doesn't specify MTLTextureUsageRenderTarget (0x04)"
|
||||
testCases.emplace_back("RGBA, UINT_2_10_10_10_REV -> RGB10_A2", PixelDataFormat::RGBA, PixelDataType::UINT_2_10_10_10_REV, TextureFormat::RGB10_A2);
|
||||
testCases.emplace_back("RGB, UINT_10F_11F_11F_REV -> R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::UINT_10F_11F_11F_REV, TextureFormat::R11F_G11F_B10F);
|
||||
testCases.emplace_back("RGB, HALF -> R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::HALF, TextureFormat::R11F_G11F_B10F);
|
||||
testCases.emplace_back("RGBA UINT_2_10_10_10_REV to RGB10_A2", PixelDataFormat::RGBA, PixelDataType::UINT_2_10_10_10_REV, TextureFormat::RGB10_A2);
|
||||
testCases.emplace_back("RGB UINT_10F_11F_11F_REV to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::UINT_10F_11F_11F_REV, TextureFormat::R11F_G11F_B10F);
|
||||
testCases.emplace_back("RGB HALF to R11F_G11F_B10F", PixelDataFormat::RGB, PixelDataType::HALF, TextureFormat::R11F_G11F_B10F);
|
||||
|
||||
// Test integer format uploads.
|
||||
// TODO: These cases fail on OpenGL and Vulkan.
|
||||
// TODO: These cases now also fail on Metal, but at some point previously worked.
|
||||
testCases.emplace_back("RGB_INTEGER, UBYTE -> RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
||||
testCases.emplace_back("RGB_INTEGER, USHORT -> RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
||||
testCases.emplace_back("RGB_INTEGER, INT -> RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
|
||||
testCases.emplace_back("RGB_INTEGER UBYTE to RGB8UI", PixelDataFormat::RGB_INTEGER, PixelDataType::UBYTE, TextureFormat::RGB8UI);
|
||||
testCases.emplace_back("RGB_INTEGER USHORT to RGB16UI", PixelDataFormat::RGB_INTEGER, PixelDataType::USHORT, TextureFormat::RGB16UI);
|
||||
testCases.emplace_back("RGB_INTEGER INT to RGB32I", PixelDataFormat::RGB_INTEGER, PixelDataType::INT, TextureFormat::RGB32I);
|
||||
|
||||
// Test uploads with buffer padding.
|
||||
// TODO: Vulkan crashes with "Assertion failed: (offset + size <= allocationSize)"
|
||||
testCases.emplace_back("RGBA, UBYTE -> RGBA8 (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u);
|
||||
testCases.emplace_back("RGBA, FLOAT -> RGBA16F (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 64u);
|
||||
testCases.emplace_back("RGB, FLOAT -> RGB32F (with buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u);
|
||||
testCases.emplace_back("RGBA UBYTE to RGBA8 (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u);
|
||||
testCases.emplace_back("RGBA FLOAT to RGBA16F (with buffer padding)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 64u);
|
||||
testCases.emplace_back("RGB FLOAT to RGB32F (with buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u);
|
||||
|
||||
// Upload subregions separately.
|
||||
// TODO: Vulkan crashes with "Offsets not yet supported"
|
||||
testCases.emplace_back("RGBA, UBYTE -> RGBA8 (subregions)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 0u, true);
|
||||
testCases.emplace_back("RGBA, FLOAT -> RGBA16F (subregions)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 0u, true);
|
||||
testCases.emplace_back("RGBA, UBYTE -> RGBA8 (subregions, buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u, true);
|
||||
testCases.emplace_back("RGB, FLOAT -> RGB32F (subregions, buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u, true);
|
||||
testCases.emplace_back("RGBA UBYTE to RGBA8 (subregions)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 0u, true);
|
||||
testCases.emplace_back("RGBA FLOAT to RGBA16F (subregions)", PixelDataFormat::RGBA, PixelDataType::FLOAT, TextureFormat::RGBA16F, 0u, true);
|
||||
testCases.emplace_back("RGBA UBYTE to RGBA8 (subregions and buffer padding)", PixelDataFormat::RGBA, PixelDataType::UBYTE, TextureFormat::RGBA8, 64u, true);
|
||||
testCases.emplace_back("RGB FLOAT to RGB32F (subregions and buffer padding)", PixelDataFormat::RGB, PixelDataType::FLOAT, TextureFormat::RGB32F, 64u, true);
|
||||
|
||||
auto& api = getDriverApi();
|
||||
|
||||
@@ -339,16 +339,14 @@ TEST_F(LoadImageTest, UpdateImage2D) {
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||
defaultRenderTarget, swapChain, shader.getProgram());
|
||||
|
||||
readPixelsAndAssertHash(t.name, 512, 512, defaultRenderTarget, expectedHash);
|
||||
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
||||
ScreenshotParams(512, 512, t.name, expectedHash));
|
||||
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
}
|
||||
|
||||
api.finish();
|
||||
api.stopCapture();
|
||||
|
||||
flushAndWait();
|
||||
}
|
||||
|
||||
TEST_F(LoadImageTest, UpdateImageSRGB) {
|
||||
@@ -372,7 +370,7 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
|
||||
getSamplerTypeName(textureFormat), fragmentTemplate);
|
||||
Shader shader(api, cleanup, ShaderConfig{
|
||||
.vertexShader = mVertexShader, .fragmentShader = fragment, .uniforms = {{
|
||||
"text_tex", DescriptorType::SAMPLER, samplerInfo
|
||||
"test_tex", DescriptorType::SAMPLER, samplerInfo
|
||||
}}});
|
||||
|
||||
// Create a texture.
|
||||
@@ -416,15 +414,12 @@ TEST_F(LoadImageTest, UpdateImageSRGB) {
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||
defaultRenderTarget, swapChain, shader.getProgram());
|
||||
|
||||
static const uint32_t expectedHash = 359858623;
|
||||
readPixelsAndAssertHash("UpdateImageSRGB", 512, 512, defaultRenderTarget, expectedHash);
|
||||
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
||||
ScreenshotParams(512, 512, "UpdateImageSRGB", 359858623));
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// This ensures all driver commands have finished before exiting the test.
|
||||
api.finish();
|
||||
api.stopCapture();
|
||||
}
|
||||
|
||||
@@ -478,15 +473,12 @@ TEST_F(LoadImageTest, UpdateImageMipLevel) {
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||
defaultRenderTarget, swapChain, shader.getProgram());
|
||||
|
||||
static const uint32_t expectedHash = 3644679986;
|
||||
readPixelsAndAssertHash("UpdateImageMipLevel", 512, 512, defaultRenderTarget, expectedHash);
|
||||
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
||||
ScreenshotParams(512, 512, "UpdateImageMipLevel", 3644679986));
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// This ensures all driver commands have finished before exiting the test.
|
||||
api.finish();
|
||||
api.stopCapture();
|
||||
}
|
||||
|
||||
@@ -538,29 +530,24 @@ TEST_F(LoadImageTest, UpdateImage3D) {
|
||||
|
||||
api.update3DImage(texture, 0, 0, 0, 0, 512, 512, 4, std::move(descriptor));
|
||||
|
||||
api.beginFrame(0, 0, 0);
|
||||
{
|
||||
RenderFrame frame(api);
|
||||
|
||||
// Update samplers.
|
||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture, {
|
||||
.filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
|
||||
});
|
||||
// Update samplers.
|
||||
DescriptorSetHandle descriptorSet = shader.createDescriptorSet(api);
|
||||
api.updateDescriptorSetTexture(descriptorSet, 0, texture,
|
||||
{ .filterMag = SamplerMagFilter::LINEAR,
|
||||
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST });
|
||||
|
||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||
api.bindDescriptorSet(descriptorSet, 1, {});
|
||||
|
||||
renderTriangle({{ DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() }},
|
||||
defaultRenderTarget, swapChain, shader.getProgram());
|
||||
renderTriangle({ { DescriptorSetLayoutHandle{}, shader.getDescriptorSetLayout() } },
|
||||
defaultRenderTarget, swapChain, shader.getProgram());
|
||||
|
||||
static const uint32_t expectedHash = 3644679986;
|
||||
readPixelsAndAssertHash("UpdateImage3D", 512, 512, defaultRenderTarget, expectedHash);
|
||||
EXPECT_IMAGE(defaultRenderTarget, getExpectations(),
|
||||
ScreenshotParams(512, 512, "UpdateImage3D", 3644679986));
|
||||
}
|
||||
|
||||
api.flush();
|
||||
api.commit(swapChain);
|
||||
api.endFrame(0);
|
||||
|
||||
// This ensures all driver commands have finished before exiting the test.
|
||||
api.finish();
|
||||
api.stopCapture();
|
||||
}
|
||||
|
||||
|
||||