Compare commits
15 Commits
v1.59.2
...
idris/rend
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ff62402c2 | ||
|
|
595a313686 | ||
|
|
21dd1319df | ||
|
|
444ac8d6a6 | ||
|
|
aac1e7dc4c | ||
|
|
5b8a1e5e58 | ||
|
|
ad1b36d2b3 | ||
|
|
315c2c273f | ||
|
|
005d835dbe | ||
|
|
cade94ab2c | ||
|
|
5cd2f5626c | ||
|
|
94bbcbf1c3 | ||
|
|
52c998d2b6 | ||
|
|
527d831c15 | ||
|
|
2790f2e64c |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.59.1'
|
||||
implementation 'com.google.android.filament:filament-android:1.59.2'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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.1'
|
||||
pod 'Filament', '~> 1.59.2'
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -7,6 +7,9 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.59.3
|
||||
|
||||
|
||||
## v1.59.2
|
||||
|
||||
- Fix build/compile errors when upgrading to MacOS 15.4
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.59.1
|
||||
VERSION_NAME=1.59.2
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -507,8 +507,10 @@ 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
|
||||
|
||||
@@ -55,4 +55,9 @@ 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
|
||||
|
||||
@@ -38,6 +38,12 @@ 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
|
||||
|
||||
@@ -54,31 +54,27 @@ using namespace utils;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
static void logCompilationError(utils::io::ostream& out,
|
||||
ShaderStage shaderType, const char* name,
|
||||
static inline std::string to_string(bool b) noexcept { return b ? "true" : "false"; }
|
||||
static inline std::string to_string(int i) noexcept { return std::to_string(i); }
|
||||
static inline std::string to_string(float f) noexcept { return "float(" + std::to_string(f) + ")"; }
|
||||
|
||||
static void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
|
||||
GLuint shaderId, CString const& sourceCode) noexcept;
|
||||
static void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept;
|
||||
|
||||
static void logProgramLinkError(utils::io::ostream& out,
|
||||
const char* name, GLuint program) noexcept;
|
||||
|
||||
static inline std::string to_string(bool b) noexcept {
|
||||
return b ? "true" : "false";
|
||||
}
|
||||
|
||||
static inline std::string to_string(int i) noexcept {
|
||||
return std::to_string(i);
|
||||
}
|
||||
|
||||
static inline std::string to_string(float f) noexcept {
|
||||
return "float(" + std::to_string(f) + ")";
|
||||
}
|
||||
static 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;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
struct ProgramData {
|
||||
GLuint program{};
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
|
||||
shaders_t shaders{};
|
||||
};
|
||||
|
||||
~OpenGLProgramToken() override;
|
||||
@@ -90,10 +86,10 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
ShaderCompilerService& compiler;
|
||||
utils::CString const& name;
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
|
||||
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
|
||||
shaders_source_t shaderSourceCode;
|
||||
void* user = nullptr;
|
||||
struct {
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
|
||||
shaders_t shaders{};
|
||||
GLuint program = 0;
|
||||
} gl; // 12 bytes
|
||||
|
||||
@@ -140,11 +136,12 @@ struct ShaderCompilerService::OpenGLProgramToken : ProgramToken {
|
||||
|
||||
ShaderCompilerService::OpenGLProgramToken::~OpenGLProgramToken() = default;
|
||||
|
||||
void ShaderCompilerService::setUserData(const program_token_t& token, void* user) noexcept {
|
||||
/* static */ void ShaderCompilerService::setUserData(const program_token_t& token,
|
||||
void* user) noexcept {
|
||||
token->user = user;
|
||||
}
|
||||
|
||||
void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept {
|
||||
/* static */ void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept {
|
||||
return token->user;
|
||||
}
|
||||
|
||||
@@ -268,101 +265,111 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
token->handle = mCallbackManager.get();
|
||||
|
||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// queue a compile job
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, &gl, program = std::move(program), token]() mutable {
|
||||
// compile the shaders
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
|
||||
compileShaders(gl,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
program.isMultiview(),
|
||||
shaders,
|
||||
token->shaderSourceCode);
|
||||
switch (mMode) {
|
||||
case Mode::THREAD_POOL: {
|
||||
// queue a compile job
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, &gl, program = std::move(program), token]() mutable {
|
||||
// compile the shaders
|
||||
shaders_t shaders{};
|
||||
compileShaders(gl,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
program.isMultiview(),
|
||||
shaders,
|
||||
token->shaderSourceCode);
|
||||
|
||||
// link the program
|
||||
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
|
||||
// link the program
|
||||
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
|
||||
|
||||
OpenGLProgramToken::ProgramData programData;
|
||||
programData.shaders = shaders;
|
||||
OpenGLProgramToken::ProgramData programData;
|
||||
programData.shaders = shaders;
|
||||
|
||||
// We need to query the link status here to guarantee that the
|
||||
// program is compiled and linked now (we don't want this to be
|
||||
// deferred to later). We don't care about the result at this point.
|
||||
GLint status = GL_FALSE;
|
||||
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
|
||||
programData.program = glProgram;
|
||||
// We need to query the link status here to guarantee that the
|
||||
// program is compiled and linked now (we don't want this to be
|
||||
// deferred to later). We don't care about the result at this point.
|
||||
GLint status = GL_FALSE;
|
||||
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
|
||||
programData.program = glProgram;
|
||||
|
||||
// we don't need to check for success here, it'll be done on the
|
||||
// main thread side.
|
||||
token->set(programData);
|
||||
// we don't need to check for success here, it'll be done on the
|
||||
// main thread side.
|
||||
token->set(programData);
|
||||
|
||||
mCallbackManager.put(token->handle);
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
// caching must be the last thing we do
|
||||
if (token->key && status == GL_TRUE) {
|
||||
// Attempt to cache. This calls glGetProgramBinary.
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, glProgram);
|
||||
}
|
||||
});
|
||||
// caching must be the last thing we do
|
||||
if (token->key && status == GL_TRUE) {
|
||||
// Attempt to cache. This calls glGetProgramBinary.
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, glProgram);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
// this cannot fail because we check compilation status after linking the program
|
||||
// shaders[] is filled with id of shader stages present.
|
||||
compileShaders(gl,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
program.isMultiview(),
|
||||
token->gl.shaders,
|
||||
token->shaderSourceCode);
|
||||
case Mode::SYNCHRONOUS:
|
||||
case Mode::ASYNCHRONOUS: {
|
||||
// this cannot fail because we check compilation status after linking the program
|
||||
// shaders[] is filled with id of shader stages present.
|
||||
compileShaders(gl,
|
||||
std::move(program.getShadersSource()),
|
||||
program.getSpecializationConstants(),
|
||||
program.isMultiview(),
|
||||
token->gl.shaders,
|
||||
token->shaderSourceCode);
|
||||
|
||||
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
||||
assert_invariant(mMode != Mode::THREAD_POOL);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// don't attempt to link this program if all shaders are not done compiling
|
||||
GLint status;
|
||||
if (token->gl.program) {
|
||||
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (auto shader: token->gl.shaders) {
|
||||
if (shader) {
|
||||
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
return false;
|
||||
runAtNextTick(priorityQueue, token, [this, token](Job const&) {
|
||||
assert_invariant(mMode != Mode::THREAD_POOL);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// don't attempt to link this program if all shaders are not done compiling
|
||||
GLint status;
|
||||
if (token->gl.program) {
|
||||
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
for (auto shader: token->gl.shaders) {
|
||||
if (shader) {
|
||||
glGetShaderiv(shader, GL_COMPLETION_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!token->gl.program) {
|
||||
// link the program, this also cannot fail because status is checked later.
|
||||
token->gl.program = linkProgram(mDriver.getContext(),
|
||||
token->gl.shaders, token->attributes);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// wait until the link finishes...
|
||||
return false;
|
||||
if (!token->gl.program) {
|
||||
// link the program, this also cannot fail because status is checked later.
|
||||
token->gl.program = linkProgram(mDriver.getContext(),
|
||||
token->gl.shaders, token->attributes);
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// wait until the link finishes...
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert_invariant(token->gl.program);
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
mCallbackManager.put(token->handle);
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
if (token->key) {
|
||||
// TODO: technically we don't have to cache right now. Is it advantageous to
|
||||
// do this later, maybe depending on CPU usage?
|
||||
// attempt to cache if we don't have a thread pool (otherwise it's done
|
||||
// by the pool).
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
}
|
||||
if (token->key) {
|
||||
// TODO: technically we don't have to cache right now. Is it advantageous to
|
||||
// do this later, maybe depending on CPU usage?
|
||||
// attempt to cache if we don't have a thread pool (otherwise it's done
|
||||
// by the pool).
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::UNDEFINED: {
|
||||
assert_invariant(false);
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
@@ -377,12 +384,12 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
||||
return program;
|
||||
}
|
||||
|
||||
void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
/* static */ void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
token->canceled = true;
|
||||
|
||||
bool const canceled = token->compiler.cancelTickOp(token);
|
||||
bool const isTickOpCanceled = token->compiler.cancelTickOp(token);
|
||||
|
||||
if (token->compiler.mMode == Mode::THREAD_POOL) {
|
||||
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||
@@ -395,7 +402,7 @@ void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
// order for future callbacks to be successfully called.
|
||||
token->compiler.mCallbackManager.put(token->handle);
|
||||
}
|
||||
} else if (canceled) {
|
||||
} else if (isTickOpCanceled) {
|
||||
// Since the tick op was canceled, we need to .put the token here.
|
||||
token->compiler.mCallbackManager.put(token->handle);
|
||||
}
|
||||
@@ -432,7 +439,8 @@ void ShaderCompilerService::notifyWhenAllProgramsAreReady(
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) noexcept {
|
||||
/* static */ void ShaderCompilerService::getProgramFromCompilerPool(
|
||||
program_token_t& token) noexcept {
|
||||
OpenGLProgramToken::ProgramData const& programData{ token->get() };
|
||||
if (!token->canceled) {
|
||||
token->gl.shaders = programData.shaders;
|
||||
@@ -443,40 +451,53 @@ void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) n
|
||||
GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
if (!token->gl.program) {
|
||||
if (mMode == Mode::THREAD_POOL) {
|
||||
// we need this program right now, remove it from the queue
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
// if we were able to remove it, we execute the job now, otherwise it means
|
||||
// it's being executed right now.
|
||||
job();
|
||||
switch (mMode) {
|
||||
case Mode::THREAD_POOL: {
|
||||
// we need this program right now, remove it from the queue
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
// if we were able to remove it, we execute the job now, otherwise it means
|
||||
// it's being executed right now.
|
||||
job();
|
||||
}
|
||||
|
||||
if (!token->canceled) {
|
||||
token->compiler.cancelTickOp(token);
|
||||
}
|
||||
|
||||
// Block until we get the program from the pool. Generally this wouldn't block
|
||||
// because we just compiled the program above, when executing job.
|
||||
ShaderCompilerService::getProgramFromCompilerPool(token);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!token->canceled) {
|
||||
case Mode::ASYNCHRONOUS: {
|
||||
// we force the program link -- which might stall, either here or below in
|
||||
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
|
||||
token->compiler.cancelTickOp(token);
|
||||
|
||||
token->gl.program =
|
||||
linkProgram(mDriver.getContext(), token->gl.shaders, token->attributes);
|
||||
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
if (token->key) {
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Block until we get the program from the pool. Generally this wouldn't block
|
||||
// because we just compiled the program above, when executing job.
|
||||
ShaderCompilerService::getProgramFromCompilerPool(token);
|
||||
} else if (mMode == Mode::ASYNCHRONOUS) {
|
||||
// we force the program link -- which might stall, either here or below in
|
||||
// checkProgramStatus(), but we don't have a choice, we need to use the program now.
|
||||
token->compiler.cancelTickOp(token);
|
||||
|
||||
token->gl.program = linkProgram(mDriver.getContext(),
|
||||
token->gl.shaders, token->attributes);
|
||||
|
||||
assert_invariant(token->gl.program);
|
||||
|
||||
mCallbackManager.put(token->handle);
|
||||
|
||||
if (token->key) {
|
||||
mBlobCache.insert(mDriver.mPlatform, token->key, token->gl.program);
|
||||
case Mode::SYNCHRONOUS: {
|
||||
// if we don't have a program yet, block until we get it.
|
||||
tick();
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::UNDEFINED: {
|
||||
assert_invariant(false);
|
||||
}
|
||||
} else {
|
||||
// if we don't have a program yet, block until we get it.
|
||||
tick();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,12 +540,12 @@ GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
|
||||
* checked until after the program is linked.
|
||||
* This always returns the GL shader IDs or zero a shader stage is not present.
|
||||
*/
|
||||
void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
/* static */ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
Program::ShaderSource shadersSource,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
|
||||
bool multiview,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
|
||||
UTILS_UNUSED_IN_RELEASE std::array<CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept {
|
||||
shaders_t& outShaders,
|
||||
UTILS_UNUSED_IN_RELEASE shaders_source_t& outShaderSourceCode) noexcept {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
|
||||
@@ -635,209 +656,13 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
}
|
||||
}
|
||||
|
||||
// If usages of the Google-style line directive are present, remove them, as some
|
||||
// drivers don't allow the quotation marks. This source modification happens in-place.
|
||||
void ShaderCompilerService::process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
|
||||
char* source, size_t len) noexcept {
|
||||
if (!context.ext.GOOGLE_cpp_style_line_directive) {
|
||||
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
|
||||
removeGoogleLineDirectives(source, len); // length is unaffected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look up the `source` to replace the number of eyes for multiview with the given number. This is
|
||||
// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine
|
||||
// the number of views, which is assumed as a single digit, for multiview.
|
||||
// This source modification happens in-place.
|
||||
void ShaderCompilerService::process_OVR_multiview2(OpenGLContext& context,
|
||||
int32_t eyeCount, char* source, size_t len) noexcept {
|
||||
// We don't use regular expression in favor of performance.
|
||||
if (context.ext.OVR_multiview2) {
|
||||
const std::string_view shader{ source, len };
|
||||
const std::string_view layout = "layout";
|
||||
const std::string_view num_views = "num_views";
|
||||
size_t found = 0;
|
||||
while (true) {
|
||||
found = shader.find(layout, found);
|
||||
if (found == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + layout.size());
|
||||
if (found == std::string_view::npos || shader[found] != '(') {
|
||||
continue;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + 1);
|
||||
if (found == std::string_view::npos) {
|
||||
continue;
|
||||
}
|
||||
if (shader.compare(found, num_views.size(), num_views) != 0) {
|
||||
continue;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + num_views.size());
|
||||
if (found == std::string_view::npos || shader[found] != '=') {
|
||||
continue;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + 1);
|
||||
if (found == std::string_view::npos) {
|
||||
continue;
|
||||
}
|
||||
// We assume the value should be one-digit number.
|
||||
assert_invariant(eyeCount < 10);
|
||||
assert_invariant(!::isdigit(source[found + 1]));
|
||||
source[found] = '0' + eyeCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
|
||||
// macOS doesn't support GL_ARB_shading_language_packing
|
||||
// Also GLES3.0 didn't have the full set of packing/unpacking functions
|
||||
std::string_view ShaderCompilerService::process_ARB_shading_language_packing(OpenGLContext& context) noexcept {
|
||||
using namespace std::literals;
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
if (!context.isAtLeastGL<4, 2>() && !context.ext.ARB_shading_language_packing) {
|
||||
return R"(
|
||||
|
||||
// these don't handle denormals, NaNs or inf
|
||||
float u16tofp32(highp uint v) {
|
||||
v <<= 16u;
|
||||
highp uint s = v & 0x80000000u;
|
||||
highp uint n = v & 0x7FFFFFFFu;
|
||||
highp uint nz = (n == 0u) ? 0u : 0xFFFFFFFFu;
|
||||
return uintBitsToFloat(s | ((((n >> 3u) + (0x70u << 23u))) & nz));
|
||||
}
|
||||
vec2 unpackHalf2x16(highp uint v) {
|
||||
return vec2(u16tofp32(v&0xFFFFu), u16tofp32(v>>16u));
|
||||
}
|
||||
uint fp32tou16(float val) {
|
||||
uint f32 = floatBitsToUint(val);
|
||||
uint f16 = 0u;
|
||||
uint sign = (f32 >> 16u) & 0x8000u;
|
||||
int exponent = int((f32 >> 23u) & 0xFFu) - 127;
|
||||
uint mantissa = f32 & 0x007FFFFFu;
|
||||
if (exponent > 15) {
|
||||
f16 = sign | (0x1Fu << 10u);
|
||||
} else if (exponent > -15) {
|
||||
exponent += 15;
|
||||
mantissa >>= 13;
|
||||
f16 = sign | uint(exponent << 10) | mantissa;
|
||||
} else {
|
||||
f16 = sign;
|
||||
}
|
||||
return f16;
|
||||
}
|
||||
highp uint packHalf2x16(vec2 v) {
|
||||
highp uint x = fp32tou16(v.x);
|
||||
highp uint y = fp32tou16(v.y);
|
||||
return (y << 16u) | x;
|
||||
}
|
||||
highp uint packUnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, 0.0, 1.0) * 255.0);
|
||||
highp uint a = uint(v.x);
|
||||
highp uint b = uint(v.y) << 8;
|
||||
highp uint c = uint(v.z) << 16;
|
||||
highp uint d = uint(v.w) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
highp uint packSnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, -1.0, 1.0) * 127.0);
|
||||
highp uint a = uint((int(v.x) & 0xff));
|
||||
highp uint b = uint((int(v.y) & 0xff)) << 8;
|
||||
highp uint c = uint((int(v.z) & 0xff)) << 16;
|
||||
highp uint d = uint((int(v.w) & 0xff)) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
mediump vec4 unpackUnorm4x8(highp uint v) {
|
||||
return vec4(float((v & 0x000000ffu) ),
|
||||
float((v & 0x0000ff00u) >> 8),
|
||||
float((v & 0x00ff0000u) >> 16),
|
||||
float((v & 0xff000000u) >> 24)) / 255.0;
|
||||
}
|
||||
mediump vec4 unpackSnorm4x8(highp uint v) {
|
||||
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
|
||||
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
|
||||
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
|
||||
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
|
||||
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
|
||||
}
|
||||
)"sv;
|
||||
}
|
||||
#endif // BACKEND_OPENGL_VERSION_GL
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
if (!context.isES2() && !context.isAtLeastGLES<3, 1>()) {
|
||||
return R"(
|
||||
|
||||
highp uint packUnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, 0.0, 1.0) * 255.0);
|
||||
highp uint a = uint(v.x);
|
||||
highp uint b = uint(v.y) << 8;
|
||||
highp uint c = uint(v.z) << 16;
|
||||
highp uint d = uint(v.w) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
highp uint packSnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, -1.0, 1.0) * 127.0);
|
||||
highp uint a = uint((int(v.x) & 0xff));
|
||||
highp uint b = uint((int(v.y) & 0xff)) << 8;
|
||||
highp uint c = uint((int(v.z) & 0xff)) << 16;
|
||||
highp uint d = uint((int(v.w) & 0xff)) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
mediump vec4 unpackUnorm4x8(highp uint v) {
|
||||
return vec4(float((v & 0x000000ffu) ),
|
||||
float((v & 0x0000ff00u) >> 8),
|
||||
float((v & 0x00ff0000u) >> 16),
|
||||
float((v & 0xff000000u) >> 24)) / 255.0;
|
||||
}
|
||||
mediump vec4 unpackSnorm4x8(highp uint v) {
|
||||
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
|
||||
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
|
||||
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
|
||||
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
|
||||
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
|
||||
}
|
||||
)"sv;
|
||||
}
|
||||
#endif // BACKEND_OPENGL_VERSION_GLES
|
||||
return ""sv;
|
||||
}
|
||||
|
||||
// split shader source code in three:
|
||||
// - the version line
|
||||
// - extensions
|
||||
// - everything else
|
||||
std::array<std::string_view, 3> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
|
||||
auto version_start = source.find("#version");
|
||||
assert_invariant(version_start != std::string_view::npos);
|
||||
|
||||
auto version_eol = source.find('\n', version_start) + 1;
|
||||
assert_invariant(version_eol != std::string_view::npos);
|
||||
|
||||
auto prolog_start = version_eol;
|
||||
auto prolog_eol = source.rfind("\n#extension"); // last #extension line
|
||||
if (prolog_eol == std::string_view::npos) {
|
||||
prolog_eol = prolog_start;
|
||||
} else {
|
||||
prolog_eol = source.find('\n', prolog_eol + 1) + 1;
|
||||
}
|
||||
auto body_start = prolog_eol;
|
||||
|
||||
std::string_view const version = source.substr(version_start, version_eol - version_start);
|
||||
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);
|
||||
std::string_view const body = source.substr(body_start, source.length() - body_start);
|
||||
return { version, prolog, body };
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a program from the given shader IDs and links it. This cannot fail because errors
|
||||
* are checked later. This always returns a valid GL program ID (which doesn't mean the
|
||||
* program itself is valid).
|
||||
*/
|
||||
GLuint ShaderCompilerService::linkProgram(OpenGLContext& context,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders,
|
||||
/* static */ GLuint ShaderCompilerService::linkProgram(OpenGLContext& context,
|
||||
shaders_t const& shaders,
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
@@ -913,7 +738,7 @@ void ShaderCompilerService::executeTickOps() noexcept {
|
||||
* Checks a program link status and logs errors and frees resources on failure.
|
||||
* Returns true on success.
|
||||
*/
|
||||
bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noexcept {
|
||||
/* static */ bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noexcept {
|
||||
|
||||
SYSTRACE_CALL();
|
||||
|
||||
@@ -948,20 +773,24 @@ bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noe
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
UTILS_NOINLINE
|
||||
void logCompilationError(io::ostream& out, ShaderStage shaderType,
|
||||
const char* name, GLuint shaderId,
|
||||
UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
||||
/* static */ void logCompilationError(io::ostream& out, ShaderStage shaderType, const char* name,
|
||||
GLuint shaderId, UTILS_UNUSED_IN_RELEASE CString const& sourceCode) noexcept {
|
||||
|
||||
auto to_string = [](ShaderStage type) -> const char* {
|
||||
switch (type) {
|
||||
case ShaderStage::VERTEX: return "vertex";
|
||||
case ShaderStage::FRAGMENT: return "fragment";
|
||||
case ShaderStage::COMPUTE: return "compute";
|
||||
case ShaderStage::VERTEX:
|
||||
return "vertex";
|
||||
case ShaderStage::FRAGMENT:
|
||||
return "fragment";
|
||||
case ShaderStage::COMPUTE:
|
||||
return "compute";
|
||||
}
|
||||
};
|
||||
|
||||
{ // scope for the temporary string storage
|
||||
{// scope for the temporary string storage
|
||||
GLint length = 0;
|
||||
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
|
||||
|
||||
@@ -969,8 +798,7 @@ void logCompilationError(io::ostream& out, ShaderStage shaderType,
|
||||
glGetShaderInfoLog(shaderId, length, nullptr, infoLog.data());
|
||||
|
||||
out << "Compilation error in " << to_string(shaderType) << " shader \"" << name << "\":\n"
|
||||
<< "\"" << infoLog.c_str() << "\""
|
||||
<< io::endl;
|
||||
<< "\"" << infoLog.c_str() << "\"" << io::endl;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -996,7 +824,7 @@ void logCompilationError(io::ostream& out, ShaderStage shaderType,
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept {
|
||||
/* static */ void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept {
|
||||
GLint length = 0;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
|
||||
|
||||
@@ -1004,9 +832,204 @@ void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noe
|
||||
glGetProgramInfoLog(program, length, nullptr, infoLog.data());
|
||||
|
||||
out << "Link error in \"" << name << "\":\n"
|
||||
<< "\"" << infoLog.c_str() << "\""
|
||||
<< io::endl;
|
||||
<< "\"" << infoLog.c_str() << "\"" << io::endl;
|
||||
}
|
||||
|
||||
// If usages of the Google-style line directive are present, remove them, as some
|
||||
// drivers don't allow the quotation marks. This source modification happens in-place.
|
||||
/* static */ void process_GOOGLE_cpp_style_line_directive(OpenGLContext& context, char* source,
|
||||
size_t len) noexcept {
|
||||
if (!context.ext.GOOGLE_cpp_style_line_directive) {
|
||||
if (UTILS_UNLIKELY(requestsGoogleLineDirectivesExtension({ source, len }))) {
|
||||
removeGoogleLineDirectives(source, len);// length is unaffected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look up the `source` to replace the number of eyes for multiview with the given number. This is
|
||||
// necessary for OpenGL because OpenGL relies on the number specified in shader files to determine
|
||||
// the number of views, which is assumed as a single digit, for multiview.
|
||||
// This source modification happens in-place.
|
||||
/* static */ void process_OVR_multiview2(OpenGLContext& context, int32_t eyeCount, char* source,
|
||||
size_t len) noexcept {
|
||||
// We don't use regular expression in favor of performance.
|
||||
if (context.ext.OVR_multiview2) {
|
||||
const std::string_view shader{ source, len };
|
||||
const std::string_view layout = "layout";
|
||||
const std::string_view num_views = "num_views";
|
||||
size_t found = 0;
|
||||
while (true) {
|
||||
found = shader.find(layout, found);
|
||||
if (found == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + layout.size());
|
||||
if (found == std::string_view::npos || shader[found] != '(') {
|
||||
continue;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + 1);
|
||||
if (found == std::string_view::npos) {
|
||||
continue;
|
||||
}
|
||||
if (shader.compare(found, num_views.size(), num_views) != 0) {
|
||||
continue;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + num_views.size());
|
||||
if (found == std::string_view::npos || shader[found] != '=') {
|
||||
continue;
|
||||
}
|
||||
found = shader.find_first_not_of(' ', found + 1);
|
||||
if (found == std::string_view::npos) {
|
||||
continue;
|
||||
}
|
||||
// We assume the value should be one-digit number.
|
||||
assert_invariant(eyeCount < 10);
|
||||
assert_invariant(!::isdigit(source[found + 1]));
|
||||
source[found] = '0' + eyeCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
|
||||
// macOS doesn't support GL_ARB_shading_language_packing
|
||||
// Also GLES3.0 didn't have the full set of packing/unpacking functions
|
||||
/* static */ std::string_view process_ARB_shading_language_packing(
|
||||
OpenGLContext& context) noexcept {
|
||||
using namespace std::literals;
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
if (!context.isAtLeastGL<4, 2>() && !context.ext.ARB_shading_language_packing) {
|
||||
return R"(
|
||||
|
||||
// these don't handle denormals, NaNs or inf
|
||||
float u16tofp32(highp uint v) {
|
||||
v <<= 16u;
|
||||
highp uint s = v & 0x80000000u;
|
||||
highp uint n = v & 0x7FFFFFFFu;
|
||||
highp uint nz = (n == 0u) ? 0u : 0xFFFFFFFFu;
|
||||
return uintBitsToFloat(s | ((((n >> 3u) + (0x70u << 23u))) & nz));
|
||||
}
|
||||
vec2 unpackHalf2x16(highp uint v) {
|
||||
return vec2(u16tofp32(v&0xFFFFu), u16tofp32(v>>16u));
|
||||
}
|
||||
uint fp32tou16(float val) {
|
||||
uint f32 = floatBitsToUint(val);
|
||||
uint f16 = 0u;
|
||||
uint sign = (f32 >> 16u) & 0x8000u;
|
||||
int exponent = int((f32 >> 23u) & 0xFFu) - 127;
|
||||
uint mantissa = f32 & 0x007FFFFFu;
|
||||
if (exponent > 15) {
|
||||
f16 = sign | (0x1Fu << 10u);
|
||||
} else if (exponent > -15) {
|
||||
exponent += 15;
|
||||
mantissa >>= 13;
|
||||
f16 = sign | uint(exponent << 10) | mantissa;
|
||||
} else {
|
||||
f16 = sign;
|
||||
}
|
||||
return f16;
|
||||
}
|
||||
highp uint packHalf2x16(vec2 v) {
|
||||
highp uint x = fp32tou16(v.x);
|
||||
highp uint y = fp32tou16(v.y);
|
||||
return (y << 16u) | x;
|
||||
}
|
||||
highp uint packUnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, 0.0, 1.0) * 255.0);
|
||||
highp uint a = uint(v.x);
|
||||
highp uint b = uint(v.y) << 8;
|
||||
highp uint c = uint(v.z) << 16;
|
||||
highp uint d = uint(v.w) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
highp uint packSnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, -1.0, 1.0) * 127.0);
|
||||
highp uint a = uint((int(v.x) & 0xff));
|
||||
highp uint b = uint((int(v.y) & 0xff)) << 8;
|
||||
highp uint c = uint((int(v.z) & 0xff)) << 16;
|
||||
highp uint d = uint((int(v.w) & 0xff)) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
mediump vec4 unpackUnorm4x8(highp uint v) {
|
||||
return vec4(float((v & 0x000000ffu) ),
|
||||
float((v & 0x0000ff00u) >> 8),
|
||||
float((v & 0x00ff0000u) >> 16),
|
||||
float((v & 0xff000000u) >> 24)) / 255.0;
|
||||
}
|
||||
mediump vec4 unpackSnorm4x8(highp uint v) {
|
||||
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
|
||||
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
|
||||
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
|
||||
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
|
||||
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
|
||||
}
|
||||
)"sv;
|
||||
}
|
||||
#endif// BACKEND_OPENGL_VERSION_GL
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
if (!context.isES2() && !context.isAtLeastGLES<3, 1>()) {
|
||||
return R"(
|
||||
|
||||
highp uint packUnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, 0.0, 1.0) * 255.0);
|
||||
highp uint a = uint(v.x);
|
||||
highp uint b = uint(v.y) << 8;
|
||||
highp uint c = uint(v.z) << 16;
|
||||
highp uint d = uint(v.w) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
highp uint packSnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, -1.0, 1.0) * 127.0);
|
||||
highp uint a = uint((int(v.x) & 0xff));
|
||||
highp uint b = uint((int(v.y) & 0xff)) << 8;
|
||||
highp uint c = uint((int(v.z) & 0xff)) << 16;
|
||||
highp uint d = uint((int(v.w) & 0xff)) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
mediump vec4 unpackUnorm4x8(highp uint v) {
|
||||
return vec4(float((v & 0x000000ffu) ),
|
||||
float((v & 0x0000ff00u) >> 8),
|
||||
float((v & 0x00ff0000u) >> 16),
|
||||
float((v & 0xff000000u) >> 24)) / 255.0;
|
||||
}
|
||||
mediump vec4 unpackSnorm4x8(highp uint v) {
|
||||
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
|
||||
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
|
||||
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
|
||||
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
|
||||
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
|
||||
}
|
||||
)"sv;
|
||||
}
|
||||
#endif// BACKEND_OPENGL_VERSION_GLES
|
||||
return ""sv;
|
||||
}
|
||||
|
||||
// split shader source code in three:
|
||||
// - the version line
|
||||
// - extensions
|
||||
// - everything else
|
||||
/* static */ std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept {
|
||||
auto version_start = source.find("#version");
|
||||
assert_invariant(version_start != std::string_view::npos);
|
||||
|
||||
auto version_eol = source.find('\n', version_start) + 1;
|
||||
assert_invariant(version_eol != std::string_view::npos);
|
||||
|
||||
auto prolog_start = version_eol;
|
||||
auto prolog_eol = source.rfind("\n#extension");// last #extension line
|
||||
if (prolog_eol == std::string_view::npos) {
|
||||
prolog_eol = prolog_start;
|
||||
} else {
|
||||
prolog_eol = source.find('\n', prolog_eol + 1) + 1;
|
||||
}
|
||||
auto body_start = prolog_eol;
|
||||
|
||||
std::string_view const version = source.substr(version_start, version_eol - version_start);
|
||||
std::string_view const prolog = source.substr(prolog_start, prolog_eol - prolog_start);
|
||||
std::string_view const body = source.substr(body_start, source.length() - body_start);
|
||||
return { version, prolog, body };
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -57,6 +57,8 @@ 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);
|
||||
|
||||
@@ -134,22 +136,9 @@ private:
|
||||
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;
|
||||
bool multiview, shaders_t& outShaders, shaders_source_t& 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,
|
||||
static GLuint linkProgram(OpenGLContext& context, shaders_t const& shaders,
|
||||
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept;
|
||||
|
||||
static bool checkProgramStatus(program_token_t const& token) noexcept;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include <backend/BufferDescriptor.h>
|
||||
#include <backend/BufferObjectStreamDescriptor.h>
|
||||
#include <backend/DescriptorSetOffsetArray.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
#include <backend/PipelineState.h>
|
||||
@@ -437,6 +438,10 @@ 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
|
||||
|
||||
@@ -327,7 +327,7 @@ void VulkanDriver::terminate() {
|
||||
|
||||
mStagePool.terminate();
|
||||
mPipelineCache.terminate();
|
||||
mFramebufferCache.reset();
|
||||
mFramebufferCache.terminate();
|
||||
mSamplerCache.terminate();
|
||||
mDescriptorSetLayoutCache.terminate();
|
||||
mDescriptorSetCache.terminate();
|
||||
@@ -1523,7 +1523,7 @@ void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain>
|
||||
swapChain->acquire(resized);
|
||||
|
||||
if (resized) {
|
||||
mFramebufferCache.reset();
|
||||
mFramebufferCache.resetFramebuffers();
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(mDefaultRenderTarget)) {
|
||||
|
||||
@@ -340,15 +340,21 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey const& config) noexcept
|
||||
return renderPass;
|
||||
}
|
||||
|
||||
void VulkanFboCache::reset() noexcept {
|
||||
for (auto pair : mFramebufferCache) {
|
||||
void VulkanFboCache::resetFramebuffers() noexcept {
|
||||
for (const auto& pair: mFramebufferCache) {
|
||||
mRenderPassRefCount[pair.first.renderPass]--;
|
||||
vkDestroyFramebuffer(mDevice, pair.second.handle, VKALLOC);
|
||||
}
|
||||
mFramebufferCache.clear();
|
||||
for (auto pair : mRenderPassCache) {
|
||||
}
|
||||
|
||||
void VulkanFboCache::terminate() noexcept {
|
||||
resetFramebuffers();
|
||||
|
||||
for (const auto& pair: mRenderPassCache) {
|
||||
vkDestroyRenderPass(mDevice, pair.second.handle, VKALLOC);
|
||||
}
|
||||
mRenderPassRefCount.clear();
|
||||
mRenderPassCache.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -106,8 +106,11 @@ 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 reset() noexcept;
|
||||
void terminate() noexcept;
|
||||
|
||||
private:
|
||||
VkDevice mDevice;
|
||||
|
||||
@@ -24,28 +24,12 @@
|
||||
#include <bluevk/BlueVK.h>
|
||||
|
||||
// Platform specific includes and defines
|
||||
#if defined(__APPLE__)
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
#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
|
||||
#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
|
||||
#ifndef VK_MVK_macos_surface
|
||||
#error VK_MVK_macos_surface is not defined
|
||||
#endif
|
||||
|
||||
using namespace bluevk;
|
||||
@@ -54,11 +38,7 @@ 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;
|
||||
}
|
||||
@@ -90,36 +70,20 @@ VkImageView VulkanPlatform::createExternalImageViewImpl(VkDevice device,
|
||||
VulkanPlatform::SurfaceBundle VulkanPlatform::createVkSurfaceKHRImpl(void* nativeWindow,
|
||||
VkInstance instance, uint64_t flags) noexcept {
|
||||
VkSurfaceKHR surface;
|
||||
#if defined(__APPLE__)
|
||||
NSView* nsview = (__bridge NSView*) nativeWindow;
|
||||
FILAMENT_CHECK_POSTCONDITION(nsview) << "Unable to obtain Metal-backed NSView.";
|
||||
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(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,
|
||||
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)
|
||||
<< "vkCreateIOSSurfaceMVK failed. error=" << static_cast<int32_t>(result);
|
||||
#endif
|
||||
return std::make_tuple(surface, VkExtent2D{});
|
||||
<< "vkCreateMacOSSurfaceMVK. error=" << static_cast<int32_t>(result);
|
||||
return std::make_tuple(surface, VkExtent2D{});
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "webgpu/WebGPUDriver.h"
|
||||
|
||||
#include "WebGPUSwapChain.h"
|
||||
#include "webgpu/WebGPUConstants.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
@@ -227,6 +228,14 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform, const Platform::DriverConfi
|
||||
driverConfig.disableHeapHandleTags) {
|
||||
#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
|
||||
}
|
||||
|
||||
@@ -286,15 +295,27 @@ 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) {
|
||||
@@ -310,13 +331,10 @@ 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) {
|
||||
@@ -326,16 +344,16 @@ void WebGPUDriver::destroyTimerQuery(Handle<HwTimerQuery> tqh) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyDescriptorSetLayout(Handle<HwDescriptorSetLayout> tqh) {
|
||||
if (tqh) {
|
||||
destructHandle<WebGPUDescriptorSetLayout>(tqh);
|
||||
}
|
||||
}
|
||||
|
||||
void WebGPUDriver::destroyDescriptorSet(Handle<HwDescriptorSet> tqh) {
|
||||
}
|
||||
|
||||
Handle<HwSwapChain> WebGPUDriver::createSwapChainS() noexcept {
|
||||
// TODO: use webgpu handle allocator from.
|
||||
// https://github.com/google/filament/pull/8566
|
||||
// return allocAndConstructHandle<HwSwapChain>();
|
||||
return Handle<HwSwapChain>((Handle<HwSwapChain>::HandleId) mNextFakeHandle++);
|
||||
return allocHandle<WebGPUSwapChain>();
|
||||
}
|
||||
|
||||
Handle<HwSwapChain> WebGPUDriver::createSwapChainHeadlessS() noexcept {
|
||||
@@ -387,7 +405,7 @@ Handle<HwDescriptorSet> WebGPUDriver::createDescriptorSetS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwRenderPrimitive> WebGPUDriver::createRenderPrimitiveS() noexcept {
|
||||
return Handle<HwRenderPrimitive>((Handle<HwRenderPrimitive>::HandleId) mNextFakeHandle++);
|
||||
return allocHandle<WGPURenderPrimitive>();
|
||||
}
|
||||
|
||||
Handle<HwVertexBufferInfo> WebGPUDriver::createVertexBufferInfoS() noexcept {
|
||||
@@ -403,8 +421,7 @@ Handle<HwRenderTarget> WebGPUDriver::createDefaultRenderTargetS() noexcept {
|
||||
}
|
||||
|
||||
Handle<HwDescriptorSetLayout> WebGPUDriver::createDescriptorSetLayoutS() noexcept {
|
||||
return Handle<HwDescriptorSetLayout>(
|
||||
(Handle<HwDescriptorSetLayout>::HandleId) mNextFakeHandle++);
|
||||
return allocHandle<WebGPUDescriptorSetLayout>();
|
||||
}
|
||||
|
||||
Handle<HwTexture> WebGPUDriver::createTextureExternalImageS() noexcept {
|
||||
@@ -420,21 +437,15 @@ Handle<HwTexture> WebGPUDriver::createTextureExternalImagePlaneS() noexcept {
|
||||
}
|
||||
|
||||
void WebGPUDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
|
||||
// TODO: use webgpu handle allocator from.
|
||||
// https://github.com/google/filament/pull/8566
|
||||
// HwSwapChain* hwSwapChain = handleCast<HwSwapChain*>(sch);
|
||||
mNativeWindow = nativeWindow;
|
||||
assert_invariant(!mSwapChain);
|
||||
wgpu::Surface surface = mPlatform.createSurface(nativeWindow, flags);
|
||||
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);
|
||||
wgpu::Extent2D surfaceSize = mPlatform.getSurfaceExtent(mNativeWindow);
|
||||
mSwapChain = constructHandle<WebGPUSwapChain>(sch, std::move(surface), surfaceSize, mAdapter,
|
||||
mDevice, flags);
|
||||
assert_invariant(mSwapChain);
|
||||
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."
|
||||
@@ -447,22 +458,27 @@ 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) {}
|
||||
uint8_t attributeCount, AttributeArray attributes) {
|
||||
constructHandle<WGPUVertexBufferInfo>(vbih, bufferCount, attributeCount, attributes);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createVertexBufferR(Handle<HwVertexBuffer> vbh, uint32_t vertexCount,
|
||||
Handle<HwVertexBufferInfo> vbih) {}
|
||||
Handle<HwVertexBufferInfo> vbih) {
|
||||
auto* vertexBufferInfo = handleCast<WGPUVertexBufferInfo>(vbih);
|
||||
constructHandle<WGPUVertexBuffer>(vbh, mDevice, vertexCount,vertexBufferInfo->bufferCount, vbih);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createIndexBufferR(Handle<HwIndexBuffer> ibh, ElementType elementType,
|
||||
uint32_t indexCount, BufferUsage usage) {}
|
||||
uint32_t indexCount, BufferUsage usage) {
|
||||
auto elementSize = (uint8_t)getElementTypeSize(elementType);
|
||||
constructHandle<WGPUIndexBuffer>(ibh, mDevice, elementSize, indexCount);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createBufferObjectR(Handle<HwBufferObject> boh, uint32_t byteCount,
|
||||
BufferObjectBinding bindingType, BufferUsage usage) {}
|
||||
@@ -495,7 +511,18 @@ void WebGPUDriver::importTextureR(Handle<HwTexture> th, intptr_t id, SamplerType
|
||||
uint32_t depth, TextureUsage usage) {}
|
||||
|
||||
void WebGPUDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph, Handle<HwVertexBuffer> vbh,
|
||||
Handle<HwIndexBuffer> ibh, PrimitiveType pt) {}
|
||||
Handle<HwIndexBuffer> ibh, PrimitiveType pt) {
|
||||
assert_invariant(mDevice);
|
||||
|
||||
auto* renderPrimitive = handleCast<WGPURenderPrimitive>(rph);
|
||||
auto* vertexBuffer = handleCast<WGPUVertexBuffer>(vbh);
|
||||
auto* indexBuffer = handleCast<WGPUIndexBuffer>(ibh);
|
||||
// auto* vertexBufferInfo = handleCast<WGPUVertexBufferInfo>(vertexBuffer->vbih);
|
||||
// renderPrimitive->setBuffers(vertexBufferInfo, vertexBuffer, indexBuffer);
|
||||
renderPrimitive->vertexBuffer = vertexBuffer;
|
||||
renderPrimitive->indexBuffer = indexBuffer;
|
||||
renderPrimitive->type = pt;
|
||||
}
|
||||
|
||||
void WebGPUDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {}
|
||||
|
||||
@@ -514,7 +541,9 @@ void WebGPUDriver::createFenceR(Handle<HwFence> fh, int) {}
|
||||
void WebGPUDriver::createTimerQueryR(Handle<HwTimerQuery> tqh, int) {}
|
||||
|
||||
void WebGPUDriver::createDescriptorSetLayoutR(Handle<HwDescriptorSetLayout> dslh,
|
||||
backend::DescriptorSetLayout&& info) {}
|
||||
backend::DescriptorSetLayout&& info) {
|
||||
constructHandle<WebGPUDescriptorSetLayout>(dslh, std::move(info), &mDevice);
|
||||
}
|
||||
|
||||
void WebGPUDriver::createDescriptorSetR(Handle<HwDescriptorSet> dsh,
|
||||
Handle<HwDescriptorSetLayout> dslh) {}
|
||||
@@ -706,9 +735,6 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
mCommandEncoder = mDevice.CreateCommandEncoder(&commandEncoderDescriptor);
|
||||
assert_invariant(mCommandEncoder);
|
||||
|
||||
mTextureView = mSwapChain->getNextSurfaceTextureView(params.viewport.width, params.viewport.height);
|
||||
assert_invariant(mTextureView);
|
||||
|
||||
// TODO: Remove this code once WebGPU pipeline is implemented
|
||||
static float red = 1.0f;
|
||||
if (red - 0.01 > 0) {
|
||||
@@ -716,7 +742,7 @@ void WebGPUDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
} 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.
|
||||
@@ -752,6 +778,14 @@ 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) {
|
||||
@@ -759,6 +793,7 @@ void WebGPUDriver::commit(Handle<HwSwapChain> sch) {
|
||||
mQueue.Submit(1, &mCommandBuffer);
|
||||
mCommandBuffer = nullptr;
|
||||
mTextureView = nullptr;
|
||||
assert_invariant(mSwapChain);
|
||||
mSwapChain->present();
|
||||
}
|
||||
|
||||
@@ -813,6 +848,70 @@ void WebGPUDriver::bindPipeline(PipelineState const& pipelineState) {
|
||||
}
|
||||
|
||||
void WebGPUDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
// VulkanCommandBuffer* commands = mCurrentRenderPass.commandBuffer;
|
||||
// VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
// auto prim = resource_ptr<VulkanRenderPrimitive>::cast(&mResourceManager, rph);
|
||||
auto* renderPrimitive = handleCast<WGPURenderPrimitive>(rph);
|
||||
// commands->acquire(prim);
|
||||
|
||||
// This *must* match the VulkanVertexBufferInfo 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);
|
||||
(void) vbi;
|
||||
|
||||
// mRenderPassEncoder.SetVertexBuffer(uint32_t slot, Buffer const& buffer = nullptr, uint64_t offset = 0, uint64_t size = kWholeSize);
|
||||
mRenderPassEncoder.SetIndexBuffer(renderPrimitive->indexBuffer->buffer, renderPrimitive->indexBuffer->indexFormat);
|
||||
|
||||
// uint32_t const bufferCount = vbi->getAttributeCount();
|
||||
// VkDeviceSize const* offsets = vbi->getOffsets();
|
||||
// VkBuffer const* buffers = prim->vertexBuffer->getVkBuffers();
|
||||
//
|
||||
// // Next bind the vertex buffers and index buffer. One potential performance improvement is to
|
||||
// // avoid rebinding these if they are already bound, but since we do not (yet) support subranges
|
||||
// // it would be rare for a client to make consecutive draw calls with the same render primitive.
|
||||
// vkCmdBindVertexBuffers(cmdbuffer, 0, bufferCount, buffers, offsets);
|
||||
// vkCmdBindIndexBuffer(cmdbuffer, prim->indexBuffer->buffer.getGpuBuffer(), 0,
|
||||
// prim->indexBuffer->indexType);
|
||||
|
||||
|
||||
// METAL
|
||||
// if (UTILS_UNLIKELY(mContext->currentRenderPassAbandoned)) {
|
||||
// return;
|
||||
// }
|
||||
// FILAMENT_CHECK_PRECONDITION(mContext->currentRenderPassEncoder != nullptr)
|
||||
// << "bindRenderPrimitive() without a valid command encoder.";
|
||||
//
|
||||
// // Bind the user vertex buffers.
|
||||
// MetalBuffer* vertexBuffers[MAX_VERTEX_BUFFER_COUNT] = {};
|
||||
// size_t vertexBufferOffsets[MAX_VERTEX_BUFFER_COUNT] = {};
|
||||
// size_t maxBufferIndex = 0;
|
||||
//
|
||||
// MetalRenderPrimitive const* const primitive = handle_cast<MetalRenderPrimitive>(rph);
|
||||
// MetalVertexBufferInfo const* const vbi =
|
||||
// handle_cast<MetalVertexBufferInfo>(primitive->vertexBuffer->vbih);
|
||||
//
|
||||
// mContext->currentRenderPrimitive = rph;
|
||||
//
|
||||
// auto vb = primitive->vertexBuffer;
|
||||
// for (auto m : vbi->bufferMapping) {
|
||||
// assert_invariant(
|
||||
// m.bufferArgumentIndex >= USER_VERTEX_BUFFER_BINDING_START &&
|
||||
// m.bufferArgumentIndex < USER_VERTEX_BUFFER_BINDING_START + MAX_VERTEX_BUFFER_COUNT);
|
||||
// size_t const vertexBufferIndex = m.bufferArgumentIndex - USER_VERTEX_BUFFER_BINDING_START;
|
||||
// vertexBuffers[vertexBufferIndex] = vb->buffers[m.sourceBufferIndex];
|
||||
// maxBufferIndex = std::max(maxBufferIndex, vertexBufferIndex);
|
||||
// }
|
||||
//
|
||||
// const auto bufferCount = maxBufferIndex + 1;
|
||||
// MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
|
||||
// USER_VERTEX_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX, vertexBuffers,
|
||||
// vertexBufferOffsets, bufferCount);
|
||||
//
|
||||
// // Bind the zero buffer, used for missing vertex attributes.
|
||||
// static const char bytes[16] = { 0 };
|
||||
// [mContext->currentRenderPassEncoder setVertexBytes:bytes
|
||||
// length:16
|
||||
// atIndex:ZERO_VERTEX_BUFFER_BINDING];
|
||||
}
|
||||
|
||||
void WebGPUDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUDRIVER_H
|
||||
|
||||
#include "WebGPUHandles.h"
|
||||
#include "webgpu/WebGPUSwapChain.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
@@ -40,6 +39,8 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUSwapChain;
|
||||
|
||||
/**
|
||||
* WebGPU backend (driver) implementation
|
||||
*/
|
||||
@@ -61,8 +62,8 @@ private:
|
||||
wgpu::Adapter mAdapter = nullptr;
|
||||
wgpu::Device mDevice = nullptr;
|
||||
wgpu::Queue mQueue = nullptr;
|
||||
// TODO consider moving to handle allocator when ready
|
||||
std::unique_ptr<WebGPUSwapChain> mSwapChain = nullptr;
|
||||
void* mNativeWindow = nullptr;
|
||||
WebGPUSwapChain* mSwapChain = nullptr;
|
||||
uint64_t mNextFakeHandle = 1;
|
||||
wgpu::CommandEncoder mCommandEncoder = nullptr;
|
||||
wgpu::TextureView mTextureView = nullptr;
|
||||
@@ -102,11 +103,17 @@ private:
|
||||
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
|
||||
|
||||
@@ -16,13 +16,150 @@
|
||||
|
||||
#include "WebGPUHandles.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
wgpu::Buffer createIndexBuffer(wgpu::Device const& device, uint8_t elementSize, uint32_t indexCount) {
|
||||
wgpu::BufferDescriptor descriptor{ .label = "index_buffer",
|
||||
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Index,
|
||||
.size = elementSize * indexCount,
|
||||
.mappedAtCreation = false };
|
||||
return device.CreateBuffer(&descriptor);
|
||||
}
|
||||
|
||||
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 wgpu::VertexFormat::Snorm8;
|
||||
case ElementType::UBYTE: return wgpu::VertexFormat::Unorm8;
|
||||
case ElementType::SHORT: return wgpu::VertexFormat::Snorm16;
|
||||
case ElementType::USHORT: return wgpu::VertexFormat::Unorm16;
|
||||
// Two Component Types
|
||||
case ElementType::BYTE2: return wgpu::VertexFormat::Snorm8x2;
|
||||
case ElementType::UBYTE2: return wgpu::VertexFormat::Unorm8x2;
|
||||
case ElementType::SHORT2: return wgpu::VertexFormat::Snorm16x2;
|
||||
case ElementType::USHORT2: return wgpu::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 wgpu::VertexFormat::Snorm8x4; // NOT MINSPEC
|
||||
case ElementType::UBYTE3: return wgpu::VertexFormat::Unorm8x4; // NOT MINSPEC
|
||||
case ElementType::SHORT3: return wgpu::VertexFormat::Snorm16x4; // NOT MINSPEC
|
||||
case ElementType::USHORT3: return wgpu::VertexFormat::Unorm16x4; // NOT MINSPEC
|
||||
// Four Component Types
|
||||
case ElementType::BYTE4: return wgpu::VertexFormat::Snorm8x4;
|
||||
case ElementType::UBYTE4: return wgpu::VertexFormat::Unorm8x4;
|
||||
case ElementType::SHORT4: return wgpu::VertexFormat::Snorm16x4;
|
||||
case ElementType::USHORT4: return wgpu::VertexFormat::Unorm8x4;
|
||||
default:
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Normalized format does not exist.";
|
||||
return wgpu::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 ? wgpu::VertexFormat::Sint8 : wgpu::VertexFormat::Float32;
|
||||
case ElementType::UBYTE: return integer ? wgpu::VertexFormat::Uint8 : wgpu::VertexFormat::Float32;
|
||||
case ElementType::SHORT: return integer ? wgpu::VertexFormat::Sint16 : wgpu::VertexFormat::Float32;
|
||||
case ElementType::USHORT: return integer ? wgpu::VertexFormat::Uint16 : wgpu::VertexFormat::Float32;
|
||||
case ElementType::HALF: return wgpu::VertexFormat::Float16;
|
||||
case ElementType::INT: return wgpu::VertexFormat::Sint32;
|
||||
case ElementType::UINT: return wgpu::VertexFormat::Uint32;
|
||||
case ElementType::FLOAT: return wgpu::VertexFormat::Float32;
|
||||
// Two Component Types
|
||||
case ElementType::BYTE2: return integer ? wgpu::VertexFormat::Sint8x2 : wgpu::VertexFormat::Float32x2;
|
||||
case ElementType::UBYTE2: return integer ? wgpu::VertexFormat::Uint8x2 : wgpu::VertexFormat::Float32x2;
|
||||
case ElementType::SHORT2: return integer ? wgpu::VertexFormat::Sint16x2 : wgpu::VertexFormat::Float32x2;
|
||||
case ElementType::USHORT2: return integer ? wgpu::VertexFormat::Uint16x2 : wgpu::VertexFormat::Float32x2;
|
||||
case ElementType::HALF2: return wgpu::VertexFormat::Float16x2;
|
||||
case ElementType::FLOAT2: return wgpu::VertexFormat::Float32x2;
|
||||
// Three Component Types
|
||||
case ElementType::BYTE3: return wgpu::VertexFormat::Sint8x4; // NOT MINSPEC
|
||||
case ElementType::UBYTE3: return wgpu::VertexFormat::Uint8x4; // NOT MINSPEC
|
||||
case ElementType::SHORT3: return wgpu::VertexFormat::Sint16x4; // NOT MINSPEC
|
||||
case ElementType::USHORT3: return wgpu::VertexFormat::Uint16x4; // NOT MINSPEC
|
||||
case ElementType::HALF3: return wgpu::VertexFormat::Float16x4; // NOT MINSPEC
|
||||
case ElementType::FLOAT3: return wgpu::VertexFormat::Float32x3;
|
||||
// Four Component Types
|
||||
case ElementType::BYTE4: return integer ? wgpu::VertexFormat::Sint8x4 : wgpu::VertexFormat::Float32x4;
|
||||
case ElementType::UBYTE4: return integer ? wgpu::VertexFormat::Uint8x4 : wgpu::VertexFormat::Float32x4;
|
||||
case ElementType::SHORT4: return integer ? wgpu::VertexFormat::Sint16x4 : wgpu::VertexFormat::Float32x4;
|
||||
case ElementType::USHORT4: return integer ? wgpu::VertexFormat::Uint16x4 : wgpu::VertexFormat::Float32x4;
|
||||
case ElementType::HALF4: return wgpu::VertexFormat::Float16x4;
|
||||
case ElementType::FLOAT4: return wgpu::VertexFormat::Float32x4;
|
||||
}
|
||||
FILAMENT_CHECK_POSTCONDITION(false) << "Vertex format should always be defined.";
|
||||
return wgpu::VertexFormat::Float32x3;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
WGPUVertexBuffer::WGPUVertexBuffer(uint32_t vextexCount, uint32_t bufferCount,
|
||||
Handle<WGPUVertexBufferInfo> vbih)
|
||||
: HwVertexBuffer(vextexCount),
|
||||
vbih(vbih),
|
||||
buffers(MAX_VERTEX_BUFFER_COUNT) {}
|
||||
WGPUVertexBufferInfo::WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
||||
AttributeArray const& attributes)
|
||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||
mVertexBufferLayout(bufferCount),
|
||||
mAttributes(bufferCount) {
|
||||
for (uint32_t attribIndex = 0; attribIndex < attributes.size(); attribIndex++) {
|
||||
Attribute 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);
|
||||
|
||||
mAttributes[attrib.buffer].push_back({
|
||||
.format = vertexFormat,
|
||||
.offset = attrib.offset,
|
||||
.shaderLocation = attribIndex,
|
||||
});
|
||||
mVertexBufferLayout[attrib.buffer] = {
|
||||
.arrayStride = attrib.stride,
|
||||
};
|
||||
}
|
||||
|
||||
for (uint32_t bufferIndex = 0; bufferIndex < bufferCount; bufferIndex++) {
|
||||
mVertexBufferLayout[bufferIndex] = {
|
||||
.attributeCount = mAttributes[bufferIndex].size(),
|
||||
.attributes = mAttributes[bufferIndex].data(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
WGPUIndexBuffer::WGPUIndexBuffer(wgpu::Device const& device, uint8_t elementSize,
|
||||
uint32_t indexCount)
|
||||
: buffer(createIndexBuffer(device, elementSize, indexCount)),
|
||||
indexFormat(elementSize == 2 ? wgpu::IndexFormat::Uint16 : wgpu::IndexFormat::Uint32) {}
|
||||
|
||||
|
||||
WGPUVertexBuffer::WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
|
||||
Handle<HwVertexBufferInfo> vbih)
|
||||
: HwVertexBuffer(vextexCount),
|
||||
vbih(vbih),
|
||||
buffers(bufferCount) {
|
||||
wgpu::BufferDescriptor descriptor {
|
||||
.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex,
|
||||
.size = vextexCount * bufferCount,
|
||||
.mappedAtCreation = false };
|
||||
|
||||
for (uint32_t i = 0; i < bufferCount; ++i) {
|
||||
descriptor.label = ("vertex_buffer_" + std::to_string(i)).c_str();
|
||||
buffers[i] = device.CreateBuffer(&descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Empty function is a place holder for verxtex buffer updates and should be
|
||||
// updated for that purpose.
|
||||
@@ -31,4 +168,100 @@ void WGPUVertexBuffer::setBuffer(WGPUBufferObject* bufferObject, uint32_t index)
|
||||
WGPUBufferObject::WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount)
|
||||
: HwBufferObject(byteCount),
|
||||
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->Get());
|
||||
|
||||
// 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??
|
||||
mLayout = device->CreateBindGroupLayout(&layoutDescriptor);
|
||||
}
|
||||
WebGPUDescriptorSetLayout::~WebGPUDescriptorSetLayout() {}
|
||||
|
||||
void WGPURenderPrimitive::setBuffers(WGPUVertexBufferInfo const* const vbi,
|
||||
WGPUVertexBuffer* vertexBuffer, WGPUIndexBuffer* indexBuffer) {
|
||||
|
||||
}
|
||||
|
||||
}// namespace filament::backend
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
// #include <utils/StructureOfArrays.h>
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
@@ -32,35 +33,52 @@
|
||||
namespace filament::backend {
|
||||
|
||||
struct WGPUBufferObject;
|
||||
// TODO: Currently WGPUVertexBufferInfo is not used by WebGPU for useful task.
|
||||
// Update the struct when used by WebGPU driver.
|
||||
|
||||
// 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.
|
||||
struct WGPUVertexBufferInfo : public HwVertexBufferInfo {
|
||||
|
||||
WGPUVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
||||
AttributeArray const& attributes)
|
||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||
attributes(attributes) {}
|
||||
AttributeArray attributes;
|
||||
AttributeArray const& attributes);
|
||||
inline wgpu::VertexBufferLayout const* getVertexBufferLayout() const {
|
||||
return mVertexBufferLayout.data();
|
||||
}
|
||||
inline uint32_t getVertexBufferLayoutSize() const {
|
||||
return mVertexBufferLayout.size();
|
||||
}
|
||||
|
||||
inline wgpu::VertexAttribute const* getVertexAttributeForIndex(uint32_t index) const {
|
||||
return mAttributes[index].data();
|
||||
}
|
||||
inline uint32_t getVertexAttributeSize(uint32_t index) const {
|
||||
return mAttributes[index].size();
|
||||
}
|
||||
private:
|
||||
std::vector<wgpu::VertexBufferLayout> mVertexBufferLayout;
|
||||
std::vector<std::vector<wgpu::VertexAttribute>> mAttributes;
|
||||
};
|
||||
|
||||
// TODO: Currently WGPUVertexBuffer is not used by WebGPU for useful task.
|
||||
// Update the struct when used by WebGPU driver.
|
||||
struct WGPUVertexBuffer : public HwVertexBuffer {
|
||||
WGPUVertexBuffer(uint32_t vextexCount, uint32_t bufferCount, Handle<WGPUVertexBufferInfo> vbih);
|
||||
void setBuffer(WGPUBufferObject* bufferObject, uint32_t index);
|
||||
WGPUVertexBuffer(wgpu::Device const &device, uint32_t vextexCount, uint32_t bufferCount,
|
||||
Handle<HwVertexBufferInfo> vbih);
|
||||
|
||||
Handle<WGPUVertexBufferInfo> vbih;
|
||||
void setBuffer(WGPUBufferObject *bufferObject, uint32_t index);
|
||||
|
||||
Handle<HwVertexBufferInfo> vbih;
|
||||
utils::FixedCapacityVector<wgpu::Buffer> buffers;
|
||||
};
|
||||
|
||||
// TODO: Currently WGPUIndexBuffer is not used by WebGPU for useful task.
|
||||
// Update the struct when used by WebGPU driver.
|
||||
struct WGPUIndexBuffer : public HwIndexBuffer {
|
||||
WGPUIndexBuffer(BufferUsage usage, uint8_t elementSize, uint32_t indexCount);
|
||||
WGPUIndexBuffer(wgpu::Device const &device, uint8_t elementSize,
|
||||
uint32_t indexCount);
|
||||
|
||||
wgpu::Buffer buffer;
|
||||
wgpu::IndexFormat indexFormat;
|
||||
};
|
||||
|
||||
// TODO: Currently WGPUVertexBufferInfo is not used by WebGPU for useful task.
|
||||
// TODO: Currently WGPUBufferObject is not used by WebGPU for useful task.
|
||||
// Update the struct when used by WebGPU driver.
|
||||
struct WGPUBufferObject : HwBufferObject {
|
||||
WGPUBufferObject(BufferObjectBinding bindingType, uint32_t byteCount);
|
||||
@@ -69,6 +87,19 @@ struct WGPUBufferObject : HwBufferObject {
|
||||
const BufferObjectBinding bufferObjectBinding;
|
||||
};
|
||||
|
||||
class WebGPUDescriptorSetLayout : public HwDescriptorSetLayout {
|
||||
public:
|
||||
WebGPUDescriptorSetLayout(DescriptorSetLayout const& layout, wgpu::Device const* device);
|
||||
~WebGPUDescriptorSetLayout();
|
||||
|
||||
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);
|
||||
|
||||
wgpu::BindGroupLayout mLayout;
|
||||
};
|
||||
|
||||
// TODO: Currently WGPUTexture is not used by WebGPU for useful task.
|
||||
// Update the struct when used by WebGPU driver.
|
||||
struct WGPUTexture : public HwTexture {
|
||||
@@ -81,6 +112,16 @@ struct WGPUTexture : public HwTexture {
|
||||
wgpu::Texture texture = 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 {
|
||||
|
||||
@@ -191,9 +191,12 @@ wgpu::CompositeAlphaMode selectAlphaMode(size_t availableAlphaModesCount,
|
||||
}
|
||||
|
||||
void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
wgpu::SurfaceCapabilities const& capabilities, bool useSRGBColorSpace) {
|
||||
wgpu::SurfaceCapabilities const& capabilities, wgpu::Extent2D const& surfaceSize,
|
||||
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 =
|
||||
@@ -205,8 +208,8 @@ void initConfig(wgpu::SurfaceConfiguration& config, wgpu::Device const& device,
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Adapter& adapter,
|
||||
wgpu::Device& device, uint64_t flags)
|
||||
WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
|
||||
wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags)
|
||||
: mSurface(surface) {
|
||||
wgpu::SurfaceCapabilities capabilities = {};
|
||||
if (!mSurface.GetCapabilities(adapter, &capabilities)) {
|
||||
@@ -217,43 +220,42 @@ WebGPUSwapChain::WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Adapter& adapter
|
||||
#endif
|
||||
}
|
||||
const bool useSRGBColorSpace = (flags & SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0;
|
||||
initConfig(mConfig, device, capabilities, useSRGBColorSpace);
|
||||
initConfig(mConfig, device, capabilities, surfaceSize, useSRGBColorSpace);
|
||||
mSurface.Configure(&mConfig);
|
||||
}
|
||||
|
||||
WebGPUSwapChain::~WebGPUSwapChain() {
|
||||
if (mConfigured) {
|
||||
mSurface.Unconfigure();
|
||||
mConfigured = false;
|
||||
}
|
||||
mSurface.Unconfigure();
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
#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;
|
||||
}
|
||||
|
||||
mSurface.GetCurrentTexture(texture);
|
||||
}
|
||||
|
||||
wgpu::TextureView WebGPUSwapChain::getNextSurfaceTextureView(uint32_t width, uint32_t height) {
|
||||
wgpu::TextureView WebGPUSwapChain::getCurrentSurfaceTextureView(
|
||||
wgpu::Extent2D const& currentSurfaceSize) {
|
||||
setExtent(currentSurfaceSize);
|
||||
wgpu::SurfaceTexture surfaceTexture;
|
||||
getCurrentTexture(width, height, &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
|
||||
// TODO: review these initiliazations as webgpu pipeline gets mature
|
||||
wgpu::TextureViewDescriptor textureViewDescriptor = {
|
||||
.label = "texture_view",
|
||||
.format = surfaceTexture.texture.GetFormat(),
|
||||
|
||||
@@ -19,26 +19,28 @@
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include <backend/Platform.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
class WebGPUSwapChain : public Platform::SwapChain {
|
||||
class WebGPUSwapChain final : public Platform::SwapChain, HwSwapChain {
|
||||
public:
|
||||
WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Adapter& adapter, wgpu::Device& device,
|
||||
uint64_t flags);
|
||||
WebGPUSwapChain(wgpu::Surface&& surface, wgpu::Extent2D const& surfaceSize,
|
||||
wgpu::Adapter& adapter, wgpu::Device& device, uint64_t flags);
|
||||
~WebGPUSwapChain();
|
||||
|
||||
wgpu::TextureView getNextSurfaceTextureView(uint32_t width, uint32_t height);
|
||||
wgpu::TextureView getCurrentSurfaceTextureView(wgpu::Extent2D const&);
|
||||
|
||||
void present();
|
||||
|
||||
private:
|
||||
void getCurrentTexture(uint32_t width, uint32_t height, wgpu::SurfaceTexture*);
|
||||
void setExtent(wgpu::Extent2D const&);
|
||||
|
||||
wgpu::Surface mSurface = {};
|
||||
wgpu::SurfaceConfiguration mConfig = {};
|
||||
bool mConfigured = false;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <utils/Panic.h>
|
||||
|
||||
#include <android/native_window.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -28,6 +29,14 @@
|
||||
|
||||
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;
|
||||
|
||||
@@ -33,6 +33,15 @@
|
||||
|
||||
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.
|
||||
|
||||
@@ -79,6 +79,74 @@
|
||||
|
||||
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,6 +30,16 @@
|
||||
|
||||
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,10 +43,12 @@ using namespace image;
|
||||
namespace test {
|
||||
|
||||
Backend BackendTest::sBackend = Backend::NOOP;
|
||||
OperatingSystem BackendTest::sOperatingSystem = OperatingSystem::OTHER;
|
||||
bool BackendTest::sIsMobilePlatform = false;
|
||||
|
||||
void BackendTest::init(Backend backend, bool isMobilePlatform) {
|
||||
void BackendTest::init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform) {
|
||||
sBackend = backend;
|
||||
sOperatingSystem = operatingSystem;
|
||||
sIsMobilePlatform = isMobilePlatform;
|
||||
}
|
||||
|
||||
@@ -199,6 +201,18 @@ void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, si
|
||||
getDriverApi().readPixels(rt, 0, 0, width, height, std::move(pbd));
|
||||
}
|
||||
|
||||
bool BackendTest::matchesEnvironment(Backend backend) {
|
||||
return sBackend == backend;
|
||||
}
|
||||
|
||||
bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem) {
|
||||
return sOperatingSystem == operatingSystem;
|
||||
}
|
||||
|
||||
bool BackendTest::matchesEnvironment(OperatingSystem operatingSystem, Backend backend) {
|
||||
return matchesEnvironment(operatingSystem) && matchesEnvironment(backend);
|
||||
}
|
||||
|
||||
class Environment : public ::testing::Environment {
|
||||
public:
|
||||
virtual void SetUp() override {
|
||||
@@ -210,8 +224,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void initTests(Backend backend, bool isMobile, int& argc, char* argv[]) {
|
||||
BackendTest::init(backend, isMobile);
|
||||
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]) {
|
||||
BackendTest::init(backend, operatingSystem, isMobile);
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
::testing::AddGlobalTestEnvironment(new Environment);
|
||||
}
|
||||
|
||||
@@ -31,9 +31,10 @@ namespace test {
|
||||
class BackendTest : public ::testing::Test {
|
||||
public:
|
||||
|
||||
static void init(Backend backend, bool isMobilePlatform);
|
||||
static void init(Backend backend, OperatingSystem operatingSystem, bool isMobilePlatform);
|
||||
|
||||
static Backend sBackend;
|
||||
static OperatingSystem sOperatingSystem;
|
||||
static bool sIsMobilePlatform;
|
||||
|
||||
protected:
|
||||
@@ -71,6 +72,10 @@ protected:
|
||||
filament::backend::DriverApi& getDriverApi() { return *commandStream; }
|
||||
filament::backend::Driver& getDriver() { return *driver; }
|
||||
|
||||
static bool matchesEnvironment(Backend backend);
|
||||
static bool matchesEnvironment(OperatingSystem operatingSystem);
|
||||
static bool matchesEnvironment(OperatingSystem operatingSystem, Backend backend);
|
||||
|
||||
private:
|
||||
|
||||
filament::backend::Driver* driver = nullptr;
|
||||
|
||||
60
filament/backend/test/PlatformRunner.cpp
Normal file
60
filament/backend/test/PlatformRunner.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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,6 +19,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "utils/CString.h"
|
||||
|
||||
namespace test {
|
||||
|
||||
@@ -34,6 +35,15 @@ 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;
|
||||
@@ -51,9 +61,10 @@ 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, bool isMobile, int& argc, char* argv[]);
|
||||
void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile, int& argc, char* argv[]);
|
||||
|
||||
/**
|
||||
* Test runners should call runTests when they are ready for tests to be run.
|
||||
@@ -68,6 +79,6 @@ int runTests();
|
||||
*/
|
||||
Backend parseArgumentsForBackend(int argc, char* argv[]);
|
||||
|
||||
}
|
||||
} // namespace test
|
||||
|
||||
#endif
|
||||
|
||||
96
filament/backend/test/Skip.cpp
Normal file
96
filament/backend/test/Skip.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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
|
||||
55
filament/backend/test/Skip.h
Normal file
55
filament/backend/test/Skip.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -51,6 +51,6 @@ int main(int argc, char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
test::initTests(backend, false, argc, argv);
|
||||
test::initTests(backend, test::OperatingSystem::LINUX, false, argc, argv);
|
||||
return test::runTests();
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ test::NativeView getNativeView() {
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
auto backend = test::parseArgumentsForBackend(argc, argv);
|
||||
test::initTests(backend, false, argc, argv);
|
||||
test::initTests(backend, test::OperatingSystem::APPLE, false, argc, argv);
|
||||
AppDelegate* delegate = [AppDelegate new];
|
||||
delegate.backend = backend;
|
||||
NSApplication* app = [NSApplication sharedApplication];
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include "Lifetimes.h"
|
||||
#include "Skip.h"
|
||||
|
||||
using namespace filament;
|
||||
using namespace filament::backend;
|
||||
@@ -24,6 +25,8 @@ using namespace filament::backend;
|
||||
namespace test {
|
||||
|
||||
TEST_F(BackendTest, FrameScheduledCallback) {
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL));
|
||||
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
@@ -81,6 +84,8 @@ TEST_F(BackendTest, FrameScheduledCallback) {
|
||||
}
|
||||
|
||||
TEST_F(BackendTest, FrameCompletedCallback) {
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL));
|
||||
|
||||
auto& api = getDriverApi();
|
||||
Cleanup cleanup(api);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "Lifetimes.h"
|
||||
#include "ShaderGenerator.h"
|
||||
#include "Skip.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
@@ -78,6 +79,8 @@ void main() {
|
||||
})";
|
||||
|
||||
TEST_F(BackendTest, PushConstants) {
|
||||
SKIP_IF(SkipEnvironment(OperatingSystem::APPLE, Backend::OPENGL));
|
||||
|
||||
auto& api = getDriverApi();
|
||||
|
||||
api.startCapture(0);
|
||||
|
||||
@@ -269,12 +269,14 @@ void FMaterialInstance::setParameterImpl(std::string_view const name,
|
||||
if (texture && texture->textureHandleCanMutate()) {
|
||||
mTextureParameters[binding] = { texture, sampler.getSamplerParams() };
|
||||
} else {
|
||||
// Ensure to erase the binding from mTextureParameters since it will not
|
||||
// be updated.
|
||||
mTextureParameters.erase(binding);
|
||||
|
||||
Handle<HwTexture> handle{};
|
||||
if (texture) {
|
||||
handle = texture->getHwHandleForSampling();
|
||||
assert_invariant(handle == texture->getHwHandle());
|
||||
} else {
|
||||
mTextureParameters.erase(binding);
|
||||
}
|
||||
mDescriptorSet.setSampler(binding, handle, sampler.getSamplerParams());
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.59.1"
|
||||
spec.version = "1.59.2"
|
||||
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
spec.homepage = "https://google.github.io/filament"
|
||||
spec.authors = "Google LLC."
|
||||
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
|
||||
spec.platform = :ios, "11.0"
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.59.1/filament-v1.59.1-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.59.2/filament-v1.59.2-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "filament",
|
||||
"version": "1.59.1",
|
||||
"version": "1.59.2",
|
||||
"description": "Real-time physically based rendering engine",
|
||||
"main": "filament.js",
|
||||
"module": "filament.js",
|
||||
|
||||
Reference in New Issue
Block a user