Merge branch 'rc/1.40.0' into release

This commit is contained in:
Benjamin Doherty
2023-06-26 13:29:06 +08:00
4225 changed files with 253237 additions and 975 deletions
+6
View File
@@ -372,6 +372,12 @@ if (ANDROID OR IOS OR WEBGL)
endif()
endif()
# Turn off exceptions on iOS debug as well. This fixes an availability error we see when using
# std::visit, which is not supported on iOS 11.0 when exceptions are enabled.
if (IOS)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-exceptions")
endif()
# With WebGL, we disable RTTI even for debug builds because we pass emscripten::val back and forth
# between C++ and JavaScript in order to efficiently access typed arrays, which are unbound.
# NOTE: This is not documented in emscripten so we should consider a different approach.
+1
View File
@@ -7,3 +7,4 @@ for next branch cut* header.
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
## Release notes for next branch cut
+2 -2
View File
@@ -31,7 +31,7 @@ repositories {
}
dependencies {
implementation 'com.google.android.filament:filament-android:1.39.0'
implementation 'com.google.android.filament:filament-android:1.40.0'
}
```
@@ -50,7 +50,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
iOS projects can use CocoaPods to install the latest release:
```
pod 'Filament', '~> 1.39.0'
pod 'Filament', '~> 1.40.0'
```
### Snapshots
+8 -1
View File
@@ -7,10 +7,17 @@ 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.40.0
- matc: fix VSM high precision option on mobile [⚠️ **Recompile materials**]
- vulkan: support sRGB swap chain
- Add new `getMaxAutomaticInstances()` API on `Engine` to get max supported automatic instances.
- UiHelper: fix jank when a `TextureView` is resized (fixes b\282220665)
- backend: parallel shader compilation support. This breaks and improves the recent `Material::compile` API.
## v1.39.0
- matc: workaround a bug in spirv-tools causing vsm to fail [⚠️ **Recompile materials**]
- UiHelper: fix jank when a `TextureView` is resized (fixes b\282220665)
## v1.38.0
+1 -1
View File
@@ -1,5 +1,5 @@
GROUP=com.google.android.filament
VERSION_NAME=1.39.0
VERSION_NAME=1.40.0
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
+2
View File
@@ -82,6 +82,8 @@ if (FILAMENT_SUPPORTS_OPENGL AND NOT FILAMENT_USE_EXTERNAL_GLES3 AND NOT FILAMEN
src/opengl/OpenGLPlatform.cpp
src/opengl/OpenGLTimerQuery.cpp
src/opengl/OpenGLTimerQuery.h
src/opengl/ShaderCompilerService.cpp
src/opengl/ShaderCompilerService.h
)
if (EGL)
list(APPEND SRCS src/opengl/platforms/PlatformEGL.cpp)
@@ -296,6 +296,14 @@ enum class Precision : uint8_t {
DEFAULT
};
/**
* Shader compiler priority queue
*/
enum class CompilerPriorityQueue : uint8_t {
HIGH,
LOW
};
//! Texture sampler type
enum class SamplerType : uint8_t {
SAMPLER_2D, //!< 2D texture
@@ -71,6 +71,8 @@ public:
~Program() noexcept;
Program& priorityQueue(CompilerPriorityQueue priorityQueue) noexcept;
// sets the material name and variant for diagnostic purposes only
Program& diagnostics(utils::CString const& name,
utils::Invocable<utils::io::ostream&(utils::io::ostream& out)>&& logger);
@@ -138,6 +140,8 @@ public:
uint64_t getCacheId() const noexcept { return mCacheId; }
CompilerPriorityQueue getPriorityQueue() const noexcept { return mPriorityQueue; }
private:
friend utils::io::ostream& operator<<(utils::io::ostream& out, const Program& builder);
@@ -150,6 +154,7 @@ private:
utils::FixedCapacityVector<SpecializationConstant> mSpecializationConstants;
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> mAttributes;
std::array<UniformInfo, Program::UNIFORM_BINDING_COUNT> mBindingUniformInfo;
CompilerPriorityQueue mPriorityQueue = CompilerPriorityQueue::HIGH;
};
} // namespace filament::backend
@@ -267,6 +267,27 @@ public:
* @return Transformed image.
*/
virtual AcquiredImage transformAcquiredImage(AcquiredImage source) noexcept;
// --------------------------------------------------------------------------------------------
/**
* Returns true if additional OpenGL contexts can be created. Default: false.
* @return true if additional OpenGL contexts can be created.
* @see createContext
*/
virtual bool isExtraContextSupported() const noexcept;
/**
* Creates an OpenGL context with the same configuration than the main context and makes it
* current to the current thread. Must not be called from the main driver thread.
* createContext() is only supported if isExtraContextSupported() returns true.
* These additional contexts will be automatically terminated in terminate.
*
* @param shared whether the new context is shared with the main context.
* @see isExtraContextSupported()
* @see terminate()
*/
virtual void createContext(bool shared);
};
} // namespace filament
@@ -50,6 +50,9 @@ protected:
// --------------------------------------------------------------------------------------------
// OpenGLPlatform Interface
bool isExtraContextSupported() const noexcept override;
void createContext(bool shared) override;
void terminate() noexcept override;
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
@@ -47,6 +47,9 @@ public:
uint32_t createDefaultRenderTarget() noexcept override;
bool isExtraContextSupported() const noexcept override;
void createContext(bool shared) override;
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
@@ -38,6 +38,8 @@ class PlatformEGL : public OpenGLPlatform {
public:
PlatformEGL() noexcept;
bool isExtraContextSupported() const noexcept override;
void createContext(bool shared) override;
protected:
@@ -124,6 +126,8 @@ protected:
EGLSurface mCurrentReadSurface = EGL_NO_SURFACE;
EGLSurface mEGLDummySurface = EGL_NO_SURFACE;
EGLConfig mEGLConfig = EGL_NO_CONFIG_KHR;
Config mContextAttribs;
std::vector<EGLContext> mAdditionalContexts;
// supported extensions detected at runtime
struct {
@@ -23,9 +23,10 @@
#include "utils/unwindows.h"
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/DriverEnums.h>
#include <vector>
namespace filament::backend {
/**
@@ -46,6 +47,9 @@ protected:
void terminate() noexcept override;
bool isExtraContextSupported() const noexcept override;
void createContext(bool shared) override;
SwapChain* createSwapChain(void* nativewindow, uint64_t flags) noexcept override;
SwapChain* createSwapChain(uint32_t width, uint32_t height, uint64_t flags) noexcept override;
void destroySwapChain(SwapChain* swapChain) noexcept override;
@@ -57,6 +61,8 @@ protected:
HWND mHWnd = NULL;
HDC mWhdc = NULL;
PIXELFORMATDESCRIPTOR mPfd = {};
std::vector<HGLRC> mAdditionalContexts;
std::vector<int> mAttribs;
};
} // namespace filament::backend
@@ -110,6 +110,15 @@ public:
// -------- End platform customization options --------
// ----------------------------------------------------
/**
* Returns whether the platform supports sRGB swapchain. This is true by default, and the client
* needs to override this method to specify otherwise.
* @return Whether the platform supports sRGB swapchain.
*/
virtual bool isSRGBSwapChainSupported() const {
return true;
}
/**
* Get the images handles and format of the memory backing the swapchain. This should be called
* after createSwapChain() or after recreateIfResized().
+5
View File
@@ -39,6 +39,11 @@ Program& Program::operator=(Program&& rhs) noexcept {
Program::~Program() noexcept = default;
Program& Program::priorityQueue(CompilerPriorityQueue priorityQueue) noexcept {
mPriorityQueue = priorityQueue;
return *this;
}
Program& Program::diagnostics(CString const& name,
Invocable<io::ostream&(io::ostream&)>&& logger) {
mName = name;
+3 -1
View File
@@ -977,7 +977,9 @@ void MetalDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh, BufferDescripto
void MetalDriver::compilePrograms(CallbackHandler* handler,
CallbackHandler::Callback callback, void* user) {
scheduleCallback(handler, user, callback);
if (callback) {
scheduleCallback(handler, user, callback);
}
}
void MetalDriver::beginRenderPass(Handle<HwRenderTarget> rth,
+3 -1
View File
@@ -262,7 +262,9 @@ void NoopDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
void NoopDriver::compilePrograms(CallbackHandler* handler,
CallbackHandler::Callback callback, void* user) {
scheduleCallback(handler, user, callback);
if (callback) {
scheduleCallback(handler, user, callback);
}
}
void NoopDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
@@ -41,6 +41,7 @@ GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
GLuint programId = 0;
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
BlobCacheKey key{ program.getCacheId(), program.getSpecializationConstants() };
// FIXME: use a static buffer to avoid systematic allocation
@@ -77,12 +78,14 @@ GLuint OpenGLBlobCache::retrieve(BlobCacheKey* outKey, Platform& platform,
using std::swap;
swap(*outKey, key);
}
#endif
return programId;
}
void OpenGLBlobCache::insert(Platform& platform,
BlobCacheKey const& key, GLuint program) noexcept {
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
SYSTRACE_CALL();
if (platform.hasBlobFunc()) {
SYSTRACE_CONTEXT();
@@ -102,6 +105,21 @@ void OpenGLBlobCache::insert(Platform& platform,
}
}
}
#endif
}
void OpenGLBlobCache::insert(Platform& platform, BlobCacheKey const& key,
GLenum format, void* data, GLsizei programBinarySize) noexcept {
SYSTRACE_CALL();
if (platform.hasBlobFunc()) {
if (programBinarySize) {
size_t const size = sizeof(Blob) + programBinarySize;
std::unique_ptr<Blob, decltype(&::free)> blob{ (Blob*)malloc(size), &::free };
blob->format = format;
memcpy(blob->data, data, programBinarySize);
platform.insertBlob(key.data(), key.size(), blob.get(), size);
}
}
}
} // namespace filament::backend
@@ -34,6 +34,9 @@ public:
static void insert(Platform& platform,
BlobCacheKey const& key, GLuint program) noexcept;
static void insert(Platform& platform, BlobCacheKey const& key,
GLenum format, void* data, GLsizei programBinarySize) noexcept;
private:
struct Blob;
};
@@ -115,6 +115,9 @@ OpenGLContext::OpenGLContext() noexcept {
procs.invalidateFramebuffer = glInvalidateFramebuffer;
#endif // BACKEND_OPENGL_LEVEL_GLES30
// no-op if not supported
procs.maxShaderCompilerThreadsKHR = +[](GLuint) {};
#ifdef BACKEND_OPENGL_VERSION_GLES
initExtensionsGLES();
if (state.major == 3) {
@@ -173,6 +176,8 @@ OpenGLContext::OpenGLContext() noexcept {
procs.invalidateFramebuffer = glDiscardFramebufferEXT;
procs.maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsKHR;
mFeatureLevel = FeatureLevel::FEATURE_LEVEL_0;
}
#endif // IOS
@@ -197,6 +202,8 @@ OpenGLContext::OpenGLContext() noexcept {
bugs.allow_read_only_ancillary_feedback_loop = true;
assert_invariant(gets.max_texture_image_units >= 16);
assert_invariant(gets.max_combined_texture_image_units >= 32);
procs.maxShaderCompilerThreadsKHR = glMaxShaderCompilerThreadsARB;
#endif
#ifdef GL_EXT_texture_filter_anisotropic
@@ -415,6 +415,8 @@ public:
void (* getQueryObjectui64v)(GLuint id, GLenum pname, GLuint64* params);
void (* invalidateFramebuffer)(GLenum target, GLsizei numAttachments, const GLenum *attachments);
void (* maxShaderCompilerThreadsKHR)(GLuint count);
} procs{};
private:
+39 -47
View File
@@ -50,13 +50,15 @@
#define HAS_MAPBUFFERS 1
#endif
#define DEBUG_MARKER_NONE 0
#define DEBUG_MARKER_OPENGL 1
#define DEBUG_MARKER_NONE 0x00 // no debug marker
#define DEBUG_MARKER_OPENGL 0x01 // markers in the gl command queue (req. driver support)
#define DEBUG_MARKER_BACKEND 0x02 // markers on the backend side (systrace)
#define DEBUG_MARKER_ALL 0x03 // all markers
// set to the desired debug marker level
#define DEBUG_MARKER_LEVEL DEBUG_MARKER_NONE
#if DEBUG_MARKER_LEVEL == DEBUG_MARKER_OPENGL
#if DEBUG_MARKER_LEVEL > DEBUG_MARKER_NONE
# define DEBUG_MARKER() \
DebugMarker _debug_marker(*this, __func__);
#else
@@ -165,9 +167,11 @@ OpenGLDriver::DebugMarker::~DebugMarker() noexcept {
// ------------------------------------------------------------------------------------------------
OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfig& driverConfig) noexcept
: mHandleAllocator("Handles", driverConfig.handleArenaSize),
mSamplerMap(32),
mPlatform(*platform) {
: mPlatform(*platform),
mContext(),
mShaderCompilerService(*this),
mHandleAllocator("Handles", driverConfig.handleArenaSize),
mSamplerMap(32) {
std::fill(mSamplerBindings.begin(), mSamplerBindings.end(), nullptr);
@@ -208,6 +212,8 @@ OpenGLDriver::OpenGLDriver(OpenGLPlatform* platform, const Platform::DriverConfi
mTimerQueryImpl = new TimerQueryFallback();
mFrameTimeSupported = false;
}
mShaderCompilerService.init();
}
OpenGLDriver::~OpenGLDriver() noexcept { // NOLINT(modernize-use-equals-default)
@@ -243,6 +249,8 @@ void OpenGLDriver::terminate() {
delete mTimerQueryImpl;
mShaderCompilerService.terminate();
mPlatform.terminate();
}
@@ -1455,7 +1463,6 @@ void OpenGLDriver::destroyProgram(Handle<HwProgram> ph) {
DEBUG_MARKER()
if (ph) {
OpenGLProgram* p = handle_cast<OpenGLProgram*>(ph);
cancelRunAtNextPassOp(p);
destruct(ph, p);
}
}
@@ -2602,18 +2609,16 @@ SyncStatus OpenGLDriver::getSyncStatus(Handle<HwSync> sh) {
void OpenGLDriver::compilePrograms(CallbackHandler* handler,
CallbackHandler::Callback callback, void* user) {
// TODO: this works because currently `executeRenderPassOps` is only used for compiling
// materials. If that changed, we'd have to only execute the callbacks related to
// material compilation.
executeRenderPassOps();
scheduleCallback(handler, user, callback);
if (callback) {
getShaderCompilerService().notifyWhenAllProgramsAreReady(handler, callback, user);
}
}
void OpenGLDriver::beginRenderPass(Handle<HwRenderTarget> rth,
const RenderPassParams& params) {
DEBUG_MARKER()
executeRenderPassOps();
getShaderCompilerService().tick();
auto& gl = mContext;
@@ -2954,29 +2959,34 @@ void OpenGLDriver::insertEventMarker(char const* string, uint32_t len) {
}
void OpenGLDriver::pushGroupMarker(char const* string, uint32_t len) {
#ifdef GL_EXT_debug_marker
auto& gl = mContext;
if (UTILS_LIKELY(gl.ext.EXT_debug_marker)) {
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
glPushGroupMarkerEXT(GLsizei(len ? len : strlen(string)), string);
} else
#endif
{
SYSTRACE_CONTEXT();
SYSTRACE_NAME_BEGIN(string);
}
#endif
#endif
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
SYSTRACE_CONTEXT();
SYSTRACE_NAME_BEGIN(string);
#endif
}
void OpenGLDriver::popGroupMarker(int) {
#ifdef GL_EXT_debug_marker
auto& gl = mContext;
if (UTILS_LIKELY(gl.ext.EXT_debug_marker)) {
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_OPENGL
if (UTILS_LIKELY(mContext.ext.EXT_debug_marker)) {
glPopGroupMarkerEXT();
} else
#endif
{
SYSTRACE_CONTEXT();
SYSTRACE_NAME_END();
}
#endif
#endif
#if DEBUG_MARKER_LEVEL & DEBUG_MARKER_BACKEND
SYSTRACE_CONTEXT();
SYSTRACE_NAME_END();
#endif
}
void OpenGLDriver::startCapture(int) {
@@ -3213,25 +3223,6 @@ void OpenGLDriver::executeEveryNowAndThenOps() noexcept {
}
}
void OpenGLDriver::runAtNextRenderPass(void* token, std::function<void()> fn) noexcept {
assert_invariant(mRunAtNextRenderPassOps.find(token) == mRunAtNextRenderPassOps.end());
mRunAtNextRenderPassOps[token] = std::move(fn);
}
void OpenGLDriver::cancelRunAtNextPassOp(void* token) noexcept {
mRunAtNextRenderPassOps.erase(token);
}
void OpenGLDriver::executeRenderPassOps() noexcept {
auto& ops = mRunAtNextRenderPassOps;
if (!ops.empty()) {
for (auto& item: ops) {
item.second();
}
ops.clear();
}
}
// ------------------------------------------------------------------------------------------------
// Rendering ops
// ------------------------------------------------------------------------------------------------
@@ -3240,6 +3231,7 @@ void OpenGLDriver::tick(int) {
DEBUG_MARKER()
executeGpuCommandsCompleteOps();
executeEveryNowAndThenOps();
getShaderCompilerService().tick();
}
void OpenGLDriver::beginFrame(
@@ -3549,7 +3541,7 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
}
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
executeRenderPassOps();
getShaderCompilerService().tick();
OpenGLProgram* p = handle_cast<OpenGLProgram*>(program);
+8 -7
View File
@@ -20,6 +20,7 @@
#include "DriverBase.h"
#include "GLUtils.h"
#include "OpenGLContext.h"
#include "ShaderCompilerService.h"
#include "private/backend/Driver.h"
#include "private/backend/HandleAllocator.h"
@@ -209,10 +210,16 @@ public:
OpenGLDriver& operator=(OpenGLDriver const&) = delete;
private:
OpenGLPlatform& mPlatform;
OpenGLContext mContext;
ShaderCompilerService mShaderCompilerService;
OpenGLContext& getContext() noexcept { return mContext; }
ShaderCompilerService& getShaderCompilerService() noexcept {
return mShaderCompilerService;
}
ShaderModel getShaderModel() const noexcept final;
/*
@@ -273,6 +280,7 @@ private:
}
friend class OpenGLProgram;
friend class ShaderCompilerService;
/* Extension management... */
@@ -373,8 +381,6 @@ private:
void detachStream(GLTexture* t) noexcept;
void replaceStream(GLTexture* t, GLStream* stream) noexcept;
OpenGLPlatform& mPlatform;
void updateTextureLodRange(GLTexture* texture, int8_t targetLevel) noexcept;
// tasks executed on the main thread after the fence signaled
@@ -387,11 +393,6 @@ private:
void executeEveryNowAndThenOps() noexcept;
std::vector<std::function<bool()>> mEveryNowAndThenOps;
void runAtNextRenderPass(void* token, std::function<void()> fn) noexcept;
void executeRenderPassOps() noexcept;
void cancelRunAtNextPassOp(void* token) noexcept;
tsl::robin_map<void*, std::function<void()>> mRunAtNextRenderPassOps;
// timer query implementation
OpenGLTimerQueryInterface* mTimerQueryImpl = nullptr;
bool mFrameTimeSupported = false;
@@ -109,4 +109,11 @@ TargetBufferFlags OpenGLPlatform::getPreservedFlags(UTILS_UNUSED SwapChain* swap
return TargetBufferFlags::NONE;
}
bool OpenGLPlatform::isExtraContextSupported() const noexcept {
return false;
}
void OpenGLPlatform::createContext(bool) {
}
} // namespace filament::backend
+41 -389
View File
@@ -16,377 +16,88 @@
#include "OpenGLProgram.h"
#include "OpenGLBlobCache.h"
#include "OpenGLDriver.h"
#include "BlobCacheKey.h"
#include "OpenGLDriver.h"
#include "ShaderCompilerService.h"
#include <utils/debug.h>
#include <utils/compiler.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/Systrace.h>
#include <private/backend/BackendUtils.h>
#include <ctype.h>
namespace filament::backend {
using namespace filament::math;
using namespace utils;
using namespace backend;
static void logCompilationError(utils::io::ostream& out,
ShaderStage shaderType, const char* name,
GLuint shaderId, CString const& sourceCode) noexcept;
struct OpenGLProgram::LazyInitializationData {
Program::UniformBlockInfo uniformBlockInfo;
Program::SamplerGroupInfo samplerGroupInfo;
std::array<Program::UniformInfo, Program::UNIFORM_BINDING_COUNT> bindingUniformInfo;
};
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) + ")";
}
OpenGLProgram::OpenGLProgram() noexcept
: mInitialized(false), mValid(true), mLazyInitializationData(nullptr) {
}
OpenGLProgram::OpenGLProgram() noexcept = default;
OpenGLProgram::OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept
: HwProgram(std::move(program.getName())),
mInitialized(false), mValid(true),
mLazyInitializationData{ new(LazyInitializationData) } {
: HwProgram(std::move(program.getName())) {
OpenGLContext& context = gld.getContext();
mLazyInitializationData->samplerGroupInfo = std::move(program.getSamplerGroupInfo());
auto* const lazyInitializationData = new(std::nothrow) LazyInitializationData();
lazyInitializationData->samplerGroupInfo = std::move(program.getSamplerGroupInfo());
if (UTILS_UNLIKELY(gld.getContext().isES2())) {
mLazyInitializationData->bindingUniformInfo = std::move(program.getBindingUniformInfo());
mLazyInitializationData->attributes = std::move(program.getAttributes());
lazyInitializationData->bindingUniformInfo = std::move(program.getBindingUniformInfo());
} else {
mLazyInitializationData->uniformBlockInfo = std::move(program.getUniformBlockBindings());
lazyInitializationData->uniformBlockInfo = std::move(program.getUniformBlockBindings());
}
BlobCacheKey key;
gl.program = OpenGLBlobCache::retrieve(&key, gld.mPlatform, program);
if (!gl.program) {
// this cannot fail because we check compilation status after linking the program
// shaders[] is filled with id of shader stages present.
OpenGLProgram::compileShaders(context,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
gl.shaders,
mLazyInitializationData->shaderSourceCode);
ShaderCompilerService& compiler = gld.getShaderCompilerService();
mToken = compiler.createProgram(name, std::move(program));
gld.runAtNextRenderPass(this, [this, &gld, &context, key = std::move(key)]() {
// by this point we must not have a GL program
assert_invariant(!gl.program);
// we also can't be in the initialized state
assert_invariant(!mInitialized);
// we must have our lazy initialization data
assert_invariant(mLazyInitializationData);
// link the program, this also cannot fail because status is checked later.
gl.program = OpenGLProgram::linkProgram(context,
mLazyInitializationData, gl.shaders);
if (key) {
// attempt to cache
OpenGLBlobCache::insert(gld.mPlatform, key, gl.program);
}
});
}
ShaderCompilerService::setUserData(mToken, lazyInitializationData);
}
OpenGLProgram::~OpenGLProgram() noexcept {
if (!mInitialized) {
// mLazyInitializationData is aliased with mIndicesRuns
delete mLazyInitializationData;
if (mToken) {
// if the token is non-nullptr it means the program has not been used, and
// we need to clean-up.
assert_invariant(gl.program == 0);
LazyInitializationData* const lazyInitializationData =
(LazyInitializationData *)ShaderCompilerService::getUserData(mToken);
delete lazyInitializationData;
ShaderCompilerService::terminate(mToken);
}
delete [] mUniformsRecords;
const GLuint program = gl.program;
UTILS_NOUNROLL
for (GLuint const shader: gl.shaders) {
if (shader) {
if (program) {
glDetachShader(program, shader);
}
glDeleteShader(shader);
}
}
if (program) {
glDeleteProgram(program);
}
}
/*
* Compile shaders in the ShaderSource. This cannot fail because compilation failures are not
* checked until after the program is linked.
* This always returns the GL shader IDs or zero a shader stage is not present.
*/
void OpenGLProgram::compileShaders(OpenGLContext& context,
Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
GLuint shaderIds[Program::SHADER_TYPE_COUNT],
UTILS_UNUSED_IN_RELEASE std::array<CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept {
void OpenGLProgram::initialize(OpenGLDriver& gld) {
SYSTRACE_CALL();
auto appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value);
s += '\n';
return s;
};
assert_invariant(gl.program == 0);
assert_invariant(mToken);
std::string specializationConstantString;
for (auto const& sc : specializationConstants) {
appendSpecConstantString(specializationConstantString, sc);
LazyInitializationData* const lazyInitializationData =
(LazyInitializationData *)ShaderCompilerService::getUserData(mToken);
ShaderCompilerService& compiler = gld.getShaderCompilerService();
gl.program = compiler.getProgram(mToken);
assert_invariant(mToken == nullptr);
if (gl.program) {
assert_invariant(lazyInitializationData);
initializeProgramState(gld.getContext(), gl.program, *lazyInitializationData);
delete lazyInitializationData;
}
if (!specializationConstantString.empty()) {
specializationConstantString += '\n';
}
// build all shaders
UTILS_NOUNROLL
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
const ShaderStage stage = static_cast<ShaderStage>(i);
GLenum glShaderType{};
switch (stage) {
case ShaderStage::VERTEX:
glShaderType = GL_VERTEX_SHADER;
break;
case ShaderStage::FRAGMENT:
glShaderType = GL_FRAGMENT_SHADER;
break;
case ShaderStage::COMPUTE:
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
glShaderType = GL_COMPUTE_SHADER;
#else
continue;
#endif
break;
}
if (UTILS_LIKELY(!shadersSource[i].empty())) {
Program::ShaderBlob& shader = shadersSource[i];
// remove GOOGLE_cpp_style_line_directive
std::string_view const source = process_GOOGLE_cpp_style_line_directive(context,
reinterpret_cast<char*>(shader.data()), shader.size());
// add support for ARB_shading_language_packing if needed
auto const packingFunctions = process_ARB_shading_language_packing(context);
// split shader source, so we can insert the specification constants and the packing functions
auto const [prolog, body] = splitShaderSource(source);
const std::array<const char*, 4> sources = {
prolog.data(),
specializationConstantString.c_str(),
packingFunctions.data(),
body.data()
};
const std::array<GLint, 4> lengths = {
(GLint)prolog.length(),
(GLint)specializationConstantString.length(),
(GLint)packingFunctions.length(),
(GLint)body.length() - 1 // null terminated
};
GLuint const shaderId = glCreateShader(glShaderType);
glShaderSource(shaderId, sources.size(), sources.data(), lengths.data());
glCompileShader(shaderId);
#ifndef NDEBUG
// for debugging we return the original shader source (without the modifications we
// made here), otherwise the line numbers wouldn't match.
outShaderSourceCode[i] = { source.data(), source.length() };
#endif
shaderIds[i] = shaderId;
}
}
}
// If usages of the Google-style line directive are present, remove them, as some
// drivers don't allow the quotation marks. This happens in-place.
std::string_view OpenGLProgram::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
}
}
return { source, len };
}
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
// macOS doesn't support GL_ARB_shading_language_packing
std::string_view OpenGLProgram::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 : 0xFFFFFFFF;
return uintBitsToFloat(s | ((((n >> 3u) + (0x70u << 23))) & 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 >> 16) & 0x8000u;
int exponent = int((f32 >> 23) & 0xFFu) - 127;
uint mantissa = f32 & 0x007FFFFFu;
if (exponent > 15) {
f16 = sign | (0x1Fu << 10);
} 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 << 16) | x;
}
)"sv;
}
#endif // BACKEND_OPENGL_VERSION_GL
return ""sv;
}
// split shader source code in two, the first section goes from the start to the line after the
// last #extension, and the 2nd part goes from there to the end.
std::array<std::string_view, 2> OpenGLProgram::splitShaderSource(std::string_view source) noexcept {
auto start = source.find("#version");
assert_invariant(start != std::string_view::npos);
auto pos = source.rfind("\n#extension");
if (pos == std::string_view::npos) {
pos = start;
} else {
++pos;
}
auto eol = source.find('\n', pos) + 1;
assert_invariant(eol != std::string_view::npos);
std::string_view const version = source.substr(start, eol - start);
std::string_view const body = source.substr(version.length(), source.length() - version.length());
return { version, 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 OpenGLProgram::linkProgram(OpenGLContext& context,
LazyInitializationData* const lazyInitializationData,
const GLuint shaderIds[Program::SHADER_TYPE_COUNT]) noexcept {
SYSTRACE_CALL();
GLuint const program = glCreateProgram();
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
if (shaderIds[i]) {
glAttachShader(program, shaderIds[i]);
}
}
if (UTILS_UNLIKELY(context.isES2())) {
for (auto const& [ name, loc ] : lazyInitializationData->attributes) {
glBindAttribLocation(program, loc, name.c_str());
}
}
glLinkProgram(program);
return program;
}
/*
* Checks a program link status and logs errors and frees resources on failure.
* Returns true on success.
*/
bool OpenGLProgram::checkProgramStatus(const char* name,
GLuint& program, GLuint shaderIds[Program::SHADER_TYPE_COUNT],
std::array<CString, Program::SHADER_TYPE_COUNT> const& shaderSourceCode) noexcept {
SYSTRACE_CALL();
GLint status;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (UTILS_LIKELY(status == GL_TRUE)) {
return true;
}
// only if the link fails, we check the compilation status
UTILS_NOUNROLL
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
const ShaderStage type = static_cast<ShaderStage>(i);
const GLuint shader = shaderIds[i];
if (shader) {
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status != GL_TRUE) {
logCompilationError(slog.e, type, name, shader, shaderSourceCode[i]);
}
glDetachShader(program, shader);
glDeleteShader(shader);
shaderIds[i] = 0;
}
}
// log the link error as well
logProgramLinkError(slog.e, name, program);
glDeleteProgram(program);
program = 0;
return false;
}
void OpenGLProgram::initialize(OpenGLContext& context) {
// by this point we must have a GL program
assert_invariant(gl.program);
// we also can't be in the initialized state
assert_invariant(!mInitialized);
// we must have our lazy initialization data
assert_invariant(mLazyInitializationData);
// we must copy mLazyInitializationData locally because it is aliased with mIndicesRuns
auto* const pInitializationData = mLazyInitializationData;
// check status of program linking and shader compilation, logs error and free all resources
// in case of error.
mValid = OpenGLProgram::checkProgramStatus(name.c_str_safe(),
gl.program, gl.shaders, pInitializationData->shaderSourceCode);
if (UTILS_LIKELY(mValid)) {
initializeProgramState(context, gl.program, *pInitializationData);
}
// and destroy all temporary init data
delete pInitializationData;
// mInitialized means mLazyInitializationData is no more valid
mInitialized = true;
}
/*
@@ -592,64 +303,5 @@ void OpenGLProgram::setRec709ColorSpace(bool rec709) const noexcept {
glUniform1i(mRec709Location, rec709);
}
UTILS_NOINLINE
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";
}
};
{ // scope for the temporary string storage
GLint length = 0;
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
CString infoLog(length);
glGetShaderInfoLog(shaderId, length, nullptr, infoLog.data());
out << "Compilation error in " << to_string(shaderType) << " shader \"" << name << "\":\n"
<< "\"" << infoLog.c_str() << "\""
<< io::endl;
}
#ifndef NDEBUG
std::string_view const shader{ sourceCode.data(), sourceCode.size() };
size_t lc = 1;
size_t start = 0;
std::string line;
while (true) {
size_t const end = shader.find('\n', start);
if (end == std::string::npos) {
line = shader.substr(start);
} else {
line = shader.substr(start, end - start);
}
out << lc++ << ": " << line.c_str() << '\n';
if (end == std::string::npos) {
break;
}
start = end + 1;
}
out << io::endl;
#endif
}
UTILS_NOINLINE
void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept {
GLint length = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
CString infoLog(length);
glGetProgramInfoLog(program, length, nullptr, infoLog.data());
out << "Link error in \"" << name << "\":\n"
<< "\"" << infoLog.c_str() << "\""
<< io::endl;
}
} // namespace filament::backend
+17 -57
View File
@@ -18,22 +18,23 @@
#define TNT_FILAMENT_BACKEND_OPENGL_OPENGLPROGRAM_H
#include "DriverBase.h"
#include "OpenGLDriver.h"
#include "private/backend/Driver.h"
#include "backend/Program.h"
#include "OpenGLContext.h"
#include "ShaderCompilerService.h"
#include <private/backend/Driver.h>
#include <backend/Program.h>
#include <utils/compiler.h>
#include <utils/Log.h>
#include <vector>
#include <utils/FixedCapacityVector.h>
#include <stddef.h>
#include <stdint.h>
namespace filament::backend {
class OpenGLDriver;
class OpenGLProgram : public HwProgram {
public:
@@ -41,11 +42,11 @@ public:
OpenGLProgram(OpenGLDriver& gld, Program&& program) noexcept;
~OpenGLProgram() noexcept;
bool isValid() const noexcept { return mValid; }
bool isValid() const noexcept { return mToken || gl.program != 0; }
void use(OpenGLDriver* const gld, OpenGLContext& context) noexcept {
if (UTILS_UNLIKELY(!mInitialized)) {
initialize(context);
if (UTILS_UNLIKELY(!gl.program)) {
initialize(*gld);
}
context.useProgram(gl.program);
@@ -67,7 +68,6 @@ public:
}
struct {
GLuint shaders[Program::SHADER_TYPE_COUNT] = {};
GLuint program = 0;
} gl; // 12 bytes
@@ -77,69 +77,29 @@ public:
private:
// keep these away from of other class attributes
struct LazyInitializationData {
Program::UniformBlockInfo uniformBlockInfo;
Program::SamplerGroupInfo samplerGroupInfo;
std::array<Program::UniformInfo, Program::UNIFORM_BINDING_COUNT> bindingUniformInfo;
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
};
struct LazyInitializationData;
static void compileShaders(OpenGLContext& context,
Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
GLuint shaderIds[Program::SHADER_TYPE_COUNT],
std::array<utils::CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept;
static std::string_view process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
char* source, size_t len) noexcept;
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
static std::array<std::string_view, 2> splitShaderSource(std::string_view source) noexcept;
static GLuint linkProgram(OpenGLContext& context,
LazyInitializationData* lazyInitializationData,
const GLuint shaderIds[Program::SHADER_TYPE_COUNT]) noexcept;
static bool checkProgramStatus(const char* name,
GLuint& program, GLuint shaderIds[Program::SHADER_TYPE_COUNT],
std::array<utils::CString, Program::SHADER_TYPE_COUNT> const& shaderSourceCode) noexcept;
void initialize(OpenGLContext& context);
void initialize(OpenGLDriver& gld);
void initializeProgramState(OpenGLContext& context, GLuint program,
LazyInitializationData& lazyInitializationData) noexcept;
void updateSamplers(OpenGLDriver* gld) const noexcept;
ShaderCompilerService::program_token_t mToken{};
// number of bindings actually used by this program
uint8_t mUsedBindingsCount = 0u;
// whether lazy initialization has been performed
bool mInitialized : 1;
// whether lazy initialization was successful
bool mValid : 1;
UTILS_UNUSED uint8_t padding[2] = {};
UTILS_UNUSED uint8_t padding[3] = {};
std::array<uint8_t, Program::SAMPLER_BINDING_COUNT> mUsedSamplerBindingPoints; // 4 bytes
// only for ES2
// only needed for ES2
using LocationInfo = utils::FixedCapacityVector<GLint>;
struct UniformsRecord {
Program::UniformInfo uniforms;
LocationInfo locations;
mutable uint16_t age = std::numeric_limits<uint16_t>::max();
};
union {
// when mInitialized == true:
// information about each USED sampler buffer per binding (no gaps)
std::array<uint8_t, Program::SAMPLER_BINDING_COUNT> mUsedSamplerBindingPoints; // 4 bytes
// when mInitialized == false:
// lazy initialization data pointer
LazyInitializationData* mLazyInitializationData;
};
// only needed for ES2
UniformsRecord const* mUniformsRecords = nullptr;
GLint mRec709Location = -1;
};
@@ -0,0 +1,897 @@
/*
* Copyright (C) 2023 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 "ShaderCompilerService.h"
#include "BlobCacheKey.h"
#include "OpenGLBlobCache.h"
#include "OpenGLDriver.h"
#include <private/backend/BackendUtils.h>
#include <backend/platforms/OpenGLPlatform.h>
#include <backend/Program.h>
#include <utils/compiler.h>
#include <utils/CString.h>
#include <utils/JobSystem.h>
#include <utils/Log.h>
#include <utils/Systrace.h>
#include <chrono>
#include <future>
#include <string>
#include <string_view>
#include <variant>
namespace filament::backend {
using namespace utils;
// ------------------------------------------------------------------------------------------------
static void logCompilationError(utils::io::ostream& out,
ShaderStage shaderType, const char* name,
GLuint shaderId, CString const& sourceCode) 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) + ")";
}
// ------------------------------------------------------------------------------------------------
struct ShaderCompilerService::ProgramToken {
struct ProgramBinary {
GLenum format{};
GLuint program{};
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
std::vector<char> blob;
};
ProgramToken(ShaderCompilerService& compiler, utils::CString const& name) noexcept
: compiler(compiler), name(name) {
}
ShaderCompilerService& compiler;
utils::CString const& name;
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> attributes;
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
void* user = nullptr;
struct {
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
GLuint program = 0;
} gl; // 12 bytes
BlobCacheKey key;
std::future<ProgramBinary> binary;
CompilerPriorityQueue priorityQueue = CompilerPriorityQueue::HIGH;
bool canceled = false;
};
void ShaderCompilerService::setUserData(const program_token_t& token, void* user) noexcept {
token->user = user;
}
void* ShaderCompilerService::getUserData(const program_token_t& token) noexcept {
return token->user;
}
// ------------------------------------------------------------------------------------------------
void ShaderCompilerService::CompilerThreadPool::init(
bool useSharedContexts, uint32_t threadCount, OpenGLPlatform& platform) noexcept {
for (size_t i = 0; i < threadCount; i++) {
mCompilerThreads.emplace_back([this, useSharedContexts, &platform]() {
// give the thread a name
JobSystem::setThreadName("CompilerThreadPool");
// create a gl context current to this thread
platform.createContext(useSharedContexts);
// process jobs from the queue until we're asked to exit
while (!mExitRequested) {
std::unique_lock lock(mQueueLock);
mQueueCondition.wait(lock, [this]() {
return mExitRequested ||
mUrgentJob ||
(!std::all_of( std::begin(mQueues), std::end(mQueues),
[](auto&& q) { return q.empty(); }));
});
if (!mExitRequested) {
Job job{ std::move(mUrgentJob) };
if (!job) {
// use the first queue that's not empty
auto& queue = [this]() -> auto& {
for (auto& q: mQueues) {
if (!q.empty()) {
return q;
}
}
return mQueues[0]; // we should never end-up here.
}();
assert_invariant(!queue.empty());
std::swap(job, queue.front().second);
queue.pop_front();
}
// execute the job without holding any locks
lock.unlock();
job();
}
}
});
}
}
auto ShaderCompilerService::CompilerThreadPool::dequeue(program_token_t const& token) -> Job {
auto& q = mQueues[size_t(token->priorityQueue)];
auto pos = std::find_if(q.begin(), q.end(), [&token](auto&& item) {
return item.first == token;
});
Job job;
if (pos != q.end()) {
std::swap(job, pos->second);
q.erase(pos);
}
return job;
}
void ShaderCompilerService::CompilerThreadPool::makeUrgent(program_token_t const& token) {
std::unique_lock const lock(mQueueLock);
assert_invariant(!mUrgentJob);
Job job{ dequeue(token) };
std::swap(job, mUrgentJob);
mQueueCondition.notify_one();
}
void ShaderCompilerService::CompilerThreadPool::queue(program_token_t const& token, Job&& job) {
std::unique_lock const lock(mQueueLock);
mQueues[size_t(token->priorityQueue)].emplace_back(token, std::move(job));
mQueueCondition.notify_one();
}
void ShaderCompilerService::CompilerThreadPool::exit() noexcept {
std::unique_lock lock(mQueueLock);
mExitRequested = true;
mQueueCondition.notify_all();
lock.unlock();
for (auto& thread: mCompilerThreads) {
if (thread.joinable()) {
thread.join();
}
}
}
// ------------------------------------------------------------------------------------------------
ShaderCompilerService::ShaderCompilerService(OpenGLDriver& driver)
: mDriver(driver),
KHR_parallel_shader_compile(driver.getContext().ext.KHR_parallel_shader_compile) {
}
ShaderCompilerService::~ShaderCompilerService() noexcept = default;
void ShaderCompilerService::init() noexcept {
// If we have KHR_parallel_shader_compile, we always use it, it should be more resource
// friendly.
if (!KHR_parallel_shader_compile) {
// - on Adreno there is a single compiler object. We can't use a pool > 1
// also glProgramBinary blocks if other threads are compiling.
// - on Mali shader compilation can be multithreaded, but program linking happens on
// a single service thread, so we don't bother using more than one thread either.
// - on desktop we could use more threads, tbd.
if (mDriver.mPlatform.isExtraContextSupported()) {
mShaderCompilerThreadCount = 1;
mCompilerThreadPool.init(mUseSharedContext,
mShaderCompilerThreadCount, mDriver.mPlatform);
}
}
}
void ShaderCompilerService::terminate() noexcept {
// FIXME: could we have some user callbacks pending here?
mCompilerThreadPool.exit();
}
ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
utils::CString const& name, Program&& program) {
auto& gl = mDriver.getContext();
auto token = std::make_shared<ProgramToken>(*this, name);
if (UTILS_UNLIKELY(gl.isES2())) {
token->attributes = std::move(program.getAttributes());
}
token->gl.program = OpenGLBlobCache::retrieve(&token->key, mDriver.mPlatform, program);
if (!token->gl.program) {
if (mShaderCompilerThreadCount) {
// set the future in the token and pass the promise to the worker thread
std::promise<ProgramToken::ProgramBinary> promise;
token->binary = promise.get_future();
token->priorityQueue = program.getPriorityQueue();
// queue a compile job
mCompilerThreadPool.queue(token,
[this, &gl, promise = std::move(promise),
program = std::move(program), token]() mutable {
// compile the shaders
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders{};
std::array<utils::CString, Program::SHADER_TYPE_COUNT> shaderSourceCode;
compileShaders(gl,
std::move(program.getShadersSource()),
program.getSpecializationConstants(),
shaders,
shaderSourceCode);
// link the program
GLuint const glProgram = linkProgram(gl, shaders, token->attributes);
ProgramToken::ProgramBinary binary;
binary.shaders = shaders;
if (UTILS_LIKELY(mUseSharedContext)) {
// 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;
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
binary.program = glProgram;
if (token->key) {
// Attempt to cache. This calls glGetProgramBinary.
OpenGLBlobCache::insert(mDriver.mPlatform,
token->key, token->gl.program);
}
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
else {
// retrieve the program binary
GLsizei programBinarySize = 0;
glGetProgramiv(glProgram, GL_PROGRAM_BINARY_LENGTH, &programBinarySize);
assert_invariant(programBinarySize);
if (programBinarySize) {
binary.blob.resize(programBinarySize);
glGetProgramBinary(glProgram, programBinarySize,
&programBinarySize, &binary.format, binary.blob.data());
}
// and we can destroy the program
glDeleteProgram(glProgram);
if (token->key) {
// attempt to cache
OpenGLBlobCache::insert(mDriver.mPlatform, token->key,
binary.format,
binary.blob.data(), GLsizei(binary.blob.size()));
}
}
#endif
// we don't need to check for success here, it'll be done on the
// main thread side.
promise.set_value(binary);
});
} 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(),
token->gl.shaders,
token->shaderSourceCode);
}
runAtNextTick(token, [this, token]() {
if (mShaderCompilerThreadCount) {
if (!token->gl.program) {
// TODO: see if we could completely eliminate this callback here
// and instead just rely on token->gl.program being atomically
// set by the compiler thread.
assert_invariant(token->binary.valid());
// we're using the compiler thread, check if the program is ready, no-op if not.
using namespace std::chrono_literals;
if (token->binary.wait_for(0s) != std::future_status::ready) {
return false;
}
// program binary is ready, retrieve it without blocking
ShaderCompilerService::getProgramFromCompilerPool(
const_cast<program_token_t&>(token));
}
} else {
if (KHR_parallel_shader_compile) {
// 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 (KHR_parallel_shader_compile) {
// wait until the link finishes...
return false;
}
}
}
assert_invariant(token->gl.program);
if (token->key && !mShaderCompilerThreadCount) {
// 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).
OpenGLBlobCache::insert(mDriver.mPlatform, token->key, token->gl.program);
}
return true;
});
}
return token;
}
bool ShaderCompilerService::isProgramReady(
const ShaderCompilerService::program_token_t& token) const noexcept {
assert_invariant(token);
if (!token->gl.program) {
return false;
}
if (KHR_parallel_shader_compile) {
GLint status = GL_FALSE;
glGetProgramiv(token->gl.program, GL_COMPLETION_STATUS, &status);
return (bool)status;
}
// If gl.program is set, this means the program was linked. Some drivers may defer the link
// in which case we might block in getProgram() when we check the program status.
// Unfortunately, this is nothing we can do about that.
return bool(token->gl.program);
}
GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t& token) {
GLuint const program = initialize(token);
assert_invariant(token == nullptr);
assert_invariant(program);
return program;
}
/* static*/ void ShaderCompilerService::terminate(program_token_t& token) {
assert_invariant(token);
token->canceled = true;
token->compiler.cancelTickOp(token);
for (GLuint& shader: token->gl.shaders) {
if (shader) {
if (token->gl.program) {
glDetachShader(token->gl.program, shader);
}
glDeleteShader(shader);
shader = 0;
}
}
if (token->gl.program) {
glDeleteProgram(token->gl.program);
}
token = nullptr;
}
void ShaderCompilerService::tick() {
executeTickOps();
}
void ShaderCompilerService::notifyWhenAllProgramsAreReady(CallbackHandler* handler,
CallbackHandler::Callback callback, void* user) {
if (KHR_parallel_shader_compile || mShaderCompilerThreadCount) {
// list all programs up to this point
utils::FixedCapacityVector<program_token_t, std::allocator<program_token_t>, false> tokens;
tokens.reserve(mRunAtNextTickOps.size());
for (auto& [token, _] : mRunAtNextTickOps) {
if (token) {
tokens.push_back(token);
}
}
runAtNextTick(nullptr, [this, tokens = std::move(tokens), handler, user, callback]() {
for (auto const& token : tokens) {
assert_invariant(token);
if (!isProgramReady(token)) {
// one of the program is not ready, try next time
return false;
}
}
if (callback) {
// all programs are ready, we can call the callbacks
mDriver.scheduleCallback(handler, user, callback);
}
// and we're done
return true;
});
return;
}
// we don't have KHR_parallel_shader_compile
runAtNextTick(nullptr, [this, handler, user, callback]() {
mDriver.scheduleCallback(handler, user, callback);
return true;
});
// TODO: we could spread the compiles over several frames, the tick() below then is not
// needed here. We keep it for now as to not change the current behavior too much.
// this will block until all programs are linked
tick();
}
// ------------------------------------------------------------------------------------------------
void ShaderCompilerService::getProgramFromCompilerPool(program_token_t& token) noexcept {
ProgramToken::ProgramBinary const binary{ token->binary.get() };
if (!token->canceled) {
token->gl.shaders = binary.shaders;
if (UTILS_LIKELY(mUseSharedContext)) {
token->gl.program = binary.program;
}
#ifndef FILAMENT_SILENCE_NOT_SUPPORTED_BY_ES2
else {
token->gl.program = glCreateProgram();
glProgramBinary(token->gl.program, binary.format,
binary.blob.data(), GLsizei(binary.blob.size()));
}
#endif
}
}
GLuint ShaderCompilerService::initialize(program_token_t& token) noexcept {
SYSTRACE_CALL();
if (!token->gl.program) {
if (mShaderCompilerThreadCount) {
// Block until the program is ready. This could take a very long time.
assert_invariant(token->binary.valid());
// we need this program right now, so move it to the head of the queue.
mCompilerThreadPool.makeUrgent(token);
if (!token->canceled) {
token->compiler.cancelTickOp(token);
}
// block until we get the program from the pool
ShaderCompilerService::getProgramFromCompilerPool(token);
} else if (KHR_parallel_shader_compile) {
// 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);
} else {
// if we don't have a program yet, block until we get it.
tick();
}
}
// by this point we must have a GL program
assert_invariant(token->gl.program);
GLuint program = 0;
// check status of program linking and shader compilation, logs error and free all resources
// in case of error.
bool const success = checkProgramStatus(token);
if (UTILS_LIKELY(success)) {
program = token->gl.program;
// no need to keep the shaders around
UTILS_NOUNROLL
for (GLuint& shader: token->gl.shaders) {
if (shader) {
glDetachShader(program, shader);
glDeleteShader(shader);
shader = 0;
}
}
}
// and destroy all temporary init data
token = nullptr;
return program;
}
/*
* Compile shaders in the ShaderSource. This cannot fail because compilation failures are not
* checked until after the program is linked.
* This always returns the GL shader IDs or zero a shader stage is not present.
*/
void ShaderCompilerService::compileShaders(OpenGLContext& context,
Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
UTILS_UNUSED_IN_RELEASE std::array<CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept {
SYSTRACE_CALL();
auto appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value);
s += '\n';
return s;
};
std::string specializationConstantString;
for (auto const& sc : specializationConstants) {
appendSpecConstantString(specializationConstantString, sc);
}
if (!specializationConstantString.empty()) {
specializationConstantString += '\n';
}
// build all shaders
UTILS_NOUNROLL
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
const ShaderStage stage = static_cast<ShaderStage>(i);
GLenum glShaderType{};
switch (stage) {
case ShaderStage::VERTEX:
glShaderType = GL_VERTEX_SHADER;
break;
case ShaderStage::FRAGMENT:
glShaderType = GL_FRAGMENT_SHADER;
break;
case ShaderStage::COMPUTE:
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
glShaderType = GL_COMPUTE_SHADER;
#else
continue;
#endif
break;
}
if (UTILS_LIKELY(!shadersSource[i].empty())) {
Program::ShaderBlob& shader = shadersSource[i];
// remove GOOGLE_cpp_style_line_directive
std::string_view const source = process_GOOGLE_cpp_style_line_directive(context,
reinterpret_cast<char*>(shader.data()), shader.size());
// add support for ARB_shading_language_packing if needed
auto const packingFunctions = process_ARB_shading_language_packing(context);
// split shader source, so we can insert the specification constants and the packing functions
auto const [prolog, body] = splitShaderSource(source);
const std::array<const char*, 4> sources = {
prolog.data(),
specializationConstantString.c_str(),
packingFunctions.data(),
body.data()
};
const std::array<GLint, 4> lengths = {
(GLint)prolog.length(),
(GLint)specializationConstantString.length(),
(GLint)packingFunctions.length(),
(GLint)body.length() - 1 // null terminated
};
GLuint const shaderId = glCreateShader(glShaderType);
glShaderSource(shaderId, sources.size(), sources.data(), lengths.data());
glCompileShader(shaderId);
#ifndef NDEBUG
// for debugging we return the original shader source (without the modifications we
// made here), otherwise the line numbers wouldn't match.
outShaderSourceCode[i] = { source.data(), source.length() };
#endif
outShaders[i] = shaderId;
}
}
}
// If usages of the Google-style line directive are present, remove them, as some
// drivers don't allow the quotation marks. This happens in-place.
std::string_view 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
}
}
return { source, len };
}
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
// macOS doesn't support GL_ARB_shading_language_packing
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 : 0xFFFFFFFF;
return uintBitsToFloat(s | ((((n >> 3u) + (0x70u << 23))) & 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 >> 16) & 0x8000u;
int exponent = int((f32 >> 23) & 0xFFu) - 127;
uint mantissa = f32 & 0x007FFFFFu;
if (exponent > 15) {
f16 = sign | (0x1Fu << 10);
} 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 << 16) | x;
}
)"sv;
}
#endif // BACKEND_OPENGL_VERSION_GL
return ""sv;
}
// split shader source code in two, the first section goes from the start to the line after the
// last #extension, and the 2nd part goes from there to the end.
std::array<std::string_view, 2> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
auto start = source.find("#version");
assert_invariant(start != std::string_view::npos);
auto pos = source.rfind("\n#extension");
if (pos == std::string_view::npos) {
pos = start;
} else {
++pos;
}
auto eol = source.find('\n', pos) + 1;
assert_invariant(eol != std::string_view::npos);
std::string_view const version = source.substr(start, eol - start);
std::string_view const body = source.substr(version.length(), source.length() - version.length());
return { version, 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,
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept {
SYSTRACE_CALL();
GLuint const program = glCreateProgram();
for (auto shader : shaders) {
if (shader) {
glAttachShader(program, shader);
}
}
if (UTILS_UNLIKELY(context.isES2())) {
for (auto const& [ name, loc ] : attributes) {
glBindAttribLocation(program, loc, name.c_str());
}
}
glLinkProgram(program);
return program;
}
// ------------------------------------------------------------------------------------------------
void ShaderCompilerService::runAtNextTick(
const program_token_t& token, std::function<bool()> fn) noexcept {
// insert items in order of priority and at the end of the range
auto& ops = mRunAtNextTickOps;
using ContainerType = std::pair<program_token_t, std::function<bool()>>;
auto const pos = std::lower_bound(ops.begin(), ops.end(),
token->priorityQueue,
[](ContainerType const& lhs, CompilerPriorityQueue priorityQueue) {
return lhs.first->priorityQueue < priorityQueue;
});
ops.emplace(pos, token, std::move(fn));
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", mRunAtNextTickOps.size());
}
void ShaderCompilerService::cancelTickOp(program_token_t token) noexcept {
// We do a linear search here, but this is rare, and we know the list is pretty small.
auto& ops = mRunAtNextTickOps;
auto pos = std::find_if(ops.begin(), ops.end(),
[&](const auto& item) {
return item.first == token;
});
if (pos != ops.end()) {
ops.erase(pos);
}
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
}
void ShaderCompilerService::executeTickOps() noexcept {
auto& ops = mRunAtNextTickOps;
auto it = ops.begin();
while (it != ops.end()) {
bool const remove = it->second();
if (remove) {
it = ops.erase(it);
} else {
++it;
}
}
SYSTRACE_CONTEXT();
SYSTRACE_VALUE32("ShaderCompilerService Jobs", ops.size());
}
// ------------------------------------------------------------------------------------------------
/*
* Checks a program link status and logs errors and frees resources on failure.
* Returns true on success.
*/
bool ShaderCompilerService::checkProgramStatus(program_token_t const& token) noexcept {
SYSTRACE_CALL();
assert_invariant(token->gl.program);
GLint status;
glGetProgramiv(token->gl.program, GL_LINK_STATUS, &status);
if (UTILS_LIKELY(status == GL_TRUE)) {
return true;
}
// only if the link fails, we check the compilation status
UTILS_NOUNROLL
for (size_t i = 0; i < Program::SHADER_TYPE_COUNT; i++) {
const ShaderStage type = static_cast<ShaderStage>(i);
const GLuint shader = token->gl.shaders[i];
if (shader) {
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status != GL_TRUE) {
logCompilationError(slog.e, type,
token->name.c_str_safe(), shader, token->shaderSourceCode[i]);
}
glDetachShader(token->gl.program, shader);
glDeleteShader(shader);
token->gl.shaders[i] = 0;
}
}
// log the link error as well
logProgramLinkError(slog.e, token->name.c_str_safe(), token->gl.program);
glDeleteProgram(token->gl.program);
token->gl.program = 0;
return false;
}
UTILS_NOINLINE
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";
}
};
{ // scope for the temporary string storage
GLint length = 0;
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &length);
CString infoLog(length);
glGetShaderInfoLog(shaderId, length, nullptr, infoLog.data());
out << "Compilation error in " << to_string(shaderType) << " shader \"" << name << "\":\n"
<< "\"" << infoLog.c_str() << "\""
<< io::endl;
}
#ifndef NDEBUG
std::string_view const shader{ sourceCode.data(), sourceCode.size() };
size_t lc = 1;
size_t start = 0;
std::string line;
while (true) {
size_t const end = shader.find('\n', start);
if (end == std::string::npos) {
line = shader.substr(start);
} else {
line = shader.substr(start, end - start);
}
out << lc++ << ": " << line.c_str() << '\n';
if (end == std::string::npos) {
break;
}
start = end + 1;
}
out << io::endl;
#endif
}
UTILS_NOINLINE
void logProgramLinkError(io::ostream& out, char const* name, GLuint program) noexcept {
GLint length = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
CString infoLog(length);
glGetProgramInfoLog(program, length, nullptr, infoLog.data());
out << "Link error in \"" << name << "\":\n"
<< "\"" << infoLog.c_str() << "\""
<< io::endl;
}
} // namespace filament::backend
@@ -0,0 +1,155 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef TNT_FILAMENT_BACKEND_OPENGL_SHADERCOMPILERSERVICE_H
#define TNT_FILAMENT_BACKEND_OPENGL_SHADERCOMPILERSERVICE_H
#include "gl_headers.h"
#include <backend/CallbackHandler.h>
#include <backend/Program.h>
#include <utils/CString.h>
#include <utils/Invocable.h>
#include <utils/FixedCapacityVector.h>
#include <atomic>
#include <condition_variable>
#include <deque>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
namespace filament::backend {
class OpenGLDriver;
class OpenGLContext;
class OpenGLPlatform;
class Program;
class CallbackHandler;
/*
* A class handling shader compilation that supports asynchronous compilation.
*/
class ShaderCompilerService {
struct ProgramToken;
public:
using program_token_t = std::shared_ptr<ProgramToken>;
explicit ShaderCompilerService(OpenGLDriver& driver);
ShaderCompilerService(ShaderCompilerService const& rhs) = delete;
ShaderCompilerService(ShaderCompilerService&& rhs) = delete;
ShaderCompilerService& operator=(ShaderCompilerService const& rhs) = delete;
ShaderCompilerService& operator=(ShaderCompilerService&& rhs) = delete;
~ShaderCompilerService() noexcept;
void init() noexcept;
void terminate() noexcept;
// creates a program (compile + link) asynchronously if supported
program_token_t createProgram(utils::CString const& name, Program&& program);
// Returns true if the program is linked (successfully or not). Guarantees that
// getProgram() won't block. Does not block.
bool isProgramReady(const program_token_t& token) const noexcept;
// Return the GL program, blocks if necessary. The Token is destroyed and becomes invalid.
GLuint getProgram(program_token_t& token);
// Must be called at least once per frame.
void tick();
// Destroys a valid token and all associated resources. Used to "cancel" a program compilation.
static void terminate(program_token_t& token);
// stores a user data pointer in the token
static void setUserData(const program_token_t& token, void* user) noexcept;
// retrieves the user data pointer stored in the token
static void* getUserData(const program_token_t& token) noexcept;
// call the callback when all active programs are ready
void notifyWhenAllProgramsAreReady(CallbackHandler* handler,
CallbackHandler::Callback callback, void* user);
private:
class CompilerThreadPool {
public:
using Job = utils::Invocable<void()>;
void init(bool useSharedContexts, uint32_t threadCount, OpenGLPlatform& platform) noexcept;
void exit() noexcept;
void queue(program_token_t const& token, Job&& job);
void makeUrgent(program_token_t const& token);
private:
std::vector<std::thread> mCompilerThreads;
std::atomic_bool mExitRequested{ false };
std::mutex mQueueLock;
std::condition_variable mQueueCondition;
std::array<std::deque<std::pair<program_token_t, Job>>, 2> mQueues;
Job mUrgentJob;
Job dequeue(program_token_t const& token); // lock must be held
};
OpenGLDriver& mDriver;
CompilerThreadPool mCompilerThreadPool;
const bool KHR_parallel_shader_compile;
uint32_t mShaderCompilerThreadCount = 0u;
// For now, we assume shared contexts are supported everywhere. If they are not,
// we don't use the shader compiler pool. However, the code supports it.
static constexpr bool mUseSharedContext = true;
GLuint initialize(ShaderCompilerService::program_token_t& token) noexcept;
static void getProgramFromCompilerPool(program_token_t& token) noexcept;
static void compileShaders(
OpenGLContext& context,
Program::ShaderSource shadersSource,
utils::FixedCapacityVector<Program::SpecializationConstant> const& specializationConstants,
std::array<GLuint, Program::SHADER_TYPE_COUNT>& outShaders,
std::array<utils::CString, Program::SHADER_TYPE_COUNT>& outShaderSourceCode) noexcept;
static std::string_view process_GOOGLE_cpp_style_line_directive(OpenGLContext& context,
char* source, size_t len) noexcept;
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
static std::array<std::string_view, 2> splitShaderSource(std::string_view source) noexcept;
static GLuint linkProgram(OpenGLContext& context,
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders,
utils::FixedCapacityVector<std::pair<utils::CString, uint8_t>> const& attributes) noexcept;
static bool checkProgramStatus(program_token_t const& token) noexcept;
void runAtNextTick(const program_token_t& token, std::function<bool()> fn) noexcept;
void executeTickOps() noexcept;
void cancelTickOp(program_token_t token) noexcept;
// order of insertion is important
std::vector<std::pair<program_token_t, std::function<bool()>>> mRunAtNextTickOps;
};
} // namespace filament::backend
#endif // TNT_FILAMENT_BACKEND_OPENGL_SHADERCOMPILERSERVICE_H
+14
View File
@@ -174,6 +174,12 @@ using namespace glext;
# define GL_ZERO_TO_ONE GL_ZERO_TO_ONE_EXT
#endif
#ifdef GL_KHR_parallel_shader_compile
# define GL_COMPLETION_STATUS GL_COMPLETION_STATUS_KHR
#else
# define GL_COMPLETION_STATUS 0x91B1
#endif
// we need GL_TEXTURE_CUBE_MAP_ARRAY defined, but we won't use it if the extension/feature
// is not available.
#if defined(GL_EXT_texture_cube_map_array)
@@ -223,6 +229,14 @@ using namespace glext;
# endif
#endif
#else // All version OpenGL below
#ifdef GL_ARB_parallel_shader_compile
# define GL_COMPLETION_STATUS GL_COMPLETION_STATUS_ARB
#else
# define GL_COMPLETION_STATUS 0x91B1
#endif
#endif // GL_ES_VERSION_2_0
// This is just to simplify the implementation (i.e. so we don't have to have #ifdefs everywhere)
@@ -50,6 +50,7 @@ struct PlatformCocoaGLImpl {
NSOpenGLContext* mGLContext = nullptr;
CocoaGLSwapChain* mCurrentSwapChain = nullptr;
std::vector<NSView*> mHeadlessSwapChains;
std::vector<NSOpenGLContext*> mAdditionalContexts;
CVOpenGLTextureCacheRef mTextureCache = nullptr;
std::unique_ptr<CocoaExternalImage::SharedGl> mExternalImageSharedGl;
void updateOpenGLContext(NSView *nsView, bool resetView, bool clearView);
@@ -171,6 +172,23 @@ Driver* PlatformCocoaGL::createDriver(void* sharedContext, const Platform::Drive
return OpenGLPlatform::createDefaultDriver(this, sharedContext, driverConfig);
}
bool PlatformCocoaGL::isExtraContextSupported() const noexcept {
return true;
}
void PlatformCocoaGL::createContext(bool shared) {
NSOpenGLPixelFormatAttribute pixelFormatAttributes[] = {
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
NSOpenGLPFAAccelerated, (NSOpenGLPixelFormatAttribute) true,
0, 0,
};
NSOpenGLContext* const sharedContext = shared ? pImpl->mGLContext : nil;
NSOpenGLPixelFormat* const pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:pixelFormatAttributes];
NSOpenGLContext* const nsOpenGLContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:sharedContext];
[nsOpenGLContext makeCurrentContext];
pImpl->mAdditionalContexts.push_back(nsOpenGLContext);
}
int PlatformCocoaGL::getOSVersion() const noexcept {
return 0;
}
@@ -179,6 +197,9 @@ void PlatformCocoaGL::terminate() noexcept {
CFRelease(pImpl->mTextureCache);
pImpl->mExternalImageSharedGl.reset();
pImpl->mGLContext = nil;
for (auto& context : pImpl->mAdditionalContexts) {
context = nil;
}
bluegl::unbind();
}
@@ -40,6 +40,7 @@ struct PlatformCocoaTouchGLImpl {
EAGLContext* mGLContext = nullptr;
CAEAGLLayer* mCurrentGlLayer = nullptr;
std::vector<CAEAGLLayer*> mHeadlessGlLayers;
std::vector<EAGLContext*> mAdditionalContexts;
CGRect mCurrentGlLayerRect;
GLuint mDefaultFramebuffer = 0;
GLuint mDefaultColorbuffer = 0;
@@ -93,6 +94,20 @@ Driver* PlatformCocoaTouchGL::createDriver(void* const sharedGLContext, const Pl
return OpenGLPlatform::createDefaultDriver(this, sharedGLContext, driverConfig);
}
bool PlatformCocoaTouchGL::isExtraContextSupported() const noexcept {
return true;
}
void PlatformCocoaTouchGL::createContext(bool shared) {
EAGLSharegroup* const sharegroup = shared ? pImpl->mGLContext.sharegroup : nil;
EAGLContext* const context = [[EAGLContext alloc]
initWithAPI:kEAGLRenderingAPIOpenGLES3
sharegroup:sharegroup];
ASSERT_POSTCONDITION(context, "Unable to create extra OpenGL ES context.");
[EAGLContext setCurrentContext:context];
pImpl->mAdditionalContexts.push_back(context);
}
void PlatformCocoaTouchGL::terminate() noexcept {
CFRelease(pImpl->mTextureCache);
pImpl->mGLContext = nil;
@@ -27,6 +27,7 @@
#include <utils/compiler.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#ifndef EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE
# define EGL_CONTEXT_OPENGL_BACKWARDS_COMPATIBLE_ANGLE 0x3483
@@ -225,6 +226,8 @@ Driver* PlatformEGL::createDriver(void* sharedContext, const Platform::DriverCon
goto error;
}
mContextAttribs = std::move(contextAttribs);
initializeGlExtensions();
// this is needed with older emulators/API levels on Android
@@ -251,6 +254,26 @@ error:
return nullptr;
}
bool PlatformEGL::isExtraContextSupported() const noexcept {
return ext.egl.KHR_no_config_context;
}
void PlatformEGL::createContext(bool shared) {
EGLContext context = eglCreateContext(mEGLDisplay, EGL_NO_CONFIG_KHR,
shared ? mEGLContext : EGL_NO_CONTEXT, mContextAttribs.data());
if (UTILS_UNLIKELY(context == EGL_NO_CONTEXT)) {
// eglCreateContext failed
logEglError("eglCreateContext");
}
assert_invariant(context != EGL_NO_CONTEXT);
eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, context);
mAdditionalContexts.push_back(context);
}
EGLBoolean PlatformEGL::makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) noexcept {
if (UTILS_UNLIKELY((drawSurface != mCurrentDrawSurface || readSurface != mCurrentReadSurface))) {
mCurrentDrawSurface = drawSurface;
@@ -264,6 +287,9 @@ void PlatformEGL::terminate() noexcept {
eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroySurface(mEGLDisplay, mEGLDummySurface);
eglDestroyContext(mEGLDisplay, mEGLContext);
for (auto context : mAdditionalContexts) {
eglDestroyContext(mEGLDisplay, context);
}
eglTerminate(mEGLDisplay);
eglReleaseThread();
}
@@ -441,7 +467,7 @@ FenceStatus PlatformEGL::waitFence(
#ifdef EGL_KHR_reusable_sync
EGLSyncKHR sync = (EGLSyncKHR) fence;
if (sync != EGL_NO_SYNC_KHR) {
EGLint status = eglClientWaitSyncKHR(mEGLDisplay, sync, 0, (EGLTimeKHR)timeout);
EGLint const status = eglClientWaitSyncKHR(mEGLDisplay, sync, 0, (EGLTimeKHR)timeout);
if (status == EGL_CONDITION_SATISFIED_KHR) {
return FenceStatus::CONDITION_SATISFIED;
}
@@ -74,10 +74,11 @@ struct WGLSwapChain {
bool isHeadless = false;
};
static PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr;
Driver* PlatformWGL::createDriver(void* const sharedGLContext,
const Platform::DriverConfig& driverConfig) noexcept {
int result = 0;
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribs = nullptr;
int pixelFormat = 0;
mPfd = {
@@ -123,13 +124,15 @@ Driver* PlatformWGL::createDriver(void* const sharedGLContext,
(PFNWGLCREATECONTEXTATTRIBSARBPROC) wglGetProcAddress("wglCreateContextAttribsARB");
// try all versions down, from GL 4.5 to 4.1
for (int minor = 5; minor >= 1; minor--) {
int const attribs[] = {
mAttribs = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
WGL_CONTEXT_MINOR_VERSION_ARB, minor,
0
};
mContext = wglCreateContextAttribs(whdc, (HGLRC)sharedGLContext, attribs);
mContext = wglCreateContextAttribs(whdc, (HGLRC)sharedGLContext, mAttribs.data());
if (mContext) {
break;
}
@@ -164,12 +167,25 @@ error:
return NULL;
}
bool PlatformWGL::isExtraContextSupported() const noexcept {
return true;
}
void PlatformWGL::createContext(bool shared) {
HGLRC context = wglCreateContextAttribs(mWhdc, shared ? mContext : nullptr, mAttribs.data());
wglMakeCurrent(mWhdc, context);
mAdditionalContexts.push_back(context);
}
void PlatformWGL::terminate() noexcept {
wglMakeCurrent(NULL, NULL);
if (mContext) {
wglDeleteContext(mContext);
mContext = NULL;
}
for (auto& context : mAdditionalContexts) {
wglDeleteContext(mContext);
}
if (mHWnd && mWhdc) {
ReleaseDC(mHWnd, mWhdc);
DestroyWindow(mHWnd);
+25 -16
View File
@@ -171,7 +171,7 @@ void VulkanBlitter::blitDepth(BlitArgs args) {
assert_invariant(src.texture && dst.texture);
if (src.texture->samples > 1 && dst.texture->samples == 1) {
blitSlowDepth(aspect, args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
blitSlowDepth(args.filter, args.srcTarget->getExtent(), src, dst, args.srcRectPair,
args.dstRectPair);
return;
}
@@ -259,27 +259,30 @@ void VulkanBlitter::lazyInit() noexcept {
// 2. Begin render pass
// 3. Draw a big triangle
// 4. End render pass.
void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
const VkExtent2D srcExtent, VulkanAttachment src, VulkanAttachment dst,
const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
void VulkanBlitter::blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, VulkanAttachment src,
VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]) {
lazyInit();
const BlitterUniforms uniforms = {
BlitterUniforms const uniforms = {
.sampleCount = src.texture->samples,
.inverseSampleCount = 1.0f / float(src.texture->samples),
};
mParamsBuffer->loadFromCpu(&uniforms, 0, sizeof(uniforms));
VkImageAspectFlags const aspect = VK_IMAGE_ASPECT_DEPTH_BIT;
// We will transition the src into sampler layout and also keep it in sampler layout for
// consistency.
VulkanLayout const samplerLayout = VulkanLayout::DEPTH_SAMPLER;
// BEGIN RENDER PASS
// -----------------
VulkanLayout const layout = VulkanLayout::DEPTH_ATTACHMENT;
const VulkanFboCache::RenderPassKey rpkey = {
.initialColorLayoutMask = 0,
.initialDepthLayout = VulkanLayout::UNDEFINED,
.renderPassDepthLayout = layout,
.finalDepthLayout = layout,
.renderPassDepthLayout = samplerLayout,
.finalDepthLayout = samplerLayout,
.depthFormat = dst.getFormat(),
.clear = {},
.discardStart = TargetBufferFlags::DEPTH,
@@ -297,7 +300,7 @@ void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
.samples = rpkey.samples,
.color = {},
.resolve = {},
.depth = dst.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT),
.depth = dst.getImageView(aspect),
};
const VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
@@ -311,12 +314,18 @@ void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
renderPassInfo.renderArea.extent.width = dstRect[1].x - dstRect[0].x;
renderPassInfo.renderArea.extent.height = dstRect[1].y - dstRect[0].y;
// Even though we don't clear anything, we have to provide a clear value.
VkClearValue clearValues[1] = {};
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = clearValues;
const VkCommandBuffer cmdbuffer = mCommands->get().cmdbuffer;
// We need to transition the source into a sampler since it'll be sampled in the shader.
const VkImageSubresourceRange srcRange = {
.aspectMask = aspect,
.baseMipLevel = src.level,
.levelCount = 1,
.baseArrayLayer = src.layer,
.layerCount = 1,
};
src.texture->transitionLayout(cmdbuffer, srcRange, samplerLayout);
vkCmdBeginRenderPass(cmdbuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport = {
@@ -378,7 +387,7 @@ void VulkanBlitter::blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter,
samplers[0] = {
.sampler = vksampler,
.imageView = src.getImageView(VK_IMAGE_ASPECT_DEPTH_BIT),
.imageLayout = ImgUtil::getVkLayout(VulkanLayout::DEPTH_ATTACHMENT),
.imageLayout = ImgUtil::getVkLayout(samplerLayout),
};
mPipelineCache.bindSamplers(samplers,
+2 -3
View File
@@ -55,9 +55,8 @@ public:
private:
void lazyInit() noexcept;
void blitSlowDepth(VkImageAspectFlags aspect, VkFilter filter, const VkExtent2D srcExtent,
VulkanAttachment src, VulkanAttachment dst, const VkOffset3D srcRect[2],
const VkOffset3D dstRect[2]);
void blitSlowDepth(VkFilter filter, const VkExtent2D srcExtent, VulkanAttachment src,
VulkanAttachment dst, const VkOffset3D srcRect[2], const VkOffset3D dstRect[2]);
VulkanBuffer* mTriangleBuffer = nullptr;
VulkanBuffer* mParamsBuffer = nullptr;
+155 -5
View File
@@ -22,6 +22,8 @@
#include "VulkanCommands.h"
#include "VulkanConstants.h"
#include "VulkanContext.h"
#include "VulkanDriver.h"
#include <utils/Log.h>
#include <utils/Panic.h>
@@ -32,6 +34,8 @@ using namespace utils;
namespace filament::backend {
using Timestamp = VulkanGroupMarkers::Timestamp;
VulkanCmdFence::VulkanCmdFence(VkDevice device, bool signaled) : device(device) {
VkFenceCreateInfo fenceCreateInfo { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
if (signaled) {
@@ -64,10 +68,51 @@ static VkCommandPool createPool(VkDevice device, uint32_t queueFamilyIndex) {
}
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex) : mDevice(device),
mQueue(queue), mPool(createPool(mDevice, queueFamilyIndex)) {
VkSemaphoreCreateInfo sci { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
for (auto& semaphore : mSubmissionSignals) {
void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept {
mMarkers.push(marker);
#if FILAMENT_VULKAN_VERBOSE
mTimestamps.push(start.time_since_epoch().count() > 0.0
? start
: std::chrono::high_resolution_clock::now());
#endif
}
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop() noexcept {
auto const marker = mMarkers.top();
mMarkers.pop();
#if FILAMENT_VULKAN_VERBOSE
auto const topTimestamp = mTimestamps.top();
mTimestamps.pop();
return std::make_pair(marker, topTimestamp);
#else
return std::make_pair(marker, Timestamp{});
#endif
}
std::pair<std::string, Timestamp> VulkanGroupMarkers::top() const {
assert_invariant(!empty());
auto const marker = mMarkers.top();
#if FILAMENT_VULKAN_VERBOSE
auto const topTimestamp = mTimestamps.top();
return std::make_pair(marker, topTimestamp);
#else
return std::make_pair(marker, Timestamp{});
#endif
}
bool VulkanGroupMarkers::empty() const noexcept {
return mMarkers.empty();
}
VulkanCommands::VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
VulkanContext* context)
: mDevice(device),
mQueue(queue),
mPool(createPool(mDevice, queueFamilyIndex)),
mContext(context) {
VkSemaphoreCreateInfo sci{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
for (auto& semaphore: mSubmissionSignals) {
vkCreateSemaphore(mDevice, &sci, nullptr, &semaphore);
}
}
@@ -139,6 +184,13 @@ VulkanCommandBuffer const& VulkanCommands::get(bool blockOnGC) {
mObserver->onCommandBuffer(*mCurrent);
}
// We push the current markers onto a temporary stack. This must be placed after mCurrent is set
// to the new command buffer since pushGroupMarker also calls get().
while (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
auto [marker, time] = mCarriedOverMarkers->pop();
pushGroupMarker(marker.c_str(), time);
}
return *mCurrent;
}
@@ -195,6 +247,17 @@ bool VulkanCommands::flush() {
<< io::endl;
}
// Before actually submitting, we need to pop any leftover group markers.
while (mGroupMarkers && !mGroupMarkers->empty()) {
if (!mCarriedOverMarkers) {
mCarriedOverMarkers = std::make_unique<VulkanGroupMarkers>();
}
auto const [marker, time] = mGroupMarkers->top();
mCarriedOverMarkers->push(marker, time);
// We still need to call through to vkCmdEndDebugUtilsLabelEXT.
popGroupMarker();
}
auto& cmdfence = mCurrent->fence;
std::unique_lock<utils::Mutex> lock(cmdfence->mutex);
cmdfence->status.store(VK_NOT_READY);
@@ -270,7 +333,94 @@ void VulkanCommands::updateFences() {
}
}
} // namespace filament::backend
void VulkanCommands::pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp) {
#if FILAMENT_VULKAN_VERBOSE
// If the timestamp is not 0, then we are carrying over a marker across buffer submits.
// If it is 0, then this is a normal marker push and we should just print debug line as usual.
if (timestamp.time_since_epoch().count() == 0.0) {
utils::slog.d << "----> " << str << utils::io::endl;
}
#endif
// TODO: Add group marker color to the Driver API
const VkCommandBuffer cmdbuffer = get().cmdbuffer;
if (!mGroupMarkers) {
mGroupMarkers = std::make_unique<VulkanGroupMarkers>();
}
mGroupMarkers->push(str, timestamp);
if (mContext->isDebugUtilsSupported()) {
VkDebugUtilsLabelEXT labelInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
.pLabelName = str,
.color = {0, 1, 0, 1},
};
vkCmdBeginDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
} else if (mContext->isDebugMarkersSupported()) {
VkDebugMarkerMarkerInfoEXT markerInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
.pMarkerName = str,
.color = {0.0f, 1.0f, 0.0f, 1.0f},
};
vkCmdDebugMarkerBeginEXT(cmdbuffer, &markerInfo);
}
}
void VulkanCommands::popGroupMarker() {
assert_invariant(mGroupMarkers);
if (!mGroupMarkers->empty()) {
const VkCommandBuffer cmdbuffer = get().cmdbuffer;
#if FILAMENT_VULKAN_VERBOSE
auto const [marker, startTime] = mGroupMarkers->pop();
auto const endTime = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = endTime - startTime;
utils::slog.d << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms\n"
<< utils::io::flush;
#else
mGroupMarkers->pop();
#endif
if (mContext->isDebugUtilsSupported()) {
vkCmdEndDebugUtilsLabelEXT(cmdbuffer);
} else if (mContext->isDebugMarkersSupported()) {
vkCmdDebugMarkerEndEXT(cmdbuffer);
}
} else if (mCarriedOverMarkers && !mCarriedOverMarkers->empty()) {
// It could be that pop is called between flush() and get() (new command buffer), in which
// case the marker is in "carried over" state. We'd just remove that
mCarriedOverMarkers->pop();
}
}
void VulkanCommands::insertEventMarker(char const* string, uint32_t len) {
VkCommandBuffer const cmdbuffer = get().cmdbuffer;
if (mContext->isDebugUtilsSupported()) {
VkDebugUtilsLabelEXT labelInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
.pLabelName = string,
.color = {1, 1, 0, 1},
};
vkCmdInsertDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
} else if (mContext->isDebugMarkersSupported()) {
VkDebugMarkerMarkerInfoEXT markerInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT,
.pMarkerName = string,
.color = {0.0f, 1.0f, 0.0f, 1.0f},
};
vkCmdDebugMarkerInsertEXT(cmdbuffer, &markerInfo);
}
}
std::string VulkanCommands::getTopGroupMarker() const {
if (!mGroupMarkers || mGroupMarkers->empty()) {
return "";
}
return std::get<0>(mGroupMarkers->top());
}
}// namespace filament::backend
#if defined(_MSC_VER)
#pragma warning( pop )
+41 -4
View File
@@ -26,8 +26,31 @@
#include <atomic>
#include <chrono>
#include <stack>
#include <string>
#include <utility>
namespace filament::backend {
struct VulkanContext;
class VulkanGroupMarkers {
public:
using Timestamp = std::chrono::time_point<std::chrono::high_resolution_clock>;
void push(std::string const& marker, Timestamp start = {}) noexcept;
std::pair<std::string, Timestamp> pop() noexcept;
std::pair<std::string, Timestamp> top() const;
bool empty() const noexcept;
private:
std::stack<std::string> mMarkers;
#if FILAMENT_VULKAN_VERBOSE
std::stack<Timestamp> mTimestamps;
#endif
};
// Wrapper to enable use of shared_ptr for implementing shared ownership of low-level Vulkan fences.
struct VulkanCmdFence {
VulkanCmdFence(VkDevice device, bool signaled = false);
@@ -86,7 +109,8 @@ public:
//
class VulkanCommands {
public:
VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex);
VulkanCommands(VkDevice device, VkQueue queue, uint32_t queueFamilyIndex,
VulkanContext* context);
~VulkanCommands();
// Creates a "current" command buffer if none exists, otherwise returns the current one.
@@ -121,11 +145,21 @@ class VulkanCommands {
// The observer's event handler can only be called during get().
void setObserver(CommandBufferObserver* observer) { mObserver = observer; }
void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {});
void popGroupMarker();
void insertEventMarker(char const* string, uint32_t len);
std::string getTopGroupMarker() const;
private:
static constexpr int CAPACITY = VK_MAX_COMMAND_BUFFERS;
const VkDevice mDevice;
const VkQueue mQueue;
const VkCommandPool mPool;
VkDevice const mDevice;
VkQueue const mQueue;
VkCommandPool const mPool;
VulkanContext const* mContext;
VulkanCommandBuffer* mCurrent = nullptr;
VkSemaphore mSubmissionSignal = {};
VkSemaphore mInjectedSignal = {};
@@ -133,6 +167,9 @@ class VulkanCommands {
VkSemaphore mSubmissionSignals[CAPACITY] = {};
size_t mAvailableCount = CAPACITY;
CommandBufferObserver* mObserver = nullptr;
std::unique_ptr<VulkanGroupMarkers> mGroupMarkers;
std::unique_ptr<VulkanGroupMarkers> mCarriedOverMarkers;
};
} // namespace filament::backend
@@ -15,6 +15,8 @@
*/
#include "VulkanContext.h"
#include "VulkanCommands.h"
#include "VulkanHandles.h"
#include "VulkanMemory.h"
#include "VulkanTexture.h"
@@ -118,8 +120,8 @@ void VulkanTimestamps::beginQuery(VulkanCommandBuffer const* commands,
vkCmdResetQueryPool(commands->cmdbuffer, mPool, index, 2);
vkCmdWriteTimestamp(commands->cmdbuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, mPool, index);
// We stash this because getResult might come before beginquery.
query->cmdbuffer.store(commands);
// We stash this because getResult might come before the query is actually processed.
query->fence = commands->fence;
}
void VulkanTimestamps::endQuery(VulkanCommandBuffer const* commands,
+1 -20
View File
@@ -17,7 +17,6 @@
#ifndef TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
#define TNT_FILAMENT_BACKEND_VULKANCONTEXT_H
#include "VulkanCommands.h"
#include "VulkanConstants.h"
#include "VulkanImageUtility.h"
#include "VulkanPipelineCache.h"
@@ -38,6 +37,7 @@ struct VulkanSwapChain;
struct VulkanTexture;
class VulkanStagePool;
struct VulkanTimerQuery;
struct VulkanCommandBuffer;
struct VulkanAttachment {
VulkanTexture* texture;
@@ -123,22 +123,6 @@ public:
inline bool isDebugUtilsSupported() const noexcept {
return mDebugUtilsSupported;
}
inline bool isPortabilitySubsetSupported() const noexcept {
return mPortabilitySubsetSupported;
}
inline bool isPortabilityEnumerationSupported() const noexcept {
return mPortabilityEnumerationSupported;
}
inline bool isMaintenance1Supported() const noexcept {
return mMaintenanceSupported[0];
}
inline bool isMaintenance2Supported() const noexcept {
return mMaintenanceSupported[1];
}
inline bool isMaintenance3Supported() const noexcept {
return mMaintenanceSupported[2];
}
private:
VkPhysicalDeviceMemoryProperties mMemoryProperties = {};
@@ -146,9 +130,6 @@ private:
VkPhysicalDeviceFeatures mPhysicalDeviceFeatures = {};
bool mDebugMarkersSupported = false;
bool mDebugUtilsSupported = false;
bool mPortabilitySubsetSupported = false;
bool mPortabilityEnumerationSupported = false;
bool mMaintenanceSupported[3] = {};
VkFormat mDepthFormat;
+46 -79
View File
@@ -30,16 +30,12 @@
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Panic.h>
#include <utils/Systrace.h>
#ifndef NDEBUG
#include <set>
#endif
#if FILAMENT_VULKAN_VERBOSE
#include <stack>
static std::stack<std::string> renderPassMarkers;
#endif
using namespace bluevk;
using utils::FixedCapacityVector;
@@ -186,7 +182,7 @@ VulkanDriver::VulkanDriver(VulkanPlatform* platform, VulkanContext const& contex
#endif
mTimestamps = std::make_unique<VulkanTimestamps>(mPlatform->getDevice());
mCommands = std::make_unique<VulkanCommands>(mPlatform->getDevice(),
mPlatform->getGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex());
mPlatform->getGraphicsQueue(), mPlatform->getGraphicsQueueFamilyIndex(), &mContext);
mCommands->setObserver(&mPipelineCache);
mPipelineCache.setDevice(mPlatform->getDevice(), mAllocator);
@@ -511,12 +507,24 @@ void VulkanDriver::createSyncR(Handle<HwSync> sh, int) {
}
void VulkanDriver::createSwapChainR(Handle<HwSwapChain> sch, void* nativeWindow, uint64_t flags) {
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 &&
!isSRGBSwapChainSupported()) {
utils::slog.w << "sRGB swapchain requested, but Platform does not support it"
<< utils::io::endl;
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator, mCommands.get(), mStagePool,
nativeWindow, flags);
}
void VulkanDriver::createSwapChainHeadlessR(Handle<HwSwapChain> sch, uint32_t width,
uint32_t height, uint64_t flags) {
if ((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0 &&
!isSRGBSwapChainSupported()) {
utils::slog.w << "sRGB swapchain requested, but Platform does not support it"
<< utils::io::endl;
flags = flags | ~(backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE);
}
assert_invariant(width > 0 && height > 0 && "Vulkan requires non-zero swap chain dimensions.");
construct<VulkanSwapChain>(sch, mPlatform, mContext, mAllocator, mCommands.get(), mStagePool,
nullptr, flags, VkExtent2D{width, height});
@@ -747,8 +755,7 @@ bool VulkanDriver::isAutoDepthResolveSupported() {
}
bool VulkanDriver::isSRGBSwapChainSupported() {
// TODO: implement SWAP_CHAIN_CONFIG_SRGB_COLORSPACE
return false;
return mPlatform->isSRGBSwapChainSupported();
}
bool VulkanDriver::isWorkaroundNeeded(Workaround workaround) {
@@ -969,7 +976,9 @@ void VulkanDriver::updateSamplerGroup(Handle<HwSamplerGroup> sbh,
void VulkanDriver::compilePrograms(CallbackHandler* handler,
CallbackHandler::Callback callback, void* user) {
scheduleCallback(handler, user, callback);
if (callback) {
scheduleCallback(handler, user, callback);
}
}
void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassParams& params) {
@@ -1133,15 +1142,18 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
VkFramebuffer vkfb = mFramebufferCache.getFramebuffer(fbkey);
// Assign a label to the framebuffer for debugging purposes.
if (UTILS_UNLIKELY(mContext.isDebugUtilsSupported()) && !mCurrentDebugMarker.empty()) {
const VkDebugUtilsObjectNameInfoEXT info = {
VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
nullptr,
VK_OBJECT_TYPE_FRAMEBUFFER,
reinterpret_cast<uint64_t>(vkfb),
mCurrentDebugMarker.c_str(),
};
vkSetDebugUtilsObjectNameEXT(mPlatform->getDevice(), &info);
if (UTILS_UNLIKELY(mContext.isDebugUtilsSupported())) {
auto const topMarker = mCommands->getTopGroupMarker();
if (!topMarker.empty()) {
const VkDebugUtilsObjectNameInfoEXT info = {
VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
nullptr,
VK_OBJECT_TYPE_FRAMEBUFFER,
reinterpret_cast<uint64_t>(vkfb),
topMarker.c_str(),
};
vkSetDebugUtilsObjectNameEXT(mPlatform->getDevice(), &info);
}
}
// The current command buffer now owns a reference to the render target and its attachments.
@@ -1168,7 +1180,7 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
VkClearValue clearValues[
MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT + MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT +
1] = {};
if (params.flags.clear != TargetBufferFlags::NONE) {
if (clearVal != TargetBufferFlags::NONE) {
// NOTE: clearValues must be populated in the same order as the attachments array in
// VulkanFboCache::getFramebuffer. Values must be provided regardless of whether Vulkan is
@@ -1362,75 +1374,29 @@ void VulkanDriver::bindSamplers(uint32_t index, Handle<HwSamplerGroup> sbh) {
}
void VulkanDriver::insertEventMarker(char const* string, uint32_t len) {
constexpr float MARKER_COLOR[] = { 0.0f, 1.0f, 0.0f, 1.0f };
VkCommandBuffer const cmdbuffer = mCommands->get().cmdbuffer;
if (mContext.isDebugUtilsSupported()) {
VkDebugUtilsLabelEXT labelInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
.pLabelName = string,
.color = {1, 1, 0, 1},
};
vkCmdInsertDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
} else if (mContext.isDebugMarkersSupported()) {
VkDebugMarkerMarkerInfoEXT markerInfo = {};
markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
memcpy(markerInfo.color, &MARKER_COLOR[0], sizeof(MARKER_COLOR));
markerInfo.pMarkerName = string;
vkCmdDebugMarkerInsertEXT(cmdbuffer, &markerInfo);
}
mCommands->insertEventMarker(string, len);
}
void VulkanDriver::pushGroupMarker(char const* string, uint32_t len) {
#if FILAMENT_VULKAN_VERBOSE
renderPassMarkers.push(std::string(string));
utils::slog.d << "----> " << string << utils::io::endl;
#endif
// TODO: Add group marker color to the Driver API
constexpr float MARKER_COLOR[] = { 0.0f, 1.0f, 0.0f, 1.0f };
const VkCommandBuffer cmdbuffer = mCommands->get().cmdbuffer;
if (mContext.isDebugUtilsSupported()) {
VkDebugUtilsLabelEXT labelInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT,
.pLabelName = string,
.color = {0, 1, 0, 1},
};
vkCmdBeginDebugUtilsLabelEXT(cmdbuffer, &labelInfo);
mCurrentDebugMarker = string;
} else if (mContext.isDebugMarkersSupported()) {
VkDebugMarkerMarkerInfoEXT markerInfo = {};
markerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
memcpy(markerInfo.color, &MARKER_COLOR[0], sizeof(MARKER_COLOR));
markerInfo.pMarkerName = string;
vkCmdDebugMarkerBeginEXT(cmdbuffer, &markerInfo);
void VulkanDriver::pushGroupMarker(char const* string, uint32_t) {
// Turns out all the markers are 0-terminated, so we can just pass it without len.
mCommands->pushGroupMarker(string);
{
SYSTRACE_CONTEXT();
SYSTRACE_NAME_BEGIN(string);
}
}
void VulkanDriver::popGroupMarker(int) {
#if FILAMENT_VULKAN_VERBOSE
std::string const& marker = renderPassMarkers.top();
renderPassMarkers.pop();
utils::slog.d << "<---- " << marker << utils::io::endl;
#endif
const VkCommandBuffer cmdbuffer = mCommands->get().cmdbuffer;
if (mContext.isDebugUtilsSupported()) {
vkCmdEndDebugUtilsLabelEXT(cmdbuffer);
mCurrentDebugMarker.clear();
} else if (mContext.isDebugMarkersSupported()) {
vkCmdDebugMarkerEndEXT(cmdbuffer);
mCommands->popGroupMarker();
{
SYSTRACE_CONTEXT();
SYSTRACE_NAME_END();
}
}
void VulkanDriver::startCapture(int) {
void VulkanDriver::startCapture(int) {}
}
void VulkanDriver::stopCapture(int) {
}
void VulkanDriver::stopCapture(int) {}
void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y,
uint32_t width, uint32_t height, PixelBufferDescriptor&& pbd) {
@@ -1439,7 +1405,8 @@ void VulkanDriver::readPixels(Handle<HwRenderTarget> src, uint32_t x, uint32_t y
VulkanTexture* srcTexture = (VulkanTexture*) srcTarget->getColor(0).texture;
assert_invariant(srcTexture);
const VkFormat srcFormat = srcTexture->getVkFormat();
const bool swizzle = srcFormat == VK_FORMAT_B8G8R8A8_UNORM;
const bool swizzle = srcFormat == VK_FORMAT_B8G8R8A8_UNORM ||
srcFormat == VK_FORMAT_B8G8R8A8_SRGB;
// Create a host visible, linearly tiled image as a staging area.
VkImageCreateInfo imageInfo {
@@ -148,7 +148,6 @@ private:
VmaAllocator mAllocator = VK_NULL_HANDLE;
VkDebugReportCallbackEXT mDebugCallback = VK_NULL_HANDLE;
VkDebugUtilsMessengerEXT mDebugMessenger = VK_NULL_HANDLE;
std::string mCurrentDebugMarker;
VulkanContext mContext = {};
HandleAllocatorVK mHandleAllocator;
+11 -7
View File
@@ -108,9 +108,8 @@ VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept :
// Make a copy of the binding map
samplerGroupInfo = builder.getSamplerGroupInfo();
if constexpr (FILAMENT_VULKAN_VERBOSE) {
utils::slog.d << "Created VulkanProgram " << builder
<< ", shaders = (" << bundle.vertex << ", " << bundle.fragment << ")"
<< utils::io::endl;
utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << bundle.vertex
<< ", " << bundle.fragment << ")" << utils::io::endl;
}
}
@@ -305,11 +304,16 @@ bool VulkanTimerQuery::isCompleted() const noexcept {
// into the command buffer, which is an error according to the validation layer that ships in
// the Android NDK. Even when AVAILABILITY_BIT is set, validation seems to require that the
// timestamp has at least been written into a processed command buffer.
VulkanCommandBuffer const* cmdbuf = cmdbuffer.load();
if (!cmdbuf || !cmdbuf->fence) { return false; }
VkResult status = cmdbuf->fence->status.load(std::memory_order_relaxed);
if (status != VK_SUCCESS) { return false; }
// This fence indicates that the corresponding buffer has been completed.
if (!fence) {
return false;
}
VkResult status = fence->status.load(std::memory_order_relaxed);
if (status != VK_SUCCESS) {
return false;
}
return true;
}
+1 -3
View File
@@ -147,9 +147,7 @@ private:
uint32_t startingQueryIndex;
uint32_t stoppingQueryIndex;
// TODO: should be safe to hold this reference (since VulkanCommanBuffers is a ring).
// But explore better options.
std::atomic<VulkanCommandBuffer const*> cmdbuffer = nullptr;
std::shared_ptr<VulkanCmdFence> fence;
friend class VulkanTimestamps;
};
@@ -101,11 +101,14 @@ getVkTransition(const VulkanLayoutTransition& transition) {
srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
srcStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
break;
case VulkanLayout::PRESENT:
case VulkanLayout::COLOR_ATTACHMENT_RESOLVE:
srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
break;
case VulkanLayout::PRESENT:
srcAccessMask = VK_ACCESS_NONE;
srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
break;
}
switch (transition.newLayout) {
@@ -65,7 +65,11 @@ public:
inline static VulkanLayout getDefaultLayout(TextureUsage usage) {
if (any(usage & TextureUsage::DEPTH_ATTACHMENT)) {
return VulkanLayout::DEPTH_ATTACHMENT;
if (any(usage & TextureUsage::SAMPLEABLE)) {
return VulkanLayout::DEPTH_SAMPLER;
} else {
return VulkanLayout::DEPTH_ATTACHMENT;
}
}
if (any(usage & TextureUsage::COLOR_ATTACHMENT)) {
@@ -75,6 +79,20 @@ public:
return VulkanLayout::READ_ONLY;
}
inline static VulkanLayout getDefaultLayout(VkImageUsageFlags vkusage) {
TextureUsage usage {};
if (vkusage & VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) {
usage = usage | TextureUsage::DEPTH_ATTACHMENT;
}
if (vkusage & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) {
usage = usage | TextureUsage::COLOR_ATTACHMENT;
}
if (vkusage & VK_IMAGE_USAGE_SAMPLED_BIT) {
usage = usage | TextureUsage::SAMPLEABLE;
}
return getDefaultLayout(usage);
}
static VkImageLayout getVkLayout(VulkanLayout layout);
static void transitionLayout(VkCommandBuffer cmdbuffer, VulkanLayoutTransition transition);
@@ -154,10 +154,12 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
<< "handle = " << utils::io::hex << mTextureImage << utils::io::dec << ", "
<< "extent = " << w << "x" << h << "x"<< depth << ", "
<< "mipLevels = " << int(levels) << ", "
<< "TextureUsage = " << static_cast<int>(usage) << ", "
<< "usage = " << imageInfo.usage << ", "
<< "samples = " << imageInfo.samples << ", "
<< "type = " << imageInfo.imageType << ", "
<< "flags = " << imageInfo.flags << ", "
<< "target = " << static_cast<int>(target) <<", "
<< "format = " << mVkFormat << utils::io::endl;
}
ASSERT_POSTCONDITION(!error, "Unable to create image.");
@@ -199,11 +201,13 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
// Transition the layout of each image slice that might be used as a render target.
// We do not transition images that are merely SAMPLEABLE, this is deferred until upload time
// because we do not know how many layers and levels will actually be used.
if (any(usage & (TextureUsage::COLOR_ATTACHMENT | TextureUsage::DEPTH_ATTACHMENT))) {
if (imageInfo.usage
& (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
| VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
const uint32_t layers = mPrimaryViewRange.layerCount;
VkImageSubresourceRange range = { getImageAspect(), 0, levels, 0, layers };
VkCommandBuffer cmdbuf = mCommands->get().cmdbuffer;
transitionLayout(cmdbuf, range, ImgUtil::getDefaultLayout(usage));
transitionLayout(cmdbuf, range, ImgUtil::getDefaultLayout(imageInfo.usage));
}
}
@@ -659,18 +659,8 @@ Driver* VulkanPlatform::createDriver(void* sharedContext,
// Store the extension support in the context
context.mDebugUtilsSupported
= instExts.find(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) != instExts.end();
context.mPortabilityEnumerationSupported
= instExts.find(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) != instExts.end();
context.mDebugMarkersSupported
= deviceExts.find(VK_EXT_DEBUG_MARKER_EXTENSION_NAME) != deviceExts.end();
context.mPortabilitySubsetSupported
= deviceExts.find(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) != deviceExts.end();
context.mMaintenanceSupported[0]
= deviceExts.find(VK_KHR_MAINTENANCE1_EXTENSION_NAME) != deviceExts.end();
context.mMaintenanceSupported[1]
= deviceExts.find(VK_KHR_MAINTENANCE2_EXTENSION_NAME) != deviceExts.end();
context.mMaintenanceSupported[2]
= deviceExts.find(VK_KHR_MAINTENANCE3_EXTENSION_NAME) != deviceExts.end();
// Choose a depth format that meets our requirements. Take care not to include stencil formats
// just yet, since that would require a corollary change to the "aspect" flags for the VkImage.
@@ -728,7 +718,7 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
bool const headless = extent.width != 0 && extent.height != 0;
if (headless) {
VulkanPlatformHeadlessSwapChain* swapchain = new VulkanPlatformHeadlessSwapChain(
mImpl->mContext, mImpl->mDevice, mImpl->mGraphicsQueue, extent);
mImpl->mContext, mImpl->mDevice, mImpl->mGraphicsQueue, extent, flags);
mImpl->mHeadlessSwapChains.insert(swapchain);
return swapchain;
}
@@ -737,7 +727,7 @@ SwapChainPtr VulkanPlatform::createSwapChain(void* nativeWindow, uint64_t flags,
// The VulkanPlatformSurfaceSwapChain now `owns` the surface.
VulkanPlatformSurfaceSwapChain* swapchain = new VulkanPlatformSurfaceSwapChain(mImpl->mContext,
mImpl->mPhysicalDevice, mImpl->mDevice, mImpl->mGraphicsQueue, mImpl->mInstance,
surface, fallbackExtent);
surface, fallbackExtent, flags);
mImpl->mSurfaceSwapChains.insert(swapchain);
return swapchain;
}
@@ -19,6 +19,8 @@
#include "vulkan/VulkanConstants.h"
#include "vulkan/VulkanUtility.h"
#include <backend/DriverEnums.h>
using namespace bluevk;
using namespace utils;
@@ -102,12 +104,13 @@ VkImage VulkanPlatformSwapChainImpl::createImage(VkExtent2D extent, VkFormat for
VulkanPlatformSurfaceSwapChain::VulkanPlatformSurfaceSwapChain(VulkanContext const& context,
VkPhysicalDevice physicalDevice, VkDevice device, VkQueue queue, VkInstance instance,
VkSurfaceKHR surface, VkExtent2D fallbackExtent)
VkSurfaceKHR surface, VkExtent2D fallbackExtent, uint64_t flags)
: VulkanPlatformSwapChainImpl(context, device, queue),
mInstance(instance),
mPhysicalDevice(physicalDevice),
mSurface(surface),
mFallbackExtent(fallbackExtent) {
mFallbackExtent(fallbackExtent),
mUsesRGB((flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0) {
assert_invariant(surface);
create();
}
@@ -141,18 +144,22 @@ VkResult VulkanPlatformSurfaceSwapChain::create() {
}
// Find a suitable surface format.
if (surfaceFormat.format == VK_FORMAT_UNDEFINED) {
FixedCapacityVector<VkSurfaceFormatKHR> const surfaceFormats
= enumerate(vkGetPhysicalDeviceSurfaceFormatsKHR, mPhysicalDevice, mSurface);
surfaceFormat = surfaceFormats[0];
for (const VkSurfaceFormatKHR& format: surfaceFormats) {
if (format.format == VK_FORMAT_R8G8B8A8_UNORM
|| format.format == VK_FORMAT_B8G8R8A8_UNORM) {
surfaceFormat = format;
break;
}
FixedCapacityVector<VkSurfaceFormatKHR> const surfaceFormats
= enumerate(vkGetPhysicalDeviceSurfaceFormatsKHR, mPhysicalDevice, mSurface);
FixedCapacityVector<VkFormat> expectedFormats
= {VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM};
if (mUsesRGB) {
expectedFormats = {VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_B8G8R8A8_SRGB};
}
for (VkSurfaceFormatKHR const& format: surfaceFormats) {
if (std::any_of(expectedFormats.begin(), expectedFormats.end(),
[&format](VkFormat f) { return f == format.format; })) {
surfaceFormat = format;
break;
}
}
ASSERT_POSTCONDITION(surfaceFormat.format != VK_FORMAT_UNDEFINED,
"Cannot find suitable swapchain format");
// Verify that our chosen present mode is supported. In practice all devices support the FIFO
// mode, but we check for it anyway for completeness. (and to avoid validation warnings)
@@ -281,11 +288,13 @@ VkResult VulkanPlatformSurfaceSwapChain::recreate() {
}
VulkanPlatformHeadlessSwapChain::VulkanPlatformHeadlessSwapChain(VulkanContext const& context,
VkDevice device, VkQueue queue, VkExtent2D extent)
VkDevice device, VkQueue queue, VkExtent2D extent, uint64_t flags)
: VulkanPlatformSwapChainImpl(context, device, queue),
mCurrentIndex(0) {
mSwapChainBundle.extent = extent;
mSwapChainBundle.colorFormat = VK_FORMAT_R8G8B8A8_UNORM;
mSwapChainBundle.colorFormat = (flags & backend::SWAP_CHAIN_CONFIG_SRGB_COLORSPACE) != 0
? VK_FORMAT_R8G8B8A8_SRGB
: VK_FORMAT_R8G8B8A8_UNORM;
auto& images = mSwapChainBundle.colors;
images.reserve(HEADLESS_SWAPCHAIN_SIZE);
@@ -81,7 +81,7 @@ protected:
struct VulkanPlatformSurfaceSwapChain : public VulkanPlatformSwapChainImpl {
VulkanPlatformSurfaceSwapChain(VulkanContext const& context, VkPhysicalDevice physicalDevice,
VkDevice device, VkQueue queue, VkInstance instance, VkSurfaceKHR surface,
VkExtent2D fallbackExtent);
VkExtent2D fallbackExtent, uint64_t flags);
~VulkanPlatformSurfaceSwapChain();
@@ -107,6 +107,7 @@ private:
VkSwapchainKHR mSwapchain = VK_NULL_HANDLE;
VkExtent2D const mFallbackExtent;
bool mUsesRGB = false;
bool mSuboptimal;
};
@@ -114,7 +115,7 @@ struct VulkanPlatformHeadlessSwapChain : public VulkanPlatformSwapChainImpl {
static constexpr size_t const HEADLESS_SWAPCHAIN_SIZE = 2;
VulkanPlatformHeadlessSwapChain(VulkanContext const& context, VkDevice device, VkQueue queue,
VkExtent2D extent);
VkExtent2D extent, uint64_t flags);
~VulkanPlatformHeadlessSwapChain();
+15
View File
@@ -497,6 +497,21 @@ public:
*/
FeatureLevel getActiveFeatureLevel() const noexcept;
/**
* Queries the maximum number of GPU instances that Filament creates when automatic instancing
* is enabled. This value is also the limit for the number of transforms that can be stored in
* an InstanceBuffer. This value may depend on the device and platform, but will remain constant
* during the lifetime of this Engine.
*
* This value does not apply when using the instances(size_t) method on
* RenderableManager::Builder.
*
* @return the number of max automatic instances
* @see setAutomaticInstancingEnabled
* @see RenderableManager::Builder::instances(size_t)
* @see RenderableManager::Builder::instances(size_t, InstanceBuffer*)
*/
size_t getMaxAutomaticInstances() const noexcept;
/**
* @return EntityManager used by filament
+2 -1
View File
@@ -42,7 +42,8 @@ public:
/**
* @param instanceCount the number of instances this InstanceBuffer will support, must be
* >= 1 and <= \c CONFIG_MAX_INSTANCES
* >= 1 and <= \c Engine::getMaxAutomaticInstances()
* @see Engine::getMaxAutomaticInstances
*/
Builder(size_t instanceCount) noexcept;
+27 -8
View File
@@ -152,11 +152,13 @@ public:
friend class FMaterial;
};
using CompilerPriorityQueue = backend:: CompilerPriorityQueue;
/**
* Asynchronously ensures that a subset of this Material's variants are compiled. After issuing
* several Material::compile() calls in a row, it is recommended to call Engine::flush()
* such that the backend can start the compilation work as soon as possible.
* The provided callback is guaranteed to be called on the main thread after all unfiltered
* The provided callback is guaranteed to be called on the main thread after all specified
* variants of the material are compiled. This can take hundreds of milliseconds.
*
* If all the material's variants are already compiled, the callback will be scheduled as
@@ -164,14 +166,31 @@ public:
* many previous frames are enqueued in the backend. This also varies by backend. Therefore,
* it is recommended to only call this method once per material shortly after creation.
*
* @param handler Handler to dispatch the callback or nullptr for the default handler
* @param callback callback called on the main thread when the compilation is done on
* by backend.
* @param variantFilter Variants to filter-out from the compile command.
* @param priority Which priority queue to use, LOW or HIGH.
* @param variants Variants to include to the compile command.
* @param handler Handler to dispatch the callback or nullptr for the default handler
* @param callback callback called on the main thread when the compilation is done on
* by backend.
*/
void compile(backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback,
UserVariantFilterMask variantFilter) noexcept;
void compile(CompilerPriorityQueue priority,
UserVariantFilterMask variants,
backend::CallbackHandler* handler = nullptr,
utils::Invocable<void(Material*)>&& callback = {}) noexcept;
inline void compile(CompilerPriorityQueue priority,
UserVariantFilterBit variants,
backend::CallbackHandler* handler = nullptr,
utils::Invocable<void(Material*)>&& callback = {}) noexcept {
compile(priority, UserVariantFilterMask(variants), handler,
std::forward<utils::Invocable<void(Material*)>>(callback));
}
inline void compile(CompilerPriorityQueue priority,
backend::CallbackHandler* handler = nullptr,
utils::Invocable<void(Material*)>&& callback = {}) noexcept {
compile(priority, UserVariantFilterBit::ALL, handler,
std::forward<utils::Invocable<void(Material*)>>(callback));
}
/**
* Creates a new instance of this material. Material instances should be freed using
@@ -435,9 +435,9 @@ public:
/**
* Specifies the number of draw instances of this renderable and an \c InstanceBuffer
* containing their local transforms. The default is 1 instance and the maximum number of
* instances allowed when supplying transforms is \c CONFIG_MAX_INSTANCES (64 on most
* platforms). 0 is invalid. The \c InstanceBuffer must not be destroyed before this
* renderable.
* instances allowed when supplying transforms is given by
* \c Engine::getMaxAutomaticInstances (64 on most platforms). 0 is invalid. The
* \c InstanceBuffer must not be destroyed before this renderable.
*
* All instances are culled using the same bounding box, so care must be taken to make
* sure all instances render inside the specified bounding box.
@@ -448,12 +448,12 @@ public:
* Only the \c VERTEX_DOMAIN_OBJECT vertex domain is supported.
*
* The local transforms of each instance can be updated with
* InstanceBuffer::setLocalTransforms.
* \c InstanceBuffer::setLocalTransforms.
*
* \see InstanceBuffer
* \see instances(size_t, * math::mat4f const*)
* @param instanceCount the number of instances, silently clamped between 1 and
* CONFIG_MAX_INSTANCES.
* the result of Engine::getMaxAutomaticInstances().
* @param instanceBuffer an InstanceBuffer containing at least instanceCount transforms
*/
Builder& instances(size_t instanceCount, InstanceBuffer* instanceBuffer) noexcept;
+4
View File
@@ -268,6 +268,10 @@ FeatureLevel Engine::getActiveFeatureLevel() const noexcept {
return downcast(this)->getActiveFeatureLevel();
}
size_t Engine::getMaxAutomaticInstances() const noexcept {
return downcast(this)->getMaxAutomaticInstances();
}
#if defined(__EMSCRIPTEN__)
void Engine::resetBackendState() noexcept {
downcast(this)->resetBackendState();
+4 -4
View File
@@ -136,10 +136,10 @@ MaterialInstance const* Material::getDefaultInstance() const noexcept {
return downcast(this)->getDefaultInstance();
}
void Material::compile(backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback,
UserVariantFilterMask variantFilter) noexcept {
downcast(this)->compile(handler, std::move(callback), variantFilter);
void Material::compile(CompilerPriorityQueue priority, UserVariantFilterMask variantFilter,
backend::CallbackHandler* handler, utils::Invocable<void(Material*)>&& callback) noexcept {
downcast(this)->compile(priority, variantFilter, handler, std::move(callback));
}
} // namespace filament
+8 -1
View File
@@ -117,7 +117,14 @@ public:
bool getShader(filaflat::ShaderContent& shader, backend::ShaderModel shaderModel,
Variant variant, backend::ShaderStage stage) noexcept;
filaflat::MaterialChunk const& getMaterialChunk() const noexcept { return mImpl.mMaterialChunk; }
bool hasShader(backend::ShaderModel model,
Variant variant, backend::ShaderStage stage) const noexcept {
return getMaterialChunk().hasShader(model, variant, stage);
}
filaflat::MaterialChunk const& getMaterialChunk() const noexcept {
return mImpl.mMaterialChunk;
}
private:
struct MaterialParserDetails {
+18 -5
View File
@@ -1085,18 +1085,29 @@ FrameGraphId<FrameGraphTexture> PostProcessManager::gaussianBlurPass(FrameGraph&
default: materialName = is2dArray ?
"separableGaussianBlur4L"sv : "separableGaussianBlur4"sv; break;
}
std::string_view sourceParameterName = is2dArray ? "sourceArray"sv : "source"sv;
auto const& separableGaussianBlur = getPostProcessMaterial(materialName);
FMaterialInstance* const mi = separableGaussianBlur.getMaterialInstance(mEngine);
const size_t kernelStorageSize = mi->getMaterial()->reflect("kernel")->size;
// Initialize the samplers with dummy textures because vulkan requires a sampler to
// be bound to a texture even if sampler might be unused.
mi->setParameter("sourceArray"sv, getZeroTextureArray(), {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
mi->setParameter("source"sv, getZeroTexture(), {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
});
float2 kernel[64];
size_t const m = computeGaussianCoefficients(kernel,
std::min(sizeof(kernel) / sizeof(*kernel), kernelStorageSize));
std::string_view sourceParameterName = is2dArray ? "sourceArray"sv : "source"sv;
// horizontal pass
mi->setParameter(sourceParameterName, hwIn, {
.filterMag = SamplerMagFilter::LINEAR,
.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST
@@ -2202,8 +2213,10 @@ void PostProcessManager::colorGradingPrepareSubpass(DriverApi& driver,
mi->commit(driver);
// load both variants
material.getMaterial(mEngine)->prepareProgram(Variant{Variant::type_t(PostProcessVariant::TRANSLUCENT)});
material.getMaterial(mEngine)->prepareProgram(Variant{Variant::type_t(PostProcessVariant::OPAQUE)});
material.getMaterial(mEngine)->prepareProgram(
Variant{ Variant::type_t(PostProcessVariant::TRANSLUCENT) });
material.getMaterial(mEngine)->prepareProgram(
Variant{ Variant::type_t(PostProcessVariant::OPAQUE) });
}
void PostProcessManager::colorGradingSubpass(DriverApi& driver,
@@ -248,17 +248,17 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng
ASSERT_PRECONDITION(mImpl->mSkinningBoneCount <= CONFIG_MAX_BONE_COUNT,
"bone count > %u", CONFIG_MAX_BONE_COUNT);
ASSERT_PRECONDITION(mImpl->mInstanceCount <= CONFIG_MAX_INSTANCES || !mImpl->mInstanceBuffer,
"instance count is %zu, but instance count is limited to CONFIG_MAX_INSTANCES (%zu) "
"instances when supplying transforms via an InstanceBuffer.",
mImpl->mInstanceCount,
CONFIG_MAX_INSTANCES);
ASSERT_PRECONDITION(
mImpl->mInstanceCount <= engine.getMaxAutomaticInstances() || !mImpl->mInstanceBuffer,
"instance count is %zu, but instance count is limited to "
"Engine::getMaxAutomaticInstances() (%zu) instances when supplying transforms via an "
"InstanceBuffer.",
mImpl->mInstanceCount, engine.getMaxAutomaticInstances());
if (mImpl->mInstanceBuffer) {
size_t bufferInstanceCount = mImpl->mInstanceBuffer->mInstanceCount;
ASSERT_PRECONDITION(mImpl->mInstanceCount <= bufferInstanceCount,
"instance count (%zu) must be less than or equal to the InstanceBuffer's instance "
"count "
"(%zu).",
"count (%zu).",
mImpl->mInstanceCount, bufferInstanceCount);
}
+4
View File
@@ -178,6 +178,10 @@ public:
return mActiveFeatureLevel;
}
size_t getMaxAutomaticInstances() const noexcept {
return CONFIG_MAX_INSTANCES;
}
PostProcessManager const& getPostProcessManager() const noexcept {
return mPostProcessManager;
}
+4 -4
View File
@@ -52,11 +52,11 @@ InstanceBuffer::Builder& InstanceBuffer::Builder::localTransforms(
InstanceBuffer* InstanceBuffer::Builder::build(Engine& engine) {
ASSERT_PRECONDITION(mImpl->mInstanceCount >= 1, "instanceCount must be >= 1.");
ASSERT_PRECONDITION(mImpl->mInstanceCount <= CONFIG_MAX_INSTANCES,
"instanceCount is %zu, but instance count is limited to CONFIG_MAX_INSTANCES (%zu) "
"instances when supplying transforms.",
ASSERT_PRECONDITION(mImpl->mInstanceCount <= engine.getMaxAutomaticInstances(),
"instanceCount is %zu, but instance count is limited to "
"Engine::getMaxAutomaticInstances() (%zu) instances when supplying transforms.",
mImpl->mInstanceCount,
CONFIG_MAX_INSTANCES);
engine.getMaxAutomaticInstances());
return downcast(engine).createInstanceBuffer(*this);
}
+38 -26
View File
@@ -403,7 +403,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
filaflat::MaterialChunk const& materialChunk{ mMaterialParser->getMaterialChunk() };
auto variants = FixedCapacityVector<Variant>::with_capacity(materialChunk.getShaderCount());
materialChunk.visitShaders([&variants](
ShaderModel model, Variant variant, ShaderStage stage) {
ShaderModel, Variant variant, ShaderStage) {
if (Variant::isValidDepthVariant(variant)) {
variants.push_back(variant);
}
@@ -418,7 +418,7 @@ FMaterial::FMaterial(FEngine& engine, const Material::Builder& builder)
if (UTILS_UNLIKELY(!mIsDefaultMaterial && !mHasCustomDepthShader)) {
FMaterial const* const pDefaultMaterial = engine.getDefaultMaterial();
auto& cachedPrograms = mCachedPrograms;
for (Variant variant : pDefaultMaterial->mDepthVariants) {
for (Variant const variant : pDefaultMaterial->mDepthVariants) {
pDefaultMaterial->prepareProgram(variant);
cachedPrograms[variant.key] = pDefaultMaterial->getProgram(variant);
}
@@ -467,31 +467,39 @@ void FMaterial::terminate(FEngine& engine) {
mDefaultInstance.terminate(engine);
}
void FMaterial::compile(backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback,
UserVariantFilterMask variantFilter) noexcept {
void FMaterial::compile(CompilerPriorityQueue priority,
UserVariantFilterMask variantSpec,
backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback) noexcept {
UserVariantFilterMask const variantFilter =
~variantSpec & UserVariantFilterMask(UserVariantFilterBit::ALL);
auto const& variants = isVariantLit() ?
VariantUtils::getLitVariants() : VariantUtils::getUnlitVariants();
for (auto const variant : variants) {
if (!variantFilter || variant == Variant::filterUserVariant(variant, variantFilter)) {
if (hasVariant(variant)) {
prepareProgram(variant);
prepareProgram(variant, priority);
}
}
}
struct Callback {
Invocable<void(Material*)> f;
Material* m;
static void func(void* user) {
auto* const c = reinterpret_cast<Callback*>(user);
c->f(c->m);
delete c;
}
};
auto* const user = new Callback{ std::move(callback), this };
mEngine.getDriverApi().compilePrograms(handler, &Callback::func, static_cast<void*>(user));
if (callback) {
struct Callback {
Invocable<void(Material*)> f;
Material* m;
static void func(void* user) {
auto* const c = reinterpret_cast<Callback*>(user);
c->f(c->m);
delete c;
}
};
auto* const user = new(std::nothrow) Callback{ std::move(callback), this };
mEngine.getDriverApi().compilePrograms(handler, &Callback::func, static_cast<void*>(user));
} else {
mEngine.getDriverApi().compilePrograms(nullptr, nullptr, nullptr);
}
}
FMaterialInstance* FMaterial::createInstance(const char* name) const noexcept {
@@ -527,25 +535,25 @@ bool FMaterial::hasVariant(Variant variant) const noexcept {
// TODO: implement MaterialDomain::COMPUTE
return false;
}
ShaderContent& vsBuilder = mEngine.getVertexShaderContent();
const ShaderModel sm = mEngine.getShaderModel();
if (!mMaterialParser->getShader(vsBuilder, sm, vertexVariant, ShaderStage::VERTEX)) {
if (!mMaterialParser->hasShader(sm, vertexVariant, ShaderStage::VERTEX)) {
return false;
}
if (!mMaterialParser->getShader(vsBuilder, sm, fragmentVariant, ShaderStage::FRAGMENT)) {
if (!mMaterialParser->hasShader(sm, fragmentVariant, ShaderStage::FRAGMENT)) {
return false;
}
return true;
}
void FMaterial::prepareProgramSlow(Variant variant) const noexcept {
void FMaterial::prepareProgramSlow(Variant variant,
backend::CompilerPriorityQueue priorityQueue) const noexcept {
assert_invariant(mEngine.hasFeatureLevel(mFeatureLevel));
switch (getMaterialDomain()) {
case MaterialDomain::SURFACE:
getSurfaceProgramSlow(variant);
getSurfaceProgramSlow(variant, priorityQueue);
break;
case MaterialDomain::POST_PROCESS:
getPostProcessProgramSlow(variant);
getPostProcessProgramSlow(variant, priorityQueue);
break;
case MaterialDomain::COMPUTE:
// TODO: implement MaterialDomain::COMPUTE
@@ -553,7 +561,8 @@ void FMaterial::prepareProgramSlow(Variant variant) const noexcept {
}
}
void FMaterial::getSurfaceProgramSlow(Variant variant) const noexcept {
void FMaterial::getSurfaceProgramSlow(Variant variant,
CompilerPriorityQueue priorityQueue) const noexcept {
// filterVariant() has already been applied in generateCommands(), shouldn't be needed here
// if we're unlit, we don't have any bits that correspond to lit materials
assert_invariant(variant == Variant::filterVariant(variant, isVariantLit()) );
@@ -564,11 +573,14 @@ void FMaterial::getSurfaceProgramSlow(Variant variant) const noexcept {
Variant const fragmentVariant = Variant::filterVariantFragment(variant);
Program pb{ getProgramWithVariants(variant, vertexVariant, fragmentVariant) };
pb.priorityQueue(priorityQueue);
createAndCacheProgram(std::move(pb), variant);
}
void FMaterial::getPostProcessProgramSlow(Variant variant) const noexcept {
void FMaterial::getPostProcessProgramSlow(Variant variant,
CompilerPriorityQueue priorityQueue) const noexcept {
Program pb{ getProgramWithVariants(variant, variant, variant) };
pb.priorityQueue(priorityQueue);
createAndCacheProgram(std::move(pb), variant);
}
+16 -13
View File
@@ -63,9 +63,10 @@ public:
return mSamplerInterfaceBlock;
}
void compile(backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback,
UserVariantFilterMask variantFilter) noexcept;
void compile(CompilerPriorityQueue priority,
UserVariantFilterMask variantFilter,
backend::CallbackHandler* handler,
utils::Invocable<void(Material*)>&& callback) noexcept;
// Create an instance of this material
FMaterialInstance* createInstance(const char* name) const noexcept;
@@ -88,10 +89,11 @@ public:
// prepareProgram creates the program for the material's given variant at the backend level.
// Must be called outside of backend render pass.
// Must be called before getProgram() below.
void prepareProgram(Variant variant) const noexcept {
void prepareProgram(Variant variant,
backend::CompilerPriorityQueue priorityQueue = CompilerPriorityQueue::HIGH) const noexcept {
// prepareProgram() is called for each RenderPrimitive in the scene, so it must be efficient.
if (UTILS_UNLIKELY(!isCached(variant))) {
prepareProgramSlow(variant);
prepareProgramSlow(variant, priorityQueue);
}
}
@@ -191,15 +193,16 @@ public:
private:
bool hasVariant(Variant variant) const noexcept;
void prepareProgramSlow(Variant variant) const noexcept;
void getSurfaceProgramSlow(Variant variant) const noexcept;
void getPostProcessProgramSlow(Variant variant) const noexcept;
backend::Program getProgramWithVariants(Variant variant, Variant vertexVariant,
Variant fragmentVariant) const noexcept;
void prepareProgramSlow(Variant variant,
CompilerPriorityQueue priorityQueue) const noexcept;
void getSurfaceProgramSlow(Variant variant,
CompilerPriorityQueue priorityQueue) const noexcept;
void getPostProcessProgramSlow(Variant variant,
CompilerPriorityQueue priorityQueue) const noexcept;
backend::Program getProgramWithVariants(Variant variant,
Variant vertexVariant, Variant fragmentVariant) const noexcept;
void createAndCacheProgram(backend::Program&& p,
Variant variant) const noexcept;
void createAndCacheProgram(backend::Program&& p, Variant variant) const noexcept;
// try to order by frequency of use
mutable std::array<backend::Handle<backend::HwProgram>, VARIANT_COUNT> mCachedPrograms;
+2 -1
View File
@@ -227,7 +227,8 @@ bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeN
// NOTE: this makes synchronous calls to the driver
driver.updateStreams(&driver);
// gives the backend a chance to execute periodic tasks
// Gives the backend a chance to execute periodic tasks. This must be called before
// the frame skipper.
driver.tick();
/*
@@ -2,7 +2,7 @@ float vmax(const float v) {
return v;
}
vec4 sourceTexLod(const highp vec2 p, float m, float l) {
highp vec4 sourceTexLod(const highp vec2 p, float m, float l) {
// This condition is optimized away at compile-time.
if (materialConstants_arraySampler) {
return textureLod(materialParams_sourceArray, vec3(p, l), m);
@@ -12,12 +12,12 @@ vec4 sourceTexLod(const highp vec2 p, float m, float l) {
}
void tap(inout highp vec4 sum, const float weight, const highp vec2 position) {
vec4 s = sourceTexLod(position, materialParams.level, materialParams.layer);
highp vec4 s = sourceTexLod(position, materialParams.level, materialParams.layer);
sum += s * weight;
}
void tapReinhard(inout highp vec4 sum, inout float totalWeight, const float weight, const highp vec2 position) {
vec4 s = sourceTexLod(position, materialParams.level, materialParams.layer);
highp vec4 s = sourceTexLod(position, materialParams.level, materialParams.layer);
float w = weight / (1.0 + vmax(s));
totalWeight += w;
sum += s * w;
@@ -4,12 +4,12 @@ material {
{
type : sampler2d,
name : source,
precision: medium
precision: high
},
{
type : sampler2dArray,
name : sourceArray,
precision: medium
precision: high
},
{
type : float2,
@@ -36,6 +36,13 @@ material {
name : kernel
}
],
outputs : [
{
type : float4,
name : color,
precision : high
}
],
constants : [
{
type : bool,
@@ -61,7 +68,5 @@ vertex {
}
fragment {
#include "separableGaussianBlur.fs"
}
+2 -2
View File
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = "Filament"
spec.version = "1.39.0"
spec.version = "1.40.0"
spec.license = { :type => "Apache 2.0", :file => "LICENSE" }
spec.homepage = "https://google.github.io/filament"
spec.authors = "Google LLC."
spec.summary = "Filament is a real-time physically based rendering engine for Android, iOS, Windows, Linux, macOS, and WASM/WebGL."
spec.platform = :ios, "11.0"
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.39.0/filament-v1.39.0-ios.tgz" }
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.40.0/filament-v1.40.0-ios.tgz" }
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
spec.pod_target_xcconfig = {
@@ -20,6 +20,7 @@
#define TNT_FILAMENT_MATERIAL_ENUM_H
#include <utils/bitset.h>
#include <utils/BitmaskEnum.h>
#include <stddef.h>
#include <stdint.h>
@@ -27,7 +28,7 @@
namespace filament {
// update this when a new version of filament wouldn't work with older materials
static constexpr size_t MATERIAL_VERSION = 39;
static constexpr size_t MATERIAL_VERSION = 40;
/**
* Supported shading models
@@ -232,7 +233,9 @@ enum class Property : uint8_t {
// when adding new Properties, make sure to update MATERIAL_PROPERTIES_COUNT
};
enum class UserVariantFilterBit : uint32_t {
using UserVariantFilterMask = uint32_t;
enum class UserVariantFilterBit : UserVariantFilterMask {
DIRECTIONAL_LIGHTING = 0x01,
DYNAMIC_LIGHTING = 0x02,
SHADOW_RECEIVER = 0x04,
@@ -240,10 +243,12 @@ enum class UserVariantFilterBit : uint32_t {
FOG = 0x10,
VSM = 0x20,
SSR = 0x40,
ALL = 0x7F,
};
using UserVariantFilterMask = uint32_t;
} // namespace filament
template<> struct utils::EnableBitMaskOperators<filament::UserVariantFilterBit>
: public std::true_type {};
#endif
@@ -54,6 +54,8 @@ public:
void visitShaders(utils::Invocable<void(ShaderModel, Variant, ShaderStage)>&& visitor) const;
bool hasShader(ShaderModel model, Variant variant, ShaderStage stage) const noexcept;
// These methods are for debugging purposes only (matdbg)
// @{
static void decodeKey(uint32_t key,
+8
View File
@@ -170,6 +170,14 @@ bool MaterialChunk::getSpirvShader(BlobDictionary const& dictionary,
return true;
}
bool MaterialChunk::hasShader(ShaderModel model, Variant variant, ShaderStage stage) const noexcept {
if (mBase == nullptr) {
return false;
}
auto pos = mOffsets.find(makeKey(model, variant, stage));
return pos != mOffsets.end();
}
bool MaterialChunk::getShader(ShaderContent& shaderContent, BlobDictionary const& dictionary,
ShaderModel shaderModel, filament::Variant variant, ShaderStage stage) {
switch (mMaterialTag) {
@@ -576,7 +576,7 @@ public:
MaterialBuilder& shaderDefine(const char* name, const char* value) noexcept;
//! Add a new fragment shader output variable. Only valid for materials in the POST_PROCESS domain.
MaterialBuilder& output(VariableQualifier qualifier, OutputTarget target,
MaterialBuilder& output(VariableQualifier qualifier, OutputTarget target, Precision precision,
OutputType type, const char* name, int location = -1) noexcept;
MaterialBuilder& enableFramebufferFetch() noexcept;
@@ -650,13 +650,14 @@ public:
struct Output {
Output() noexcept = default;
Output(const char* outputName, VariableQualifier qualifier, OutputTarget target,
OutputType type, int location) noexcept
: name(outputName), qualifier(qualifier), target(target), type(type),
location(location) { }
Precision precision, OutputType type, int location) noexcept
: name(outputName), qualifier(qualifier), target(target), precision(precision),
type(type), location(location) { }
utils::CString name;
VariableQualifier qualifier;
OutputTarget target;
Precision precision;
OutputType type;
int location;
};
+4 -3
View File
@@ -1006,7 +1006,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
}
MaterialBuilder& MaterialBuilder::output(VariableQualifier qualifier, OutputTarget target,
OutputType type, const char* name, int location) noexcept {
Precision precision, OutputType type, const char* name, int location) noexcept {
ASSERT_PRECONDITION(target != OutputTarget::DEPTH || type == OutputType::FLOAT,
"Depth outputs must be of type FLOAT.");
@@ -1023,7 +1023,7 @@ MaterialBuilder& MaterialBuilder::output(VariableQualifier qualifier, OutputTarg
}
// Unconditionally add this output, then we'll check if we've maxed on on any particular target.
mOutputs.emplace_back(name, qualifier, target, type, location);
mOutputs.emplace_back(name, qualifier, target, precision, type, location);
uint8_t colorOutputCount = 0;
uint8_t depthOutputCount = 0;
@@ -1074,7 +1074,8 @@ error:
// Add a default color output.
if (mMaterialDomain == MaterialDomain::POST_PROCESS && mOutputs.empty()) {
output(VariableQualifier::OUT, OutputTarget::COLOR, OutputType::FLOAT4, "color");
output(VariableQualifier::OUT,
OutputTarget::COLOR, Precision::DEFAULT, OutputType::FLOAT4, "color");
}
// TODO: maybe check MaterialDomain::COMPUTE has outputs
+10 -7
View File
@@ -363,6 +363,7 @@ io::sstream& CodeGenerator::generateShaderInputs(io::sstream& out, ShaderStage t
io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type,
const CString& name, size_t index,
MaterialBuilder::VariableQualifier qualifier,
MaterialBuilder::Precision precision,
MaterialBuilder::OutputType outputType) const {
if (name.empty() || type == ShaderStage::VERTEX) {
return out;
@@ -388,16 +389,18 @@ io::sstream& CodeGenerator::generateOutput(io::sstream& out, ShaderStage type,
}
}
const char* precisionString = getPrecisionQualifier(precision);
const char* materialTypeString = getOutputTypeName(materialOutputType);
const char* typeString = getOutputTypeName(outputType);
out << "\n#define FRAG_OUTPUT" << index << " " << name.c_str() << "\n";
out << "\n#define FRAG_OUTPUT_AT" << index << " output_" << name.c_str() << "\n";
out << "\n#define FRAG_OUTPUT_MATERIAL_TYPE" << index << " " << materialTypeString << "\n";
out << "\n#define FRAG_OUTPUT_TYPE" << index << " " << typeString << "\n";
out << "\n#define FRAG_OUTPUT_SWIZZLE" << index << " " << swizzleString << "\n";
out << "layout(location=" << index << ") out " << typeString <<
" output_" << name.c_str() << ";\n";
out << "\n#define FRAG_OUTPUT" << index << " " << name.c_str();
out << "\n#define FRAG_OUTPUT_AT" << index << " output_" << name.c_str();
out << "\n#define FRAG_OUTPUT_MATERIAL_TYPE" << index << " " << materialTypeString;
out << "\n#define FRAG_OUTPUT_PRECISION" << index << " " << precisionString;
out << "\n#define FRAG_OUTPUT_TYPE" << index << " " << typeString;
out << "\n#define FRAG_OUTPUT_SWIZZLE" << index << " " << swizzleString;
out << "\nlayout(location=" << index << ") out " << precisionString << " "
<< typeString << " output_" << name.c_str() << ";\n";
return out;
}
+1
View File
@@ -114,6 +114,7 @@ public:
utils::io::sstream& generateOutput(utils::io::sstream& out, ShaderStage type,
const utils::CString& name, size_t index,
MaterialBuilder::VariableQualifier qualifier,
MaterialBuilder::Precision precision,
MaterialBuilder::OutputType outputType) const;
// generate no-op shader for depth prepass
+1 -1
View File
@@ -717,7 +717,7 @@ std::string ShaderGenerator::createPostProcessFragmentProgram(ShaderModel sm,
for (const auto& output : mOutputs) {
if (output.target == MaterialBuilder::OutputTarget::COLOR) {
cg.generateOutput(fs, ShaderStage::FRAGMENT, output.name, output.location,
output.qualifier, output.type);
output.qualifier, output.precision, output.type);
}
if (output.target == MaterialBuilder::OutputTarget::DEPTH) {
CodeGenerator::generateDefine(fs, "FRAG_OUTPUT_DEPTH", 1u);
+10
View File
@@ -119,6 +119,16 @@ Material* ArchiveCache::getMaterial(const ArchiveRequirements& reqs) {
.package(spec.package, spec.packageByteCount)
.build(mEngine);
}
// compile everything at low priority
mMaterials[i]->compile(Material::CompilerPriorityQueue::LOW);
// promote variants we care about to high priority
mMaterials[i]->compile(Material::CompilerPriorityQueue::HIGH,
UserVariantFilterBit::DIRECTIONAL_LIGHTING |
UserVariantFilterBit::DYNAMIC_LIGHTING |
UserVariantFilterBit::SHADOW_RECEIVER);
return mMaterials[i];
}
}
-1
View File
@@ -141,5 +141,4 @@ inline constexpr bool any(Enum lhs) noexcept {
return !none(lhs);
}
#endif // TNT_UTILS_BITMASKENUM_H
+1 -1
View File
@@ -1,5 +1,5 @@
#if defined(VARIANT_HAS_VSM)
layout(location = 0) out vec4 fragColor;
layout(location = 0) out highp vec4 fragColor;
#elif defined(VARIANT_HAS_PICKING)
# if __VERSION__ == 100
highp vec4 outPicking;
-27
View File
@@ -4,33 +4,6 @@ void main() {
// Invoke user code
postProcess(inputs);
#if defined(TARGET_MOBILE)
#if defined(FRAG_OUTPUT0)
inputs.FRAG_OUTPUT0 = clamp(inputs.FRAG_OUTPUT0, -MEDIUMP_FLT_MAX, MEDIUMP_FLT_MAX);
#endif
#if defined(FRAG_OUTPUT1)
inputs.FRAG_OUTPUT1 = clamp(inputs.FRAG_OUTPUT1, -MEDIUMP_FLT_MAX, MEDIUMP_FLT_MAX);
#endif
#if defined(FRAG_OUTPUT2)
inputs.FRAG_OUTPUT2 = clamp(inputs.FRAG_OUTPUT2, -MEDIUMP_FLT_MAX, MEDIUMP_FLT_MAX);
#endif
#if defined(FRAG_OUTPUT3)
inputs.FRAG_OUTPUT3 = clamp(inputs.FRAG_OUTPUT3, -MEDIUMP_FLT_MAX, MEDIUMP_FLT_MAX);
#endif
#if defined(FRAG_OUTPUT4)
inputs.FRAG_OUTPUT4 = clamp(inputs.FRAG_OUTPUT4, -MEDIUMP_FLT_MAX, MEDIUMP_FLT_MAX);
#endif
#if defined(FRAG_OUTPUT5)
inputs.FRAG_OUTPUT5 = clamp(inputs.FRAG_OUTPUT5, -MEDIUMP_FLT_MAX, MEDIUMP_FLT_MAX);
#endif
#if defined(FRAG_OUTPUT6)
inputs.FRAG_OUTPUT6 = clamp(inputs.FRAG_OUTPUT6, -MEDIUMP_FLT_MAX, MEDIUMP_FLT_MAX);
#endif
#if defined(FRAG_OUTPUT7)
inputs.FRAG_OUTPUT7 = clamp(inputs.FRAG_OUTPUT7, -MEDIUMP_FLT_MAX, MEDIUMP_FLT_MAX);
#endif
#endif
#if defined(FRAG_OUTPUT0)
FRAG_OUTPUT_AT0 FRAG_OUTPUT_SWIZZLE0 = inputs.FRAG_OUTPUT0;
#endif
+8 -8
View File
@@ -1,28 +1,28 @@
struct PostProcessInputs {
#if defined(FRAG_OUTPUT0)
FRAG_OUTPUT_MATERIAL_TYPE0 FRAG_OUTPUT0;
FRAG_OUTPUT_PRECISION0 FRAG_OUTPUT_MATERIAL_TYPE0 FRAG_OUTPUT0;
#endif
#if defined(FRAG_OUTPUT1)
FRAG_OUTPUT_MATERIAL_TYPE1 FRAG_OUTPUT1;
FRAG_OUTPUT_PRECISION1 FRAG_OUTPUT_MATERIAL_TYPE1 FRAG_OUTPUT1;
#endif
#if defined(FRAG_OUTPUT2)
FRAG_OUTPUT_MATERIAL_TYPE2 FRAG_OUTPUT2;
FRAG_OUTPUT_PRECISION2 FRAG_OUTPUT_MATERIAL_TYPE2 FRAG_OUTPUT2;
#endif
#if defined(FRAG_OUTPUT3)
FRAG_OUTPUT_MATERIAL_TYPE3 FRAG_OUTPUT3;
FRAG_OUTPUT_PRECISION3 FRAG_OUTPUT_MATERIAL_TYPE3 FRAG_OUTPUT3;
#endif
#if defined(FRAG_OUTPUT4)
FRAG_OUTPUT_MATERIAL_TYPE4 FRAG_OUTPUT4;
FRAG_OUTPUT_PRECISION4 FRAG_OUTPUT_MATERIAL_TYPE4 FRAG_OUTPUT4;
#endif
#if defined(FRAG_OUTPUT5)
FRAG_OUTPUT_MATERIAL_TYPE5 FRAG_OUTPUT5;
FRAG_OUTPUT_PRECISION5 FRAG_OUTPUT_MATERIAL_TYPE5 FRAG_OUTPUT5;
#endif
#if defined(FRAG_OUTPUT6)
FRAG_OUTPUT_MATERIAL_TYPE6 FRAG_OUTPUT6;
FRAG_OUTPUT_PRECISION6 FRAG_OUTPUT_MATERIAL_TYPE6 FRAG_OUTPUT6;
#endif
#if defined(FRAG_OUTPUT7)
FRAG_OUTPUT_MATERIAL_TYPE7 FRAG_OUTPUT7;
FRAG_OUTPUT_PRECISION7 FRAG_OUTPUT_MATERIAL_TYPE7 FRAG_OUTPUT7;
#endif
#if defined(FRAG_OUTPUT_DEPTH)
float depth;
+2 -2
View File
@@ -466,7 +466,7 @@ float evaluateShadowVSM(const highp vec2 moments, const highp float depth) {
return chebyshevUpperBound(moments, depth, minVariance, frameUniforms.vsmLightBleedReduction);
}
float ShadowSample_VSM(const bool ELVSM, const mediump sampler2DArray shadowMap,
float ShadowSample_VSM(const bool ELVSM, const highp sampler2DArray shadowMap,
const highp vec4 scissorNormalized,
const uint layer, const highp vec4 shadowPosition) {
@@ -548,7 +548,7 @@ float shadow(const bool DIRECTIONAL,
// Shadow requiring a sampler2D sampler (VSM, DPCF and PCSS)
float shadow(const bool DIRECTIONAL,
const mediump sampler2DArray shadowMap,
const highp sampler2DArray shadowMap,
const int index, highp vec4 shadowPosition, highp float zLight) {
highp vec4 scissorNormalized = shadowUniforms.shadows[index].scissorNormalized;
uint layer = shadowUniforms.shadows[index].layer;
+8 -4
View File
@@ -14,15 +14,15 @@ jobs:
name: "Build ${{ matrix.platform }}"
strategy:
matrix:
platform: [windows-latest, ubuntu-18.04, ubuntu-20.04, macos-latest]
platform: [windows-latest, ubuntu-20.04, ubuntu-22.04, macos-latest]
env:
PARALLEL: -j 2
runs-on: "${{ matrix.platform }}"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: '3.x'
@@ -50,6 +50,10 @@ jobs:
cmake --build . --config Release
cmake --build . --config Release --target install
- uses: actions/upload-artifact@v3
with:
name: ${{ matrix.platform }}-binaries
path: build/output
- name: Test SPIRV-Cross
shell: bash
working-directory: ${{github.workspace}}/build
@@ -60,7 +64,7 @@ jobs:
container: khronosgroup/docker-images:asciidoctor-spec
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
# REUSE license checker
- name: license-check
+12 -6
View File
@@ -242,7 +242,19 @@ set(spirv-cross-util-sources
${CMAKE_CURRENT_SOURCE_DIR}/spirv_cross_util.cpp
${CMAKE_CURRENT_SOURCE_DIR}/spirv_cross_util.hpp)
set(spirv-cross-abi-major 0)
set(spirv-cross-abi-minor 57)
set(spirv-cross-abi-patch 0)
set(SPIRV_CROSS_VERSION ${spirv-cross-abi-major}.${spirv-cross-abi-minor}.${spirv-cross-abi-patch})
if (SPIRV_CROSS_STATIC)
if (NOT SPIRV_CROSS_SKIP_INSTALL)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/pkg-config/spirv-cross-c.pc.in
${CMAKE_CURRENT_BINARY_DIR}/spirv-cross-c.pc @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/spirv-cross-c.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
endif()
spirv_cross_add_library(spirv-cross-core spirv_cross_core STATIC
${spirv-cross-core-sources})
@@ -331,13 +343,7 @@ if (SPIRV_CROSS_STATIC)
endif()
endif()
set(spirv-cross-abi-major 0)
set(spirv-cross-abi-minor 56)
set(spirv-cross-abi-patch 0)
if (SPIRV_CROSS_SHARED)
set(SPIRV_CROSS_VERSION ${spirv-cross-abi-major}.${spirv-cross-abi-minor}.${spirv-cross-abi-patch})
if (NOT SPIRV_CROSS_SKIP_INSTALL)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/pkg-config/spirv-cross-c-shared.pc.in
+4
View File
@@ -732,6 +732,7 @@ struct CLIArguments
bool hlsl_nonwritable_uav_texture_as_srv = false;
bool hlsl_enable_16bit_types = false;
bool hlsl_flatten_matrix_vertex_input_semantics = false;
bool hlsl_preserve_structured_buffers = false;
HLSLBindingFlags hlsl_binding_flags = 0;
bool vulkan_semantics = false;
bool flatten_multidimensional_arrays = false;
@@ -839,6 +840,7 @@ static void print_help_hlsl()
"\t\tOpName reflection information must be intact.\n"
"\t[--hlsl-enable-16bit-types]:\n\t\tEnables native use of half/int16_t/uint16_t and ByteAddressBuffer interaction with these types. Requires SM 6.2.\n"
"\t[--hlsl-flatten-matrix-vertex-input-semantics]:\n\t\tEmits matrix vertex inputs with input semantics as if they were independent vectors, e.g. TEXCOORD{2,3,4} rather than matrix form TEXCOORD2_{0,1,2}.\n"
"\t[--hlsl-preserve-structured-buffers]:\n\t\tEmit SturucturedBuffer<T> rather than ByteAddressBuffer. Requires UserTypeGOOGLE to be emitted. Intended for DXC roundtrips.\n"
);
// clang-format on
}
@@ -1421,6 +1423,7 @@ static string compile_iteration(const CLIArguments &args, std::vector<uint32_t>
hlsl_opts.nonwritable_uav_texture_as_srv = args.hlsl_nonwritable_uav_texture_as_srv;
hlsl_opts.enable_16bit_types = args.hlsl_enable_16bit_types;
hlsl_opts.flatten_matrix_vertex_input_semantics = args.hlsl_flatten_matrix_vertex_input_semantics;
hlsl_opts.preserve_structured_buffers = args.hlsl_preserve_structured_buffers;
hlsl->set_hlsl_options(hlsl_opts);
hlsl->set_resource_binding_flags(args.hlsl_binding_flags);
if (args.hlsl_base_vertex_index_explicit_binding)
@@ -1622,6 +1625,7 @@ static int main_inner(int argc, char *argv[])
cbs.add("--hlsl-enable-16bit-types", [&args](CLIParser &) { args.hlsl_enable_16bit_types = true; });
cbs.add("--hlsl-flatten-matrix-vertex-input-semantics",
[&args](CLIParser &) { args.hlsl_flatten_matrix_vertex_input_semantics = true; });
cbs.add("--hlsl-preserve-structured-buffers", [&args](CLIParser &) { args.hlsl_preserve_structured_buffers = true; });
cbs.add("--vulkan-semantics", [&args](CLIParser &) { args.vulkan_semantics = true; });
cbs.add("-V", [&args](CLIParser &) { args.vulkan_semantics = true; });
cbs.add("--flatten-multidimensional-arrays", [&args](CLIParser &) { args.flatten_multidimensional_arrays = true; });
@@ -8,7 +8,7 @@ sharedlibdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/spirv_cross
Name: spirv-cross-c-shared
Description: C API for SPIRV-Cross
Description: C API for SPIRV-Cross (shared)
Version: @SPIRV_CROSS_VERSION@
Requires:
+15
View File
@@ -0,0 +1,15 @@
# Copyright 2020-2021 Hans-Kristian Arntzen
# SPDX-License-Identifier: Apache-2.0
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/spirv_cross
Name: spirv-cross-c
Description: C API for SPIRV-Cross (static)
Version: @SPIRV_CROSS_VERSION@
Requires:
Libs: -L${libdir} -lspirv-cross-c
Cflags: -I${includedir}
@@ -0,0 +1,19 @@
RWByteAddressBuffer _4 : register(u0);
void comp_main()
{
uint _21 = _4.Load(_4.Load(0) * 4 + 4);
for (uint _23 = 0u; _23 < 64u; )
{
_4.Store(_23 * 4 + 4, 0u);
_23++;
continue;
}
_4.Store(_4.Load(0) * 4 + 4, _21);
}
[numthreads(1, 1, 1)]
void main()
{
comp_main();
}
@@ -0,0 +1,22 @@
RWByteAddressBuffer u0_counter : register(u1);
RWBuffer<uint> u0 : register(u0);
static uint3 gl_GlobalInvocationID;
struct SPIRV_Cross_Input
{
uint3 gl_GlobalInvocationID : SV_DispatchThreadID;
};
void comp_main()
{
uint _29;
u0_counter.InterlockedAdd(0, -1, _29);
u0[asint(asfloat(_29))] = uint(int(gl_GlobalInvocationID.x)).x;
}
[numthreads(4, 1, 1)]
void main(SPIRV_Cross_Input stage_input)
{
gl_GlobalInvocationID = stage_input.gl_GlobalInvocationID;
comp_main();
}
@@ -0,0 +1,22 @@
RWByteAddressBuffer u0_counter : register(u1);
RWBuffer<uint> u0 : register(u0);
static uint3 gl_GlobalInvocationID;
struct SPIRV_Cross_Input
{
uint3 gl_GlobalInvocationID : SV_DispatchThreadID;
};
void comp_main()
{
uint _29;
u0_counter.InterlockedAdd(0, 1, _29);
u0[asint(asfloat(_29))] = uint(int(gl_GlobalInvocationID.x)).x;
}
[numthreads(4, 1, 1)]
void main(SPIRV_Cross_Input stage_input)
{
gl_GlobalInvocationID = stage_input.gl_GlobalInvocationID;
comp_main();
}
@@ -0,0 +1,20 @@
RWByteAddressBuffer _5 : register(u0);
RWByteAddressBuffer _6 : register(u1);
void comp_main()
{
_6.Store4(0, uint4(bool4(int(_5.Load4(16).x) < int4(_5.Load4(0)).x, int(_5.Load4(16).y) < int4(_5.Load4(0)).y, int(_5.Load4(16).z) < int4(_5.Load4(0)).z, int(_5.Load4(16).w) < int4(_5.Load4(0)).w)));
_6.Store4(0, uint4(bool4(int(_5.Load4(16).x) <= int4(_5.Load4(0)).x, int(_5.Load4(16).y) <= int4(_5.Load4(0)).y, int(_5.Load4(16).z) <= int4(_5.Load4(0)).z, int(_5.Load4(16).w) <= int4(_5.Load4(0)).w)));
_6.Store4(0, uint4(bool4(_5.Load4(16).x < uint(int4(_5.Load4(0)).x), _5.Load4(16).y < uint(int4(_5.Load4(0)).y), _5.Load4(16).z < uint(int4(_5.Load4(0)).z), _5.Load4(16).w < uint(int4(_5.Load4(0)).w))));
_6.Store4(0, uint4(bool4(_5.Load4(16).x <= uint(int4(_5.Load4(0)).x), _5.Load4(16).y <= uint(int4(_5.Load4(0)).y), _5.Load4(16).z <= uint(int4(_5.Load4(0)).z), _5.Load4(16).w <= uint(int4(_5.Load4(0)).w))));
_6.Store4(0, uint4(bool4(int(_5.Load4(16).x) > int4(_5.Load4(0)).x, int(_5.Load4(16).y) > int4(_5.Load4(0)).y, int(_5.Load4(16).z) > int4(_5.Load4(0)).z, int(_5.Load4(16).w) > int4(_5.Load4(0)).w)));
_6.Store4(0, uint4(bool4(int(_5.Load4(16).x) >= int4(_5.Load4(0)).x, int(_5.Load4(16).y) >= int4(_5.Load4(0)).y, int(_5.Load4(16).z) >= int4(_5.Load4(0)).z, int(_5.Load4(16).w) >= int4(_5.Load4(0)).w)));
_6.Store4(0, uint4(bool4(_5.Load4(16).x > uint(int4(_5.Load4(0)).x), _5.Load4(16).y > uint(int4(_5.Load4(0)).y), _5.Load4(16).z > uint(int4(_5.Load4(0)).z), _5.Load4(16).w > uint(int4(_5.Load4(0)).w))));
_6.Store4(0, uint4(bool4(_5.Load4(16).x >= uint(int4(_5.Load4(0)).x), _5.Load4(16).y >= uint(int4(_5.Load4(0)).y), _5.Load4(16).z >= uint(int4(_5.Load4(0)).z), _5.Load4(16).w >= uint(int4(_5.Load4(0)).w))));
}
[numthreads(1, 1, 1)]
void main()
{
comp_main();
}
@@ -0,0 +1,39 @@
struct A
{
int a;
int b;
};
RWByteAddressBuffer C1 : register(u1);
cbuffer C2 : register(b2)
{
A C2_1_Data[1024] : packoffset(c0);
};
RWByteAddressBuffer C3 : register(u0);
cbuffer B : register(b3)
{
A C4_Data[1024] : packoffset(c0);
};
static uint3 gl_GlobalInvocationID;
struct SPIRV_Cross_Input
{
uint3 gl_GlobalInvocationID : SV_DispatchThreadID;
};
void comp_main()
{
C1.Store(gl_GlobalInvocationID.x * 8 + 0, uint(C2_1_Data[gl_GlobalInvocationID.x].a));
C1.Store(gl_GlobalInvocationID.x * 8 + 4, uint(C2_1_Data[gl_GlobalInvocationID.x].b));
C3.Store(gl_GlobalInvocationID.x * 8 + 0, uint(C4_Data[gl_GlobalInvocationID.x].a));
C3.Store(gl_GlobalInvocationID.x * 8 + 4, uint(C4_Data[gl_GlobalInvocationID.x].b));
}
[numthreads(1, 1, 1)]
void main(SPIRV_Cross_Input stage_input)
{
gl_GlobalInvocationID = stage_input.gl_GlobalInvocationID;
comp_main();
}
@@ -0,0 +1,41 @@
RWByteAddressBuffer bar : register(u0);
RWByteAddressBuffer foo : register(u1);
void comp_main()
{
bar.Store4(0, asuint(asfloat(foo.Load4(0))));
bar.Store4(16, asuint(asfloat(foo.Load4(16))));
bar.Store4(32, asuint(asfloat(foo.Load4(32))));
bar.Store4(48, asuint(asfloat(foo.Load4(48))));
bar.Store4(64, asuint(asfloat(foo.Load4(64))));
bar.Store4(80, asuint(asfloat(foo.Load4(80))));
bar.Store4(96, asuint(asfloat(foo.Load4(96))));
bar.Store4(112, asuint(asfloat(foo.Load4(112))));
bar.Store4(128, asuint(asfloat(foo.Load4(128))));
bar.Store4(144, asuint(asfloat(foo.Load4(144))));
bar.Store4(160, asuint(asfloat(foo.Load4(160))));
bar.Store4(176, asuint(asfloat(foo.Load4(176))));
bar.Store4(192, asuint(asfloat(foo.Load4(192))));
bar.Store4(208, asuint(asfloat(foo.Load4(208))));
bar.Store4(224, asuint(asfloat(foo.Load4(224))));
bar.Store4(240, asuint(asfloat(foo.Load4(240))));
[loop]
for (int _137 = 0; _137 < 16; )
{
bar.Store4((15 - _137) * 16 + 0, asuint(asfloat(foo.Load4(_137 * 16 + 0))));
_137++;
continue;
}
[branch]
if (asfloat(bar.Load(160)) > 10.0f)
{
foo.Store4(320, asuint(5.0f.xxxx));
}
foo.Store4(320, asuint(20.0f.xxxx));
}
[numthreads(1, 1, 1)]
void main()
{
comp_main();
}
@@ -0,0 +1,9 @@
void comp_main()
{
}
[numthreads(1, 1, 1)]
void main()
{
comp_main();
}
@@ -0,0 +1,87 @@
RWByteAddressBuffer _4 : register(u0);
void comp_main()
{
_4.Store(0, asuint(isnan(asfloat(_4.Load(96))) ? asfloat(_4.Load(48)) : (isnan(asfloat(_4.Load(48))) ? asfloat(_4.Load(96)) : min(asfloat(_4.Load(48)), asfloat(_4.Load(96))))));
bool2 _146 = isnan(asfloat(_4.Load2(56)));
bool2 _147 = isnan(asfloat(_4.Load2(104)));
float2 _148 = min(asfloat(_4.Load2(56)), asfloat(_4.Load2(104)));
float2 _149 = float2(_146.x ? asfloat(_4.Load2(104)).x : _148.x, _146.y ? asfloat(_4.Load2(104)).y : _148.y);
_4.Store2(8, asuint(float2(_147.x ? asfloat(_4.Load2(56)).x : _149.x, _147.y ? asfloat(_4.Load2(56)).y : _149.y)));
bool3 _151 = isnan(asfloat(_4.Load3(64)));
bool3 _152 = isnan(asfloat(_4.Load3(112)));
float3 _153 = min(asfloat(_4.Load3(64)), asfloat(_4.Load3(112)));
float3 _154 = float3(_151.x ? asfloat(_4.Load3(112)).x : _153.x, _151.y ? asfloat(_4.Load3(112)).y : _153.y, _151.z ? asfloat(_4.Load3(112)).z : _153.z);
_4.Store3(16, asuint(float3(_152.x ? asfloat(_4.Load3(64)).x : _154.x, _152.y ? asfloat(_4.Load3(64)).y : _154.y, _152.z ? asfloat(_4.Load3(64)).z : _154.z)));
bool4 _156 = isnan(asfloat(_4.Load4(80)));
bool4 _157 = isnan(asfloat(_4.Load4(128)));
float4 _158 = min(asfloat(_4.Load4(80)), asfloat(_4.Load4(128)));
float4 _159 = float4(_156.x ? asfloat(_4.Load4(128)).x : _158.x, _156.y ? asfloat(_4.Load4(128)).y : _158.y, _156.z ? asfloat(_4.Load4(128)).z : _158.z, _156.w ? asfloat(_4.Load4(128)).w : _158.w);
_4.Store4(32, asuint(float4(_157.x ? asfloat(_4.Load4(80)).x : _159.x, _157.y ? asfloat(_4.Load4(80)).y : _159.y, _157.z ? asfloat(_4.Load4(80)).z : _159.z, _157.w ? asfloat(_4.Load4(80)).w : _159.w)));
_4.Store(0, asuint(isnan(asfloat(_4.Load(96))) ? asfloat(_4.Load(48)) : (isnan(asfloat(_4.Load(48))) ? asfloat(_4.Load(96)) : max(asfloat(_4.Load(48)), asfloat(_4.Load(96))))));
bool2 _166 = isnan(asfloat(_4.Load2(56)));
bool2 _167 = isnan(asfloat(_4.Load2(104)));
float2 _168 = max(asfloat(_4.Load2(56)), asfloat(_4.Load2(104)));
float2 _169 = float2(_166.x ? asfloat(_4.Load2(104)).x : _168.x, _166.y ? asfloat(_4.Load2(104)).y : _168.y);
_4.Store2(8, asuint(float2(_167.x ? asfloat(_4.Load2(56)).x : _169.x, _167.y ? asfloat(_4.Load2(56)).y : _169.y)));
bool3 _171 = isnan(asfloat(_4.Load3(64)));
bool3 _172 = isnan(asfloat(_4.Load3(112)));
float3 _173 = max(asfloat(_4.Load3(64)), asfloat(_4.Load3(112)));
float3 _174 = float3(_171.x ? asfloat(_4.Load3(112)).x : _173.x, _171.y ? asfloat(_4.Load3(112)).y : _173.y, _171.z ? asfloat(_4.Load3(112)).z : _173.z);
_4.Store3(16, asuint(float3(_172.x ? asfloat(_4.Load3(64)).x : _174.x, _172.y ? asfloat(_4.Load3(64)).y : _174.y, _172.z ? asfloat(_4.Load3(64)).z : _174.z)));
bool4 _176 = isnan(asfloat(_4.Load4(80)));
bool4 _177 = isnan(asfloat(_4.Load4(128)));
float4 _178 = max(asfloat(_4.Load4(80)), asfloat(_4.Load4(128)));
float4 _179 = float4(_176.x ? asfloat(_4.Load4(128)).x : _178.x, _176.y ? asfloat(_4.Load4(128)).y : _178.y, _176.z ? asfloat(_4.Load4(128)).z : _178.z, _176.w ? asfloat(_4.Load4(128)).w : _178.w);
_4.Store4(32, asuint(float4(_177.x ? asfloat(_4.Load4(80)).x : _179.x, _177.y ? asfloat(_4.Load4(80)).y : _179.y, _177.z ? asfloat(_4.Load4(80)).z : _179.z, _177.w ? asfloat(_4.Load4(80)).w : _179.w)));
float _180 = isnan(asfloat(_4.Load(48))) ? asfloat(_4.Load(0)) : (isnan(asfloat(_4.Load(0))) ? asfloat(_4.Load(48)) : max(asfloat(_4.Load(0)), asfloat(_4.Load(48))));
_4.Store(0, asuint(isnan(asfloat(_4.Load(96))) ? _180 : (isnan(_180) ? asfloat(_4.Load(96)) : min(_180, asfloat(_4.Load(96))))));
bool2 _193 = isnan(asfloat(_4.Load2(8)));
bool2 _194 = isnan(asfloat(_4.Load2(56)));
float2 _195 = max(asfloat(_4.Load2(8)), asfloat(_4.Load2(56)));
float2 _196 = float2(_193.x ? asfloat(_4.Load2(56)).x : _195.x, _193.y ? asfloat(_4.Load2(56)).y : _195.y);
float2 _191 = float2(_194.x ? asfloat(_4.Load2(8)).x : _196.x, _194.y ? asfloat(_4.Load2(8)).y : _196.y);
bool2 _198 = isnan(_191);
bool2 _199 = isnan(asfloat(_4.Load2(104)));
float2 _200 = min(_191, asfloat(_4.Load2(104)));
float2 _201 = float2(_198.x ? asfloat(_4.Load2(104)).x : _200.x, _198.y ? asfloat(_4.Load2(104)).y : _200.y);
_4.Store2(8, asuint(float2(_199.x ? _191.x : _201.x, _199.y ? _191.y : _201.y)));
bool3 _204 = isnan(asfloat(_4.Load3(16)));
bool3 _205 = isnan(asfloat(_4.Load3(64)));
float3 _206 = max(asfloat(_4.Load3(16)), asfloat(_4.Load3(64)));
float3 _207 = float3(_204.x ? asfloat(_4.Load3(64)).x : _206.x, _204.y ? asfloat(_4.Load3(64)).y : _206.y, _204.z ? asfloat(_4.Load3(64)).z : _206.z);
float3 _202 = float3(_205.x ? asfloat(_4.Load3(16)).x : _207.x, _205.y ? asfloat(_4.Load3(16)).y : _207.y, _205.z ? asfloat(_4.Load3(16)).z : _207.z);
bool3 _209 = isnan(_202);
bool3 _210 = isnan(asfloat(_4.Load3(112)));
float3 _211 = min(_202, asfloat(_4.Load3(112)));
float3 _212 = float3(_209.x ? asfloat(_4.Load3(112)).x : _211.x, _209.y ? asfloat(_4.Load3(112)).y : _211.y, _209.z ? asfloat(_4.Load3(112)).z : _211.z);
_4.Store3(16, asuint(float3(_210.x ? _202.x : _212.x, _210.y ? _202.y : _212.y, _210.z ? _202.z : _212.z)));
bool4 _215 = isnan(asfloat(_4.Load4(32)));
bool4 _216 = isnan(asfloat(_4.Load4(80)));
float4 _217 = max(asfloat(_4.Load4(32)), asfloat(_4.Load4(80)));
float4 _218 = float4(_215.x ? asfloat(_4.Load4(80)).x : _217.x, _215.y ? asfloat(_4.Load4(80)).y : _217.y, _215.z ? asfloat(_4.Load4(80)).z : _217.z, _215.w ? asfloat(_4.Load4(80)).w : _217.w);
float4 _213 = float4(_216.x ? asfloat(_4.Load4(32)).x : _218.x, _216.y ? asfloat(_4.Load4(32)).y : _218.y, _216.z ? asfloat(_4.Load4(32)).z : _218.z, _216.w ? asfloat(_4.Load4(32)).w : _218.w);
bool4 _220 = isnan(_213);
bool4 _221 = isnan(asfloat(_4.Load4(128)));
float4 _222 = min(_213, asfloat(_4.Load4(128)));
float4 _223 = float4(_220.x ? asfloat(_4.Load4(128)).x : _222.x, _220.y ? asfloat(_4.Load4(128)).y : _222.y, _220.z ? asfloat(_4.Load4(128)).z : _222.z, _220.w ? asfloat(_4.Load4(128)).w : _222.w);
_4.Store4(32, asuint(float4(_221.x ? _213.x : _223.x, _221.y ? _213.y : _223.y, _221.z ? _213.z : _223.z, _221.w ? _213.w : _223.w)));
for (int _139 = 0; _139 < 2; )
{
bool2 _225 = isnan(asfloat(_4.Load2(56)));
bool2 _226 = isnan(asfloat(_4.Load2(104)));
float2 _227 = min(asfloat(_4.Load2(56)), asfloat(_4.Load2(104)));
float2 _228 = float2(_225.x ? asfloat(_4.Load2(104)).x : _227.x, _225.y ? asfloat(_4.Load2(104)).y : _227.y);
_4.Store2(8, asuint(float2(_226.x ? asfloat(_4.Load2(56)).x : _228.x, _226.y ? asfloat(_4.Load2(56)).y : _228.y)));
float _229 = isnan(asfloat(_4.Load(56))) ? asfloat(_4.Load(0)) : (isnan(asfloat(_4.Load(0))) ? asfloat(_4.Load(56)) : max(asfloat(_4.Load(0)), asfloat(_4.Load(56))));
_4.Store(0, asuint(isnan(asfloat(_4.Load(60))) ? _229 : (isnan(_229) ? asfloat(_4.Load(60)) : min(_229, asfloat(_4.Load(60))))));
_139++;
continue;
}
}
[numthreads(1, 1, 1)]
void main()
{
comp_main();
}
@@ -0,0 +1,30 @@
RWByteAddressBuffer _4 : register(u0);
void comp_main()
{
_4.Store(0, asuint(min(asfloat(_4.Load(48)), asfloat(_4.Load(96)))));
_4.Store2(8, asuint(min(asfloat(_4.Load2(56)), asfloat(_4.Load2(104)))));
_4.Store3(16, asuint(min(asfloat(_4.Load3(64)), asfloat(_4.Load3(112)))));
_4.Store4(32, asuint(min(asfloat(_4.Load4(80)), asfloat(_4.Load4(128)))));
_4.Store(0, asuint(max(asfloat(_4.Load(48)), asfloat(_4.Load(96)))));
_4.Store2(8, asuint(max(asfloat(_4.Load2(56)), asfloat(_4.Load2(104)))));
_4.Store3(16, asuint(max(asfloat(_4.Load3(64)), asfloat(_4.Load3(112)))));
_4.Store4(32, asuint(max(asfloat(_4.Load4(80)), asfloat(_4.Load4(128)))));
_4.Store(0, asuint(clamp(asfloat(_4.Load(0)), asfloat(_4.Load(48)), asfloat(_4.Load(96)))));
_4.Store2(8, asuint(clamp(asfloat(_4.Load2(8)), asfloat(_4.Load2(56)), asfloat(_4.Load2(104)))));
_4.Store3(16, asuint(clamp(asfloat(_4.Load3(16)), asfloat(_4.Load3(64)), asfloat(_4.Load3(112)))));
_4.Store4(32, asuint(clamp(asfloat(_4.Load4(32)), asfloat(_4.Load4(80)), asfloat(_4.Load4(128)))));
for (int _139 = 0; _139 < 2; )
{
_4.Store2(8, asuint(min(asfloat(_4.Load2(56)), asfloat(_4.Load2(104)))));
_4.Store(0, asuint(clamp(asfloat(_4.Load(0)), asfloat(_4.Load(56)), asfloat(_4.Load(60)))));
_139++;
continue;
}
}
[numthreads(1, 1, 1)]
void main()
{
comp_main();
}
@@ -0,0 +1,26 @@
struct Data
{
float4 Color;
};
StructuredBuffer<Data> Colors[2] : register(t0);
static float4 out_var_SV_Target;
struct SPIRV_Cross_Output
{
float4 out_var_SV_Target : SV_Target0;
};
void frag_main()
{
out_var_SV_Target = Colors[1][3u].Color;
}
SPIRV_Cross_Output main()
{
frag_main();
SPIRV_Cross_Output stage_output;
stage_output.out_var_SV_Target = out_var_SV_Target;
return stage_output;
}
@@ -0,0 +1,26 @@
cbuffer _4_5 : register(b0)
{
column_major float2x4 _5_m0 : packoffset(c0);
float4 _5_m1 : packoffset(c4);
};
static float2 _3;
struct SPIRV_Cross_Output
{
float2 _3 : SV_Target0;
};
void frag_main()
{
_3 = mul(_5_m0, _5_m1);
}
SPIRV_Cross_Output main()
{
frag_main();
SPIRV_Cross_Output stage_output;
stage_output._3 = _3;
return stage_output;
}
@@ -0,0 +1,30 @@
Texture2D<float4> uTex : register(t1);
SamplerState uSampler : register(s0);
static float4 FragColor;
static float2 vUV;
struct SPIRV_Cross_Input
{
float2 vUV : TEXCOORD0;
};
struct SPIRV_Cross_Output
{
float4 FragColor : SV_Target0;
};
void frag_main()
{
FragColor = uTex.Sample(uSampler, vUV);
FragColor += uTex.Sample(uSampler, vUV, int2(1, 1));
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
vUV = stage_input.vUV;
frag_main();
SPIRV_Cross_Output stage_output;
stage_output.FragColor = FragColor;
return stage_output;
}
@@ -0,0 +1,8 @@
void frag_main()
{
}
void main()
{
frag_main();
}
@@ -0,0 +1,29 @@
static float4 FragColor;
static float4 vA;
static float4 vB;
struct SPIRV_Cross_Input
{
float4 vA : TEXCOORD0;
float4 vB : TEXCOORD1;
};
struct SPIRV_Cross_Output
{
float4 FragColor : SV_Target0;
};
void frag_main()
{
FragColor = fmod(vA, vB);
}
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
{
vA = stage_input.vA;
vB = stage_input.vB;
frag_main();
SPIRV_Cross_Output stage_output;
stage_output.FragColor = FragColor;
return stage_output;
}
@@ -0,0 +1,19 @@
static float4 FragColor;
struct SPIRV_Cross_Output
{
float4 FragColor : SV_Target0;
};
void frag_main()
{
FragColor = 10.0f.xxxx;
}
SPIRV_Cross_Output main()
{
frag_main();
SPIRV_Cross_Output stage_output;
stage_output.FragColor = FragColor;
return stage_output;
}

Some files were not shown because too many files have changed in this diff Show More