Merge branch 'rc/1.40.0' into release
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -7,3 +7,4 @@ for next branch cut* header.
|
||||
appropriate header in [RELEASE_NOTES.md](./RELEASE_NOTES.md).
|
||||
|
||||
## Release notes for next branch cut
|
||||
|
||||
|
||||
@@ -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
@@ -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,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.
|
||||
|
||||
|
||||
@@ -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().
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +178,10 @@ public:
|
||||
return mActiveFeatureLevel;
|
||||
}
|
||||
|
||||
size_t getMaxAutomaticInstances() const noexcept {
|
||||
return CONFIG_MAX_INSTANCES;
|
||||
}
|
||||
|
||||
PostProcessManager const& getPostProcessManager() const noexcept {
|
||||
return mPostProcessManager;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,5 +141,4 @@ inline constexpr bool any(Enum lhs) noexcept {
|
||||
return !none(lhs);
|
||||
}
|
||||
|
||||
|
||||
#endif // TNT_UTILS_BITMASKENUM_H
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Vendored
+4
@@ -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:
|
||||
|
||||
@@ -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}
|
||||
Vendored
+19
@@ -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();
|
||||
}
|
||||
+22
@@ -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();
|
||||
}
|
||||
+22
@@ -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();
|
||||
}
|
||||
+20
@@ -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();
|
||||
}
|
||||
Vendored
+39
@@ -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();
|
||||
}
|
||||
+41
@@ -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();
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
void comp_main()
|
||||
{
|
||||
}
|
||||
|
||||
[numthreads(1, 1, 1)]
|
||||
void main()
|
||||
{
|
||||
comp_main();
|
||||
}
|
||||
+87
@@ -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();
|
||||
}
|
||||
Vendored
+30
@@ -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();
|
||||
}
|
||||
+26
@@ -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;
|
||||
}
|
||||
+26
@@ -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;
|
||||
}
|
||||
Vendored
+30
@@ -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;
|
||||
}
|
||||
+8
@@ -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;
|
||||
}
|
||||
Vendored
+19
@@ -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
Reference in New Issue
Block a user