Compare commits
2 Commits
ebridgewat
...
pf/test-ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8f088140c | ||
|
|
1c72d108fb |
18
.github/workflows/presubmit.yml
vendored
@@ -108,21 +108,9 @@ jobs:
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- 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: |
|
||||
bash test/utils/get_mesa.sh
|
||||
- name: Run Test
|
||||
- name: Install python prereqs
|
||||
run: pip install mako setuptools pyyaml
|
||||
- name: Run script
|
||||
run: |
|
||||
bash test/renderdiff/test.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -801,7 +801,6 @@ 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)
|
||||
|
||||
@@ -7,3 +7,5 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
- Fix build/compile errors when upgrading to MacOS 15.4
|
||||
|
||||
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.59.3'
|
||||
implementation 'com.google.android.filament:filament-android:1.59.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.59.3'
|
||||
pod 'Filament', '~> 1.59.0'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,16 +7,6 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.59.4
|
||||
|
||||
|
||||
## v1.59.3
|
||||
|
||||
|
||||
## v1.59.2
|
||||
|
||||
- Fix build/compile errors when upgrading to MacOS 15.4
|
||||
|
||||
## v1.59.1
|
||||
|
||||
|
||||
|
||||
@@ -26,10 +26,6 @@ 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)
|
||||
@@ -44,7 +40,6 @@ set_target_properties(shaders PROPERTIES IMPORTED_LOCATION
|
||||
|
||||
set(FILAMAT_INCLUDE_DIRS
|
||||
../../libs/utils/include
|
||||
../../third_party/perfetto
|
||||
)
|
||||
|
||||
include_directories(${FILAMENT_DIR}/include)
|
||||
@@ -60,7 +55,6 @@ target_link_libraries(filamat-jni
|
||||
filabridge
|
||||
shaders
|
||||
utils
|
||||
perfetto
|
||||
log
|
||||
smol-v
|
||||
$<$<STREQUAL:${FILAMENT_SUPPORTS_WEBGPU},ON>:tint>
|
||||
|
||||
@@ -21,10 +21,6 @@ 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)
|
||||
@@ -127,7 +123,6 @@ target_link_libraries(filament-jni
|
||||
PRIVATE android
|
||||
PRIVATE jnigraphics
|
||||
PRIVATE utils
|
||||
PRIVATE perfetto
|
||||
|
||||
# libgeometry is PUBLIC because gltfio uses it.
|
||||
PUBLIC geometry
|
||||
@@ -146,7 +141,6 @@ 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,10 +35,6 @@ 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)
|
||||
@@ -125,7 +121,6 @@ set(GLTFIO_INCLUDE_DIRS
|
||||
../../third_party/meshoptimizer/src
|
||||
../../third_party/robin-map
|
||||
../../third_party/stb
|
||||
../../third_party/perfetto
|
||||
../../libs/utils/include
|
||||
../../libs/ktxreader/include
|
||||
)
|
||||
@@ -134,7 +129,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 perfetto uberzlib log stb ktxreader basis_transcoder zstd uberarchive)
|
||||
target_link_libraries(gltfio-jni filament-jni utils 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)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.59.3
|
||||
VERSION_NAME=1.59.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -56,16 +56,10 @@ 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}"
|
||||
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
|
||||
echo "#include <${include}>" >> out/check-headers/temp.cpp
|
||||
clang -std=c++17 -I "${FILAMENT_HEADERS}" out/check-headers/temp.cpp -c -o /dev/null
|
||||
done
|
||||
echo "Done!"
|
||||
|
||||
@@ -257,11 +257,8 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
src/webgpu/WebGPUConstants.h
|
||||
src/webgpu/WebGPUDriver.cpp
|
||||
src/webgpu/WebGPUDriver.h
|
||||
src/webgpu/WebGPUHandles.cpp
|
||||
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)
|
||||
@@ -510,10 +507,8 @@ if (APPLE OR LINUX)
|
||||
test/Arguments.cpp
|
||||
test/ImageExpectations.cpp
|
||||
test/Lifetimes.cpp
|
||||
test/PlatformRunner.cpp
|
||||
test/Shader.cpp
|
||||
test/SharedShaders.cpp
|
||||
test/Skip.cpp
|
||||
test/test_FeedbackLoops.cpp
|
||||
test/test_Blit.cpp
|
||||
test/test_MissingRequiredAttributes.cpp
|
||||
@@ -537,9 +532,6 @@ if (APPLE OR LINUX)
|
||||
filamat
|
||||
SPIRV
|
||||
spirv-cross-glsl)
|
||||
# Create input/output directories for test result images.
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/images/actual_images)
|
||||
file(COPY test/expected_images DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/images)
|
||||
endif()
|
||||
|
||||
# TODO: Disabling IOS test due to breakage wrt glslang update
|
||||
|
||||
@@ -55,9 +55,4 @@ public:
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
utils::io::ostream& operator<<(utils::io::ostream& out,
|
||||
const filament::backend::BufferObjectStreamDescriptor& b);
|
||||
#endif
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_BUFFEROBJECTSTREAMDESCRIPTOR_H
|
||||
|
||||
@@ -149,6 +149,13 @@ public:
|
||||
* - PlatformEGLAndroid
|
||||
*/
|
||||
bool assertNativeWindowIsValid = false;
|
||||
|
||||
/**
|
||||
* The action to take if a Drawable cannot be acquired. If true, the
|
||||
* frame is aborted instead of panic. This is only supported for:
|
||||
* - PlatformMetal
|
||||
*/
|
||||
bool metalDisablePanicOnDrawableFailure = false;
|
||||
};
|
||||
|
||||
Platform() noexcept;
|
||||
|
||||
@@ -382,26 +382,9 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
using ImageData = std::pair<VkImage, VkDeviceMemory>;
|
||||
virtual ImageData createVkImageFromExternal(ExternalImageHandleRef image) const {
|
||||
return {};
|
||||
return { VK_NULL_HANDLE, VK_NULL_HANDLE };
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
@@ -38,12 +38,6 @@ public:
|
||||
|
||||
[[nodiscard]] wgpu::Instance& getInstance() noexcept { return mInstance; }
|
||||
|
||||
// TODO consider that this functionality is not WebGPU-specific, and thus could be
|
||||
// placed in a generic place and even reused across backends. Alternatively,
|
||||
// a 3rd party library could be considered. However, this was a simple and
|
||||
// quick change and works for now.
|
||||
// gets the size (height and width) of the surface/window
|
||||
[[nodiscard]] wgpu::Extent2D getSurfaceExtent(void* nativeWindow) const;
|
||||
// either returns a valid surface or panics
|
||||
[[nodiscard]] wgpu::Surface createSurface(void* nativeWindow, uint64_t flags);
|
||||
// either returns a valid adapter or panics
|
||||
|
||||
@@ -84,7 +84,7 @@ void CommandStream::execute(void* buffer) {
|
||||
|
||||
Profiler profiler;
|
||||
|
||||
if constexpr (SYSTRACE_TAG) {
|
||||
if (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 constexpr (SYSTRACE_TAG) {
|
||||
if (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);
|
||||
primitive->vertexBuffer = vertexBuffer;
|
||||
primitive->indexBuffer = indexBuffer;
|
||||
MetalVertexBufferInfo const* const vbi = handle_cast<MetalVertexBufferInfo>(vertexBuffer->vbih);
|
||||
primitive->setBuffers(vbi, vertexBuffer, indexBuffer);
|
||||
primitive->type = pt;
|
||||
}
|
||||
|
||||
|
||||
@@ -194,8 +194,12 @@ 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;
|
||||
};
|
||||
@@ -376,6 +380,7 @@ public:
|
||||
|
||||
math::uint2 getAttachmentSize() noexcept;
|
||||
|
||||
bool isDefaultRenderTarget() const { return defaultRenderTarget; }
|
||||
uint8_t getSamples() const { return samples; }
|
||||
|
||||
Attachment getDrawColorAttachment(size_t index);
|
||||
|
||||
@@ -536,6 +536,15 @@ 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));
|
||||
|
||||
@@ -45,6 +45,9 @@ PlatformMetal::~PlatformMetal() noexcept {
|
||||
}
|
||||
|
||||
Driver* PlatformMetal::createDriver(void* /*sharedContext*/, const Platform::DriverConfig& driverConfig) noexcept {
|
||||
pImpl->mDrawableFailureBehavior = driverConfig.metalDisablePanicOnDrawableFailure
|
||||
? DrawableFailureBehavior::ABORT_FRAME
|
||||
: DrawableFailureBehavior::PANIC;
|
||||
return MetalDriverFactory::create(this, driverConfig);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,15 +16,9 @@
|
||||
|
||||
#include "GLUtils.h"
|
||||
|
||||
#include "private/backend/Driver.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/trap.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <stddef.h>
|
||||
#include "private/backend/Driver.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -34,31 +28,38 @@ using namespace utils;
|
||||
namespace GLUtils {
|
||||
|
||||
UTILS_NOINLINE
|
||||
std::string_view getGLErrorString(GLenum error) noexcept {
|
||||
const char* getGLError(GLenum error) noexcept {
|
||||
const char* string = "unknown";
|
||||
switch (error) {
|
||||
case GL_NO_ERROR:
|
||||
return "GL_NO_ERROR";
|
||||
string = "GL_NO_ERROR";
|
||||
break;
|
||||
case GL_INVALID_ENUM:
|
||||
return "GL_INVALID_ENUM";
|
||||
string = "GL_INVALID_ENUM";
|
||||
break;
|
||||
case GL_INVALID_VALUE:
|
||||
return "GL_INVALID_VALUE";
|
||||
string = "GL_INVALID_VALUE";
|
||||
break;
|
||||
case GL_INVALID_OPERATION:
|
||||
return "GL_INVALID_OPERATION";
|
||||
string = "GL_INVALID_OPERATION";
|
||||
break;
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
return "GL_INVALID_FRAMEBUFFER_OPERATION";
|
||||
string = "GL_INVALID_FRAMEBUFFER_OPERATION";
|
||||
break;
|
||||
case GL_OUT_OF_MEMORY:
|
||||
return "GL_OUT_OF_MEMORY";
|
||||
string = "GL_OUT_OF_MEMORY";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "unknown";
|
||||
return string;
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
GLenum checkGLError(io::ostream& out, const char* function, size_t line) noexcept {
|
||||
GLenum const error = glGetError();
|
||||
if (UTILS_VERY_UNLIKELY(error != GL_NO_ERROR)) {
|
||||
auto const string = getGLErrorString(error);
|
||||
if (error != GL_NO_ERROR) {
|
||||
const char* string = getGLError(error);
|
||||
out << "OpenGL error " << io::hex << error << " (" << string << ") in \""
|
||||
<< function << "\" at line " << io::dec << line << io::endl;
|
||||
}
|
||||
@@ -68,39 +69,46 @@ 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 (UTILS_VERY_UNLIKELY(err != GL_NO_ERROR)) {
|
||||
if (err != GL_NO_ERROR) {
|
||||
debug_trap();
|
||||
}
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
std::string_view getFramebufferStatusString(GLenum status) noexcept {
|
||||
const char* getFramebufferStatus(GLenum status) noexcept {
|
||||
const char* string = "unknown";
|
||||
switch (status) {
|
||||
case GL_FRAMEBUFFER_COMPLETE:
|
||||
return "GL_FRAMEBUFFER_COMPLETE";
|
||||
string = "GL_FRAMEBUFFER_COMPLETE";
|
||||
break;
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
|
||||
return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
|
||||
string = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
|
||||
break;
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
|
||||
return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
|
||||
string = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
|
||||
break;
|
||||
case GL_FRAMEBUFFER_UNSUPPORTED:
|
||||
return "GL_FRAMEBUFFER_UNSUPPORTED";
|
||||
string = "GL_FRAMEBUFFER_UNSUPPORTED";
|
||||
break;
|
||||
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
|
||||
case GL_FRAMEBUFFER_UNDEFINED:
|
||||
return "GL_FRAMEBUFFER_UNDEFINED";
|
||||
string = "GL_FRAMEBUFFER_UNDEFINED";
|
||||
break;
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
|
||||
return "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE";
|
||||
string = "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE";
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "unknown";
|
||||
return string;
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
GLenum checkFramebufferStatus(io::ostream& out, GLenum target, const char* function, size_t line) noexcept {
|
||||
GLenum const status = glCheckFramebufferStatus(target);
|
||||
if (UTILS_VERY_UNLIKELY(status != GL_FRAMEBUFFER_COMPLETE)) {
|
||||
auto const string = getFramebufferStatusString(status);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
const char* string = getFramebufferStatus(status);
|
||||
out << "OpenGL framebuffer error " << io::hex << status << " (" << string << ") in \""
|
||||
<< function << "\" at line " << io::dec << line << io::endl;
|
||||
}
|
||||
@@ -110,7 +118,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 (UTILS_VERY_UNLIKELY(status != GL_FRAMEBUFFER_COMPLETE)) {
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
debug_trap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,26 +17,29 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H
|
||||
#define TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "gl_headers.h"
|
||||
|
||||
namespace filament::backend::GLUtils {
|
||||
namespace filament::backend {
|
||||
namespace GLUtils {
|
||||
|
||||
std::string_view getGLErrorString(GLenum error) noexcept;
|
||||
const char* getGLError(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;
|
||||
|
||||
std::string_view getFramebufferStatusString(GLenum err) noexcept;
|
||||
const char* getFramebufferStatus(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;
|
||||
|
||||
@@ -50,7 +53,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 const type) noexcept {
|
||||
constexpr GLuint getComponentCount(ElementType type) noexcept {
|
||||
using ElementType = ElementType;
|
||||
switch (type) {
|
||||
case ElementType::BYTE:
|
||||
@@ -84,29 +87,27 @@ constexpr GLuint getComponentCount(ElementType const type) noexcept {
|
||||
case ElementType::USHORT4:
|
||||
return 4;
|
||||
}
|
||||
// should never happen
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Our enums to GLenum conversions
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
constexpr GLbitfield getAttachmentBitfield(TargetBufferFlags const flags) noexcept {
|
||||
constexpr GLbitfield getAttachmentBitfield(TargetBufferFlags 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 const usage) noexcept {
|
||||
constexpr GLenum getBufferUsage(BufferUsage usage) noexcept {
|
||||
switch (usage) {
|
||||
case BufferUsage::STATIC:
|
||||
return GL_STATIC_DRAW;
|
||||
@@ -115,7 +116,7 @@ constexpr GLenum getBufferUsage(BufferUsage const usage) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
constexpr GLenum getBufferBindingType(BufferObjectBinding const bindingType) noexcept {
|
||||
constexpr GLenum getBufferBindingType(BufferObjectBinding bindingType) noexcept {
|
||||
switch (bindingType) {
|
||||
case BufferObjectBinding::VERTEX:
|
||||
return GL_ARRAY_BUFFER;
|
||||
@@ -134,15 +135,13 @@ constexpr GLenum getBufferBindingType(BufferObjectBinding const bindingType) noe
|
||||
return 0x90D2; // just to return something
|
||||
#endif
|
||||
}
|
||||
// should never happen
|
||||
return GL_ARRAY_BUFFER;
|
||||
}
|
||||
|
||||
constexpr GLboolean getNormalization(bool const normalized) noexcept {
|
||||
constexpr GLboolean getNormalization(bool normalized) noexcept {
|
||||
return GLboolean(normalized ? GL_TRUE : GL_FALSE);
|
||||
}
|
||||
|
||||
constexpr GLenum getComponentType(ElementType const type) noexcept {
|
||||
constexpr GLenum getComponentType(ElementType type) noexcept {
|
||||
using ElementType = ElementType;
|
||||
switch (type) {
|
||||
case ElementType::BYTE:
|
||||
@@ -185,11 +184,9 @@ constexpr GLenum getComponentType(ElementType const type) noexcept {
|
||||
return GL_HALF_FLOAT_OES;
|
||||
#endif
|
||||
}
|
||||
// should never happen
|
||||
return GL_INT;
|
||||
}
|
||||
|
||||
constexpr GLenum getTextureTargetNotExternal(SamplerType const target) noexcept {
|
||||
constexpr GLenum getTextureTargetNotExternal(SamplerType target) noexcept {
|
||||
switch (target) {
|
||||
case SamplerType::SAMPLER_2D:
|
||||
return GL_TEXTURE_2D;
|
||||
@@ -205,16 +202,14 @@ constexpr GLenum getTextureTargetNotExternal(SamplerType const target) noexcept
|
||||
// we should never be here
|
||||
return GL_TEXTURE_2D;
|
||||
}
|
||||
// should never happen
|
||||
return GL_TEXTURE_2D;
|
||||
}
|
||||
|
||||
constexpr GLenum getCubemapTarget(uint16_t const layer) noexcept {
|
||||
constexpr GLenum getCubemapTarget(uint16_t layer) noexcept {
|
||||
assert_invariant(layer <= 5);
|
||||
return GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer;
|
||||
}
|
||||
|
||||
constexpr GLenum getWrapMode(SamplerWrapMode const mode) noexcept {
|
||||
constexpr GLenum getWrapMode(SamplerWrapMode mode) noexcept {
|
||||
using SamplerWrapMode = SamplerWrapMode;
|
||||
switch (mode) {
|
||||
case SamplerWrapMode::REPEAT:
|
||||
@@ -224,8 +219,6 @@ constexpr GLenum getWrapMode(SamplerWrapMode const mode) noexcept {
|
||||
case SamplerWrapMode::MIRRORED_REPEAT:
|
||||
return GL_MIRRORED_REPEAT;
|
||||
}
|
||||
// should never happen
|
||||
return GL_CLAMP_TO_EDGE;
|
||||
}
|
||||
|
||||
constexpr GLenum getTextureFilter(SamplerMinFilter filter) noexcept {
|
||||
@@ -241,8 +234,6 @@ 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 {
|
||||
@@ -250,7 +241,7 @@ constexpr GLenum getTextureFilter(SamplerMagFilter filter) noexcept {
|
||||
}
|
||||
|
||||
|
||||
constexpr GLenum getBlendEquationMode(BlendEquation const mode) noexcept {
|
||||
constexpr GLenum getBlendEquationMode(BlendEquation mode) noexcept {
|
||||
using BlendEquation = BlendEquation;
|
||||
switch (mode) {
|
||||
case BlendEquation::ADD: return GL_FUNC_ADD;
|
||||
@@ -259,11 +250,9 @@ constexpr GLenum getBlendEquationMode(BlendEquation const 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 const mode) noexcept {
|
||||
constexpr GLenum getBlendFunctionMode(BlendFunction mode) noexcept {
|
||||
using BlendFunction = BlendFunction;
|
||||
switch (mode) {
|
||||
case BlendFunction::ZERO: return GL_ZERO;
|
||||
@@ -278,11 +267,9 @@ constexpr GLenum getBlendFunctionMode(BlendFunction const 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 const func) noexcept {
|
||||
constexpr GLenum getCompareFunc(SamplerCompareFunc func) noexcept {
|
||||
switch (func) {
|
||||
case SamplerCompareFunc::LE: return GL_LEQUAL;
|
||||
case SamplerCompareFunc::GE: return GL_GEQUAL;
|
||||
@@ -293,30 +280,28 @@ constexpr GLenum getCompareFunc(SamplerCompareFunc const 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 const mode) noexcept {
|
||||
constexpr GLenum getTextureCompareMode(SamplerCompareMode mode) noexcept {
|
||||
return mode == SamplerCompareMode::NONE ?
|
||||
GL_NONE : GL_COMPARE_REF_TO_TEXTURE;
|
||||
}
|
||||
|
||||
constexpr GLenum getTextureCompareFunc(SamplerCompareFunc const func) noexcept {
|
||||
constexpr GLenum getTextureCompareFunc(SamplerCompareFunc func) noexcept {
|
||||
return getCompareFunc(func);
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr GLenum getDepthFunc(SamplerCompareFunc const func) noexcept {
|
||||
constexpr GLenum getDepthFunc(SamplerCompareFunc func) noexcept {
|
||||
return getCompareFunc(func);
|
||||
}
|
||||
|
||||
constexpr GLenum getStencilFunc(SamplerCompareFunc const func) noexcept {
|
||||
constexpr GLenum getStencilFunc(SamplerCompareFunc func) noexcept {
|
||||
return getCompareFunc(func);
|
||||
}
|
||||
|
||||
constexpr GLenum getStencilOp(StencilOperation const op) noexcept {
|
||||
constexpr GLenum getStencilOp(StencilOperation op) noexcept {
|
||||
switch (op) {
|
||||
case StencilOperation::KEEP: return GL_KEEP;
|
||||
case StencilOperation::ZERO: return GL_ZERO;
|
||||
@@ -327,11 +312,9 @@ constexpr GLenum getStencilOp(StencilOperation const 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 const format) noexcept {
|
||||
constexpr GLenum getFormat(PixelDataFormat format) noexcept {
|
||||
using PixelDataFormat = PixelDataFormat;
|
||||
switch (format) {
|
||||
case PixelDataFormat::RGB: return GL_RGB;
|
||||
@@ -353,11 +336,9 @@ constexpr GLenum getFormat(PixelDataFormat const format) noexcept {
|
||||
default: return GL_NONE;
|
||||
#endif
|
||||
}
|
||||
// should never happen
|
||||
return GL_RGBA;
|
||||
}
|
||||
|
||||
constexpr GLenum getType(PixelDataType const type) noexcept {
|
||||
constexpr GLenum getType(PixelDataType type) noexcept {
|
||||
using PixelDataType = PixelDataType;
|
||||
switch (type) {
|
||||
case PixelDataType::UBYTE: return GL_UNSIGNED_BYTE;
|
||||
@@ -379,12 +360,10 @@ constexpr GLenum getType(PixelDataType const 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 const c) noexcept {
|
||||
constexpr GLenum getSwizzleChannel(TextureSwizzle c) noexcept {
|
||||
using TextureSwizzle = TextureSwizzle;
|
||||
switch (c) {
|
||||
case TextureSwizzle::SUBSTITUTE_ZERO: return GL_ZERO;
|
||||
@@ -394,12 +373,10 @@ constexpr GLenum getSwizzleChannel(TextureSwizzle const 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 const mode) noexcept {
|
||||
constexpr GLenum getCullingMode(CullingMode mode) noexcept {
|
||||
switch (mode) {
|
||||
case CullingMode::NONE:
|
||||
// should never happen
|
||||
@@ -411,13 +388,11 @@ constexpr GLenum getCullingMode(CullingMode const 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 const format) noexcept {
|
||||
TextureFormat format) noexcept {
|
||||
switch (format) {
|
||||
case TextureFormat::R8: return { 0x1909 /*GL_LUMINANCE*/, GL_UNSIGNED_BYTE };
|
||||
case TextureFormat::RGB8: return { GL_RGB, GL_UNSIGNED_BYTE };
|
||||
@@ -438,7 +413,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 const format) noexcept {
|
||||
constexpr /* inline */ GLenum getInternalFormat(TextureFormat format) noexcept {
|
||||
switch (format) {
|
||||
|
||||
/* Formats supported by our ES2 implementations */
|
||||
@@ -686,7 +661,7 @@ public:
|
||||
|
||||
unordered_string_set split(const char* extensions) noexcept;
|
||||
|
||||
} // namespace filament::backend::GLUtils
|
||||
|
||||
} // namespace GLUtils
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_OPENGL_GLUTILS_H
|
||||
|
||||
@@ -2098,7 +2098,6 @@ 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,7 +85,6 @@ OpenGLProgram::~OpenGLProgram() noexcept {
|
||||
delete lazyInitializationData;
|
||||
|
||||
ShaderCompilerService::terminate(mToken);
|
||||
assert_invariant(!mToken);
|
||||
}
|
||||
|
||||
delete [] mUniformsRecords;
|
||||
|
||||
@@ -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 <array>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <tuple>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class OpenGLDriver;
|
||||
@@ -57,8 +57,6 @@ class ShaderCompilerService {
|
||||
|
||||
public:
|
||||
using program_token_t = std::shared_ptr<OpenGLProgramToken>;
|
||||
using shaders_t = std::array<GLuint, Program::SHADER_TYPE_COUNT>;
|
||||
using shaders_source_t = std::array<utils::CString, Program::SHADER_TYPE_COUNT>;
|
||||
|
||||
explicit ShaderCompilerService(OpenGLDriver& driver);
|
||||
|
||||
@@ -84,7 +82,6 @@ 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
|
||||
@@ -93,12 +90,6 @@ 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);
|
||||
@@ -106,7 +97,7 @@ public:
|
||||
private:
|
||||
struct Job {
|
||||
template<typename FUNC>
|
||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {} // NOLINT(*-explicit-constructor)
|
||||
Job(FUNC&& fn) : fn(std::forward<FUNC>(fn)) {}
|
||||
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) {
|
||||
@@ -135,49 +126,39 @@ private:
|
||||
using ContainerType = std::tuple<CompilerPriorityQueue, program_token_t, Job>;
|
||||
std::vector<ContainerType> mRunAtNextTickOps;
|
||||
|
||||
GLuint initialize(program_token_t& token);
|
||||
void ensureTokenIsReady(program_token_t const& token);
|
||||
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
|
||||
|
||||
void runAtNextTick(CompilerPriorityQueue priority, program_token_t const& token,
|
||||
Job job) noexcept;
|
||||
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
|
||||
|
||||
static void compileShaders(
|
||||
OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
bool multiview,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
|
||||
std::array<utils::CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept;
|
||||
|
||||
static void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
|
||||
char* source, size_t len) noexcept;
|
||||
|
||||
static void process_OVR_multiview2(OpenGLContext& 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;
|
||||
|
||||
static GLuint linkProgram(OpenGLContext& context,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> 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 executeTickOps() noexcept;
|
||||
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;
|
||||
bool cancelTickOp(program_token_t token) noexcept;
|
||||
// order of insertion is important
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
#define COREVIDEO_SILENCE_GL_DEPRECATION
|
||||
|
||||
#include "CocoaExternalImage.h"
|
||||
#include "../GLUtils.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Log.h>
|
||||
#include "../GLUtils.h"
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
#include <backend/BufferDescriptor.h>
|
||||
#include <backend/BufferObjectStreamDescriptor.h>
|
||||
#include <backend/DescriptorSetOffsetArray.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/PipelineState.h>
|
||||
@@ -438,10 +437,6 @@ io::ostream& operator<<(io::ostream& out, BufferDescriptor const& b) {
|
||||
<< ", user=" << b.getUser() << " }";
|
||||
}
|
||||
|
||||
io::ostream& operator<<(io::ostream& out, const BufferObjectStreamDescriptor& b) {
|
||||
return out << "BufferObjectStreamDescriptor{ streams(" << b.mStreams.size() << ")=... }";
|
||||
}
|
||||
|
||||
io::ostream& operator<<(io::ostream& out, PixelBufferDescriptor const& b) {
|
||||
BufferDescriptor const& base = static_cast<BufferDescriptor const&>(b);
|
||||
return out << "PixelBufferDescriptor{ " << base
|
||||
|
||||
@@ -295,8 +295,6 @@ 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];
|
||||
@@ -306,7 +304,6 @@ void CommandBufferPool::gc() {
|
||||
}
|
||||
});
|
||||
mSubmitted &= ~reclaimed;
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void CommandBufferPool::update() {
|
||||
@@ -336,9 +333,7 @@ void CommandBufferPool::wait() {
|
||||
mSubmitted.forEachSetBit([this, &count, &fences] (size_t index) {
|
||||
fences[count++] = mBuffers[index]->getVkFence();
|
||||
});
|
||||
if (count) {
|
||||
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
|
||||
}
|
||||
vkWaitForFences(mDevice, count, fences, VK_TRUE, UINT64_MAX);
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG)
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_FORWARDED_FLAG | FVK_DEBUG_VALIDATION)
|
||||
#else
|
||||
#define FVK_DEBUG_FLAGS 0
|
||||
#endif
|
||||
@@ -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_CALL()
|
||||
#define FVK_PROFILE_MARKER(marker) SYSTRACE_CALL()
|
||||
#define FVK_SYSTRACE_SCOPE() SYSTRACE_NAME(__func__)
|
||||
#define FVK_PROFILE_MARKER(marker) FVK_SYSTRACE_SCOPE()
|
||||
|
||||
#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,10 +203,11 @@ public:
|
||||
DescriptorInfinitePool(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VkDescriptorSet obtainSet(DescriptorCount const& count, VkDescriptorSetLayout vklayout) {
|
||||
VkDescriptorSet obtainSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
auto const vklayout = layout->getVkLayout();
|
||||
DescriptorPool* sameTypePool = nullptr;
|
||||
for (auto& pool: mPools) {
|
||||
if (!pool->canAllocate(count)) {
|
||||
if (!pool->canAllocate(layout->count)) {
|
||||
continue;
|
||||
}
|
||||
if (auto set = pool->obtainSet(vklayout); set != VK_NULL_HANDLE) {
|
||||
@@ -224,7 +225,8 @@ public:
|
||||
}
|
||||
|
||||
// We need to increase the set of pools by one.
|
||||
mPools.push_back(std::make_unique<DescriptorPool>(mDevice, count, capacity));
|
||||
mPools.push_back(std::make_unique<DescriptorPool>(mDevice,
|
||||
DescriptorCount::fromLayoutBitmask(layout->bitmask), capacity));
|
||||
auto& pool = mPools.back();
|
||||
auto ret = pool->obtainSet(vklayout);
|
||||
assert_invariant(ret != VK_NULL_HANDLE && "failed to obtain a set?");
|
||||
@@ -274,36 +276,39 @@ void VulkanDescriptorSetCache::unbind(uint8_t setIndex) {
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::commit(VulkanCommandBuffer* commands,
|
||||
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask const& useExternalSamplers,
|
||||
fvkutils::DescriptorSetMask const& setMask) {
|
||||
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask const& setMask) {
|
||||
// setMask indicates the set of descriptor sets the driver wants to bind, curMask is the
|
||||
// actual set of sets that *needs* to be bound.
|
||||
fvkutils::DescriptorSetMask curMask = setMask;
|
||||
|
||||
auto const& updateSets = mStashedSets;
|
||||
curMask.forEachSetBit([&](size_t index) {
|
||||
if (!updateSets[index]) {
|
||||
curMask.unset(index);
|
||||
}
|
||||
});
|
||||
auto& updateSets = mStashedSets;
|
||||
bool const pipelineLayoutIsSame = mLastBoundInfo.pipelineLayout == pipelineLayout;
|
||||
|
||||
if (mLastBoundInfo.pipelineLayout == pipelineLayout) {
|
||||
if (pipelineLayoutIsSame) {
|
||||
auto& lastBoundSets = mLastBoundInfo.boundSets;
|
||||
curMask.forEachSetBit([&](size_t index) {
|
||||
if (updateSets[index] == lastBoundSets[index] && !useExternalSamplers[index]) {
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
setMask.forEachSetBit([&](size_t index) {
|
||||
if (!updateSets[index]) {
|
||||
curMask.unset(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
curMask.forEachSetBit([&](size_t index) {
|
||||
curMask.forEachSetBit([&updateSets, commands, pipelineLayout](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, &vkset, set->uniqueDynamicUboCount, set->getOffsets()->data());
|
||||
1, &set->getVkSet(), set->uniqueDynamicUboCount, set->getOffsets()->data());
|
||||
commands->acquire(set);
|
||||
});
|
||||
|
||||
@@ -329,7 +334,7 @@ void VulkanDescriptorSetCache::updateBuffer(fvkmemory::resource_ptr<VulkanDescri
|
||||
if (set->dynamicUboMask.test(binding)) {
|
||||
type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
|
||||
}
|
||||
VkWriteDescriptorSet descriptorWrite = {
|
||||
VkWriteDescriptorSet const descriptorWrite = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.dstSet = set->getVkSet(),
|
||||
.dstBinding = binding,
|
||||
@@ -338,17 +343,12 @@ 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::updateSamplerImpl(VkDescriptorSet vkset, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept {
|
||||
void VulkanDescriptorSetCache::updateSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
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,29 +364,16 @@ void VulkanDescriptorSetCache::updateSamplerImpl(VkDescriptorSet vkset, uint8_t
|
||||
.imageLayout = fvkutils::getVkLayout(texture->getDefaultLayout()),
|
||||
};
|
||||
|
||||
VkWriteDescriptorSet descriptorWrite = {
|
||||
VkWriteDescriptorSet const descriptorWrite = {
|
||||
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
|
||||
.pNext = nullptr,
|
||||
.dstSet = vkset,
|
||||
.dstSet = set->getVkSet(),
|
||||
.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);
|
||||
}
|
||||
|
||||
@@ -396,32 +383,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 = mDescriptorPool->obtainSet(layout->count, layout->getVkLayout());
|
||||
auto const vkSet = getVkSet(layout);
|
||||
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*) { this->manualRecycle(count, vklayout, vkSet); },
|
||||
vkSet);
|
||||
auto set = fvkmemory::resource_ptr<VulkanDescriptorSet>::make(mResourceManager, handle,
|
||||
layout->bitmask.dynamicUbo, layout->count.dynamicUbo,
|
||||
[vkSet, count, vklayout, this](VulkanDescriptorSet*) {
|
||||
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts
|
||||
// down).
|
||||
if (mDescriptorPool) {
|
||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||
}
|
||||
});
|
||||
set->setVkSet(vkSet);
|
||||
return set;
|
||||
}
|
||||
|
||||
VkDescriptorSet VulkanDescriptorSetCache::getVkSet(DescriptorCount const& count,
|
||||
VkDescriptorSetLayout vklayout) {
|
||||
return mDescriptorPool->obtainSet(count, vklayout);
|
||||
VkDescriptorSet VulkanDescriptorSetCache::getVkSet(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
return mDescriptorPool->obtainSet(layout);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::manualRecycle(VulkanDescriptorSetLayout::Count const& count,
|
||||
void VulkanDescriptorSetCache::manualRecyle(VulkanDescriptorSetLayout::Count const& count,
|
||||
VkDescriptorSetLayout vklayout, VkDescriptorSet vkSet) {
|
||||
// Note that mDescriptorPool could be gone due to terminate (when the backend shuts
|
||||
// down).
|
||||
if (mDescriptorPool) {
|
||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||
}
|
||||
mDescriptorPool->recycle(count, vklayout, vkSet);
|
||||
}
|
||||
|
||||
void VulkanDescriptorSetCache::gc() { mStashedSets = {}; }
|
||||
|
||||
@@ -43,7 +43,6 @@ 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();
|
||||
@@ -57,10 +56,6 @@ 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;
|
||||
|
||||
@@ -70,17 +65,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 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 (or internally within this
|
||||
// class).
|
||||
VkDescriptorSet getVkSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
// This method is meant to be used with external samplers
|
||||
void manualRecycle(VulkanDescriptorSetLayout::Count const& count, VkDescriptorSetLayout vklayout,
|
||||
// This method is only meant to be used with external samplers.
|
||||
void manualRecyle(VulkanDescriptorSetLayout::Count const& count, VkDescriptorSetLayout vklayout,
|
||||
VkDescriptorSet vkSet);
|
||||
|
||||
DescriptorSetArray const& getBoundSets() const { return mStashedSets; }
|
||||
@@ -88,9 +83,6 @@ public:
|
||||
void gc();
|
||||
|
||||
private:
|
||||
void updateSamplerImpl(VkDescriptorSet set, uint8_t binding,
|
||||
fvkmemory::resource_ptr<VulkanTexture> texture, VkSampler sampler) noexcept;
|
||||
|
||||
class DescriptorInfinitePool;
|
||||
|
||||
VkDevice mDevice;
|
||||
|
||||
@@ -127,7 +127,6 @@ void VulkanDescriptorSetLayoutCache::terminate() noexcept {
|
||||
|
||||
VkDescriptorSetLayout VulkanDescriptorSetLayoutCache::getVkLayout(
|
||||
VulkanDescriptorSetLayout::Bitmask const& bitmasks,
|
||||
fvkutils::SamplerBitmask externalSamplers,
|
||||
utils::FixedCapacityVector<VkSampler> immutableSamplers) {
|
||||
LayoutKey key = {
|
||||
.bitmask = bitmasks,
|
||||
@@ -142,7 +141,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, externalSamplers,
|
||||
count += appendSamplerBindings(&toBind[count], bitmasks.sampler, bitmasks.externalSampler,
|
||||
immutableSamplers);
|
||||
count += appendBindings(&toBind[count], VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT,
|
||||
bitmasks.inputAttachment);
|
||||
@@ -161,9 +160,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,
|
||||
std::move(info), getVkLayout(maskGroup, maskGroup.externalSampler));
|
||||
info);
|
||||
layout->setVkLayout(getVkLayout(layout->bitmask));
|
||||
return layout;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ 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,6 +30,7 @@
|
||||
#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>
|
||||
@@ -197,7 +198,7 @@ Dispatcher VulkanDriver::getDispatcher() const noexcept {
|
||||
}
|
||||
|
||||
VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig)
|
||||
Platform::DriverConfig const& driverConfig) noexcept
|
||||
: mPlatform(platform),
|
||||
mResourceManager(driverConfig.handleArenaSize, driverConfig.disableHandleUseAfterFreeCheck,
|
||||
driverConfig.disableHeapHandleTags),
|
||||
@@ -250,7 +251,7 @@ VulkanDriver::~VulkanDriver() noexcept = default;
|
||||
|
||||
UTILS_NOINLINE
|
||||
Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig) {
|
||||
Platform::DriverConfig const& driverConfig) noexcept {
|
||||
#if 0
|
||||
// this is useful for development, but too verbose even for debug builds
|
||||
// For reference on a 64-bits machine in Release mode:
|
||||
@@ -330,7 +331,7 @@ void VulkanDriver::terminate() {
|
||||
|
||||
mStagePool.terminate();
|
||||
mPipelineCache.terminate();
|
||||
mFramebufferCache.terminate();
|
||||
mFramebufferCache.reset();
|
||||
mSamplerCache.terminate();
|
||||
mDescriptorSetLayoutCache.terminate();
|
||||
mDescriptorSetCache.terminate();
|
||||
@@ -437,7 +438,6 @@ void VulkanDriver::updateDescriptorSetTexture(
|
||||
};
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(cacheParams);
|
||||
mDescriptorSetCache.updateSampler(set, binding, texture, vksampler);
|
||||
mExternalImageManager.clearTextureBinding(set, binding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +458,6 @@ 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);
|
||||
@@ -469,14 +468,12 @@ 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();
|
||||
@@ -486,14 +483,12 @@ 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);
|
||||
@@ -504,14 +499,12 @@ 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);
|
||||
@@ -522,14 +515,12 @@ 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();
|
||||
@@ -539,7 +530,6 @@ void VulkanDriver::destroyBufferObject(Handle<HwBufferObject> boh) {
|
||||
if (!boh) {
|
||||
return;
|
||||
}
|
||||
FVK_SYSTRACE_SCOPE();
|
||||
auto bo = resource_ptr<VulkanBufferObject>::cast(&mResourceManager, boh);
|
||||
bo.dec();
|
||||
}
|
||||
@@ -585,49 +575,36 @@ 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 metadata = mPlatform->extractExternalImageMetadata(externalImage);
|
||||
auto const& metadata = mPlatform->extractExternalImageMetadata(externalImage);
|
||||
|
||||
assert_invariant(width == metadata.width);
|
||||
assert_invariant(height == metadata.height);
|
||||
|
||||
// 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.
|
||||
// In theory the following are reasonable expectations, but in practice it's hard for client's
|
||||
// to match up the dimensions of the texture with that of the AHB.
|
||||
// assert_invariant(width == metadata.width);
|
||||
// assert_invariant(height == metadata.height);
|
||||
// assert_invariant(format == metadata.filamentFormat);
|
||||
// assert_invariant(fvkutils::getVkFormat(format) == metadata.format);
|
||||
|
||||
auto imgData = mPlatform->createVkImageFromExternal(externalImage);
|
||||
VkImage vkimg;
|
||||
VkDeviceMemory deviceMemory;
|
||||
std::tie(vkimg, deviceMemory) = 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 =
|
||||
VkSamplerYcbcrConversion conversion =
|
||||
mExternalImageManager.getVkSamplerYcbcrConversion(metadata);
|
||||
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mContext,
|
||||
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()) {
|
||||
auto texture = resource_ptr<VulkanTexture>::make(&mResourceManager, th, mContext,
|
||||
mPlatform->getDevice(), mAllocator, &mResourceManager, &mCommands, vkimg, deviceMemory,
|
||||
metadata.format, conversion, metadata.samples, metadata.width, metadata.height,
|
||||
metadata.layers, usage, mStagePool);
|
||||
|
||||
if (conversion != VK_NULL_HANDLE) {
|
||||
mExternalImageManager.addExternallySampledTexture(texture, externalImage);
|
||||
}
|
||||
|
||||
// Unlike uploaded textures or swapchains, we need to explicit transition this
|
||||
// texture into the read layout.
|
||||
auto& commands = mCommands.get();
|
||||
texture->transitionLayout(&commands, texture->getPrimaryViewRange(), VulkanLayout::READ_ONLY);
|
||||
|
||||
texture.inc();
|
||||
}
|
||||
|
||||
@@ -777,7 +754,6 @@ 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.
|
||||
@@ -819,7 +795,6 @@ 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();
|
||||
}
|
||||
@@ -834,6 +809,7 @@ void VulkanDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||
|
||||
if (layout->hasExternalSamplers()) {
|
||||
mAppState.hasExternalSamplerLayouts = true;
|
||||
mExternalImageManager.addDescriptorSet(layout, set);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,13 +930,17 @@ void VulkanDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
void VulkanDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> dslh) {
|
||||
auto layout = resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, dslh);
|
||||
layout.dec();
|
||||
|
||||
if (layout->hasExternalSamplers()) {
|
||||
mExternalImageManager.removeDescriptorSetLayout(layout);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::destroyDescriptorSet(Handle<HwDescriptorSet> dsh) {
|
||||
auto set = resource_ptr<VulkanDescriptorSet>::cast(&mResourceManager, dsh);
|
||||
set.dec();
|
||||
|
||||
if (mAppState.hasExternalSamplers() && set->getExternalSamplerVkSet() != VK_NULL_HANDLE) {
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
mExternalImageManager.removeDescriptorSet(set);
|
||||
}
|
||||
}
|
||||
@@ -1563,7 +1543,7 @@ void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
|
||||
swapChain->acquire(resized);
|
||||
|
||||
if (resized) {
|
||||
mFramebufferCache.resetFramebuffers();
|
||||
mFramebufferCache.reset();
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(mDefaultRenderTarget)) {
|
||||
@@ -1764,69 +1744,37 @@ 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]);
|
||||
|
||||
// We need to determine whether to delay bindning until draw().
|
||||
mPipelineState.bindInDraw.first = false;
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
auto const haveExternalSamplers = [&](auto layoutHandle) {
|
||||
if (!layoutHandle) {
|
||||
auto& layouts = pipelineState.pipelineLayout.setLayout;
|
||||
auto haveExternalSamplers = [&](auto hwHandle) {
|
||||
if (!hwHandle) {
|
||||
return false;
|
||||
}
|
||||
return layoutHandle->hasExternalSamplers();
|
||||
auto layout =
|
||||
resource_ptr<VulkanDescriptorSetLayout>::cast(&mResourceManager, hwHandle);
|
||||
return layout->hasExternalSamplers();
|
||||
};
|
||||
if (std::any_of(layoutHandles.begin(), layoutHandles.end(), haveExternalSamplers)) {
|
||||
BindInDrawBundle bundle = {
|
||||
.pipelineState = pipelineState,
|
||||
.dsLayoutHandles = layoutHandles,
|
||||
.descriptorSetMask = descriptorSetMask,
|
||||
};
|
||||
mPipelineState.bindInDraw = { true, bundle };
|
||||
if (std::any_of(layouts.begin(), layouts.end(), haveExternalSamplers)) {
|
||||
mPipelineState.bindInDraw = { true, pipelineState };
|
||||
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);
|
||||
bindPipelineImpl(pipelineState);
|
||||
}
|
||||
|
||||
void VulkanDriver::bindPipelineImpl(PipelineState const& pipelineState,
|
||||
VkPipelineLayout pipelineLayout, fvkutils::DescriptorSetMask descriptorSetMask) {
|
||||
void VulkanDriver::bindPipelineImpl(PipelineState const& pipelineState) {
|
||||
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, pipelineState.program);
|
||||
auto program = resource_ptr<VulkanProgram>::cast(&mResourceManager, programHandle);
|
||||
commands->acquire(program);
|
||||
|
||||
// Update the VK raster state.
|
||||
@@ -1868,11 +1816,29 @@ void VulkanDriver::bindPipelineImpl(PipelineState const& pipelineState,
|
||||
mPipelineCache.bindPrimitiveTopology(topology);
|
||||
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi->getAttributeCount());
|
||||
|
||||
// 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;
|
||||
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};
|
||||
|
||||
mPipelineState = {
|
||||
.program = program,
|
||||
.pipelineLayout = pipelineLayout,
|
||||
.descriptorSetMask = fvkutils::DescriptorSetMask(descriptorSetMaskTable[layoutCount]),
|
||||
.bindInDraw = {false, {}},
|
||||
};
|
||||
|
||||
mPipelineCache.bindLayout(pipelineLayout);
|
||||
mPipelineCache.bindPipeline(mCurrentRenderPass.commandBuffer);
|
||||
@@ -1917,36 +1883,20 @@ 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;
|
||||
|
||||
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);
|
||||
if (mAppState.hasExternalSamplers()) {
|
||||
auto const& [bindInDraw, pipelineSt] = mPipelineState.bindInDraw;
|
||||
bool const hasUpdated =
|
||||
mExternalImageManager.prepareBindSets(mDescriptorSetCache.getBoundSets());
|
||||
if (bindInDraw || hasUpdated) {
|
||||
bindPipelineImpl(pipelineSt);
|
||||
}
|
||||
mPipelineState.bindInDraw.first = false;
|
||||
}
|
||||
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer, mPipelineState.pipelineLayout,
|
||||
setsWithExternalSamplers, mPipelineState.descriptorSetMask);
|
||||
|
||||
mDescriptorSetCache.commit(mCurrentRenderPass.commandBuffer,
|
||||
mPipelineState.pipelineLayout,
|
||||
mPipelineState.descriptorSetMask);
|
||||
|
||||
// Finally, make the actual draw call. TODO: support subranges
|
||||
uint32_t const firstIndex = indexOffset;
|
||||
|
||||
@@ -55,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);
|
||||
Platform::DriverConfig const& driverConfig) noexcept;
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
|
||||
// Encapsulates the VK_EXT_debug_utils extension. In particular, we use
|
||||
@@ -90,8 +90,8 @@ private:
|
||||
void debugCommandBegin(CommandStream* cmds, bool synchronous,
|
||||
const char* methodName) noexcept override;
|
||||
|
||||
VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig);
|
||||
inline VulkanDriver(VulkanPlatform* platform, VulkanContext const& context,
|
||||
Platform::DriverConfig const& driverConfig) noexcept;
|
||||
|
||||
~VulkanDriver() noexcept override;
|
||||
|
||||
@@ -120,8 +120,7 @@ private:
|
||||
|
||||
private:
|
||||
void collectGarbage();
|
||||
void bindPipelineImpl(PipelineState const& pipelineState, VkPipelineLayout pipelineLayout,
|
||||
fvkutils::DescriptorSetMask descriptorSetMask);
|
||||
void bindPipelineImpl(PipelineState const& pipelineState);
|
||||
|
||||
VulkanPlatform* mPlatform = nullptr;
|
||||
fvkmemory::ResourceManager mResourceManager;
|
||||
@@ -149,24 +148,14 @@ private:
|
||||
VulkanExternalImageManager mExternalImageManager;
|
||||
|
||||
// This is necessary for us to write to push constants after binding a pipeline.
|
||||
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 = {};
|
||||
};
|
||||
|
||||
struct {
|
||||
// For push constant
|
||||
resource_ptr<VulkanProgram> program = {};
|
||||
resource_ptr<VulkanProgram> program;
|
||||
// For push commiting dynamic ubos in draw()
|
||||
VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||
fvkutils::DescriptorSetMask descriptorSetMask = {};
|
||||
VkPipelineLayout pipelineLayout;
|
||||
fvkutils::DescriptorSetMask descriptorSetMask;
|
||||
|
||||
std::pair<bool, BindInDrawBundle> bindInDraw = {false, {}};
|
||||
std::pair<bool, PipelineState> bindInDraw = {false, {}};
|
||||
} mPipelineState = {};
|
||||
|
||||
struct {
|
||||
|
||||
@@ -31,51 +31,13 @@ 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) {
|
||||
void erase(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
|
||||
} // anonymous
|
||||
|
||||
VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform,
|
||||
VulkanSamplerCache* samplerCache, VulkanYcbcrConversionCache* ycbcrConversionCache,
|
||||
@@ -90,6 +52,7 @@ VulkanExternalImageManager::VulkanExternalImageManager(VulkanPlatform* platform,
|
||||
VulkanExternalImageManager::~VulkanExternalImageManager() = default;
|
||||
|
||||
void VulkanExternalImageManager::terminate() {
|
||||
mSetAndLayouts.clear();
|
||||
mSetBindings.clear();
|
||||
mImages.clear();
|
||||
}
|
||||
@@ -98,50 +61,45 @@ 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) {
|
||||
bool VulkanExternalImageManager::prepareBindSets(SetArray const& sets) {
|
||||
bool hasUpdated = false;
|
||||
for (auto set: sets) {
|
||||
if (!set) {
|
||||
continue;
|
||||
}
|
||||
if (hasExternalSampler(set)) {
|
||||
updateSetAndLayout(set, layout);
|
||||
shouldUseExternalSampler.set(i);
|
||||
if (auto itr = std::find_if(mSetAndLayouts.begin(), mSetAndLayouts.end(),
|
||||
[&](auto const& setAndLayout) { return setAndLayout.first == set; });
|
||||
itr != mSetAndLayouts.end()) {
|
||||
hasUpdated = updateSetAndLayout(itr->first, itr->second) || hasUpdated;
|
||||
}
|
||||
}
|
||||
return shouldUseExternalSampler;
|
||||
return hasUpdated;
|
||||
}
|
||||
|
||||
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(
|
||||
bool VulkanExternalImageManager::updateSetAndLayout(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout) {
|
||||
utils::FixedCapacityVector<
|
||||
std::tuple<uint8_t, VkSampler, fvkmemory::resource_ptr<VulkanTexture>>>
|
||||
samplerAndBindings;
|
||||
auto findImage = [&](fvkmemory::resource_ptr<VulkanTexture> texture) -> ImageData* {
|
||||
auto itr = std::find_if(mImages.begin(), mImages.end(), [&](ImageData const& data) {
|
||||
return data.ptr == texture;
|
||||
});
|
||||
assert_invariant(itr != mImages.end());
|
||||
return &(*itr);
|
||||
};
|
||||
|
||||
//std::vector<std::pair<uint8_t, ImageData*>> externalImages;
|
||||
utils::FixedCapacityVector<std::pair<uint8_t, VkSampler>> samplerAndBindings;
|
||||
samplerAndBindings.reserve(MAX_SAMPLER_COUNT);
|
||||
|
||||
fvkutils::SamplerBitmask actualExternalSamplers;
|
||||
bool hasImageUpdates = false;
|
||||
for (auto& bindingInfo : mSetBindings) {
|
||||
if (bindingInfo.set != set || bindingInfo.bound) {
|
||||
if (bindingInfo.set != set) {
|
||||
continue;
|
||||
}
|
||||
auto& imageData = findImage(mImages, bindingInfo.image);
|
||||
updateImage(&imageData);
|
||||
auto imageData = findImage(bindingInfo.image);
|
||||
hasImageUpdates = updateImage(imageData) || hasImageUpdates;
|
||||
|
||||
auto samplerParams = bindingInfo.samplerParams;
|
||||
// according to spec, these must match chromaFilter
|
||||
@@ -151,64 +109,88 @@ void VulkanExternalImageManager::updateSetAndLayout(
|
||||
|
||||
auto sampler = mSamplerCache->getSampler({
|
||||
.sampler = samplerParams,
|
||||
.conversion = imageData.conversion,
|
||||
.conversion = imageData->conversion,
|
||||
});
|
||||
actualExternalSamplers.set(bindingInfo.binding);
|
||||
samplerAndBindings.push_back({ bindingInfo.binding, sampler, bindingInfo.image });
|
||||
bindingInfo.bound = true;
|
||||
samplerAndBindings.push_back({ bindingInfo.binding, sampler });
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
// We need to sort by binding number
|
||||
std::sort(samplerAndBindings.begin(), samplerAndBindings.end());
|
||||
|
||||
utils::FixedCapacityVector<VkSampler> outSamplers;
|
||||
outSamplers.reserve(MAX_SAMPLER_COUNT);
|
||||
std::for_each(samplerAndBindings.begin(), samplerAndBindings.end(),
|
||||
[&](auto const& b) { outSamplers.push_back(std::get<1>(b)); });
|
||||
[&](auto const& b) { outSamplers.push_back(b.second); });
|
||||
|
||||
VkDescriptorSetLayout const oldLayout = layout->getExternalSamplerVkLayout();
|
||||
VkDescriptorSetLayout const newLayout = mDescriptorSetLayoutCache->getVkLayout(layout->bitmask,
|
||||
actualExternalSamplers, outSamplers);
|
||||
VkDescriptorSetLayout const oldLayout = layout->getVkLayout();
|
||||
VkDescriptorSetLayout const newLayout =
|
||||
mDescriptorSetLayoutCache->getVkLayout(layout->bitmask, outSamplers);
|
||||
bool const hasLayoutUpdate = oldLayout != newLayout;
|
||||
layout->setVkLayout(newLayout);
|
||||
|
||||
// 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);
|
||||
assert_invariant(
|
||||
(!hasImageUpdates && !hasLayoutUpdate) ||
|
||||
(hasImageUpdates && hasLayoutUpdate));
|
||||
|
||||
// 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);
|
||||
if (!hasLayoutUpdate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
set->setExternalSamplerVkSet(newSet, [&](VulkanDescriptorSet*) {
|
||||
mDescriptorSetCache->manualRecycle(layout->count, newLayout, newSet);
|
||||
auto foldBitsInHalf = [](auto bitset) {
|
||||
constexpr size_t BITMASK_LOWER_BITS_LEN = sizeof(bitset) * 4;
|
||||
decltype(bitset) outBitset;
|
||||
bitset.forEachSetBit([&](size_t index) { outBitset.set(index % BITMASK_LOWER_BITS_LEN); });
|
||||
return outBitset;
|
||||
};
|
||||
// We need to build a new descriptor set from the new layout
|
||||
VkDescriptorSet oldSet = set->getVkSet();
|
||||
VkDescriptorSet newSet = mDescriptorSetCache->getVkSet(layout);
|
||||
|
||||
using Bitmask = fvkutils::UniformBufferBitmask;
|
||||
static_assert(sizeof(Bitmask) * 8 == fvkutils::MAX_DESCRIPTOR_SET_BITMASK_BITS);
|
||||
|
||||
auto const ubo = layout->bitmask.ubo | layout->bitmask.dynamicUbo;
|
||||
auto const samplers = layout->bitmask.sampler & (~layout->bitmask.externalSampler);
|
||||
|
||||
// each bitmask denotes a binding index, and separated into two stages - vertex and buffer
|
||||
// We fold the two stages into just the lower half of the bits to denote a combined set of
|
||||
// bindings.
|
||||
Bitmask const copyBindings = foldBitsInHalf(ubo | samplers);
|
||||
|
||||
// TODO: fix the size for better memory
|
||||
std::vector<VkCopyDescriptorSet> copies;
|
||||
copyBindings.forEachSetBit([&](size_t index) {
|
||||
copies.push_back({
|
||||
.sType = VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET,
|
||||
.srcSet = oldSet,
|
||||
.srcBinding = (uint32_t) index,
|
||||
.dstSet = newSet,
|
||||
.dstBinding = (uint32_t) index,
|
||||
.descriptorCount = 1,
|
||||
});
|
||||
if (oldLayout != newLayout) {
|
||||
layout->setExternalSamplerVkLayout(newLayout);
|
||||
}
|
||||
}
|
||||
});
|
||||
vkUpdateDescriptorSets(mPlatform->getDevice(), 0, nullptr, copies.size(), copies.data());
|
||||
|
||||
// Update the external samplers in the set
|
||||
for (auto& [binding, sampler, image]: samplerAndBindings) {
|
||||
mDescriptorSetCache->updateSamplerForExternalSamplerSet(set, binding, image);
|
||||
set->setVkSet(newSet);
|
||||
|
||||
// We need to release the vkset, which is no longer used, back into the pool.
|
||||
mDescriptorSetCache->manualRecyle(layout->count, oldLayout, oldSet);
|
||||
|
||||
// We need to update the external samplers in the set
|
||||
for (auto& bindingInfo: mSetBindings) {
|
||||
if (bindingInfo.set != set) {
|
||||
continue;
|
||||
}
|
||||
mDescriptorSetCache->updateSampler(set, bindingInfo.binding, bindingInfo.image,
|
||||
VK_NULL_HANDLE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
VkSamplerYcbcrConversion VulkanExternalImageManager::getVkSamplerYcbcrConversion(
|
||||
VulkanPlatform::ExternalImageMetadata const& metadata) {
|
||||
// This external image does not require external sampler (YUV conversion).
|
||||
if (metadata.externalFormat == 0 && !fvkutils::isVKYcbcrConversionFormat(metadata.format)) {
|
||||
if (metadata.externalFormat == 0) {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
VulkanYcbcrConversionCache::Params ycbcrParams = {
|
||||
@@ -225,70 +207,74 @@ VkSamplerYcbcrConversion VulkanExternalImageManager::getVkSamplerYcbcrConversion
|
||||
// Unclear where to get the chromaFilter, we just assume it's nearest.
|
||||
.chromaFilter = SamplerMagFilter::NEAREST,
|
||||
},
|
||||
.format = metadata.format,
|
||||
.format = metadata.filamentFormat,
|
||||
.externalFormat = metadata.externalFormat,
|
||||
};
|
||||
return mYcbcrConversionCache->getConversion(ycbcrParams);
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::updateImage(ImageData* image) {
|
||||
bool VulkanExternalImageManager::updateImage(ImageData* image) {
|
||||
if (image->hasBeenValidated) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
image->hasBeenValidated = true;
|
||||
|
||||
auto metadata = mPlatform->extractExternalImageMetadata(image->platformHandle);
|
||||
auto vkYcbcr = getVkSamplerYcbcrConversion(metadata);
|
||||
if (vkYcbcr == image->conversion) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
image->image->setYcbcrConversion(vkYcbcr);
|
||||
image->ptr->setYcbcrConversion(vkYcbcr, metadata.externalFormat != 0);
|
||||
image->conversion = vkYcbcr;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::addDescriptorSet(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set) {
|
||||
mSetAndLayouts.push_back({set, layout});
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::removeDescriptorSet(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> inSet) {
|
||||
erasep<SetBindingInfo>(mSetBindings,
|
||||
erase<SetAndLayout>(mSetAndLayouts,
|
||||
[&](auto const& setLayout) { return (setLayout.first == inSet); });
|
||||
erase<SetBindingInfo>(mSetBindings,
|
||||
[&](auto const& bindingInfo) { return (bindingInfo.set == inSet); });
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::removeDescriptorSetLayout(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> inLayout) {
|
||||
erase<SetAndLayout>(mSetAndLayouts,
|
||||
[&](auto const& setLayout) { return (setLayout.second == inLayout); });
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::bindExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set, uint8_t bindingPoint,
|
||||
fvkmemory::resource_ptr<VulkanTexture> image, SamplerParams samplerParams) {
|
||||
// Should we do duplicate validation here?
|
||||
auto& imageData = findImage(mImages, image);
|
||||
mSetBindings.push_back({ bindingPoint, imageData.image, set, samplerParams });
|
||||
mSetBindings.push_back({ bindingPoint, image, set, samplerParams });
|
||||
}
|
||||
|
||||
void VulkanExternalImageManager::addExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
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,
|
||||
erase<SetBindingInfo>(mSetBindings,
|
||||
[&](auto const& bindingInfo) { return (bindingInfo.image == image); });
|
||||
erasep<ImageData>(mImages, [&](auto const& imageData) {
|
||||
return imageData.image == image;
|
||||
});
|
||||
erase<ImageData>(mImages, [&](auto const& imageData) { return imageData.ptr == image; });
|
||||
}
|
||||
|
||||
bool VulkanExternalImageManager::isExternallySampledTexture(
|
||||
fvkmemory::resource_ptr<VulkanTexture> image) const {
|
||||
return std::find_if(mImages.begin(), mImages.end(), [&](auto const& imageData) {
|
||||
return imageData.image == image;
|
||||
}) != mImages.end();
|
||||
return std::find_if(mImages.begin(), mImages.end(),
|
||||
[&](auto const& imageData) { return imageData.ptr == 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
|
||||
|
||||
@@ -51,14 +51,13 @@ public:
|
||||
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>;
|
||||
// This sets the currently bound layouts objects for the pipeline
|
||||
bool prepareBindSets(SetArray const& layouts);
|
||||
|
||||
using VkLayoutArray = VulkanDescriptorSetLayout::DescriptorSetLayoutArray;
|
||||
void addDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||
|
||||
// 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 removeDescriptorSetLayout(fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
void removeDescriptorSet(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||
|
||||
@@ -66,10 +65,7 @@ public:
|
||||
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,
|
||||
void addExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image,
|
||||
Platform::ExternalImageHandleRef platformHandleRef);
|
||||
|
||||
void removeExternallySampledTexture(fvkmemory::resource_ptr<VulkanTexture> image);
|
||||
@@ -79,20 +75,18 @@ public:
|
||||
VkSamplerYcbcrConversion getVkSamplerYcbcrConversion(
|
||||
VulkanPlatform::ExternalImageMetadata const& metadata);
|
||||
|
||||
private:
|
||||
|
||||
struct ImageData {
|
||||
fvkmemory::resource_ptr<VulkanTexture> image;
|
||||
fvkmemory::resource_ptr<VulkanTexture> ptr;
|
||||
Platform::ExternalImageHandle platformHandle;
|
||||
bool hasBeenValidated = false; // indicates whether the image has been validated *this frame*
|
||||
VkSamplerYcbcrConversion conversion = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
private:
|
||||
bool hasExternalSampler(fvkmemory::resource_ptr<VulkanDescriptorSet> set);
|
||||
|
||||
void updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
bool updateSetAndLayout(fvkmemory::resource_ptr<VulkanDescriptorSet> set,
|
||||
fvkmemory::resource_ptr<VulkanDescriptorSetLayout> layout);
|
||||
|
||||
void updateImage(ImageData* imageData);
|
||||
bool updateImage(ImageData* imageData);
|
||||
|
||||
VulkanPlatform* mPlatform;
|
||||
VulkanSamplerCache* mSamplerCache;
|
||||
@@ -108,10 +102,10 @@ private:
|
||||
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<SetAndLayout> mSetAndLayouts;
|
||||
std::vector<SetBindingInfo> mSetBindings;
|
||||
std::vector<ImageData> mImages;
|
||||
};
|
||||
|
||||
@@ -330,39 +330,25 @@ 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 ";
|
||||
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
|
||||
FVK_LOGD << "Created render pass " << renderPass << " with "
|
||||
<< "samples = " << int(config.samples) << ", "
|
||||
<< "depth = " << (hasDepth ? 1 : 0) << ", "
|
||||
<< "colorAttachmentCount[0] = " << subpasses[0].colorAttachmentCount
|
||||
<< utils::io::endl;
|
||||
#endif
|
||||
|
||||
return renderPass;
|
||||
}
|
||||
|
||||
void VulkanFboCache::resetFramebuffers() noexcept {
|
||||
for (const auto& pair: mFramebufferCache) {
|
||||
void VulkanFboCache::reset() noexcept {
|
||||
for (auto pair : mFramebufferCache) {
|
||||
mRenderPassRefCount[pair.first.renderPass]--;
|
||||
vkDestroyFramebuffer(mDevice, pair.second.handle, VKALLOC);
|
||||
}
|
||||
mFramebufferCache.clear();
|
||||
}
|
||||
|
||||
void VulkanFboCache::terminate() noexcept {
|
||||
resetFramebuffers();
|
||||
|
||||
for (const auto& pair: mRenderPassCache) {
|
||||
for (auto pair : mRenderPassCache) {
|
||||
vkDestroyRenderPass(mDevice, pair.second.handle, VKALLOC);
|
||||
}
|
||||
mRenderPassRefCount.clear();
|
||||
mRenderPassCache.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -106,11 +106,8 @@ public:
|
||||
// Evicts old unused Vulkan objects. Call this once per frame.
|
||||
void gc() noexcept;
|
||||
|
||||
// Frees all Framebuffer objects. Call this every time a the swapchain is resized
|
||||
void resetFramebuffers() noexcept;
|
||||
|
||||
// Frees all Vulkan objects. Call this during shutdown before the device is destroyed.
|
||||
void terminate() noexcept;
|
||||
void reset() noexcept;
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
|
||||
@@ -149,18 +149,11 @@ void VulkanDescriptorSet::acquire(fvkmemory::resource_ptr<VulkanBufferObject> ob
|
||||
mResources.push_back(obj);
|
||||
}
|
||||
|
||||
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout&& layout,
|
||||
VkDescriptorSetLayout vkLayout)
|
||||
VulkanDescriptorSetLayout::VulkanDescriptorSetLayout(DescriptorSetLayout const& layout)
|
||||
: bitmask(fromBackendLayout(layout)),
|
||||
count(Count::fromLayoutBitmask(bitmask)),
|
||||
mVkLayout(vkLayout) {}
|
||||
count(Count::fromLayoutBitmask(bitmask)) {}
|
||||
|
||||
VulkanDescriptorSetLayout::Bitmask VulkanDescriptorSetLayout::Bitmask::fromLayoutDescription(
|
||||
DescriptorSetLayout const& layout) {
|
||||
return fromBackendLayout(layout);
|
||||
}
|
||||
|
||||
PushConstantDescription::PushConstantDescription(backend::Program const& program) {
|
||||
PushConstantDescription::PushConstantDescription(backend::Program const& program) noexcept {
|
||||
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 sampler field.
|
||||
// This is a subset of the bitmask.sampler field.
|
||||
fvkutils::SamplerBitmask externalSampler; // 8 bytes
|
||||
|
||||
bool operator==(Bitmask const& right) const {
|
||||
@@ -79,8 +79,6 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
||||
inputAttachment == right.inputAttachment &&
|
||||
externalSampler == right.externalSampler;
|
||||
}
|
||||
|
||||
static Bitmask fromLayoutDescription(DescriptorSetLayout const& layout);
|
||||
};
|
||||
static_assert(sizeof(Bitmask) == 40);
|
||||
|
||||
@@ -122,20 +120,16 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
||||
}
|
||||
};
|
||||
|
||||
VulkanDescriptorSetLayout(DescriptorSetLayout&& layout, VkDescriptorSetLayout vkLayout);
|
||||
VulkanDescriptorSetLayout(DescriptorSetLayout const& layout);
|
||||
|
||||
// Note that we don't destroy the vklayout. This is done by the layout cache.
|
||||
~VulkanDescriptorSetLayout() = default;
|
||||
|
||||
VkDescriptorSetLayout getVkLayout() const noexcept { return mVkLayout; }
|
||||
VkDescriptorSetLayout const& getVkLayout() const noexcept { return mVkLayout; }
|
||||
|
||||
VkDescriptorSetLayout getExternalSamplerVkLayout() const noexcept {
|
||||
return mExternalSamplerVkLayout;
|
||||
}
|
||||
|
||||
void setExternalSamplerVkLayout(VkDescriptorSetLayout vklayout) noexcept {
|
||||
mExternalSamplerVkLayout = vklayout;
|
||||
}
|
||||
// It is possible to have the layout switch out due to AHardwarebuffer (external image) format
|
||||
// changes.
|
||||
void setVkLayout(VkDescriptorSetLayout vklayout) noexcept { mVkLayout = vklayout; }
|
||||
|
||||
bool hasExternalSamplers() const noexcept { return bitmask.externalSampler.count() > 0; }
|
||||
|
||||
@@ -143,11 +137,7 @@ struct VulkanDescriptorSetLayout : public HwDescriptorSetLayout, fvkmemory::Reso
|
||||
Count const count;
|
||||
|
||||
private:
|
||||
// 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;
|
||||
VkDescriptorSetLayout mVkLayout = VK_NULL_HANDLE;
|
||||
};
|
||||
|
||||
struct VulkanDescriptorSet : public HwDescriptorSet, fvkmemory::Resource {
|
||||
@@ -159,37 +149,23 @@ public:
|
||||
VulkanDescriptorSet(
|
||||
fvkutils::UniformBufferBitmask const& dynamicUboMask,
|
||||
uint8_t uniqueDynamicUboCount,
|
||||
OnRecycle&& onRecycleFn, VkDescriptorSet vkSet)
|
||||
OnRecycle&& onRecycleFn)
|
||||
: dynamicUboMask(dynamicUboMask),
|
||||
uniqueDynamicUboCount(uniqueDynamicUboCount),
|
||||
mVkSet(vkSet),
|
||||
mOnRecycleFn(std::move(onRecycleFn)) {}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-exception-escape)
|
||||
~VulkanDescriptorSet() {
|
||||
if (mOnRecycleFn) {
|
||||
mOnRecycleFn(this);
|
||||
}
|
||||
if (mOnRecycleExternalSamplerFn) {
|
||||
mOnRecycleExternalSamplerFn(this);
|
||||
}
|
||||
}
|
||||
|
||||
VkDescriptorSet getVkSet() const noexcept {
|
||||
VkDescriptorSet const& getVkSet() const noexcept {
|
||||
return mVkSet;
|
||||
}
|
||||
|
||||
VkDescriptorSet getExternalSamplerVkSet() const noexcept {
|
||||
return mExternalSamplerVkSet;
|
||||
}
|
||||
|
||||
void setExternalSamplerVkSet(VkDescriptorSet vkset, OnRecycle onRecycle) {
|
||||
mExternalSamplerVkSet = vkset;
|
||||
if (mOnRecycleExternalSamplerFn) {
|
||||
mOnRecycleExternalSamplerFn(this);
|
||||
}
|
||||
mOnRecycleExternalSamplerFn = onRecycle;
|
||||
}
|
||||
// Note that the only case where you'd set it more than once is with external images/samplers.
|
||||
void setVkSet(VkDescriptorSet vkset) noexcept { mVkSet = vkset; }
|
||||
|
||||
void setOffsets(backend::DescriptorSetOffsetArray&& offsets) noexcept {
|
||||
mOffsets = std::move(offsets);
|
||||
@@ -206,20 +182,17 @@ public:
|
||||
uint8_t const uniqueDynamicUboCount;
|
||||
|
||||
private:
|
||||
VkDescriptorSet const mVkSet;
|
||||
VkDescriptorSet mExternalSamplerVkSet = VK_NULL_HANDLE;
|
||||
|
||||
VkDescriptorSet mVkSet = 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);
|
||||
explicit PushConstantDescription(backend::Program const& program) noexcept;
|
||||
|
||||
VkPushConstantRange const* getVkRanges() const noexcept { return mRanges; }
|
||||
uint32_t getVkRangeCount() const noexcept { return mRangeCount; }
|
||||
|
||||
@@ -35,12 +35,7 @@ using namespace bluevk;
|
||||
namespace filament::backend {
|
||||
|
||||
VulkanPipelineCache::VulkanPipelineCache(VkDevice device)
|
||||
: mDevice(device) {
|
||||
VkPipelineCacheCreateInfo createInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO,
|
||||
};
|
||||
bluevk::vkCreatePipelineCache(mDevice, &createInfo, VKALLOC, &mPipelineCache);
|
||||
}
|
||||
: mDevice(device) {}
|
||||
|
||||
void VulkanPipelineCache::bindLayout(VkPipelineLayout layout) noexcept {
|
||||
mPipelineRequirements.layout = layout;
|
||||
@@ -66,12 +61,8 @@ 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");
|
||||
|
||||
|
||||
static PipelineEqual equal;
|
||||
if (!equal(mBoundPipeline, mPipelineRequirements)) {
|
||||
mBoundPipeline = mPipelineRequirements;
|
||||
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle);
|
||||
}
|
||||
mBoundPipeline = mPipelineRequirements;
|
||||
vkCmdBindPipeline(cmdbuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cacheEntry->handle);
|
||||
}
|
||||
|
||||
VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() noexcept {
|
||||
@@ -224,7 +215,7 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
PipelineCacheEntry cacheEntry = {
|
||||
.lastUsed = mCurrentTime,
|
||||
};
|
||||
VkResult error = vkCreateGraphicsPipelines(mDevice, mPipelineCache, 1, &pipelineCreateInfo,
|
||||
VkResult error = vkCreateGraphicsPipelines(mDevice, VK_NULL_HANDLE, 1, &pipelineCreateInfo,
|
||||
VKALLOC, &cacheEntry.handle);
|
||||
assert_invariant(error == VK_SUCCESS);
|
||||
if (error != VK_SUCCESS) {
|
||||
@@ -280,8 +271,6 @@ void VulkanPipelineCache::terminate() noexcept {
|
||||
}
|
||||
mPipelines.clear();
|
||||
mBoundPipeline = {};
|
||||
|
||||
vkDestroyPipelineCache(mDevice, mPipelineCache, VKALLOC);
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::gc() noexcept {
|
||||
|
||||
@@ -198,10 +198,6 @@ private:
|
||||
// Immutable state.
|
||||
VkDevice mDevice = VK_NULL_HANDLE;
|
||||
|
||||
// Vuklan Driver pipeline cache handle. In the cases a pipeline has been evicted by the `gc`,
|
||||
// recreating the same pipeline is cheaper, helping with frame stalling.
|
||||
VkPipelineCache mPipelineCache = VK_NULL_HANDLE;
|
||||
|
||||
// Current requirements for the pipeline layout, pipeline, and descriptor sets.
|
||||
PipelineKey mPipelineRequirements = {};
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ void VulkanReadPixels::run(fvkmemory::resource_ptr<VulkanRenderTarget> srcTarget
|
||||
mTaskHandler->post(std::move(waitFenceFunc), std::move(cleanPbdFunc));
|
||||
}
|
||||
|
||||
void VulkanReadPixels::runUntilComplete() {
|
||||
void VulkanReadPixels::runUntilComplete() noexcept {
|
||||
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();
|
||||
void runUntilComplete() noexcept;
|
||||
|
||||
private:
|
||||
VkDevice mDevice = VK_NULL_HANDLE;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "vulkan/utils/Conversion.h"
|
||||
#include "vulkan/vulkan_core.h"
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
@@ -28,7 +29,7 @@ namespace filament::backend {
|
||||
VulkanSamplerCache::VulkanSamplerCache(VkDevice device)
|
||||
: mDevice(device) {}
|
||||
|
||||
VkSampler VulkanSamplerCache::getSampler(Params params) {
|
||||
VkSampler VulkanSamplerCache::getSampler(Params params) noexcept {
|
||||
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);
|
||||
VkSampler getSampler(Params params) noexcept;
|
||||
void terminate() noexcept;
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
static constexpr uint32_t TIME_BEFORE_EVICTION = 3;
|
||||
static constexpr uint32_t TIME_BEFORE_EVICTION = FVK_MAX_COMMAND_BUFFERS;
|
||||
|
||||
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.push_back(stage);
|
||||
mUsedStages.insert(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.push_back(stage);
|
||||
mUsedStages.insert(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.push_back(image);
|
||||
mUsedImages.insert(image);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ VulkanStageImage const* VulkanStagePool::acquireImage(PixelDataFormat format, Pi
|
||||
.lastAccessed = mCurrentFrame,
|
||||
});
|
||||
|
||||
mUsedImages.push_back(image);
|
||||
mUsedImages.insert(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.push_back(stage);
|
||||
mUsedStages.insert(stage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ void VulkanStagePool::gc() noexcept {
|
||||
image->lastAccessed = mCurrentFrame;
|
||||
mFreeImages.insert(image);
|
||||
} else {
|
||||
mUsedImages.push_back(image);
|
||||
mUsedImages.insert(image);
|
||||
}
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -74,10 +73,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::vector<VulkanStage const*> mUsedStages;
|
||||
std::unordered_set<VulkanStage const*> mUsedStages;
|
||||
|
||||
std::unordered_set<VulkanStageImage const*> mFreeImages;
|
||||
std::vector<VulkanStageImage const*> mUsedImages;
|
||||
std::unordered_set<VulkanStageImage const*> mUsedImages;
|
||||
|
||||
// Store the current "time" (really just a frame count) and LRU eviction parameters.
|
||||
uint64_t mCurrentFrame = 0;
|
||||
|
||||
@@ -223,7 +223,8 @@ 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, VkImageUsageFlags usage, bool isProtected)
|
||||
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat, VkImageUsageFlags usage,
|
||||
bool isProtected)
|
||||
: mStagePool(stagePool),
|
||||
mCommands(commands),
|
||||
mAllocator(allocator),
|
||||
@@ -233,17 +234,17 @@ VulkanTextureState::VulkanTextureState(VulkanStagePool& stagePool, VulkanCommand
|
||||
mVkFormat(format),
|
||||
mViewType(viewType),
|
||||
mFullViewRange{ fvkutils::getImageAspect(format), 0, levels, 0, layerCount },
|
||||
mYcbcr{ ycbcrConversion },
|
||||
mYcbcr{ ycbcrConversion, isExternalFormat },
|
||||
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 {
|
||||
@@ -271,7 +272,7 @@ VkImageView VulkanTextureState::getImageView(VkImageSubresourceRange range, VkIm
|
||||
.flags = 0,
|
||||
.image = mTextureImage,
|
||||
.viewType = viewType,
|
||||
.format = mYcbcr.conversion != VK_NULL_HANDLE ? VK_FORMAT_UNDEFINED : mVkFormat,
|
||||
.format = mYcbcr.isExternalFormat ? VK_FORMAT_UNDEFINED : mVkFormat,
|
||||
.components = swizzle,
|
||||
.subresourceRange = range,
|
||||
};
|
||||
@@ -293,6 +294,7 @@ 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;
|
||||
@@ -422,7 +424,8 @@ 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 */, imageInfo.usage, isProtected);
|
||||
VK_NULL_HANDLE /* ycbcrConversion */, false /*isExternalFormat*/, imageInfo.usage,
|
||||
isProtected);
|
||||
|
||||
// Spec out the "primary" VkImageView that shaders use to sample from the image.
|
||||
mPrimaryViewRange = mState->mFullViewRange;
|
||||
@@ -761,13 +764,15 @@ void VulkanTexture::setLayout(VkImageSubresourceRange const& range, VulkanLayout
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanTexture::setYcbcrConversion(VkSamplerYcbcrConversion conversion) {
|
||||
void VulkanTexture::setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternalFormat) {
|
||||
// Note that this comparison is valid because we only ever create VkSamplerYcbcrConversion from
|
||||
// a cache. So for each set of parameters, there is exactly one conversion (similar to
|
||||
// samplers).
|
||||
VulkanTextureState::Ycbcr ycbcr = {
|
||||
.conversion = conversion,
|
||||
.isExternalFormat = isExternalFormat,
|
||||
};
|
||||
|
||||
if (mState->mYcbcr != ycbcr) {
|
||||
mState->mYcbcr = ycbcr;
|
||||
mState->clearCachedImageViews();
|
||||
|
||||
@@ -40,7 +40,8 @@ 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, VkImageUsageFlags usage, bool isProtected);
|
||||
VkSamplerYcbcrConversion ycbcrConversion, bool isExternalFormat,
|
||||
VkImageUsageFlags usage, bool isProtected);
|
||||
|
||||
~VulkanTextureState();
|
||||
|
||||
@@ -85,9 +86,10 @@ private:
|
||||
// conversion matrix per-frame.
|
||||
struct Ycbcr {
|
||||
VkSamplerYcbcrConversion conversion;
|
||||
bool isExternalFormat;
|
||||
|
||||
bool operator==(Ycbcr const& other) const {
|
||||
return conversion == other.conversion;
|
||||
return conversion == other.conversion && isExternalFormat == other.isExternalFormat;
|
||||
}
|
||||
|
||||
bool operator!=(Ycbcr const& other) const {
|
||||
@@ -207,7 +209,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);
|
||||
void setYcbcrConversion(VkSamplerYcbcrConversion conversion, bool isExternal);
|
||||
|
||||
#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) {
|
||||
VulkanYcbcrConversionCache::Params params) noexcept {
|
||||
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 = params.format,
|
||||
.format = fvkutils::getVkFormat(params.format),
|
||||
.ycbcrModel = fvkutils::getYcbcrModelConversion(chroma.ycbcrModel),
|
||||
.ycbcrRange = fvkutils::getYcbcrRange(chroma.ycbcrRange),
|
||||
.components = fvkutils::getSwizzleMap(swizzleArray),
|
||||
|
||||
@@ -30,14 +30,15 @@ namespace filament::backend {
|
||||
class VulkanYcbcrConversionCache {
|
||||
public:
|
||||
struct Params {
|
||||
SamplerYcbcrConversion conversion = {}; // 4
|
||||
VkFormat format; // 4
|
||||
uint64_t externalFormat = 0; // 8
|
||||
SamplerYcbcrConversion conversion = {};
|
||||
TextureFormat format = {};
|
||||
uint16_t padding = 0;
|
||||
uint64_t externalFormat = 0;
|
||||
};
|
||||
static_assert(sizeof(Params) == 16);
|
||||
|
||||
explicit VulkanYcbcrConversionCache(VkDevice device);
|
||||
VkSamplerYcbcrConversion getConversion(Params params);
|
||||
VkSamplerYcbcrConversion getConversion(Params params) noexcept;
|
||||
void terminate() noexcept;
|
||||
|
||||
private:
|
||||
@@ -47,8 +48,7 @@ private:
|
||||
bool operator()(Params lhs, Params rhs) const noexcept {
|
||||
SamplerYcbcrConversion::EqualTo equal;
|
||||
return equal(lhs.conversion, rhs.conversion) &&
|
||||
lhs.externalFormat == rhs.externalFormat &&
|
||||
lhs.format == rhs.format;
|
||||
lhs.externalFormat == rhs.externalFormat;
|
||||
}
|
||||
};
|
||||
using ConversionHashFn = utils::hash::MurmurHashFn<Params>;
|
||||
|
||||
@@ -31,8 +31,6 @@ 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);
|
||||
@@ -51,7 +49,6 @@ void ResourceManager::gc() noexcept {
|
||||
GcList gcs;
|
||||
std::swap(gcs, mGcList);
|
||||
destroyAll(gcs);
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void ResourceManager::terminate() noexcept {
|
||||
|
||||
@@ -212,8 +212,6 @@ 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__)
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
|
||||
#include "vulkan/VulkanConstants.h"
|
||||
#include "vulkan/VulkanContext.h"
|
||||
#include "vulkan/vulkan_core.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <private/backend/BackendUtilsAndroid.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include "vulkan/utils/Image.h"
|
||||
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
@@ -39,14 +39,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -155,6 +147,7 @@ std::pair<TextureFormat, TextureUsage> getFilamentFormatAndUsage(const AHardware
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}// namespace
|
||||
|
||||
VulkanPlatformAndroid::ExternalImageVulkanAndroid::~ExternalImageVulkanAndroid() {
|
||||
@@ -227,6 +220,7 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::extractExternalImag
|
||||
}
|
||||
metadata.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
|
||||
VkAndroidHardwareBufferFormatPropertiesANDROID formatInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID,
|
||||
};
|
||||
@@ -242,16 +236,13 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::extractExternalImag
|
||||
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;
|
||||
// Choose either externalFormat > 0 or metadata.format and prefer the latter.
|
||||
if (metadata.externalFormat > 0 && metadata.format != VK_FORMAT_UNDEFINED) {
|
||||
// See VUID-VkImageCreateInfo-pNext-09457
|
||||
metadata.usage = VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
metadata.externalFormat = 0;
|
||||
}
|
||||
|
||||
metadata.allocationSize = properties.allocationSize;
|
||||
@@ -266,127 +257,91 @@ VulkanPlatform::ExternalImageMetadata VulkanPlatformAndroid::extractExternalImag
|
||||
return metadata;
|
||||
}
|
||||
|
||||
VulkanPlatform::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
|
||||
VulkanPlatformAndroid::ImageData VulkanPlatformAndroid::createVkImageFromExternal(
|
||||
ExternalImageHandleRef externalImage) const {
|
||||
auto metadata = extractExternalImageMetadata(externalImage);
|
||||
auto const& metadata = extractExternalImageMetadata(externalImage);
|
||||
|
||||
auto const* fvkExternalImage =
|
||||
static_cast<ExternalImageVulkanAndroid const*>(externalImage.get());
|
||||
AHardwareBuffer* buffer = fvkExternalImage->aHardwareBuffer;
|
||||
|
||||
// if external format we need to specifiy it in the allocation
|
||||
bool const useExternalFormat = metadata.format == VK_FORMAT_UNDEFINED;
|
||||
|
||||
VkExternalFormatANDROID const externalFormat = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,
|
||||
.pNext = nullptr,
|
||||
.externalFormat = metadata.externalFormat,
|
||||
};
|
||||
VkExternalMemoryImageCreateInfo const externalCreateInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,
|
||||
.pNext = useExternalFormat ? &externalFormat : nullptr,
|
||||
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
|
||||
};
|
||||
|
||||
VkImageCreateInfo const imageInfo{
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||
.pNext = &externalCreateInfo,
|
||||
.flags = useExternalFormat ? VK_IMAGE_CREATE_ALIAS_BIT : 0u,
|
||||
.imageType = VK_IMAGE_TYPE_2D,
|
||||
.format = metadata.format,
|
||||
.extent = {
|
||||
metadata.width,
|
||||
metadata.height,
|
||||
1u,
|
||||
},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = metadata.layers,
|
||||
.samples = metadata.samples,
|
||||
.usage = metadata.usage,
|
||||
};
|
||||
|
||||
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)
|
||||
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;
|
||||
|
||||
// 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,
|
||||
};
|
||||
|
||||
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;
|
||||
VkPhysicalDeviceMemoryProperties memoryProperties;
|
||||
vkGetPhysicalDeviceMemoryProperties(getPhysicalDevice(), &memoryProperties);
|
||||
|
||||
if (any(metadata.filamentUsage & TextureUsage::PROTECTED)) {
|
||||
requiredMemoryFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
|
||||
}
|
||||
|
||||
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
|
||||
metadata.memoryTypeBits, requiredMemoryFlags);
|
||||
VkMemoryPropertyFlags const requiredMemoryFlags =
|
||||
any(metadata.filamentUsage & TextureUsage::UPLOADABLE)
|
||||
? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
||||
: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
|
||||
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;
|
||||
uint32_t const memoryTypeIndex = VulkanContext::selectMemoryType(memoryProperties,
|
||||
metadata.memoryTypeBits, requiredMemoryFlags);
|
||||
|
||||
VkMemoryAllocateInfo const allocInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
||||
.pNext = &memoryDedicatedAllocateInfo,
|
||||
.allocationSize = metadata.allocationSize,
|
||||
.memoryTypeIndex = memoryTypeIndex,
|
||||
};
|
||||
VkDeviceMemory memory;
|
||||
result = vkAllocateMemory(device, &allocInfo, VKALLOC, &memory);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkAllocateMemory failed with error=" << static_cast<int32_t>(result);
|
||||
|
||||
VulkanPlatform::ImageData::Bundle internal = {}, external = {};
|
||||
auto img = buildImage(metadata);
|
||||
auto mem = allocMem(img, metadata);
|
||||
|
||||
// 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 };
|
||||
}
|
||||
|
||||
return {
|
||||
.internal = internal,
|
||||
.external = external,
|
||||
};
|
||||
result = vkBindImageMemory(getDevice(), image, memory, 0);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkBindImageMemory error=" << static_cast<int32_t>(result);
|
||||
return { image, memory };
|
||||
}
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatformAndroid::getSwapchainInstanceExtensions() const {
|
||||
@@ -400,7 +355,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,
|
||||
};
|
||||
|
||||
@@ -24,12 +24,28 @@
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
// Platform specific includes and defines
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#if defined(__APPLE__)
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
#ifndef VK_MVK_macos_surface
|
||||
#error VK_MVK_macos_surface is not defined
|
||||
#ifndef VK_MVK_macos_surface
|
||||
#error VK_MVK_macos_surface is not defined
|
||||
#endif
|
||||
#elif defined(FILAMENT_IOS)
|
||||
// Metal is not available when building for the iOS simulator on Desktop.
|
||||
#define METAL_AVAILABLE __has_include(<QuartzCore/CAMetalLayer.h>)
|
||||
#if METAL_AVAILABLE
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#endif
|
||||
|
||||
#ifndef VK_MVK_ios_surface
|
||||
#error VK_MVK_ios_surface is not defined
|
||||
#endif
|
||||
#define METALVIEW_TAG 255
|
||||
#else
|
||||
#error Not a supported Apple + Vulkan platform
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
@@ -38,7 +54,11 @@ namespace filament::backend {
|
||||
|
||||
VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl() {
|
||||
ExtensionSet const ret = {
|
||||
#if defined(__APPLE__)
|
||||
VK_MVK_MACOS_SURFACE_EXTENSION_NAME, // TODO: replace with VK_EXT_metal_surface
|
||||
#elif defined(FILAMENT_IOS) && defined(METAL_AVAILABLE)
|
||||
VK_MVK_IOS_SURFACE_EXTENSION_NAME,
|
||||
#endif
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
@@ -46,20 +66,36 @@ VulkanPlatform::ExtensionSet VulkanPlatform::getSwapchainInstanceExtensionsImpl(
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
NSView* nsview = (__bridge NSView*) nativeWindow;
|
||||
FILAMENT_CHECK_POSTCONDITION(nsview) << "Unable to obtain Metal-backed NSView.";
|
||||
#if defined(__APPLE__)
|
||||
NSView* nsview = (__bridge NSView*) nativeWindow;
|
||||
FILAMENT_CHECK_POSTCONDITION(nsview) << "Unable to obtain Metal-backed NSView.";
|
||||
|
||||
// Create the VkSurface.
|
||||
FILAMENT_CHECK_POSTCONDITION(vkCreateMacOSSurfaceMVK)
|
||||
<< "Unable to load vkCreateMacOSSurfaceMVK.";
|
||||
VkMacOSSurfaceCreateInfoMVK createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
||||
createInfo.pView = (__bridge void*) nsview;
|
||||
VkResult result = vkCreateMacOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateMacOSSurfaceMVK. error=" << static_cast<int32_t>(result);
|
||||
#elif defined(FILAMENT_IOS) && defined(METAL_AVAILABLE)
|
||||
CAMetalLayer* metalLayer = (CAMetalLayer*) nativeWindow;
|
||||
// Create the VkSurface.
|
||||
FILAMENT_CHECK_POSTCONDITION(vkCreateMacOSSurfaceMVK)
|
||||
<< "Unable to load vkCreateMacOSSurfaceMVK.";
|
||||
VkMacOSSurfaceCreateInfoMVK createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
||||
createInfo.pView = (__bridge void*) nsview;
|
||||
VkResult result = vkCreateMacOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
|
||||
FILAMENT_CHECK_POSTCONDITION(vkCreateIOSSurfaceMVK)
|
||||
<< "Unable to load vkCreateIOSSurfaceMVK function.";
|
||||
VkIOSSurfaceCreateInfoMVK createInfo = {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
|
||||
createInfo.pNext = NULL;
|
||||
createInfo.flags = 0;
|
||||
createInfo.pView = metalLayer;
|
||||
VkResult result = vkCreateIOSSurfaceMVK((VkInstance) instance, &createInfo, VKALLOC,
|
||||
(VkSurfaceKHR*) &surface);
|
||||
FILAMENT_CHECK_POSTCONDITION(result == VK_SUCCESS)
|
||||
<< "vkCreateMacOSSurfaceMVK. error=" << static_cast<int32_t>(result);
|
||||
return std::make_tuple(surface, VkExtent2D{});
|
||||
<< "vkCreateIOSSurfaceMVK failed. error=" << static_cast<int32_t>(result);
|
||||
#endif
|
||||
return std::make_tuple(surface, VkExtent2D{});
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -51,8 +51,6 @@ 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,46 +184,6 @@ 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,8 +98,6 @@ bool isVkDepthFormat(VkFormat format);
|
||||
|
||||
bool isVkStencilFormat(VkFormat format);
|
||||
|
||||
bool isVKYcbcrConversionFormat(VkFormat format);
|
||||
|
||||
VkImageAspectFlags getImageAspect(VkFormat format);
|
||||
|
||||
uint8_t reduceSampleCount(uint8_t sampleCount, VkSampleCountFlags mask);
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
#include "webgpu/WebGPUDriver.h"
|
||||
|
||||
#include "WebGPUSwapChain.h"
|
||||
#include "webgpu/WebGPUConstants.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include "CommandStreamDispatcher.h"
|
||||
@@ -183,6 +183,8 @@ void printAdapterDetails(wgpu::Adapter const& adapter) {
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
void printDeviceDetails(wgpu::Device const& device) {
|
||||
wgpu::SupportedFeatures supportedFeatures{};
|
||||
@@ -226,15 +228,6 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfi
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
printInstanceDetails(mPlatform.getInstance());
|
||||
#endif
|
||||
mAdapter = mPlatform.requestAdapter(nullptr);
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
printAdapterDetails(mAdapter);
|
||||
#endif
|
||||
mDevice = mPlatform.requestDevice(mAdapter);
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
printDeviceDetails(mDevice);
|
||||
#endif
|
||||
mQueue = mDevice.GetQueue();
|
||||
}
|
||||
|
||||
WebGPUDriver::~WebGPUDriver() noexcept = default;
|
||||
@@ -263,7 +256,6 @@ void WebGPUDriver::terminate() {
|
||||
}
|
||||
|
||||
void WebGPUDriver::tick(int) {
|
||||
mDevice.Tick();
|
||||
}
|
||||
|
||||
void WebGPUDriver::beginFrame(int64_t monotonic_clock_ns,
|
||||
@@ -293,52 +285,37 @@ void WebGPUDriver::finish(int) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
if (rph) {
|
||||
destructHandle<WGPURenderPrimitive>(rph);
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyVertexBufferInfo(Handle<HwVertexBufferInfo> vbih) {
|
||||
if (vbih) {
|
||||
destructHandle<WGPUVertexBufferInfo>(vbih);
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyVertexBuffer(Handle<HwVertexBuffer> vbh) {
|
||||
if (vbh) {
|
||||
destructHandle<WGPUVertexBuffer>(vbh);
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyIndexBuffer(Handle<HwIndexBuffer> ibh) {
|
||||
if (ibh) {
|
||||
destructHandle<WGPUIndexBuffer>(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) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroySwapChain(Handle<HwSwapChain> sch) {
|
||||
if (sch) {
|
||||
destructHandle<WebGPUSwapChain>(sch);
|
||||
}
|
||||
mSwapChain = nullptr;
|
||||
// TODO: use webgpu handle allocator from
|
||||
// https://github.com/google/filament/pull/8566
|
||||
// if (sch) {
|
||||
// HwSwapChain* hwSwapChain = handleCast<HwSwapChain*>(sch);
|
||||
// destruct(sch, hwSwapChain);
|
||||
// }
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyStream(Handle<HwStream> sh) {
|
||||
@@ -348,19 +325,16 @@ void WebGPUDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh) {
|
||||
if (tqh) {
|
||||
destructHandle<WebGPUDescriptorSetLayout>(tqh);
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
|
||||
if (tqh) {
|
||||
destructHandle<WebGPUDescriptorSet>(tqh);
|
||||
}
|
||||
}
|
||||
|
||||
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
|
||||
return allocHandle<WebGPUSwapChain>();
|
||||
// TODO: use webgpu handle allocator from.
|
||||
// https://github.com/google/filament/pull/8566
|
||||
// return allocAndConstructHandle<HwSwapChain>();
|
||||
return Handle<HwSwapChain>((Handle<HwSwapChain>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwSwapChain> WebGPUDriver::createSwapChainHeadlessS() noexcept {
|
||||
@@ -368,7 +342,7 @@ Handle<HwSwapChain> WebGPUDriver::createSwapChainHeadlessS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwTexture> WebGPUDriver::createTextureS() noexcept {
|
||||
return allocHandle<WGPUTexture>();
|
||||
return Handle<HwTexture>((Handle<HwTexture>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwTexture> WebGPUDriver::importTextureS() noexcept {
|
||||
@@ -376,7 +350,7 @@ Handle<HwTexture> WebGPUDriver::importTextureS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwProgram> WebGPUDriver::createProgramS() noexcept {
|
||||
return allocHandle<WGPUProgram>();
|
||||
return Handle<HwProgram>((Handle<HwProgram>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwFence> WebGPUDriver::createFenceS() noexcept {
|
||||
@@ -396,27 +370,27 @@ Handle<HwTexture> WebGPUDriver::createTextureViewS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwBufferObject> WebGPUDriver::createBufferObjectS() noexcept {
|
||||
return allocHandle<WGPUBufferObject>();
|
||||
return Handle<HwBufferObject>((Handle<HwBufferObject>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwRenderTarget> WebGPUDriver::createRenderTargetS() noexcept {
|
||||
return allocHandle<WGPURenderTarget>();
|
||||
return Handle<HwRenderTarget>((Handle<HwRenderTarget>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwVertexBuffer> WebGPUDriver::createVertexBufferS() noexcept {
|
||||
return allocHandle<WGPUVertexBuffer>();
|
||||
return Handle<HwVertexBuffer>((Handle<HwVertexBuffer>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwDescriptorSet> WebGPUDriver::createDescriptorSetS() noexcept {
|
||||
return allocHandle<WebGPUDescriptorSet>();
|
||||
return Handle<HwDescriptorSet>((Handle<HwDescriptorSet>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwRenderPrimitive> WebGPUDriver::createRenderPrimitiveS() noexcept {
|
||||
return allocHandle<WGPURenderPrimitive>();
|
||||
return Handle<HwRenderPrimitive>((Handle<HwRenderPrimitive>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwVertexBufferInfo> WebGPUDriver::createVertexBufferInfoS() noexcept {
|
||||
return allocHandle<WGPUVertexBufferInfo>();
|
||||
return Handle<HwVertexBufferInfo>((Handle<HwVertexBufferInfo>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleS() noexcept {
|
||||
@@ -424,11 +398,12 @@ Handle<HwTexture> WebGPUDriver::createTextureViewSwizzleS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept {
|
||||
return allocHandle<WGPURenderTarget>();
|
||||
return Handle<HwRenderTarget>((Handle<HwRenderTarget>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwDescriptorSetLayout> WebGPUDriver::createDescriptorSetLayoutS() noexcept {
|
||||
return allocHandle<WebGPUDescriptorSetLayout>();
|
||||
return Handle<HwDescriptorSetLayout>(
|
||||
(Handle<HwDescriptorSetLayout>::HandleId) mNextFakeHandle++);
|
||||
}
|
||||
|
||||
Handle<HwTexture> WebGPUDriver::createTextureExternalImageS() noexcept {
|
||||
@@ -444,14 +419,21 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
|
||||
}
|
||||
|
||||
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||
mNativeWindow = nativeWindow;
|
||||
assert_invariant(!mSwapChain);
|
||||
// TODO: use webgpu handle allocator from.
|
||||
// https://github.com/google/filament/pull/8566
|
||||
// HwSwapChain* hwSwapChain = handleCast<HwSwapChain*>(sch);
|
||||
mSwapChain = nullptr;
|
||||
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
|
||||
|
||||
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
|
||||
mSwapChain = constructHandle<WebGPUSwapChain>(sch, std::move(surface), surfaceSize, mAdapter,
|
||||
mDevice, flags);
|
||||
assert_invariant(mSwapChain);
|
||||
mAdapter = mPlatform.requestAdapter(surface);
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
printAdapterDetails(mAdapter);
|
||||
#endif
|
||||
mDevice = mPlatform.requestDevice(mAdapter);
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
printDeviceDetails(mDevice);
|
||||
#endif
|
||||
mQueue = mDevice.GetQueue();
|
||||
mSwapChain = std::make_unique<WebGPUSwapChain>(std::move(surface), mAdapter, mDevice, flags);
|
||||
FWGPU_LOGW << "WebGPU support is still essentially a no-op at this point in development (only "
|
||||
"background components have been instantiated/selected, such as surface/screen, "
|
||||
"graphics device/GPU, etc.), thus nothing is being drawn to the screen."
|
||||
@@ -464,33 +446,25 @@ void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow,
|
||||
"rebuilding Filament with that flag, e.g. ./build.sh -x "
|
||||
<< FWGPU_PRINT_SYSTEM << " ..." << utils::io::endl;
|
||||
#endif
|
||||
// TODO: use webgpu handle allocator from.
|
||||
// https://github.com/google/filament/pull/8566
|
||||
// hwSwapChain->swapChain = mSwapChain.get();
|
||||
}
|
||||
|
||||
void WebGPUDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
|
||||
uint32_t height, uint64_t flags) {}
|
||||
|
||||
void WebGPUDriver::createVertexBufferInfoR(Handle<HwVertexBufferInfo> vbih, uint8_t bufferCount,
|
||||
uint8_t attributeCount, AttributeArray attributes) {
|
||||
constructHandle<WGPUVertexBufferInfo>(vbih, bufferCount, attributeCount, attributes);
|
||||
}
|
||||
uint8_t attributeCount, AttributeArray attributes) {}
|
||||
|
||||
void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount,
|
||||
Handle<HwVertexBufferInfo> vbih) {
|
||||
auto* vertexBufferInfo = handleCast<WGPUVertexBufferInfo>(vbih);
|
||||
constructHandle<WGPUVertexBuffer>(vbh, mDevice, vertexCount, vertexBufferInfo->bufferCount,
|
||||
vbih);
|
||||
}
|
||||
Handle<HwVertexBufferInfo> vbih) {}
|
||||
|
||||
void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
|
||||
uint32_t indexCount, BufferUsage usage) {
|
||||
auto elementSize = static_cast<uint8_t>(getElementTypeSize(elementType));
|
||||
constructHandle<WGPUIndexBuffer>(ibh, mDevice, elementSize, indexCount);
|
||||
}
|
||||
uint32_t indexCount, BufferUsage usage) {}
|
||||
|
||||
void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType, BufferUsage usage) {
|
||||
constructHandle<WGPUBufferObject>(boh, mDevice, bindingType, byteCount);
|
||||
}
|
||||
BufferObjectBinding bindingType, BufferUsage usage) {}
|
||||
|
||||
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,
|
||||
@@ -520,26 +494,11 @@ 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) {
|
||||
assert_invariant(mDevice);
|
||||
Handle<HwIndexBuffer> ibh, PrimitiveType pt) {}
|
||||
|
||||
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) {}
|
||||
|
||||
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
constructHandle<WGPUProgram>(ph, mDevice, program);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {
|
||||
assert_invariant(!mDefaultRenderTarget);
|
||||
mDefaultRenderTarget = constructHandle<WGPURenderTarget>(rth);
|
||||
assert_invariant(mDefaultRenderTarget);
|
||||
}
|
||||
void WebGPUDriver::createDefaultRenderTargetR(Handle<HwRenderTarget> rth, int) {}
|
||||
|
||||
void WebGPUDriver::createRenderTargetR(Handle<HwRenderTarget> rth, TargetBufferFlags targets,
|
||||
uint32_t width, uint32_t height, uint8_t samples, uint8_t layerCount, MRT color,
|
||||
@@ -550,15 +509,10 @@ void WebGPUDriver::createFenceR(Handle<HwFence> fh, int) {}
|
||||
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
|
||||
|
||||
void WebGPUDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
|
||||
backend::DescriptorSetLayout&& info) {
|
||||
constructHandle<WebGPUDescriptorSetLayout>(dslh, std::move(info), mDevice);
|
||||
}
|
||||
backend::DescriptorSetLayout&& info) {}
|
||||
|
||||
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||
Handle<HwDescriptorSetLayout> dslh) {
|
||||
auto layout = handleCast<WebGPUDescriptorSetLayout>(dslh);
|
||||
constructHandle<WebGPUDescriptorSet>(dsh, layout->getLayout(), layout->getLayoutSize());
|
||||
}
|
||||
Handle<HwDescriptorSetLayout> dslh) {}
|
||||
|
||||
Handle<HwStream> WebGPUDriver::createStreamNative(void* nativeStream) {
|
||||
return {};
|
||||
@@ -598,7 +552,7 @@ bool WebGPUDriver::isTextureFormatSupported(TextureFormat format) {
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isTextureSwizzleSupported() {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebGPUDriver::isTextureFormatMipmappable(TextureFormat format) {
|
||||
@@ -687,30 +641,24 @@ size_t WebGPUDriver::getMaxArrayTextureLayers() {
|
||||
|
||||
void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> ibh, BufferDescriptor&& p,
|
||||
uint32_t byteOffset) {
|
||||
updateGPUBuffer(handleCast<WGPUIndexBuffer>(ibh), std::move(p), byteOffset);
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> ibh, BufferDescriptor&& p,
|
||||
uint32_t byteOffset) {
|
||||
updateGPUBuffer(handleCast<WGPUBufferObject>(ibh), std::move(p), 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::updateBufferObjectUnsynchronized(Handle<HwBufferObject> ibh, BufferDescriptor&& p,
|
||||
uint32_t byteOffset) {
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
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,
|
||||
Handle<HwBufferObject> boh) {
|
||||
auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh);
|
||||
auto* bufferObject = handleCast<WGPUBufferObject>(boh);
|
||||
assert_invariant(index < vertexBuffer->buffers.size());
|
||||
assert_invariant(bufferObject->buffer.GetUsage() & wgpu::BufferUsage::Vertex);
|
||||
vertexBuffer->buffers[index] = bufferObject->buffer;
|
||||
}
|
||||
|
||||
void WebGPUDriver::update3DImage(Handle<HwTexture> th,
|
||||
@@ -743,72 +691,18 @@ void WebGPUDriver::compilePrograms(CompilerPriorityQueue priority,
|
||||
}
|
||||
|
||||
void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
|
||||
wgpu::CommandEncoderDescriptor commandEncoderDescriptor = {
|
||||
.label = "command_encoder"
|
||||
};
|
||||
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) {
|
||||
red -= 0.01;
|
||||
} else {
|
||||
red = 1.0f;
|
||||
}
|
||||
assert_invariant(mTextureView);
|
||||
wgpu::RenderPassColorAttachment renderPassColorAttachment = {
|
||||
.view = mTextureView,
|
||||
// TODO: remove this code once WebGPU Pipeline is implemented with render targets, pipeline and buffers.
|
||||
.depthSlice = wgpu::kDepthSliceUndefined,
|
||||
.loadOp = wgpu::LoadOp::Clear,
|
||||
.storeOp = wgpu::StoreOp::Store,
|
||||
.clearValue = wgpu::Color{red, 0 , 0 , 1},
|
||||
};
|
||||
|
||||
wgpu::RenderPassDescriptor renderPassDescriptor = {
|
||||
.colorAttachmentCount = 1,
|
||||
.colorAttachments = &renderPassColorAttachment,
|
||||
.depthStencilAttachment = nullptr,
|
||||
.timestampWrites = nullptr,
|
||||
};
|
||||
|
||||
mRenderPassEncoder = mCommandEncoder.BeginRenderPass(&renderPassDescriptor);
|
||||
mRenderPassEncoder.SetViewport(params.viewport.left, params.viewport.bottom,
|
||||
params.viewport.width, params.viewport.height, params.depthRange.near, params.depthRange.far);
|
||||
}
|
||||
|
||||
void WebGPUDriver::endRenderPass(int) {
|
||||
mRenderPassEncoder.End();
|
||||
mRenderPassEncoder = nullptr;
|
||||
wgpu::CommandBufferDescriptor commandBufferDescriptor {
|
||||
.label = "command_buffer",
|
||||
};
|
||||
mCommandBuffer = mCommandEncoder.Finish(&commandBufferDescriptor);
|
||||
assert_invariant(mCommandBuffer);
|
||||
}
|
||||
|
||||
void WebGPUDriver::nextSubpass(int) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
|
||||
ASSERT_PRECONDITION_NON_FATAL(drawSch == readSch,
|
||||
"WebGPU driver does not support distinct draw/read swap chains.");
|
||||
auto* swapChain = handleCast<WebGPUSwapChain>(drawSch);
|
||||
mSwapChain = swapChain;
|
||||
assert_invariant(mSwapChain);
|
||||
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
|
||||
mTextureView = mSwapChain->getCurrentSurfaceTextureView(surfaceSize);
|
||||
assert_invariant(mTextureView);
|
||||
}
|
||||
|
||||
void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
|
||||
mCommandEncoder = nullptr;
|
||||
mQueue.Submit(1, &mCommandBuffer);
|
||||
mCommandBuffer = nullptr;
|
||||
mTextureView = nullptr;
|
||||
assert_invariant(mSwapChain);
|
||||
mSwapChain->present();
|
||||
}
|
||||
|
||||
void WebGPUDriver::setPushConstant(backend::ShaderStage stage, uint8_t index,
|
||||
@@ -836,7 +730,7 @@ void WebGPUDriver::readPixels(Handle<HwRenderTarget> src,
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
|
||||
void WebGPUDriver::readBufferSubData(Handle<HwBufferObject> boh,
|
||||
void WebGPUDriver::readBufferSubData(backend::BufferObjectHandle boh,
|
||||
uint32_t offset, uint32_t size, backend::BufferDescriptor&& p) {
|
||||
scheduleDestroy(std::move(p));
|
||||
}
|
||||
@@ -862,19 +756,6 @@ 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) {
|
||||
@@ -900,48 +781,25 @@ 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(
|
||||
backend::DescriptorSetHandle dsh,
|
||||
backend::descriptor_binding_t binding,
|
||||
backend::BufferObjectHandle 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) {
|
||||
/*
|
||||
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::updateDescriptorSetTexture(
|
||||
backend::DescriptorSetHandle dsh,
|
||||
backend::descriptor_binding_t binding,
|
||||
backend::TextureHandle th,
|
||||
SamplerParams params) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::bindDescriptorSet(Handle<HwDescriptorSet> dsh, backend::descriptor_set_t set,
|
||||
void WebGPUDriver::bindDescriptorSet(
|
||||
backend::DescriptorSetHandle 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) {
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
|
||||
|
||||
#include "WebGPUHandles.h"
|
||||
#include "webgpu/WebGPUConstants.h"
|
||||
#include "webgpu/WebGPUSwapChain.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
@@ -35,13 +34,11 @@
|
||||
#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 {
|
||||
|
||||
class WebGPUSwapChain;
|
||||
|
||||
/**
|
||||
* WebGPU backend (driver) implementation
|
||||
*/
|
||||
@@ -57,41 +54,16 @@ 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;
|
||||
wgpu::Adapter mAdapter = nullptr;
|
||||
wgpu::Device mDevice = nullptr;
|
||||
wgpu::Queue mQueue = nullptr;
|
||||
void* mNativeWindow = nullptr;
|
||||
WebGPUSwapChain* mSwapChain = nullptr;
|
||||
// TODO consider moving to handle allocator when ready
|
||||
std::unique_ptr<WebGPUSwapChain> mSwapChain = nullptr;
|
||||
uint64_t mNextFakeHandle = 1;
|
||||
wgpu::CommandEncoder mCommandEncoder = nullptr;
|
||||
wgpu::TextureView mTextureView = nullptr;
|
||||
wgpu::RenderPassEncoder mRenderPassEncoder = nullptr;
|
||||
wgpu::CommandBuffer mCommandBuffer = nullptr;
|
||||
WGPURenderTarget* mDefaultRenderTarget = nullptr;
|
||||
|
||||
/*
|
||||
* Driver interface
|
||||
*/
|
||||
@@ -121,21 +93,6 @@ private:
|
||||
return mHandleAllocator.allocate<D>();
|
||||
}
|
||||
|
||||
template<typename D, typename B, typename... ARGS>
|
||||
D* constructHandle(Handle<B>& handle, ARGS&&... args) noexcept {
|
||||
return mHandleAllocator.construct<D>(handle, std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
template<typename D, typename B>
|
||||
D* handleCast(Handle<B> handle) noexcept {
|
||||
return mHandleAllocator.handle_cast<D*>(handle);
|
||||
}
|
||||
|
||||
template<typename D, typename B>
|
||||
void destructHandle(Handle<B>& handle) noexcept {
|
||||
auto* p = mHandleAllocator.handle_cast<D*>(handle);
|
||||
mHandleAllocator.deallocate(handle, p);
|
||||
}
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
/*
|
||||
* 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 <utility>
|
||||
|
||||
namespace {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
wgpu::ShaderStage retStages = wgpu::ShaderStage::None;
|
||||
if (any(ShaderStageFlags::VERTEX & fFlags)) {
|
||||
retStages |= wgpu::ShaderStage::Vertex;
|
||||
}
|
||||
if (any(ShaderStageFlags::FRAGMENT & fFlags)) {
|
||||
retStages |= wgpu::ShaderStage::Fragment;
|
||||
}
|
||||
if (any(ShaderStageFlags::COMPUTE & fFlags)) {
|
||||
retStages |= wgpu::ShaderStage::Compute;
|
||||
}
|
||||
return retStages;
|
||||
}
|
||||
|
||||
WebGPUDescriptorSetLayout::WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout,
|
||||
wgpu::Device const& device) {
|
||||
assert_invariant(device);
|
||||
|
||||
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
||||
// debugging. For now, hack an incrementing value.
|
||||
static int layoutNum = 0;
|
||||
|
||||
uint samplerCount =
|
||||
std::count_if(layout.bindings.begin(), layout.bindings.end(), [](auto& fEntry) {
|
||||
return fEntry.type == DescriptorType::SAMPLER ||
|
||||
fEntry.type == DescriptorType::SAMPLER_EXTERNAL;
|
||||
});
|
||||
|
||||
|
||||
std::vector<wgpu::BindGroupLayoutEntry> wEntries;
|
||||
wEntries.reserve(layout.bindings.size() + samplerCount);
|
||||
|
||||
for (auto fEntry: layout.bindings) {
|
||||
auto& wEntry = wEntries.emplace_back();
|
||||
wEntry.visibility = filamentStageToWGPUStage(fEntry.stageFlags);
|
||||
wEntry.binding = fEntry.binding * 2;
|
||||
|
||||
switch (fEntry.type) {
|
||||
// TODO Metal treats these the same. Is this fine?
|
||||
case DescriptorType::SAMPLER_EXTERNAL:
|
||||
case DescriptorType::SAMPLER: {
|
||||
// Sampler binding is 2n+1 due to split.
|
||||
auto& samplerEntry = wEntries.emplace_back();
|
||||
samplerEntry.binding = fEntry.binding * 2 + 1;
|
||||
samplerEntry.visibility = wEntry.visibility;
|
||||
// We are simply hoping that undefined and defaults suffices here.
|
||||
samplerEntry.sampler.type = wgpu::SamplerBindingType::Undefined;
|
||||
wEntry.texture.sampleType = wgpu::TextureSampleType::Undefined;
|
||||
break;
|
||||
}
|
||||
case DescriptorType::UNIFORM_BUFFER: {
|
||||
wEntry.buffer.hasDynamicOffset =
|
||||
any(fEntry.flags & DescriptorFlags::DYNAMIC_OFFSET);
|
||||
wEntry.buffer.type = wgpu::BufferBindingType::Uniform;
|
||||
// TODO: Ideally we fill minBindingSize
|
||||
break;
|
||||
}
|
||||
|
||||
case DescriptorType::INPUT_ATTACHMENT: {
|
||||
// TODO: support INPUT_ATTACHMENT. Metal does not currently.
|
||||
PANIC_POSTCONDITION("Input Attachment is not supported");
|
||||
break;
|
||||
}
|
||||
|
||||
case DescriptorType::SHADER_STORAGE_BUFFER: {
|
||||
// TODO: Vulkan does not support this, can we?
|
||||
PANIC_POSTCONDITION("Shader storage is not supported");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Currently flags are only used to specify dynamic offset.
|
||||
|
||||
// UNUSED
|
||||
// fEntry.count
|
||||
}
|
||||
|
||||
wgpu::BindGroupLayoutDescriptor layoutDescriptor{
|
||||
// TODO: layoutDescriptor has a "Label". Ideally we can get info on what this layout is for
|
||||
// debugging. For now, hack an incrementing value.
|
||||
.label{ "layout_" + std::to_string(++layoutNum) },
|
||||
.entryCount = wEntries.size(),
|
||||
.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
|
||||
@@ -1,213 +0,0 @@
|
||||
/*
|
||||
* 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_WEBGPUHANDLES_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
|
||||
|
||||
#include "DriverBase.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#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;
|
||||
|
||||
// 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);
|
||||
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 vertexCount, uint32_t bufferCount,
|
||||
Handle<HwVertexBufferInfo> vbih);
|
||||
|
||||
Handle<HwVertexBufferInfo> vbih;
|
||||
utils::FixedCapacityVector<wgpu::Buffer> buffers;
|
||||
};
|
||||
|
||||
struct WGPUIndexBuffer : public HwIndexBuffer {
|
||||
WGPUIndexBuffer(wgpu::Device const &device, uint8_t elementSize,
|
||||
uint32_t indexCount);
|
||||
|
||||
wgpu::Buffer buffer;
|
||||
wgpu::IndexFormat indexFormat;
|
||||
};
|
||||
|
||||
struct WGPUBufferObject : HwBufferObject {
|
||||
WGPUBufferObject(wgpu::Device const &device, BufferObjectBinding bindingType, uint32_t byteCount);
|
||||
|
||||
wgpu::Buffer buffer = nullptr;
|
||||
const BufferObjectBinding bufferObjectBinding;
|
||||
};
|
||||
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 {
|
||||
WGPUTexture(SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples,
|
||||
uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage) noexcept;
|
||||
|
||||
// constructors for creating texture views
|
||||
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() {}
|
||||
|
||||
void setBuffers(WGPUVertexBufferInfo const* const vbi,
|
||||
WGPUVertexBuffer* vertexBuffer, WGPUIndexBuffer* indexBuffer);
|
||||
|
||||
WGPUVertexBuffer* vertexBuffer = nullptr;
|
||||
WGPUIndexBuffer* indexBuffer = nullptr;
|
||||
};
|
||||
|
||||
// TODO: Currently WGPURenderTarget is not used by WebGPU for useful task.
|
||||
// Update the struct when used by WebGPU driver.
|
||||
struct WGPURenderTarget : public HwRenderTarget {
|
||||
class Attachment {
|
||||
public:
|
||||
friend struct WGPURenderTarget;
|
||||
|
||||
Attachment() = default;
|
||||
Attachment(WGPUTexture* gpuTexture, uint8_t level = 0, uint16_t layer = 0)
|
||||
: level(level),
|
||||
layer(layer),
|
||||
texture(gpuTexture->texture),
|
||||
mWGPUTexture(gpuTexture) {}
|
||||
|
||||
uint8_t level = 0;
|
||||
uint16_t layer = 0;
|
||||
|
||||
private:
|
||||
wgpu::Texture texture = nullptr;
|
||||
WGPUTexture* mWGPUTexture = nullptr;
|
||||
};
|
||||
|
||||
WGPURenderTarget(uint32_t width, uint32_t height, uint8_t samples,
|
||||
Attachment colorAttachments[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT]);
|
||||
WGPURenderTarget()
|
||||
: HwRenderTarget(0, 0),
|
||||
defaultRenderTarget(true) {}
|
||||
|
||||
void setUpRenderPassAttachments(wgpu::RenderPassDescriptor* descriptor,
|
||||
const RenderPassParams& params);
|
||||
|
||||
math::uint2 getAttachmentSize() noexcept;
|
||||
|
||||
bool isDefaultRenderTarget() const { return defaultRenderTarget; }
|
||||
uint8_t getSamples() const { return samples; }
|
||||
|
||||
Attachment getDrawColorAttachment(size_t index);
|
||||
Attachment getReadColorAttachment(size_t index);
|
||||
|
||||
private:
|
||||
static wgpu::LoadOp getLoadAction(const RenderPassParams& params, TargetBufferFlags buffer);
|
||||
static wgpu::LoadOp getStoreAction(const RenderPassParams& params, TargetBufferFlags buffer);
|
||||
|
||||
bool defaultRenderTarget = false;
|
||||
uint8_t samples = 1;
|
||||
|
||||
Attachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT] = {};
|
||||
math::uint2 attachmentSize = {};
|
||||
};
|
||||
|
||||
}// namespace filament::backend
|
||||
#endif// TNT_FILAMENT_BACKEND_WEBGPUHANDLES_H
|
||||
@@ -190,13 +190,10 @@ wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount,
|
||||
}
|
||||
}
|
||||
|
||||
void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
wgpu::SurfaceCapabilities const& capabilities, wgpu::Extent2D const& surfaceSize,
|
||||
bool useSRGBColorSpace) {
|
||||
void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device& device,
|
||||
wgpu::SurfaceCapabilities const& capabilities, bool useSRGBColorSpace) {
|
||||
config.device = device;
|
||||
config.usage = wgpu::TextureUsage::RenderAttachment;
|
||||
config.width = surfaceSize.width;
|
||||
config.height = surfaceSize.height;
|
||||
config.format =
|
||||
selectColorFormat(capabilities.formatCount, capabilities.formats, useSRGBColorSpace);
|
||||
config.presentMode =
|
||||
@@ -208,8 +205,8 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
|
||||
wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags)
|
||||
WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Adapter& adapter,
|
||||
wgpu::Device& device, uint64_t flags)
|
||||
: mSurface(surface) {
|
||||
wgpu::SurfaceCapabilities capabilities = {};
|
||||
if (!mSurface.GetCapabilities(adapter, &capabilities)) {
|
||||
@@ -220,57 +217,33 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const&
|
||||
#endif
|
||||
}
|
||||
const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0;
|
||||
initConfig(mConfig, device, capabilities, surfaceSize, useSRGBColorSpace);
|
||||
mSurface.Configure(&mConfig);
|
||||
initConfig(mConfig, device, capabilities, useSRGBColorSpace);
|
||||
}
|
||||
|
||||
WebGPUSwapChain::~WebGPUSwapChain() {
|
||||
mSurface.Unconfigure();
|
||||
if (mConfigured) {
|
||||
mSurface.Unconfigure();
|
||||
mConfigured = false;
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUSwapChain::setExtent(wgpu::Extent2D const& currentSurfaceSize) {
|
||||
FILAMENT_CHECK_POSTCONDITION(currentSurfaceSize.width > 0 || currentSurfaceSize.height > 0)
|
||||
<< "WebGPUSwapChain::setExtent: Invalid width " << currentSurfaceSize.width
|
||||
<< " and/or height " << currentSurfaceSize.height << " requested.";
|
||||
if (mConfig.width != currentSurfaceSize.width || mConfig.height != currentSurfaceSize.height) {
|
||||
mConfig.width = currentSurfaceSize.width;
|
||||
mConfig.height = currentSurfaceSize.height;
|
||||
void WebGPUSwapChain::GetCurrentTexture(uint32_t width, uint32_t height, wgpu::SurfaceTexture* texture) {
|
||||
if (width < 1 || height < 1) {
|
||||
PANIC_LOG("WebGPUSwapChain::GetCurrentTexture: Invalid width and/or height requested.");
|
||||
return;
|
||||
}
|
||||
if (mConfig.width != width || mConfig.height != height || !mConfigured) {
|
||||
mConfig.width = width;
|
||||
mConfig.height = height;
|
||||
#if FWGPU_ENABLED(FWGPU_PRINT_SYSTEM)
|
||||
printSurfaceConfiguration(mConfig);
|
||||
#endif
|
||||
FWGPU_LOGD << "Resizing to width " << mConfig.width << " height " << mConfig.height
|
||||
<< utils::io::endl;
|
||||
// TODO we may need to ensure no surface texture is flight when we do this. some
|
||||
// synchronization may be necessary
|
||||
mSurface.Configure(&mConfig);
|
||||
mConfigured = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
wgpu::TextureView WebGPUSwapChain::getCurrentSurfaceTextureView(
|
||||
wgpu::Extent2D const& currentSurfaceSize) {
|
||||
setExtent(currentSurfaceSize);
|
||||
wgpu::SurfaceTexture surfaceTexture;
|
||||
mSurface.GetCurrentTexture(&surfaceTexture);
|
||||
if (surfaceTexture.status != wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal) {
|
||||
return nullptr;
|
||||
}
|
||||
// Create a view for this surface texture
|
||||
// TODO: review these initiliazations as webgpu pipeline gets mature
|
||||
wgpu::TextureViewDescriptor textureViewDescriptor = {
|
||||
.label = "texture_view",
|
||||
.format = surfaceTexture.texture.GetFormat(),
|
||||
.dimension = wgpu::TextureViewDimension::e2D,
|
||||
.baseMipLevel = 0,
|
||||
.mipLevelCount = 1,
|
||||
.baseArrayLayer = 0,
|
||||
.arrayLayerCount = 1
|
||||
};
|
||||
return surfaceTexture.texture.CreateView(&textureViewDescriptor);
|
||||
}
|
||||
|
||||
void WebGPUSwapChain::present() {
|
||||
assert_invariant(mSurface);
|
||||
mSurface.Present();
|
||||
mSurface.GetCurrentTexture(texture);
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -19,28 +19,24 @@
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUSwapChain final : public Platform::SwapChain, HwSwapChain {
|
||||
class WebGPUSwapChain : public Platform::SwapChain {
|
||||
public:
|
||||
WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
|
||||
wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags);
|
||||
WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Adapter& adapter, wgpu::Device& device,
|
||||
uint64_t flags);
|
||||
~WebGPUSwapChain();
|
||||
|
||||
wgpu::TextureView getCurrentSurfaceTextureView(wgpu::Extent2D const&);
|
||||
|
||||
void present();
|
||||
void GetCurrentTexture(uint32_t width, uint32_t height, wgpu::SurfaceTexture*);
|
||||
|
||||
private:
|
||||
void setExtent(wgpu::Extent2D const&);
|
||||
|
||||
wgpu::Surface mSurface = {};
|
||||
wgpu::SurfaceConfiguration mConfig = {};
|
||||
bool mConfigured = false;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <android/native_window.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -29,14 +28,6 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
|
||||
ANativeWindow* window = static_cast<ANativeWindow*>(nativeWindow);
|
||||
return wgpu::Extent2D{
|
||||
.width = static_cast<uint32_t>(ANativeWindow_getWidth(window)),
|
||||
.height = static_cast<uint32_t>(ANativeWindow_getHeight(window))
|
||||
};
|
||||
}
|
||||
|
||||
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
|
||||
wgpu::SurfaceSourceAndroidNativeWindow surfaceSourceAndroidWindow{};
|
||||
surfaceSourceAndroidWindow.window = nativeWindow;
|
||||
|
||||
@@ -24,8 +24,21 @@
|
||||
#include <cstdint>
|
||||
|
||||
// Platform specific includes and defines
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#if defined(__APPLE__)
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#elif defined(FILAMENT_IOS)
|
||||
// Metal is not available when building for the iOS simulator on Desktop.
|
||||
#define METAL_AVAILABLE __has_include(<QuartzCore/CAMetalLayer.h>)
|
||||
#if METAL_AVAILABLE
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#endif
|
||||
// is this needed?
|
||||
#define METALVIEW_TAG 255
|
||||
#else
|
||||
#error Not a supported Apple + WebGPU platform
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Apple (Mac OS and IOS) specific implementation aspects of the WebGPU backend
|
||||
@@ -33,19 +46,14 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
|
||||
// Both IOS and MacOS expects CAMetalLayer.
|
||||
CAMetalLayer* metalLayer = (__bridge CAMetalLayer*) nativeWindow;
|
||||
return wgpu::Extent2D{
|
||||
.width = static_cast<uint32_t>(metalLayer.drawableSize.width),
|
||||
.height = static_cast<uint32_t>(metalLayer.drawableSize.height)
|
||||
};
|
||||
}
|
||||
|
||||
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
|
||||
wgpu::Surface surface = nullptr;
|
||||
// Both IOS and MacOS expects CAMetalLayer.
|
||||
CAMetalLayer* metalLayer = (__bridge CAMetalLayer*) nativeWindow;
|
||||
#if defined(__APPLE__)
|
||||
auto nsView = (__bridge NSView*) nativeWindow;
|
||||
FILAMENT_CHECK_POSTCONDITION(nsView) << "Unable to obtain Metal-backed NSView.";
|
||||
[nsView setWantsLayer:YES];
|
||||
id metalLayer = [CAMetalLayer layer];
|
||||
[nsView setLayer:metalLayer];
|
||||
wgpu::SurfaceSourceMetalLayer surfaceSourceMetalLayer{};
|
||||
surfaceSourceMetalLayer.layer = (__bridge void*) metalLayer;
|
||||
wgpu::SurfaceDescriptor surfaceDescriptor = {
|
||||
@@ -54,6 +62,19 @@ wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags
|
||||
};
|
||||
surface = mInstance.CreateSurface(&surfaceDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Metal-backed surface.";
|
||||
#elif defined(FILAMENT_IOS)
|
||||
CAMetalLayer* metalLayer = (CAMetalLayer*) nativeWindow;
|
||||
wgpu::SurfaceSourceMetalLayer surfaceSourceMetalLayer{};
|
||||
surfaceSourceMetalLayer.layer = (__bridge void*) metalLayer;
|
||||
wgpu::SurfaceDescriptor surfaceDescriptor = {
|
||||
.nextInChain = &surfaceSourceMetalLayer,
|
||||
.label = "metal_surface",
|
||||
};
|
||||
surface = mInstance.CreateSurface(&surfaceDescriptor);
|
||||
FILAMENT_CHECK_POSTCONDITION(surface != nullptr) << "Unable to create Metal-backed surface.";
|
||||
#else
|
||||
#error Not a supported Apple + WebGPU platform
|
||||
#endif
|
||||
return surface;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,74 +79,6 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
|
||||
auto surfaceExtent = wgpu::Extent2D{};
|
||||
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
wl* ptrval = reinterpret_cast<wl*>(nativeWindow);
|
||||
surfaceExtent.width = ptrval->width;
|
||||
surfaceExtent.height = ptrval->height;
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Unable to get window size for Linux Wayland-backed surface.";
|
||||
#elif defined(LINUX_OR_FREEBSD) && defined(FILAMENT_SUPPORTS_X11)
|
||||
if (g_x11.library == nullptr) {
|
||||
g_x11.library = dlopen(LIBRARY_X11, RTLD_LOCAL | RTLD_NOW);
|
||||
FILAMENT_CHECK_PRECONDITION(g_x11.library) << "Unable to open X11 library.";
|
||||
#if defined(FILAMENT_SUPPORTS_XCB)
|
||||
g_x11.xcbConnect = (XCB_CONNECT) dlsym(g_x11.library, "xcb_connect");
|
||||
int screen = 0;
|
||||
g_x11.connection = g_x11.xcbConnect(nullptr, &screen);
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
g_x11.openDisplay = (X11_OPEN_DISPLAY) dlsym(g_x11.library, "XOpenDisplay");
|
||||
g_x11.display = g_x11.openDisplay(NULL);
|
||||
FILAMENT_CHECK_PRECONDITION(g_x11.display) << "Unable to open X11 display.";
|
||||
#endif
|
||||
}
|
||||
#if defined(FILAMENT_SUPPORTS_XCB) || defined(FILAMENT_SUPPORTS_XLIB)
|
||||
bool useXcb = false;
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XCB)
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
useXcb = (SWAP_CHAIN_CONFIG_ENABLE_XCB) != 0;
|
||||
#else
|
||||
useXcb = true;
|
||||
#endif
|
||||
if (useXcb) {
|
||||
const xcb_setup_t* setup = xcb_get_setup(g_x11.connection);
|
||||
xcb_screen_iterator_t screen_iter = xcb_setup_roots_iterator(setup);
|
||||
xcb_screen_t* screen = screen_iter.data;
|
||||
surfaceExtent.width = static_cast<uint32_t>(screen->width_in_pixels);
|
||||
surfaceExtent.height = static_cast<uint32_t>(screen->height_in_pixels);
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Unable to get window surface size for Linux (or FreeBSD) "
|
||||
"XCB-backed surface.";
|
||||
}
|
||||
#endif
|
||||
#if defined(FILAMENT_SUPPORTS_XLIB)
|
||||
if (!useXcb) {
|
||||
int screenNumber = DefaultScreen(g_x11.display);
|
||||
Screen* screen = ScreenOfDisplay(g_x11.display, screenNumber);
|
||||
surfaceExtent.width = static_cast<uint32_t>(WidthOfScreen(screen));
|
||||
surfaceExtent.height = static_cast<uint32_t>(HeightOfScreen(screen));
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Unable to get window surface size for Linux (or FreeBSD) "
|
||||
"XLib-backed surface.";
|
||||
}
|
||||
#endif
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Cannot get window surface size for X11 surface for Linux (or FreeBSD) OS "
|
||||
"(not built with support for XCB or XLIB?)";
|
||||
#elif defined(__linux__)
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Cannot get window surface size for Linux (or FreeBSD) OS "
|
||||
"(not built with support for Wayland or X11?)";
|
||||
#else
|
||||
FILAMENT_CHECK_POSTCONDITION(surfaceExtent.width != 0 && surfaceExtent.height != 0)
|
||||
<< "Not a supported (Linux) OS + WebGPU platform";
|
||||
#endif
|
||||
return surfaceExtent;
|
||||
}
|
||||
|
||||
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t flags) {
|
||||
wgpu::Surface surface = nullptr;
|
||||
#if defined(__linux__) && defined(FILAMENT_SUPPORTS_WAYLAND)
|
||||
|
||||
@@ -30,16 +30,6 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
wgpu::Extent2D WebGPUPlatform::getSurfaceExtent(void* nativeWindow) const {
|
||||
HWND window = static_cast<HWND>(nativeWindow);
|
||||
RECT windowRect;
|
||||
GetWindowRect(window, &windowRect);
|
||||
return wgpu::Extent2D{
|
||||
.width = static_cast<uint32_t>(windowRect.right - windowRect.left),
|
||||
.height = static_cast<uint32_t>(windowRect.bottom - windowRect.top)
|
||||
};
|
||||
}
|
||||
|
||||
wgpu::Surface WebGPUPlatform::createSurface(void* nativeWindow, uint64_t /*flags*/) {
|
||||
// TODO verify this is necessary for Dawn implementation as well:
|
||||
// On (at least) NVIDIA drivers, the Vulkan implementation (specifically the call to
|
||||
|
||||
@@ -43,29 +43,24 @@ using namespace image;
|
||||
namespace test {
|
||||
|
||||
Backend BackendTest::sBackend = Backend::NOOP;
|
||||
OperatingSystem BackendTest::sOperatingSystem = OperatingSystem::OTHER;
|
||||
bool BackendTest::sIsMobilePlatform = false;
|
||||
|
||||
void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform) {
|
||||
void BackendTest::init(Backend backend, bool isMobilePlatform) {
|
||||
sBackend = backend;
|
||||
sOperatingSystem = operatingSystem;
|
||||
sIsMobilePlatform = isMobilePlatform;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -159,16 +154,49 @@ void BackendTest::renderTriangle(
|
||||
api.endRenderPass();
|
||||
}
|
||||
|
||||
bool BackendTest::matchesEnvironment(Backend backend) {
|
||||
return sBackend == backend;
|
||||
}
|
||||
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);
|
||||
|
||||
bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem) {
|
||||
return sOperatingSystem == operatingSystem;
|
||||
}
|
||||
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;
|
||||
|
||||
bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem, Backend backend) {
|
||||
return matchesEnvironment(operatingSystem) && matchesEnvironment(backend);
|
||||
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));
|
||||
}
|
||||
|
||||
class Environment : public ::testing::Environment {
|
||||
@@ -182,8 +210,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]) {
|
||||
BackendTest::init(backend, operatingSystem, isMobile);
|
||||
void initTests(Backend backend, bool isMobile, int& argc, char* argv[]) {
|
||||
BackendTest::init(backend, isMobile);
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
::testing::AddGlobalTestEnvironment(new Environment);
|
||||
}
|
||||
|
||||
@@ -25,17 +25,15 @@
|
||||
#include "private/backend/DriverApi.h"
|
||||
|
||||
#include "PlatformRunner.h"
|
||||
#include "ImageExpectations.h"
|
||||
|
||||
namespace test {
|
||||
|
||||
class BackendTest : public ::testing::Test {
|
||||
public:
|
||||
|
||||
static void init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform);
|
||||
static void init(Backend backend, bool isMobilePlatform);
|
||||
|
||||
static Backend sBackend;
|
||||
static OperatingSystem sOperatingSystem;
|
||||
static bool sIsMobilePlatform;
|
||||
|
||||
protected:
|
||||
@@ -66,14 +64,13 @@ 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;
|
||||
@@ -81,10 +78,6 @@ 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
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "ImageExpectations.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "utils/Hash.h"
|
||||
@@ -27,17 +28,14 @@
|
||||
#ifndef FILAMENT_IOS
|
||||
|
||||
#include <imageio/ImageEncoder.h>
|
||||
#include <imageio/ImageDecoder.h>
|
||||
#include <image/ColorTransform.h>
|
||||
|
||||
#endif
|
||||
|
||||
ScreenshotParams::ScreenshotParams(int width, int height, std::string fileName,
|
||||
uint32_t expectedHash)
|
||||
: mWidth(width),
|
||||
mHeight(height),
|
||||
mExpectedPixelHash(expectedHash),
|
||||
mFileName(std::move(fileName)) {}
|
||||
uint32_t expectedPixelHash)
|
||||
: mWidth(width), mHeight(height), mFileName(std::move(fileName)),
|
||||
mExpectedPixelHash(expectedPixelHash) {}
|
||||
|
||||
int ScreenshotParams::width() const {
|
||||
return mWidth;
|
||||
@@ -51,28 +49,24 @@ uint32_t ScreenshotParams::expectedHash() const {
|
||||
return mExpectedPixelHash;
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::actualDirectoryPath() {
|
||||
return "images/actual_images";
|
||||
std::string ScreenshotParams::outputDirectoryPath() const {
|
||||
return ".";
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::actualFileName() const {
|
||||
std::string ScreenshotParams::generatedActualFileName() const {
|
||||
return absl::StrFormat("%s_actual.png", mFileName);
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::actualFilePath() const {
|
||||
return absl::StrFormat("%s/%s", actualDirectoryPath(), actualFileName());
|
||||
std::string ScreenshotParams::generatedActualFilePath() const {
|
||||
return absl::StrFormat("%s/%s", outputDirectoryPath(), generatedActualFileName());
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::expectedDirectoryPath() {
|
||||
return "images/expected_images";
|
||||
std::string ScreenshotParams::goldenFileName() const {
|
||||
return absl::StrFormat("%s_golden.png", mFileName);
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::expectedFileName() const {
|
||||
return absl::StrFormat("%s.png", mFileName);
|
||||
}
|
||||
|
||||
std::string ScreenshotParams::expectedFilePath() const {
|
||||
return absl::StrFormat("%s/%s", expectedDirectoryPath(), expectedFileName());
|
||||
std::string ScreenshotParams::goldenFilePath() const {
|
||||
return absl::StrFormat("%s/%s", outputDirectoryPath(), goldenFileName());
|
||||
}
|
||||
|
||||
ImageExpectation::ImageExpectation(const char* fileName, int lineNumber,
|
||||
@@ -97,22 +91,11 @@ 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) {
|
||||
// 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)) << 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.
|
||||
EXPECT_THAT(actualHash, testing::Eq(mParams.expectedHash()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,13 +109,12 @@ ImageExpectations::~ImageExpectations() {
|
||||
|
||||
void ImageExpectations::addExpectation(const char* fileName, int lineNumber,
|
||||
filament::backend::RenderTargetHandle renderTarget, ScreenshotParams params) {
|
||||
mExpectations.emplace_back(std::make_unique<ImageExpectation>(fileName, lineNumber, mApi,
|
||||
std::move(params), renderTarget));
|
||||
mExpectations.emplace_back(fileName, lineNumber, mApi, std::move(params), renderTarget);
|
||||
}
|
||||
|
||||
void ImageExpectations::evaluate() {
|
||||
for (auto& expectation: mExpectations) {
|
||||
expectation->evaluate();
|
||||
expectation.evaluate();
|
||||
}
|
||||
mExpectations.clear();
|
||||
}
|
||||
@@ -140,28 +122,32 @@ 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(),
|
||||
internal->params.width() * 4, (uint8_t*)buffer);
|
||||
std::string filePath = internal->params.actualFilePath();
|
||||
std::string filePath = internal->params.generatedActualFilePath();
|
||||
std::ofstream pngStream(filePath, std::ios::binary | std::ios::trunc);
|
||||
image::ImageEncoder::encode(pngStream, image::ImageEncoder::Format::PNG, image, "",
|
||||
filePath);
|
||||
#endif
|
||||
internal->bytesFilled = true;
|
||||
};
|
||||
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,30 +169,4 @@ bool RenderTargetDump::bytesFilled() const {
|
||||
return mInternal->bytesFilled;
|
||||
}
|
||||
|
||||
RenderTargetDump::Internal::Internal(const ScreenshotParams& params) : params(params) {}
|
||||
|
||||
LoadedPng::LoadedPng(std::string filePath) : mFilePath(std::move(filePath)) {
|
||||
#ifndef FILAMENT_IOS
|
||||
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() *
|
||||
loadedImage.getChannels();
|
||||
// The linear image is loaded with each component as [0.0, 1.0] but should be [0, 255], so
|
||||
// convert them.
|
||||
mBytes = std::vector<unsigned char>(valuesInImage);
|
||||
for (int i = 0; i < valuesInImage; ++i) {
|
||||
mBytes[i] = static_cast<uint8_t>(loadedImage.get<float>()[i] * 255.0f);
|
||||
}
|
||||
#endif
|
||||
// For platforms that don't support the image loading library, leave the loaded data blank.
|
||||
}
|
||||
|
||||
uint32_t LoadedPng::hash() const {
|
||||
EXPECT_THAT(mBytes, testing::Not(testing::IsEmpty()))
|
||||
<< "Failed to load expected test result: " << mFilePath;
|
||||
if (mBytes.empty()) {
|
||||
return 0;
|
||||
}
|
||||
return utils::hash::murmur3((uint32_t*)mBytes.data(), mBytes.size() / 4, 0);
|
||||
}
|
||||
RenderTargetDump::Internal::Internal(const ScreenshotParams& params) : params(params) {}
|
||||
@@ -46,12 +46,11 @@ public:
|
||||
int height() const;
|
||||
uint32_t expectedHash() const;
|
||||
|
||||
static std::string actualDirectoryPath();
|
||||
std::string actualFileName() const;
|
||||
std::string actualFilePath() const;
|
||||
static std::string expectedDirectoryPath();
|
||||
std::string expectedFileName() const;
|
||||
std::string expectedFilePath() const;
|
||||
std::string outputDirectoryPath() const;
|
||||
std::string generatedActualFileName() const;
|
||||
std::string generatedActualFilePath() const;
|
||||
std::string goldenFileName() const;
|
||||
std::string goldenFilePath() const;
|
||||
|
||||
private:
|
||||
int mWidth;
|
||||
@@ -99,17 +98,6 @@ private:
|
||||
std::unique_ptr<Internal> mInternal;
|
||||
};
|
||||
|
||||
class LoadedPng {
|
||||
public:
|
||||
explicit LoadedPng(std::string filePath);
|
||||
|
||||
uint32_t hash() const;
|
||||
|
||||
private:
|
||||
std::string mFilePath;
|
||||
std::vector<unsigned char> mBytes;
|
||||
};
|
||||
|
||||
class ImageExpectation {
|
||||
public:
|
||||
ImageExpectation(const char* fileName, int lineNumber, filament::backend::DriverApi& api,
|
||||
@@ -142,8 +130,7 @@ public:
|
||||
|
||||
private:
|
||||
filament::backend::DriverApi& mApi;
|
||||
// Store expectations in unique pointers because they are self referential.
|
||||
std::vector<std::unique_ptr<ImageExpectation>> mExpectations;
|
||||
std::vector<ImageExpectation> mExpectations;
|
||||
};
|
||||
|
||||
#endif //TNT_IMAGE_EXPECTATIONS_H
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 "PlatformRunner.h"
|
||||
|
||||
namespace utils {
|
||||
|
||||
template<>
|
||||
CString to_string<test::Backend>(test::Backend backend) noexcept {
|
||||
switch (backend) {
|
||||
case test::Backend::OPENGL: {
|
||||
return "OpenGL";
|
||||
}
|
||||
case test::Backend::VULKAN: {
|
||||
return "Vulkan";
|
||||
}
|
||||
case test::Backend::METAL: {
|
||||
return "Metal";
|
||||
}
|
||||
case test::Backend::WEBGPU: {
|
||||
return "WebGPU";
|
||||
}
|
||||
case test::Backend::NOOP:
|
||||
default: {
|
||||
return "No-op";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
CString to_string(test::OperatingSystem os) noexcept {
|
||||
switch (os) {
|
||||
case test::OperatingSystem::LINUX: {
|
||||
return "Linux";
|
||||
}
|
||||
case test::OperatingSystem::APPLE: {
|
||||
return "Apple";
|
||||
}
|
||||
case test::OperatingSystem::OTHER:
|
||||
default: {
|
||||
return "Other";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "utils/CString.h"
|
||||
|
||||
namespace test {
|
||||
|
||||
@@ -35,15 +34,6 @@ enum class Backend : uint8_t {
|
||||
NOOP = 5,
|
||||
};
|
||||
|
||||
enum class OperatingSystem: uint8_t {
|
||||
OTHER = 1,
|
||||
// Also represents android phones.
|
||||
LINUX = 2,
|
||||
// Also represents iOS phones.
|
||||
APPLE = 3,
|
||||
// TODO: When tests support windows add it here.
|
||||
};
|
||||
|
||||
struct NativeView {
|
||||
void* ptr = nullptr;
|
||||
size_t width = 0, height = 0;
|
||||
@@ -61,10 +51,9 @@ NativeView getNativeView();
|
||||
* No tests will be run yet.
|
||||
*
|
||||
* @param backend The backend to run the tests on.
|
||||
* @param operatingSystem The operating system the tests are being run on.
|
||||
* @param isMobile True if the platform is a mobile platform (iOS or Android).
|
||||
*/
|
||||
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]);
|
||||
void initTests(Backend backend, bool isMobile, int& argc, char* argv[]);
|
||||
|
||||
/**
|
||||
* Test runners should call runTests when they are ready for tests to be run.
|
||||
@@ -79,6 +68,6 @@ int runTests();
|
||||
*/
|
||||
Backend parseArgumentsForBackend(int argc, char* argv[]);
|
||||
|
||||
} // namespace test
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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 "Skip.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace test {
|
||||
|
||||
SkipEnvironment::SkipEnvironment(test::Backend backend) : backend(backend) {}
|
||||
SkipEnvironment::SkipEnvironment(test::OperatingSystem os) : os(os) {}
|
||||
SkipEnvironment::SkipEnvironment(test::OperatingSystem os, test::Backend backend)
|
||||
: backend(backend),
|
||||
os(os) {}
|
||||
|
||||
bool SkipEnvironment::matches() {
|
||||
bool backendMatches = !backend.has_value() || *backend == BackendTest::sBackend;
|
||||
bool osMatches = !os.has_value() || *os == BackendTest::sOperatingSystem;
|
||||
bool isMobileMatches = !isMobile.has_value() || *isMobile == BackendTest::sIsMobilePlatform;
|
||||
return backendMatches && osMatches && isMobileMatches;
|
||||
}
|
||||
|
||||
std::string SkipEnvironment::describe() {
|
||||
std::stringstream result;
|
||||
if (matches()) {
|
||||
result << "environment matches because " << describe_actual_environment() << ".";
|
||||
} else {
|
||||
result << "environment does not match because " << describe_requirements() << " but "
|
||||
<< describe_actual_environment() << ".";
|
||||
}
|
||||
return result.str();
|
||||
}
|
||||
|
||||
std::string SkipEnvironment::describe_actual_environment() {
|
||||
bool resultWritten = false;
|
||||
std::stringstream reality;
|
||||
if (backend.has_value()) {
|
||||
reality << "backend was " << utils::to_string(BackendTest::sBackend).c_str();
|
||||
resultWritten = true;
|
||||
}
|
||||
if (os.has_value()) {
|
||||
if (resultWritten) {
|
||||
reality << ", and ";
|
||||
}
|
||||
reality << "operating system was "
|
||||
<< utils::to_string(BackendTest::sOperatingSystem).c_str();
|
||||
resultWritten = true;
|
||||
}
|
||||
if (isMobile.has_value()) {
|
||||
if (resultWritten) {
|
||||
reality << ", and ";
|
||||
}
|
||||
reality << "device " << (BackendTest::sIsMobilePlatform ? "was" : "was not") << " mobile";
|
||||
resultWritten = true;
|
||||
}
|
||||
return reality.str();
|
||||
}
|
||||
|
||||
std::string SkipEnvironment::describe_requirements() {
|
||||
bool resultWritten = false;
|
||||
std::stringstream requirement;
|
||||
if (backend.has_value()) {
|
||||
requirement << "backend needs to be " << utils::to_string(*backend).c_str();
|
||||
resultWritten = true;
|
||||
}
|
||||
if (os.has_value()) {
|
||||
if (resultWritten) {
|
||||
requirement << ", and ";
|
||||
}
|
||||
requirement << "operating system needs to be " << utils::to_string(*os).c_str();
|
||||
resultWritten = true;
|
||||
}
|
||||
if (isMobile.has_value() && BackendTest::sIsMobilePlatform != isMobile) {
|
||||
if (resultWritten) {
|
||||
requirement << ", and ";
|
||||
}
|
||||
requirement << "device needs to " << (*isMobile ? "be" : "not be") << " mobile";
|
||||
resultWritten = true;
|
||||
}
|
||||
return requirement.str();
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 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_SKIP_H
|
||||
#define TNT_SKIP_H
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "BackendTest.h"
|
||||
|
||||
// skipEnvironment must be a test::SkipEnvironment
|
||||
#define SKIP_IF(skipEnvironment) \
|
||||
do { \
|
||||
SkipEnvironment skip(skipEnvironment); \
|
||||
if (skip.matches()) { \
|
||||
GTEST_SKIP() << "Skipping test as the " << skip.describe(); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
namespace test {
|
||||
|
||||
struct SkipEnvironment {
|
||||
SkipEnvironment(const SkipEnvironment&) = default;
|
||||
explicit SkipEnvironment(test::Backend backend);
|
||||
explicit SkipEnvironment(test::OperatingSystem os);
|
||||
SkipEnvironment(test::OperatingSystem os, test::Backend backend);
|
||||
|
||||
std::optional<test::Backend> backend;
|
||||
std::optional<test::OperatingSystem> os;
|
||||
std::optional<bool> isMobile;
|
||||
|
||||
bool matches();
|
||||
// Describes the current state of either matching or mismatching.
|
||||
std::string describe();
|
||||
// Describe all the non-null requirements.
|
||||
std::string describe_requirements();
|
||||
// Describes the environment's status for all the attributes that are non-null.
|
||||
std::string describe_actual_environment();
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
|
||||
#endif// TNT_SKIP_H
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 982 B |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |