Compare commits
1 Commits
v1.49.0
...
pf/fix-fee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7244464a3 |
@@ -743,8 +743,6 @@ endif()
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
add_subdirectory(${LIBRARIES}/bluevk)
|
||||
add_subdirectory(${EXTERNAL}/vkmemalloc/tnt)
|
||||
set(SPIRV_HEADERS_SKIP_EXAMPLES ON)
|
||||
add_subdirectory(${EXTERNAL}/spirv-headers)
|
||||
endif()
|
||||
|
||||
set(FILAMENT_SAMPLES_BINARY_DIR ${PROJECT_BINARY_DIR}/samples)
|
||||
|
||||
@@ -7,4 +7,3 @@ 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.49.0'
|
||||
implementation 'com.google.android.filament:filament-android:1.47.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ Here are all the libraries available in the group `com.google.android.filament`:
|
||||
iOS projects can use CocoaPods to install the latest release:
|
||||
|
||||
```shell
|
||||
pod 'Filament', '~> 1.49.0'
|
||||
pod 'Filament', '~> 1.47.0'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -7,11 +7,6 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.49.0
|
||||
|
||||
- matc: Fix ESSL 1.0 codegen when using external samplers [⚠️ **Recompile materials**]
|
||||
- matc: Generate skinning variants for FL0 materials [⚠️ **Recompile materials**]
|
||||
|
||||
## v1.48.0
|
||||
|
||||
- matc: New option `-1` to disable generation of ESSL 1.0 code in Feature Level 0 materials
|
||||
|
||||
@@ -38,7 +38,7 @@ Java_com_google_android_filament_EntityManager_nCreateArray(JNIEnv* env, jclass,
|
||||
// (which it is), but still.
|
||||
em->create((size_t) n, reinterpret_cast<Entity *>(entities));
|
||||
|
||||
env->ReleaseIntArrayElements(entities_, entities, JNI_ABORT);
|
||||
env->ReleaseIntArrayElements(entities_, entities, 0);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
|
||||
@@ -205,7 +205,7 @@ Java_com_google_android_filament_MaterialInstance_nSetIntParameterArray(JNIEnv *
|
||||
break;
|
||||
}
|
||||
|
||||
env->ReleaseIntArrayElements(v_, v, JNI_ABORT);
|
||||
env->ReleaseIntArrayElements(v_, v, 0);
|
||||
|
||||
env->ReleaseStringUTFChars(name_, name);
|
||||
}
|
||||
|
||||
@@ -114,6 +114,6 @@ Java_com_google_android_filament_Scene_nGetEntities(JNIEnv *env, jclass ,
|
||||
out[i++] = (jint) entity.getId();
|
||||
}
|
||||
});
|
||||
env->ReleaseIntArrayElements(outArray, (jint*) out, 0);
|
||||
env->ReleaseIntArrayElements(outArray, (jint*) out, JNI_ABORT);
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
@@ -182,85 +182,28 @@ public class UiHelper {
|
||||
void detach();
|
||||
}
|
||||
|
||||
private class SurfaceViewHandler implements RenderSurface, SurfaceHolder.Callback {
|
||||
@NonNull private final SurfaceView mSurfaceView;
|
||||
private static class SurfaceViewHandler implements RenderSurface {
|
||||
private final SurfaceView mSurfaceView;
|
||||
|
||||
SurfaceViewHandler(@NonNull SurfaceView surfaceView) {
|
||||
mSurfaceView = surfaceView;
|
||||
|
||||
@NonNull SurfaceHolder holder = surfaceView.getHolder();
|
||||
holder.addCallback(this);
|
||||
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceView's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
surfaceCreated(holder);
|
||||
// there is no way to retrieve the actual PixelFormat, since it is not used
|
||||
// in the callback, we can use whatever we want.
|
||||
surfaceChanged(holder, PixelFormat.RGBA_8888,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
SurfaceViewHandler(SurfaceView surface) {
|
||||
mSurfaceView = surface;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height) {
|
||||
@NonNull SurfaceHolder holder = mSurfaceView.getHolder();
|
||||
holder.setFixedSize(width, height);
|
||||
mSurfaceView.getHolder().setFixedSize(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
@NonNull SurfaceHolder holder = mSurfaceView.getHolder();
|
||||
holder.removeCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(
|
||||
@NonNull SurfaceHolder holder, int format, int width, int height) {
|
||||
// Note: this is always called at least once after surfaceCreated()
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
}
|
||||
|
||||
private class SurfaceHolderHandler implements RenderSurface, SurfaceHolder.Callback {
|
||||
private static class SurfaceHolderHandler implements RenderSurface {
|
||||
private final SurfaceHolder mSurfaceHolder;
|
||||
|
||||
SurfaceHolderHandler(@NonNull SurfaceHolder holder) {
|
||||
mSurfaceHolder = holder;
|
||||
holder.addCallback(this);
|
||||
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceHolder's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
surfaceCreated(holder);
|
||||
// there is no way to retrieve the actual PixelFormat, since it is not used
|
||||
// in the callback, we can use whatever we want.
|
||||
surfaceChanged(holder, PixelFormat.RGBA_8888,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
SurfaceHolderHandler(SurfaceHolder surface) {
|
||||
mSurfaceHolder = surface;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -270,55 +213,19 @@ public class UiHelper {
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
mSurfaceHolder.removeCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
|
||||
// Note: this is always called at least once after surfaceCreated()
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
}
|
||||
|
||||
private class TextureViewHandler implements RenderSurface, TextureView.SurfaceTextureListener {
|
||||
private class TextureViewHandler implements RenderSurface {
|
||||
private final TextureView mTextureView;
|
||||
private Surface mSurface;
|
||||
|
||||
TextureViewHandler(@NonNull TextureView view) {
|
||||
mTextureView = view;
|
||||
mTextureView.setSurfaceTextureListener(this);
|
||||
// in case the View's SurfaceTexture already existed
|
||||
if (view.isAvailable()) {
|
||||
SurfaceTexture surfaceTexture = view.getSurfaceTexture();
|
||||
if (surfaceTexture != null) {
|
||||
this.onSurfaceTextureAvailable(surfaceTexture,
|
||||
mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
TextureViewHandler(TextureView surface) { mTextureView = surface; }
|
||||
|
||||
@Override
|
||||
public void resize(int width, int height) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
final SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
|
||||
if (surfaceTexture != null) {
|
||||
surfaceTexture.setDefaultBufferSize(width, height);
|
||||
}
|
||||
mTextureView.getSurfaceTexture().setDefaultBufferSize(width, height);
|
||||
}
|
||||
if (mRenderCallback != null) {
|
||||
// the call above won't cause TextureView.onSurfaceTextureSizeChanged()
|
||||
@@ -328,69 +235,10 @@ public class UiHelper {
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
mTextureView.setSurfaceTextureListener(null);
|
||||
setSurface(null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(
|
||||
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
|
||||
final Surface surface = new Surface(surfaceTexture);
|
||||
setSurface(surface);
|
||||
createSwapChain(surface);
|
||||
|
||||
if (mRenderCallback != null) {
|
||||
// Call this the first time because onSurfaceTextureSizeChanged()
|
||||
// isn't called at initialization time
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(
|
||||
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()");
|
||||
if (mRenderCallback != null) {
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
mRenderCallback.onResized(mDesiredWidth, mDesiredHeight);
|
||||
} else {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
// We must recreate the SwapChain to guarantee that it sees the new size.
|
||||
// More precisely, for an EGL client, the EGLSurface must be recreated. For
|
||||
// a Vulkan client, the SwapChain must be recreated. Calling
|
||||
// onNativeWindowChanged() will accomplish that.
|
||||
// This requirement comes from SurfaceTexture.setDefaultBufferSize()
|
||||
// documentation.
|
||||
final Surface surface = getSurface();
|
||||
if (surface != null) {
|
||||
mRenderCallback.onNativeWindowChanged(surface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()");
|
||||
destroySwapChain();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { }
|
||||
|
||||
|
||||
private void setSurface(@Nullable Surface surface) {
|
||||
void setSurface(Surface surface) {
|
||||
if (surface == null) {
|
||||
if (mSurface != null) {
|
||||
mSurface.release();
|
||||
@@ -399,7 +247,7 @@ public class UiHelper {
|
||||
mSurface = surface;
|
||||
}
|
||||
|
||||
private Surface getSurface() {
|
||||
public Surface getSurface() {
|
||||
return mSurface;
|
||||
}
|
||||
}
|
||||
@@ -554,8 +402,48 @@ public class UiHelper {
|
||||
view.setZOrderOnTop(translucent);
|
||||
}
|
||||
|
||||
view.getHolder().setFormat(isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT);
|
||||
int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
|
||||
view.getHolder().setFormat(format);
|
||||
|
||||
mRenderSurface = new SurfaceViewHandler(view);
|
||||
|
||||
final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(
|
||||
@NonNull SurfaceHolder holder, int format, int width, int height) {
|
||||
// Note: this is always called at least once after surfaceCreated()
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
};
|
||||
|
||||
SurfaceHolder holder = view.getHolder();
|
||||
holder.addCallback(callback);
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceView's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
callback.surfaceCreated(holder);
|
||||
callback.surfaceChanged(holder, format,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +455,76 @@ public class UiHelper {
|
||||
public void attachTo(@NonNull TextureView view) {
|
||||
if (attach(view)) {
|
||||
view.setOpaque(isOpaque());
|
||||
|
||||
mRenderSurface = new TextureViewHandler(view);
|
||||
|
||||
TextureView.SurfaceTextureListener listener = new TextureView.SurfaceTextureListener() {
|
||||
@Override
|
||||
public void onSurfaceTextureAvailable(
|
||||
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureAvailable()");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
|
||||
Surface surface = new Surface(surfaceTexture);
|
||||
TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface;
|
||||
textureViewHandler.setSurface(surface);
|
||||
|
||||
createSwapChain(surface);
|
||||
|
||||
if (mRenderCallback != null) {
|
||||
// Call this the first time because onSurfaceTextureSizeChanged()
|
||||
// isn't called at initialization time
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureSizeChanged(
|
||||
@NonNull SurfaceTexture surfaceTexture, int width, int height) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureSizeChanged()");
|
||||
if (mRenderCallback != null) {
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
surfaceTexture.setDefaultBufferSize(mDesiredWidth, mDesiredHeight);
|
||||
mRenderCallback.onResized(mDesiredWidth, mDesiredHeight);
|
||||
} else {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
// We must recreate the SwapChain to guarantee that it sees the new size.
|
||||
// More precisely, for an EGL client, the EGLSurface must be recreated. For
|
||||
// a Vulkan client, the SwapChain must be recreated. Calling
|
||||
// onNativeWindowChanged() will accomplish that.
|
||||
// This requirement comes from SurfaceTexture.setDefaultBufferSize()
|
||||
// documentation.
|
||||
TextureViewHandler textureViewHandler = (TextureViewHandler) mRenderSurface;
|
||||
mRenderCallback.onNativeWindowChanged(textureViewHandler.getSurface());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "onSurfaceTextureDestroyed()");
|
||||
destroySwapChain();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { }
|
||||
};
|
||||
|
||||
view.setSurfaceTextureListener(listener);
|
||||
|
||||
// in case the View's SurfaceTexture already existed
|
||||
if (view.isAvailable()) {
|
||||
SurfaceTexture surfaceTexture = view.getSurfaceTexture();
|
||||
if (surfaceTexture != null) {
|
||||
listener.onSurfaceTextureAvailable(surfaceTexture, mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,8 +535,46 @@ public class UiHelper {
|
||||
*/
|
||||
public void attachTo(@NonNull SurfaceHolder holder) {
|
||||
if (attach(holder)) {
|
||||
holder.setFormat(isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT);
|
||||
int format = isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
|
||||
holder.setFormat(format);
|
||||
|
||||
mRenderSurface = new SurfaceHolderHandler(holder);
|
||||
|
||||
final SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
|
||||
@Override
|
||||
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceCreated()");
|
||||
createSwapChain(holder.getSurface());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
|
||||
// Note: this is always called at least once after surfaceCreated()
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceChanged(" + width + ", " + height + ")");
|
||||
if (mRenderCallback != null) {
|
||||
mRenderCallback.onResized(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
|
||||
if (LOGGING) Log.d(LOG_TAG, "surfaceDestroyed()");
|
||||
destroySwapChain();
|
||||
}
|
||||
};
|
||||
|
||||
holder.addCallback(callback);
|
||||
if (mDesiredWidth > 0 && mDesiredHeight > 0) {
|
||||
holder.setFixedSize(mDesiredWidth, mDesiredHeight);
|
||||
}
|
||||
|
||||
// in case the SurfaceHolder's surface already existed
|
||||
final Surface surface = holder.getSurface();
|
||||
if (surface != null && surface.isValid()) {
|
||||
callback.surfaceCreated(holder);
|
||||
callback.surfaceChanged(holder, format,
|
||||
holder.getSurfaceFrame().width(), holder.getSurfaceFrame().height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.49.0
|
||||
VERSION_NAME=1.47.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ function replace {
|
||||
FIND_STR="${1//\{\{VERSION\}\}/${VERSION_REGEX}}"
|
||||
REPLACE_STR="${1//\{\{VERSION\}\}/${NEW_VERSION}}"
|
||||
local FILE_NAME="$2"
|
||||
if [ $IS_DARWIN == 1 ]; then
|
||||
if [ IS_DARWIN ]; then
|
||||
sed -i '' -E "s/${FIND_STR}/${REPLACE_STR}/" "${FILE_NAME}"
|
||||
else
|
||||
sed -i -E "s/${FIND_STR}/${REPLACE_STR}/" "${FILE_NAME}"
|
||||
|
||||
@@ -169,8 +169,6 @@ if (FILAMENT_SUPPORTS_VULKAN)
|
||||
src/vulkan/platform/VulkanPlatform.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.cpp
|
||||
src/vulkan/platform/VulkanPlatformSwapChainImpl.h
|
||||
src/vulkan/spirv/VulkanSpirvUtils.cpp
|
||||
src/vulkan/spirv/VulkanSpirvUtils.h
|
||||
src/vulkan/VulkanBlitter.cpp
|
||||
src/vulkan/VulkanBlitter.h
|
||||
src/vulkan/VulkanBuffer.cpp
|
||||
@@ -315,7 +313,6 @@ endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_VULKAN)
|
||||
target_link_libraries(${TARGET} PUBLIC bluevk vkmemalloc vkshaders smol-v)
|
||||
target_link_libraries(${TARGET} PRIVATE SPIRV-Headers)
|
||||
endif()
|
||||
|
||||
if (FILAMENT_SUPPORTS_METAL)
|
||||
|
||||
@@ -1180,8 +1180,8 @@ using FrameScheduledCallback = void(*)(PresentCallable callable, void* user);
|
||||
enum class Workaround : uint16_t {
|
||||
// The EASU pass must split because shader compiler flattens early-exit branch
|
||||
SPLIT_EASU,
|
||||
// Backend allows feedback loop with ancillary buffers (depth/stencil) as long as they're
|
||||
// read-only for the whole render pass.
|
||||
// Backend allows feedback loop with ancillary buffers (depth/stencil) as long as they're read-only for
|
||||
// the whole render pass.
|
||||
ALLOW_READ_ONLY_ANCILLARY_FEEDBACK_LOOP,
|
||||
// for some uniform arrays, it's needed to do an initialization to avoid crash on adreno gpu
|
||||
ADRENO_UNIFORM_ARRAY_CRASH,
|
||||
|
||||
@@ -262,12 +262,7 @@ void OpenGLDriver::bindTexture(GLuint unit, GLTexture const* t) noexcept {
|
||||
mContext.bindTexture(unit, t->gl.target, t->gl.id, t->gl.targetIndex);
|
||||
}
|
||||
|
||||
bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
if (UTILS_UNLIKELY(!p->isValid())) {
|
||||
// If the program is not valid, we can't call use().
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
// set-up textures and samplers in the proper TMUs (as specified in setSamplers)
|
||||
p->use(this, mContext);
|
||||
|
||||
@@ -282,7 +277,6 @@ bool OpenGLDriver::useProgram(OpenGLProgram* p) noexcept {
|
||||
// when mPlatform.isSRGBSwapChainSupported() is false (no need to check though).
|
||||
p->setRec709ColorSpace(mRec709OutputColorspace);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -3537,17 +3531,18 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
|
||||
OpenGLProgram* p = handle_cast<OpenGLProgram*>(state.program);
|
||||
|
||||
bool const success = useProgram(p);
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
// Avoid fatal (or cascading) errors that can occur during the draw call when the program
|
||||
// is invalid. The shader compile error has already been dumped to the console at this
|
||||
// point, so it's fine to simply return early.
|
||||
// If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur
|
||||
// during the draw call when the program is invalid. The shader compile error has already been
|
||||
// dumped to the console at this point, so it's fine to simply return early.
|
||||
if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!p->isValid())) {
|
||||
return;
|
||||
}
|
||||
|
||||
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
useProgram(p);
|
||||
|
||||
GLRenderPrimitive* rp = handle_cast<GLRenderPrimitive *>(rph);
|
||||
|
||||
// Gracefully do nothing if the render primitive has not been set up.
|
||||
VertexBufferHandle vb = rp->gl.vertexBufferWithObjects;
|
||||
@@ -3558,7 +3553,7 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
|
||||
gl.bindVertexArray(&rp->gl);
|
||||
|
||||
// If necessary, mutate the bindings in the VAO.
|
||||
GLVertexBuffer const* const glvb = handle_cast<GLVertexBuffer*>(vb);
|
||||
const GLVertexBuffer* glvb = handle_cast<GLVertexBuffer*>(vb);
|
||||
if (UTILS_UNLIKELY(rp->gl.vertexBufferVersion != glvb->bufferObjectsVersion)) {
|
||||
updateVertexArrayObject(rp, glvb);
|
||||
}
|
||||
@@ -3592,16 +3587,17 @@ void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph, uint
|
||||
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
getShaderCompilerService().tick();
|
||||
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(program);
|
||||
OpenGLProgram* p = handle_cast<OpenGLProgram*>(program);
|
||||
|
||||
bool const success = useProgram(p);
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
// Avoid fatal (or cascading) errors that can occur during the draw call when the program
|
||||
// is invalid. The shader compile error has already been dumped to the console at this
|
||||
// point, so it's fine to simply return early.
|
||||
// If the material debugger is enabled, avoid fatal (or cascading) errors and that can occur
|
||||
// during the draw call when the program is invalid. The shader compile error has already been
|
||||
// dumped to the console at this point, so it's fine to simply return early.
|
||||
if (FILAMENT_ENABLE_MATDBG && UTILS_UNLIKELY(!p->isValid())) {
|
||||
return;
|
||||
}
|
||||
|
||||
useProgram(p);
|
||||
|
||||
#if defined(BACKEND_OPENGL_LEVEL_GLES31)
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
|
||||
@@ -318,7 +318,7 @@ private:
|
||||
|
||||
void bindTexture(GLuint unit, GLTexture const* t) noexcept;
|
||||
void bindSampler(GLuint unit, GLuint sampler) noexcept;
|
||||
inline bool useProgram(OpenGLProgram* p) noexcept;
|
||||
inline void useProgram(OpenGLProgram* p) noexcept;
|
||||
|
||||
enum class ResolveAction { LOAD, STORE };
|
||||
void resolvePass(ResolveAction action, GLRenderTarget const* rt,
|
||||
|
||||
@@ -129,7 +129,7 @@ void OpenGLProgram::initializeProgramState(OpenGLContext& context, GLuint progra
|
||||
#endif
|
||||
{
|
||||
// ES2 initialization of (fake) UBOs
|
||||
UniformsRecord* const uniformsRecords = new(std::nothrow) UniformsRecord[Program::UNIFORM_BINDING_COUNT];
|
||||
UniformsRecord* const uniformsRecords = new UniformsRecord[Program::UNIFORM_BINDING_COUNT];
|
||||
UTILS_NOUNROLL
|
||||
for (GLuint binding = 0, n = Program::UNIFORM_BINDING_COUNT; binding < n; binding++) {
|
||||
Program::UniformInfo& uniforms = lazyInitializationData.bindingUniformInfo[binding];
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
// - the content of any bound sampler buffer has changed
|
||||
// ... since last time we used this program
|
||||
|
||||
// Turns out the former might be relatively cheap to check, the latter requires
|
||||
// turns out the former might be relatively cheap to check, the later requires
|
||||
// a bit less. Compared to what updateSamplers() actually does, which is
|
||||
// pretty little, I'm not sure if we'll get ahead.
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <private/backend/BackendUtils.h>
|
||||
|
||||
#include <backend/platforms/OpenGLPlatform.h>
|
||||
#include <backend/Program.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
@@ -253,6 +254,8 @@ ShaderCompilerService::program_token_t ShaderCompilerService::createProgram(
|
||||
glGetProgramiv(glProgram, GL_LINK_STATUS, &status);
|
||||
programData.program = glProgram;
|
||||
|
||||
token->gl.program = programData.program;
|
||||
|
||||
// we don't need to check for success here, it'll be done on the
|
||||
// main thread side.
|
||||
token->set(programData);
|
||||
@@ -334,12 +337,12 @@ GLuint ShaderCompilerService::getProgram(ShaderCompilerService::program_token_t&
|
||||
return program;
|
||||
}
|
||||
|
||||
void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
/* static*/ void ShaderCompilerService::terminate(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
token->canceled = true;
|
||||
|
||||
bool const canceled = token->compiler.cancelTickOp(token);
|
||||
bool canceled = token->compiler.cancelTickOp(token);
|
||||
|
||||
if (token->compiler.mShaderCompilerThreadCount) {
|
||||
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||
|
||||
@@ -54,8 +54,7 @@ VmaAllocator createAllocator(VkInstance instance, VkPhysicalDevice physicalDevic
|
||||
VmaAllocator allocator;
|
||||
VmaVulkanFunctions const funcs {
|
||||
#if VMA_DYNAMIC_VULKAN_FUNCTIONS
|
||||
.vkGetInstanceProcAddr = vkGetInstanceProcAddr,
|
||||
.vkGetDeviceProcAddr = vkGetDeviceProcAddr,
|
||||
.vkGetInstanceProcAddr = vkGetInstanceProcAddr, .vkGetDeviceProcAddr = vkGetDeviceProcAddr,
|
||||
#else
|
||||
.vkGetPhysicalDeviceProperties = vkGetPhysicalDeviceProperties,
|
||||
.vkGetPhysicalDeviceMemoryProperties = vkGetPhysicalDeviceMemoryProperties,
|
||||
@@ -427,8 +426,7 @@ void VulkanDriver::destroyTexture(Handle<HwTexture> th) {
|
||||
}
|
||||
|
||||
void VulkanDriver::createProgramR(Handle<HwProgram> ph, Program&& program) {
|
||||
auto vkprogram
|
||||
= mResourceAllocator.construct<VulkanProgram>(ph, mPlatform->getDevice(), program);
|
||||
auto vkprogram = mResourceAllocator.construct<VulkanProgram>(ph, mPlatform->getDevice(), program);
|
||||
mResourceManager.acquire(vkprogram);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,10 @@
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanMemory.h"
|
||||
#include "spirv/VulkanSpirvUtils.h"
|
||||
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
|
||||
#include <utils/Panic.h> // ASSERT_POSTCONDITION
|
||||
#include <utils/Panic.h>
|
||||
|
||||
using namespace bluevk;
|
||||
|
||||
@@ -47,39 +46,61 @@ static void clampToFramebuffer(VkRect2D* rect, uint32_t fbWidth, uint32_t fbHeig
|
||||
rect->extent.height = std::max(top - y, 0);
|
||||
}
|
||||
|
||||
VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
VulkanProgram::VulkanProgram(VkDevice device, const Program& builder) noexcept
|
||||
: HwProgram(builder.getName()),
|
||||
VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mInfo(new PipelineInfo()),
|
||||
mInfo(new PipelineInfo(builder.getSpecializationConstants().size())),
|
||||
mDevice(device) {
|
||||
Program::ShaderSource const& blobs = builder.getShadersSource();
|
||||
auto& blobs = builder.getShadersSource();
|
||||
auto& modules = mInfo->shaders;
|
||||
|
||||
auto const& specializationConstants = builder.getSpecializationConstants();
|
||||
|
||||
std::vector<uint32_t> shader;
|
||||
for (size_t i = 0; i < MAX_SHADER_MODULES; i++) {
|
||||
Program::ShaderBlob const& blob = blobs[i];
|
||||
|
||||
uint32_t* data = (uint32_t*) blob.data();
|
||||
size_t dataSize = blob.size();
|
||||
|
||||
if (!specializationConstants.empty()) {
|
||||
workaroundSpecConstant(blob, specializationConstants, shader);
|
||||
data = (uint32_t*) shader.data();
|
||||
dataSize = shader.size() * 4;
|
||||
}
|
||||
|
||||
const auto& blob = blobs[i];
|
||||
uint32_t* data = (uint32_t*)blob.data();
|
||||
VkShaderModule& module = modules[i];
|
||||
VkShaderModuleCreateInfo moduleInfo = {
|
||||
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
||||
.codeSize = dataSize,
|
||||
.codeSize = blob.size(),
|
||||
.pCode = data,
|
||||
};
|
||||
VkResult result = vkCreateShaderModule(mDevice, &moduleInfo, VKALLOC, &module);
|
||||
ASSERT_POSTCONDITION(result == VK_SUCCESS, "Unable to create shader module.");
|
||||
}
|
||||
|
||||
// Note that bools are 4-bytes in Vulkan
|
||||
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkBool32.html
|
||||
constexpr uint32_t const CONSTANT_SIZE = 4;
|
||||
|
||||
// populate the specialization constants requirements right now
|
||||
auto const& specializationConstants = builder.getSpecializationConstants();
|
||||
uint32_t const specConstCount = static_cast<uint32_t>(specializationConstants.size());
|
||||
char* specData = mInfo->specConstData.get();
|
||||
if (specConstCount > 0) {
|
||||
mInfo->specializationInfo = {
|
||||
.mapEntryCount = specConstCount,
|
||||
.pMapEntries = mInfo->specConsts.data(),
|
||||
.dataSize = specConstCount * CONSTANT_SIZE,
|
||||
.pData = specData,
|
||||
};
|
||||
}
|
||||
for (uint32_t i = 0; i < specConstCount; ++i) {
|
||||
uint32_t const offset = i * CONSTANT_SIZE;
|
||||
mInfo->specConsts[i] = {
|
||||
.constantID = specializationConstants[i].id,
|
||||
.offset = offset,
|
||||
.size = CONSTANT_SIZE,
|
||||
};
|
||||
using SpecConstant = Program::SpecializationConstant::Type;
|
||||
char const* addr = (char*)specData + offset;
|
||||
SpecConstant const& arg = specializationConstants[i].value;
|
||||
if (std::holds_alternative<bool>(arg)) {
|
||||
*((VkBool32*)addr) = std::get<bool>(arg) ? VK_TRUE : VK_FALSE;
|
||||
} else if (std::holds_alternative<float>(arg)) {
|
||||
*((float*)addr) = std::get<float>(arg);
|
||||
} else {
|
||||
*((int32_t*)addr) = std::get<int32_t>(arg);
|
||||
}
|
||||
}
|
||||
|
||||
auto& groupInfo = builder.getSamplerGroupInfo();
|
||||
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
|
||||
auto& usage = mInfo->usage;
|
||||
@@ -102,7 +123,7 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
VulkanProgram::VulkanProgram(VkDevice device, VkShaderModule vs, VkShaderModule fs,
|
||||
CustomSamplerInfoList const& samplerInfo) noexcept
|
||||
: VulkanResource(VulkanResourceType::PROGRAM),
|
||||
mInfo(new PipelineInfo()),
|
||||
mInfo(new PipelineInfo(0)),
|
||||
mDevice(device) {
|
||||
mInfo->shaders[0] = vs;
|
||||
mInfo->shaders[1] = fs;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "VulkanResources.h"
|
||||
#include "VulkanSwapChain.h"
|
||||
#include "VulkanTexture.h"
|
||||
#include "VulkanUtility.h"
|
||||
|
||||
#include "private/backend/SamplerGroup.h"
|
||||
|
||||
@@ -37,7 +38,7 @@ class VulkanTimestamps;
|
||||
|
||||
struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
|
||||
VulkanProgram(VkDevice device, Program const& builder) noexcept;
|
||||
VulkanProgram(VkDevice device, const Program& builder) noexcept;
|
||||
|
||||
struct CustomSamplerInfo {
|
||||
uint8_t groupIndex;
|
||||
@@ -64,14 +65,21 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
return mInfo->bindingToSamplerIndex;
|
||||
}
|
||||
|
||||
inline VkSpecializationInfo const& getSpecConstInfo() const {
|
||||
return mInfo->specializationInfo;
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO: handle compute shaders.
|
||||
// The expected order of shaders - from frontend to backend - is vertex, fragment, compute.
|
||||
static constexpr uint8_t const MAX_SHADER_MODULES = 2;
|
||||
static constexpr uint8_t MAX_SHADER_MODULES = 2;
|
||||
|
||||
struct PipelineInfo {
|
||||
PipelineInfo()
|
||||
: bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff) {}
|
||||
PipelineInfo(size_t specConstsCount) :
|
||||
bindingToSamplerIndex(MAX_SAMPLER_COUNT, 0xffff),
|
||||
specConsts(specConstsCount, VkSpecializationMapEntry{}),
|
||||
specConstData(new char[specConstsCount * 4])
|
||||
{}
|
||||
|
||||
// This bitset maps to each of the sampler in the sampler groups associated with this
|
||||
// program, and whether each sampler is used in which shader (i.e. vert, frag, compute).
|
||||
@@ -79,7 +87,10 @@ private:
|
||||
|
||||
// We store the samplerGroupIndex as the top 8-bit and the index within each group as the lower 8-bit.
|
||||
utils::FixedCapacityVector<uint16_t> bindingToSamplerIndex;
|
||||
VkShaderModule shaders[MAX_SHADER_MODULES] = { VK_NULL_HANDLE };
|
||||
VkShaderModule shaders[MAX_SHADER_MODULES] = {VK_NULL_HANDLE};
|
||||
VkSpecializationInfo specializationInfo = {};
|
||||
utils::FixedCapacityVector<VkSpecializationMapEntry> specConsts;
|
||||
std::unique_ptr<char[]> specConstData;
|
||||
};
|
||||
|
||||
PipelineInfo* mInfo;
|
||||
|
||||
@@ -360,7 +360,9 @@ VulkanPipelineCache::PipelineCacheEntry* VulkanPipelineCache::createPipeline() n
|
||||
|
||||
// If we reach this point, we need to create and stash a brand new pipeline object.
|
||||
shaderStages[0].module = mPipelineRequirements.shaders[0];
|
||||
shaderStages[0].pSpecializationInfo = mSpecializationRequirements;
|
||||
shaderStages[1].module = mPipelineRequirements.shaders[1];
|
||||
shaderStages[1].pSpecializationInfo = mSpecializationRequirements;
|
||||
|
||||
// Expand our size-optimized structs into the proper Vk structs.
|
||||
uint32_t numVertexAttribs = 0;
|
||||
@@ -566,6 +568,7 @@ VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreateP
|
||||
void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept {
|
||||
mPipelineRequirements.shaders[0] = program->getVertexShader();
|
||||
mPipelineRequirements.shaders[1] = program->getFragmentShader();
|
||||
mSpecializationRequirements = &program->getSpecConstInfo();
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindRasterState(const RasterState& rasterState) noexcept {
|
||||
|
||||
@@ -416,6 +416,7 @@ private:
|
||||
RasterState mCurrentRasterState;
|
||||
PipelineKey mPipelineRequirements = {};
|
||||
DescriptorKey mDescriptorRequirements = {};
|
||||
VkSpecializationInfo const* mSpecializationRequirements = nullptr;
|
||||
|
||||
// Current bindings for the pipeline and descriptor sets.
|
||||
PipelineKey mBoundPipeline = {};
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* 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 "VulkanSpirvUtils.h"
|
||||
|
||||
#include <utils/compiler.h> // UTILS_UNUSED_IN_RELEASE, UTILS_FALLTHROUGH
|
||||
#include <utils/debug.h> // assert_invariant
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <spirv/unified1/spirv.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
namespace {
|
||||
|
||||
// This function transforms an OpSpecConstant instruction into just a OpConstant instruction.
|
||||
// Additionally, it will adjust the value of the constant as given by the program.
|
||||
void getTransformedConstantInst(SpecConstantValue* value, uint32_t* inst) {
|
||||
|
||||
// The first word is the size of the instruction and the instruction type.
|
||||
// The second word is the type of the instruction.
|
||||
// The third word is the "result id" (i.e. the variable to store into).
|
||||
// The fourth word (only for floats and ints) is the literal representing the value.
|
||||
// We only want to change the first and fourth words.
|
||||
constexpr size_t const OP = 0;
|
||||
constexpr size_t const VALUE = 3;
|
||||
|
||||
if (!value) {
|
||||
// If a specialization wasn't provided, just switch the opcode.
|
||||
uint32_t const op = inst[OP] & 0x0000FFFF;
|
||||
if (op == spv::Op::OpSpecConstantFalse) {
|
||||
inst[OP] = (3 << 16) | spv::Op::OpConstantFalse;
|
||||
} else if (op == spv::Op::OpSpecConstantTrue) {
|
||||
inst[OP] = (3 << 16) | spv::Op::OpConstantTrue;
|
||||
} else if (op == spv::Op::OpSpecConstant) {
|
||||
inst[OP] = (4 << 16) | spv::Op::OpConstant;
|
||||
}
|
||||
} else if (std::holds_alternative<bool>(*value)) {
|
||||
inst[OP] = (3 << 16)
|
||||
| (std::get<bool>(*value) ? spv::Op::OpConstantTrue : spv::Op::OpConstantFalse);
|
||||
} else if (std::holds_alternative<float>(*value)) {
|
||||
float const fval = std::get<float>(*value);
|
||||
inst[OP] = (4 << 16) | spv::Op::OpConstant;
|
||||
inst[VALUE] = *reinterpret_cast<uint32_t const*>(&fval);
|
||||
} else {
|
||||
int const ival = std::get<int>(*value);
|
||||
inst[OP] = (4 << 16) | spv::Op::OpConstant;
|
||||
inst[VALUE] = *reinterpret_cast<uint32_t const*>(&ival);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void workaroundSpecConstant(Program::ShaderBlob const& blob,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants,
|
||||
std::vector<uint32_t>& output) {
|
||||
using WordMap = std::unordered_map<uint32_t, uint32_t>;
|
||||
using SpecValueMap = std::unordered_map<uint32_t, SpecConstantValue>;
|
||||
constexpr size_t const HEADER_SIZE = 5;
|
||||
|
||||
WordMap varToIdMap;
|
||||
SpecValueMap idToValue;
|
||||
|
||||
for (auto spec: specConstants) {
|
||||
idToValue[spec.id] = spec.value;
|
||||
}
|
||||
|
||||
// The follow loop will iterate through all the instructions and look for instructions of the
|
||||
// form:
|
||||
//
|
||||
// OpDecorate %1 SpecId 0
|
||||
// %1 = OpSpecConstantFalse %bool
|
||||
//
|
||||
// We want to track the mapping between the variable id %1 and the specId 0. And when seeing a
|
||||
// %1 as the result, we want to change that instruction to just an OpConsant where the constant
|
||||
// value is adjusted by the spec values provided in the program.
|
||||
// Additionally, we will turn the SpecId decorations to no-ops.
|
||||
size_t const dataSize = blob.size() / 4;
|
||||
|
||||
output.resize(dataSize);
|
||||
uint32_t const* data = (uint32_t*) blob.data();
|
||||
uint32_t* outputData = output.data();
|
||||
|
||||
std::memcpy(&outputData[0], &data[0], HEADER_SIZE * 4);
|
||||
size_t outputCursor = HEADER_SIZE;
|
||||
|
||||
for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) {
|
||||
uint32_t const firstWord = data[cursor];
|
||||
uint32_t const wordCount = firstWord >> 16;
|
||||
uint32_t const op = firstWord & 0x0000FFFF;
|
||||
|
||||
switch(op) {
|
||||
case spv::Op::OpSpecConstant:
|
||||
case spv::Op::OpSpecConstantTrue:
|
||||
case spv::Op::OpSpecConstantFalse: {
|
||||
uint32_t const targetVar = data[cursor + 2];
|
||||
|
||||
UTILS_UNUSED_IN_RELEASE WordMap::const_iterator idItr = varToIdMap.find(targetVar);
|
||||
assert_invariant(idItr != varToIdMap.end() &&
|
||||
"Cannot find variable previously decorated with SpecId");
|
||||
|
||||
auto valItr = idToValue.find(idItr->second);
|
||||
|
||||
SpecConstantValue* val = (valItr != idToValue.end()) ? &valItr->second : nullptr;
|
||||
std::memcpy(&outputData[outputCursor], &data[cursor], wordCount * 4);
|
||||
getTransformedConstantInst(val, &outputData[outputCursor]);
|
||||
outputCursor += wordCount;
|
||||
break;
|
||||
}
|
||||
case spv::Op::OpDecorate: {
|
||||
if (data[cursor + 2] == spv::Decoration::DecorationSpecId) {
|
||||
uint32_t const targetVar = data[cursor + 1];
|
||||
uint32_t const specId = data[cursor + 3];
|
||||
varToIdMap[targetVar] = specId;
|
||||
|
||||
// Note these decorations do not need to be written to the output.
|
||||
break;
|
||||
}
|
||||
// else fallthrough and copy like all other instructions
|
||||
UTILS_FALLTHROUGH;
|
||||
}
|
||||
default:
|
||||
std::memcpy(&outputData[outputCursor], &data[cursor], wordCount * 4);
|
||||
outputCursor += wordCount;
|
||||
break;
|
||||
}
|
||||
cursor += wordCount;
|
||||
}
|
||||
output.resize(outputCursor);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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_VULKANSPIRVUTILS_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H
|
||||
|
||||
#include "backend/Program.h"
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
using SpecConstantValue = Program::SpecializationConstant::Type;
|
||||
|
||||
// For certain drivers, using spec constant can lead to compile errors [1] or undesirable behaviors
|
||||
// [2]. In those instances, we simply change the spirv and set them to constants.
|
||||
//
|
||||
// (Implemenation note: we cannot write to the blob because spirv-validator does not properly handle
|
||||
// the Nop (no-op) instruction, and swiftshader validates the shader before compilation. So we need
|
||||
// to skip those instructions instead).
|
||||
//
|
||||
// [1]: QC driver cannot use spec constant to size fields
|
||||
// (https://github.com/google/filament/issues/6444).
|
||||
// [2]: An internal driver does not DCE a block guarded by a spec-const boolean set to false
|
||||
// (b/310603393).
|
||||
void workaroundSpecConstant(Program::ShaderBlob const& blob,
|
||||
utils::FixedCapacityVector<Program::SpecializationConstant> const& specConstants,
|
||||
std::vector<uint32_t>& output);
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_VULKANSPIRVUTILS_H
|
||||
@@ -18,11 +18,16 @@
|
||||
|
||||
#include <private/backend/PlatformFactory.h>
|
||||
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
#include <backend/platforms/VulkanPlatform.h>
|
||||
#endif
|
||||
|
||||
#include "BackendTest.h"
|
||||
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include "ShaderGenerator.h"
|
||||
#include "TrianglePrimitive.h"
|
||||
@@ -33,6 +38,25 @@ static constexpr size_t CONFIG_COMMAND_BUFFERS_SIZE = 3 * CONFIG_MIN_COMMAND
|
||||
using namespace filament;
|
||||
using namespace filament::backend;
|
||||
|
||||
#if defined(FILAMENT_DRIVER_SUPPORTS_VULKAN)
|
||||
namespace filament {
|
||||
class InternalVulkanPlatform : public VulkanPlatform {
|
||||
public:
|
||||
InternalVulkanPlatform() {
|
||||
mCustomization = {
|
||||
.gpu = { .index = 0 },
|
||||
};
|
||||
}
|
||||
|
||||
virtual VulkanPlatform::Customization getCustomization() const noexcept override {
|
||||
return mCustomization;
|
||||
}
|
||||
private:
|
||||
VulkanPlatform::Customization mCustomization;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef IOS
|
||||
#include <imageio/ImageEncoder.h>
|
||||
#include <image/ColorTransform.h>
|
||||
@@ -40,6 +64,11 @@ using namespace filament::backend;
|
||||
using namespace image;
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
std::string const SHADER_VULKAN_TAG = "// select(vulkan) ";
|
||||
std::string const SHADER_METAL_TAG = "// select(metal) ";
|
||||
}
|
||||
|
||||
namespace test {
|
||||
|
||||
Backend BackendTest::sBackend = Backend::NOOP;
|
||||
@@ -67,7 +96,14 @@ BackendTest::~BackendTest() {
|
||||
|
||||
void BackendTest::initializeDriver() {
|
||||
auto backend = static_cast<filament::backend::Backend>(sBackend);
|
||||
Platform* platform = PlatformFactory::create(&backend);
|
||||
|
||||
Platform* platform = nullptr;
|
||||
if (backend == filament::backend::Backend::VULKAN) {
|
||||
platform = new InternalVulkanPlatform();
|
||||
} else {
|
||||
platform = PlatformFactory::create(&backend);
|
||||
}
|
||||
|
||||
assert_invariant(static_cast<uint8_t>(backend) == static_cast<uint8_t>(sBackend));
|
||||
Platform::DriverConfig const driverConfig;
|
||||
driver = platform->createDriver(nullptr, driverConfig);
|
||||
@@ -193,6 +229,32 @@ void BackendTest::readPixelsAndAssertHash(const char* testName, size_t width, si
|
||||
getDriverApi().readPixels(rt, 0, 0, width, height, std::move(pbd));
|
||||
}
|
||||
|
||||
std::string BackendTest::transformShaderText(std::string const& shader) const {
|
||||
std::string replacement;
|
||||
switch (sBackend) {
|
||||
case test::Backend::VULKAN:
|
||||
replacement = SHADER_VULKAN_TAG;
|
||||
break;
|
||||
case test::Backend::METAL:
|
||||
replacement = SHADER_METAL_TAG;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (replacement.size() == 0) {
|
||||
return shader;
|
||||
}
|
||||
std::string replaced = shader;
|
||||
while (true) {
|
||||
size_t pos = replaced.find(replacement);
|
||||
if (pos == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
replaced.replace(pos, replacement.length(), "");
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
|
||||
class Environment : public ::testing::Environment {
|
||||
public:
|
||||
virtual void SetUp() override {
|
||||
|
||||
@@ -66,6 +66,8 @@ protected:
|
||||
filament::backend::DriverApi& getDriverApi() { return *commandStream; }
|
||||
filament::backend::Driver& getDriver() { return *driver; }
|
||||
|
||||
std::string transformShaderText(std::string const& shader) const;
|
||||
|
||||
private:
|
||||
|
||||
filament::backend::Driver* driver = nullptr;
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
using namespace image;
|
||||
#endif
|
||||
|
||||
#define DUMP_INTERMEDIATE_SCREENSHOTS 1
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Shaders
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -42,6 +44,7 @@ layout(location = 0) in vec4 mesh_position;
|
||||
void main() {
|
||||
// Hack: move and scale triangle so that it covers entire viewport.
|
||||
gl_Position = vec4((mesh_position.xy + 0.5) * 5.0, 0.0, 1.0);
|
||||
// select(vulkan) gl_Position.y = - gl_Position.y;
|
||||
})";
|
||||
|
||||
static std::string fullscreenFs = R"(#version 450 core
|
||||
@@ -50,7 +53,7 @@ layout(location = 0) out vec4 fragColor;
|
||||
|
||||
// Filament's Vulkan backend requires a descriptor set index of 1 for all samplers.
|
||||
// This parameter is ignored for other backends.
|
||||
layout(location = 0, set = 1) uniform sampler2D test_tex;
|
||||
layout(binding = 0, set = 1) uniform sampler2D test_tex;
|
||||
|
||||
uniform Params {
|
||||
highp float fbWidth;
|
||||
@@ -60,7 +63,18 @@ uniform Params {
|
||||
} params;
|
||||
void main() {
|
||||
vec2 fbsize = vec2(params.fbWidth, params.fbHeight);
|
||||
vec2 uv = (gl_FragCoord.xy + 0.5) / fbsize;
|
||||
float offset = 0;
|
||||
// select(vulkan) offset = -offset;
|
||||
vec2 uv = (gl_FragCoord.xy + offset) / fbsize;
|
||||
// select(vulkan) uv.y = 1.0 - uv.y;
|
||||
vec4 oo = vec4(0, 0, 0, 0);
|
||||
if (uv.x > .5) {
|
||||
oo += vec4(0.5, 0.0, 0.0, 1.0);
|
||||
}
|
||||
if (uv.y > .5) {
|
||||
oo += vec4(0.0, 0.5, 0.0, 1.0);
|
||||
}
|
||||
// fragColor = oo;
|
||||
fragColor = textureLod(test_tex, uv, params.sourceLevel);
|
||||
})";
|
||||
|
||||
@@ -99,23 +113,58 @@ static void uploadUniforms(DriverApi& dapi, Handle<HwBufferObject> ubh, Material
|
||||
dapi.updateBufferObject(ubh, std::move(bd), 0);
|
||||
}
|
||||
|
||||
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> rt) {
|
||||
const size_t size = kTexWidth * kTexHeight * 4;
|
||||
static void readScreenshot(DriverApi& dapi, Handle<HwRenderTarget> renderTargets[kNumLevels],
|
||||
uint32_t target, std::function<void(void*)> output) {
|
||||
Handle<HwRenderTarget> rt = renderTargets[target];
|
||||
size_t const readWidth = kTexWidth >> target;
|
||||
size_t const readHeight = kTexHeight >> target;
|
||||
size_t const size = readWidth * readHeight * 4;
|
||||
void* buffer = calloc(1, size);
|
||||
auto cb = [](void* buffer, size_t size, void* user) {
|
||||
int w = kTexWidth, h = kTexHeight;
|
||||
const uint32_t* texels = (uint32_t*) buffer;
|
||||
sPixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
|
||||
#ifndef IOS
|
||||
LinearImage image(w, h, 4);
|
||||
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
|
||||
std::ofstream pngstrm("feedback.png", std::ios::binary | std::ios::trunc);
|
||||
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", "feedback.png");
|
||||
#endif
|
||||
free(buffer);
|
||||
struct ReadPixelUserData {
|
||||
std::function<void(void*)> onRead;
|
||||
};
|
||||
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb);
|
||||
dapi.readPixels(rt, 0, 0, kTexWidth, kTexHeight, std::move(pb));
|
||||
ReadPixelUserData* userdata = new ReadPixelUserData { output };
|
||||
auto cb = [](void* buffer, size_t size, void* user) {
|
||||
ReadPixelUserData* data = (ReadPixelUserData*) user;
|
||||
data->onRead(buffer);
|
||||
free(buffer);
|
||||
delete data;
|
||||
};
|
||||
PixelBufferDescriptor pb(buffer, size, PixelDataFormat::RGBA, PixelDataType::UBYTE, cb,
|
||||
userdata);
|
||||
dapi.readPixels(rt, 0, 0, readWidth, readHeight, std::move(pb));
|
||||
}
|
||||
|
||||
static void dumpScreenshot(DriverApi& dapi, Handle<HwRenderTarget> renderTargets[kNumLevels],
|
||||
uint32_t target, uint32_t numFrame, bool downscale, test::Backend backend,
|
||||
std::string fname="") {
|
||||
Handle<HwRenderTarget> rt = renderTargets[target];
|
||||
size_t const readWidth = kTexWidth >> target;
|
||||
size_t const readHeight = kTexHeight >> target;
|
||||
size_t const size = readWidth * readHeight * 4;
|
||||
if (fname.empty()) {
|
||||
std::string backendStr = "gl";
|
||||
if (backend == test::Backend::METAL) {
|
||||
backendStr = "mtl";
|
||||
} else if (backend == test::Backend::VULKAN) {
|
||||
backendStr = "vk";
|
||||
}
|
||||
std::string const down = downscale ? "down" : "up";
|
||||
fname = "feedback_" + backendStr + "_" + std::to_string(numFrame) + "_" + down + "_" +
|
||||
std::to_string(target) + ".png";
|
||||
}
|
||||
utils::slog.e <<"writing: " << fname << utils::io::endl;
|
||||
readScreenshot(dapi, renderTargets, target,
|
||||
[w = readWidth, h = readHeight, size, fname](void* buffer) {
|
||||
uint32_t const* texels = (uint32_t*) buffer;
|
||||
sPixelHashResult = utils::hash::murmur3(texels, size / 4, 0);
|
||||
#ifndef IOS
|
||||
LinearImage image(w, h, 4);
|
||||
image = toLinearWithAlpha<uint8_t>(w, h, w * 4, (uint8_t*) buffer);
|
||||
std::ofstream pngstrm(fname.c_str(), std::ios::binary | std::ios::trunc);
|
||||
ImageEncoder::encode(pngstrm, ImageEncoder::Format::PNG, image, "", fname.c_str());
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: This test needs work to get Metal and OpenGL to agree on results.
|
||||
@@ -131,6 +180,8 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
auto swapChain = createSwapChain();
|
||||
api.makeCurrent(swapChain, swapChain);
|
||||
|
||||
bool const backendIsVulkan = sBackend == test::Backend::VULKAN;
|
||||
|
||||
// Create a program.
|
||||
ProgramHandle program;
|
||||
{
|
||||
@@ -139,10 +190,12 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
.stageFlags(backend::ShaderStageFlags::ALL_SHADER_STAGE_FLAGS)
|
||||
.add( {{"tex", SamplerType::SAMPLER_2D, SamplerFormat::FLOAT, Precision::HIGH }} )
|
||||
.build();
|
||||
ShaderGenerator shaderGen(fullscreenVs, fullscreenFs, sBackend, sIsMobilePlatform, &sib);
|
||||
ShaderGenerator shaderGen(transformShaderText(fullscreenVs),
|
||||
transformShaderText(fullscreenFs), sBackend, sIsMobilePlatform, &sib);
|
||||
Program prog = shaderGen.getProgram(api);
|
||||
Program::Sampler psamplers[] = { utils::CString("test_tex"), 0 };
|
||||
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers, sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
Program::Sampler psamplers[] = {utils::CString("test_tex"), 0};
|
||||
prog.setSamplerGroup(0, ShaderStageFlags::ALL_SHADER_STAGE_FLAGS, psamplers,
|
||||
sizeof(psamplers) / sizeof(psamplers[0]));
|
||||
prog.uniformBlockBindings({{"params", 1}});
|
||||
program = api.createProgram(std::move(prog));
|
||||
}
|
||||
@@ -167,10 +220,25 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
const size_t size = kTexHeight * kTexWidth * 4;
|
||||
uint8_t* buffer = (uint8_t*) malloc(size);
|
||||
for (int r = 0, i = 0; r < kTexHeight; r++) {
|
||||
int const adjustedR = backendIsVulkan ? r : kTexHeight - r - 1;
|
||||
for (int c = 0; c < kTexWidth; c++, i += 4) {
|
||||
auto hval = 0x1f * c / (kTexWidth - 1);
|
||||
if (hval > 0x05) {
|
||||
hval = 0x1f;
|
||||
} else {
|
||||
hval = 0x00;
|
||||
}
|
||||
auto vval = 0x1f * adjustedR / (kTexHeight - 1);
|
||||
if (vval > 0x05) {
|
||||
vval = 0x1f;
|
||||
} else {
|
||||
vval = 0x00;
|
||||
}
|
||||
buffer[i + 0] = 0x10;
|
||||
buffer[i + 1] = 0x1f * r / (kTexHeight - 1);
|
||||
buffer[i + 1] = 0x1f * adjustedR / (kTexHeight - 1);
|
||||
// buffer[i + 1] = vval;
|
||||
buffer[i + 2] = 0x1f * c / (kTexWidth - 1);
|
||||
// buffer[i + 2] = hval;
|
||||
buffer[i + 3] = 0xf0;
|
||||
}
|
||||
}
|
||||
@@ -179,7 +247,6 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
api.update3DImage(texture, 0, 0, 0, 0, kTexWidth, kTexHeight, 1, std::move(pb));
|
||||
|
||||
for (int frame = 0; frame < kNumFrames; frame++) {
|
||||
|
||||
// Prep for rendering.
|
||||
RenderPassParams params = {};
|
||||
params.flags.clear = TargetBufferFlags::NONE;
|
||||
@@ -191,8 +258,9 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
state.program = program;
|
||||
SamplerGroup samplers(1);
|
||||
SamplerParams sparams = {};
|
||||
sparams.filterMag = SamplerMagFilter::LINEAR;
|
||||
sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
|
||||
sparams.filterMag = SamplerMagFilter::NEAREST;
|
||||
// sparams.filterMin = SamplerMinFilter::LINEAR_MIPMAP_NEAREST;
|
||||
sparams.filterMin = SamplerMinFilter::NEAREST;
|
||||
samplers.setSampler(0, { texture, sparams });
|
||||
auto sgroup =
|
||||
api.createSamplerGroup(samplers.getSize(), utils::FixedSizeString<32>("Test"));
|
||||
@@ -207,19 +275,24 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
// Downsample passes
|
||||
params.flags.discardStart = TargetBufferFlags::ALL;
|
||||
state.rasterState.disableBlending();
|
||||
for (int targetLevel = 1; targetLevel < kNumLevels; targetLevel++) {
|
||||
const uint32_t sourceLevel = targetLevel - 1;
|
||||
params.viewport.width = kTexWidth >> targetLevel;
|
||||
params.viewport.height = kTexHeight >> targetLevel;
|
||||
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
|
||||
uploadUniforms(getDriverApi(), ubuffer, {
|
||||
.fbWidth = float(params.viewport.width),
|
||||
.fbHeight = float(params.viewport.height),
|
||||
.sourceLevel = float(sourceLevel),
|
||||
});
|
||||
api.beginRenderPass(renderTargets[targetLevel], params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
for (int targetLevel = 0; targetLevel < kNumLevels; targetLevel++) {
|
||||
if (targetLevel > 0) {
|
||||
const uint32_t sourceLevel = targetLevel - 1;
|
||||
params.viewport.width = kTexWidth >> targetLevel;
|
||||
params.viewport.height = kTexHeight >> targetLevel;
|
||||
getDriverApi().setMinMaxLevels(texture, sourceLevel, sourceLevel);
|
||||
uploadUniforms(getDriverApi(), ubuffer, {
|
||||
.fbWidth = float(params.viewport.width),
|
||||
.fbHeight = float(params.viewport.height),
|
||||
.sourceLevel = float(sourceLevel),
|
||||
});
|
||||
api.beginRenderPass(renderTargets[targetLevel], params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
}
|
||||
#if DUMP_INTERMEDIATE_SCREENSHOTS
|
||||
dumpScreenshot(api, renderTargets, targetLevel, frame, true, sBackend);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Upsample passes
|
||||
@@ -239,16 +312,22 @@ TEST_F(BackendTest, FeedbackLoops) {
|
||||
api.beginRenderPass(renderTargets[targetLevel], params);
|
||||
api.draw(state, triangle.getRenderPrimitive(), 1);
|
||||
api.endRenderPass();
|
||||
|
||||
#if DUMP_INTERMEDIATE_SCREENSHOTS
|
||||
dumpScreenshot(api, renderTargets, targetLevel, frame, false, sBackend);
|
||||
#endif
|
||||
}
|
||||
|
||||
getDriverApi().setMinMaxLevels(texture, 0, 0x7f);
|
||||
getDriverApi().destroySamplerGroup(sgroup);
|
||||
getDriverApi().destroyBufferObject(ubuffer);
|
||||
|
||||
// Read back the render target corresponding to the base level.
|
||||
//
|
||||
// NOTE: Calling glReadPixels on any miplevel other than the base level
|
||||
// seems to be un-reliable on some GPU's.
|
||||
if (frame == kNumFrames - 1) {
|
||||
dumpScreenshot(api, renderTargets[0]);
|
||||
dumpScreenshot(api, renderTargets, 0, frame, false, sBackend, "Feedback.png");
|
||||
}
|
||||
|
||||
api.flush();
|
||||
|
||||
@@ -50,11 +50,11 @@ class UTILS_PUBLIC Material : public FilamentAPI {
|
||||
struct BuilderDetails;
|
||||
|
||||
public:
|
||||
using BlendingMode = filament::BlendingMode;
|
||||
using Shading = filament::Shading;
|
||||
using Interpolation = filament::Interpolation;
|
||||
using VertexDomain = filament::VertexDomain;
|
||||
using TransparencyMode = filament::TransparencyMode;
|
||||
using BlendingMode = BlendingMode;
|
||||
using Shading = Shading;
|
||||
using Interpolation = Interpolation;
|
||||
using VertexDomain = VertexDomain;
|
||||
using TransparencyMode = TransparencyMode;
|
||||
|
||||
using ParameterType = backend::UniformType;
|
||||
using Precision = backend::Precision;
|
||||
|
||||
@@ -66,26 +66,26 @@ class Viewport;
|
||||
*/
|
||||
class UTILS_PUBLIC View : public FilamentAPI {
|
||||
public:
|
||||
using QualityLevel = filament::QualityLevel;
|
||||
using BlendMode = filament::BlendMode;
|
||||
using AntiAliasing = filament::AntiAliasing;
|
||||
using Dithering = filament::Dithering;
|
||||
using ShadowType = filament::ShadowType;
|
||||
using QualityLevel = QualityLevel;
|
||||
using BlendMode = BlendMode;
|
||||
using AntiAliasing = AntiAliasing;
|
||||
using Dithering = Dithering;
|
||||
using ShadowType = ShadowType;
|
||||
|
||||
using DynamicResolutionOptions = filament::DynamicResolutionOptions;
|
||||
using BloomOptions = filament::BloomOptions;
|
||||
using FogOptions = filament::FogOptions;
|
||||
using DepthOfFieldOptions = filament::DepthOfFieldOptions;
|
||||
using VignetteOptions = filament::VignetteOptions;
|
||||
using RenderQuality = filament::RenderQuality;
|
||||
using AmbientOcclusionOptions = filament::AmbientOcclusionOptions;
|
||||
using TemporalAntiAliasingOptions = filament::TemporalAntiAliasingOptions;
|
||||
using MultiSampleAntiAliasingOptions = filament::MultiSampleAntiAliasingOptions;
|
||||
using VsmShadowOptions = filament::VsmShadowOptions;
|
||||
using SoftShadowOptions = filament::SoftShadowOptions;
|
||||
using ScreenSpaceReflectionsOptions = filament::ScreenSpaceReflectionsOptions;
|
||||
using GuardBandOptions = filament::GuardBandOptions;
|
||||
using StereoscopicOptions = filament::StereoscopicOptions;
|
||||
using DynamicResolutionOptions = DynamicResolutionOptions;
|
||||
using BloomOptions = BloomOptions;
|
||||
using FogOptions = FogOptions;
|
||||
using DepthOfFieldOptions = DepthOfFieldOptions;
|
||||
using VignetteOptions = VignetteOptions;
|
||||
using RenderQuality = RenderQuality;
|
||||
using AmbientOcclusionOptions = AmbientOcclusionOptions;
|
||||
using TemporalAntiAliasingOptions = TemporalAntiAliasingOptions;
|
||||
using MultiSampleAntiAliasingOptions = MultiSampleAntiAliasingOptions;
|
||||
using VsmShadowOptions = VsmShadowOptions;
|
||||
using SoftShadowOptions = SoftShadowOptions;
|
||||
using ScreenSpaceReflectionsOptions = ScreenSpaceReflectionsOptions;
|
||||
using GuardBandOptions = GuardBandOptions;
|
||||
using StereoscopicOptions = StereoscopicOptions;
|
||||
|
||||
/**
|
||||
* Sets the View's name. Only useful for debugging.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.49.0"
|
||||
spec.version = "1.47.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.49.0/filament-v1.49.0-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.47.0/filament-v1.47.0-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -28,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 = 49;
|
||||
static constexpr size_t MATERIAL_VERSION = 47;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
|
||||
@@ -912,8 +912,7 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
|
||||
|
||||
if (targetApi == TargetApi::OPENGL) {
|
||||
if (targetLanguage == TargetLanguage::SPIRV) {
|
||||
ShaderGenerator::fixupExternalSamplers(shaderModel, shader, featureLevel,
|
||||
info);
|
||||
ShaderGenerator::fixupExternalSamplers(shaderModel, shader, info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1163,6 +1162,7 @@ error:
|
||||
mVariantFilter |= uint32_t(UserVariantFilterBit::DIRECTIONAL_LIGHTING);
|
||||
mVariantFilter |= uint32_t(UserVariantFilterBit::DYNAMIC_LIGHTING);
|
||||
mVariantFilter |= uint32_t(UserVariantFilterBit::SHADOW_RECEIVER);
|
||||
mVariantFilter |= uint32_t(UserVariantFilterBit::SKINNING);
|
||||
mVariantFilter |= uint32_t(UserVariantFilterBit::VSM);
|
||||
mVariantFilter |= uint32_t(UserVariantFilterBit::SSR);
|
||||
mVariantFilter |= uint32_t(UserVariantFilterBit::STE);
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "glslang/Include/intermediate.h"
|
||||
|
||||
const TBuiltInResource DefaultTBuiltInResource = {
|
||||
/* .MaxLights = */ 32,
|
||||
/* .MaxClipPlanes = */ 6,
|
||||
|
||||
@@ -778,7 +778,7 @@ io::sstream& CodeGenerator::generateSubpass(io::sstream& out, SubpassInfo subpas
|
||||
}
|
||||
|
||||
void CodeGenerator::fixupExternalSamplers(
|
||||
std::string& shader, SamplerInterfaceBlock const& sib, FeatureLevel featureLevel) noexcept {
|
||||
std::string& shader, SamplerInterfaceBlock const& sib) noexcept {
|
||||
auto const& infos = sib.getSamplerInfoList();
|
||||
if (infos.empty()) {
|
||||
return;
|
||||
@@ -813,10 +813,7 @@ void CodeGenerator::fixupExternalSamplers(
|
||||
while (shader[index] != '\n') index++;
|
||||
index++;
|
||||
|
||||
const char *extensionLine = (featureLevel >= FeatureLevel::FEATURE_LEVEL_1)
|
||||
? "#extension GL_OES_EGL_image_external_essl3 : require\n\n"
|
||||
: "#extension GL_OES_EGL_image_external : require\n\n";
|
||||
shader.insert(index, extensionLine);
|
||||
shader.insert(index, "#extension GL_OES_EGL_image_external_essl3 : require\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -161,8 +161,7 @@ public:
|
||||
static utils::io::sstream& generateParameters(utils::io::sstream& out, ShaderStage type);
|
||||
|
||||
static void fixupExternalSamplers(
|
||||
std::string& shader, filament::SamplerInterfaceBlock const& sib,
|
||||
FeatureLevel featureLevel) noexcept;
|
||||
std::string& shader, filament::SamplerInterfaceBlock const& sib) noexcept;
|
||||
|
||||
// These constants must match the equivalent in MetalState.h.
|
||||
// These values represent the starting index for uniform, ssbo, and sampler group [[buffer(n)]]
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
#include <utils/CString.h>
|
||||
|
||||
#include "backend/DriverEnums.h"
|
||||
#include "filamat/MaterialBuilder.h"
|
||||
#include "CodeGenerator.h"
|
||||
#include "SibGenerator.h"
|
||||
@@ -57,7 +56,7 @@ void ShaderGenerator::generateSurfaceMaterialVariantDefines(utils::io::sstream&
|
||||
switch (stage) {
|
||||
case ShaderStage::VERTEX:
|
||||
CodeGenerator::generateDefine(out, "VARIANT_HAS_SKINNING_OR_MORPHING",
|
||||
hasSkinningOrMorphing(variant, featureLevel));
|
||||
variant.hasSkinningOrMorphing());
|
||||
break;
|
||||
case ShaderStage::FRAGMENT:
|
||||
CodeGenerator::generateDefine(out, "VARIANT_HAS_FOG",
|
||||
@@ -343,13 +342,12 @@ ShaderGenerator::ShaderGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderGenerator::fixupExternalSamplers(ShaderModel sm, std::string& shader,
|
||||
MaterialBuilder::FeatureLevel featureLevel,
|
||||
MaterialInfo const& material) noexcept {
|
||||
void ShaderGenerator::fixupExternalSamplers(ShaderModel sm,
|
||||
std::string& shader, MaterialInfo const& material) noexcept {
|
||||
// External samplers are only supported on GL ES at the moment, we must
|
||||
// skip the fixup on desktop targets
|
||||
if (material.hasExternalSamplers && sm == ShaderModel::MOBILE) {
|
||||
CodeGenerator::fixupExternalSamplers(shader, material.sib, featureLevel);
|
||||
CodeGenerator::fixupExternalSamplers(shader, material.sib);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,7 +396,7 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel,
|
||||
generateSurfaceMaterialVariantProperties(vs, mProperties, mDefines);
|
||||
|
||||
AttributeBitset attributes = material.requiredAttributes;
|
||||
if (hasSkinningOrMorphing(variant, featureLevel)) {
|
||||
if (variant.hasSkinningOrMorphing()) {
|
||||
attributes.set(VertexAttribute::BONE_INDICES);
|
||||
attributes.set(VertexAttribute::BONE_WEIGHTS);
|
||||
if (material.useLegacyMorphing) {
|
||||
@@ -438,7 +436,7 @@ std::string ShaderGenerator::createVertexProgram(ShaderModel shaderModel,
|
||||
UniformBindingPoints::SHADOW, UibGenerator::getShadowUib());
|
||||
}
|
||||
|
||||
if (hasSkinningOrMorphing(variant, featureLevel)) {
|
||||
if (variant.hasSkinningOrMorphing()) {
|
||||
cg.generateUniforms(vs, ShaderStage::VERTEX,
|
||||
UniformBindingPoints::PER_RENDERABLE_BONES,
|
||||
UibGenerator::getPerRenderableBonesUib());
|
||||
@@ -749,13 +747,4 @@ std::string ShaderGenerator::createPostProcessFragmentProgram(ShaderModel sm,
|
||||
return fs.c_str();
|
||||
}
|
||||
|
||||
bool ShaderGenerator::hasSkinningOrMorphing(
|
||||
filament::Variant variant, MaterialBuilder::FeatureLevel featureLevel) noexcept {
|
||||
return variant.hasSkinningOrMorphing()
|
||||
// HACK(exv): Ignore skinning/morphing variant when targeting ESSL 1.0. We should
|
||||
// either properly support skinning on FL0 or build a system in matc which allows
|
||||
// the set of included variants to differ per-feature level.
|
||||
&& featureLevel > MaterialBuilder::FeatureLevel::FEATURE_LEVEL_0;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -75,7 +75,6 @@ public:
|
||||
* the optimizations have been applied.
|
||||
*/
|
||||
static void fixupExternalSamplers(filament::backend::ShaderModel sm, std::string& shader,
|
||||
MaterialBuilder::FeatureLevel featureLevel,
|
||||
MaterialInfo const& material) noexcept;
|
||||
|
||||
private:
|
||||
@@ -111,10 +110,6 @@ private:
|
||||
static void appendShader(utils::io::sstream& ss,
|
||||
const utils::CString& shader, size_t lineOffset) noexcept;
|
||||
|
||||
static bool hasSkinningOrMorphing(
|
||||
filament::Variant variant,
|
||||
MaterialBuilder::FeatureLevel featureLevel) noexcept;
|
||||
|
||||
MaterialBuilder::PropertyList mProperties;
|
||||
MaterialBuilder::VariableList mVariables;
|
||||
MaterialBuilder::OutputList mOutputs;
|
||||
|
||||
5
third_party/spirv-headers/FILAMENT_README.md
vendored
5
third_party/spirv-headers/FILAMENT_README.md
vendored
@@ -1,5 +0,0 @@
|
||||
This project is pulled in as a dependency for spirv-tools. The Vulkan
|
||||
backend also has a dependency on this project. This project should not
|
||||
be updated directly, instead, please see
|
||||
/third_party/spirv-tools/FILAMENT_README.md for instructions for
|
||||
update.
|
||||
@@ -1,27 +0,0 @@
|
||||
diff --git a/third_party/spirv-headers/CMakeLists.txt b/third_party/spirv-headers/CMakeLists.txt
|
||||
index 9cfba73af..2b8bb5e59 100644
|
||||
--- a/third_party/spirv-headers/CMakeLists.txt
|
||||
+++ b/third_party/spirv-headers/CMakeLists.txt
|
||||
@@ -28,7 +28,7 @@
|
||||
# The SPIR-V headers from the SPIR-V Registry
|
||||
# https://www.khronos.org/registry/spir-v/
|
||||
#
|
||||
-cmake_minimum_required(VERSION 3.0)
|
||||
+cmake_minimum_required(VERSION 3.19)
|
||||
project(SPIRV-Headers VERSION 1.5.5)
|
||||
|
||||
# There are two ways to use this project.
|
||||
@@ -44,11 +44,9 @@ project(SPIRV-Headers VERSION 1.5.5)
|
||||
# 2. cmake ..
|
||||
# 3. cmake --build . --target install
|
||||
|
||||
-option(SPIRV_HEADERS_SKIP_EXAMPLES "Skip building examples"
|
||||
- ${SPIRV_HEADERS_SKIP_EXAMPLES})
|
||||
+option(SPIRV_HEADERS_SKIP_EXAMPLES "Skip building examples" ON)
|
||||
|
||||
-option(SPIRV_HEADERS_SKIP_INSTALL "Skip install"
|
||||
- ${SPIRV_HEADERS_SKIP_INSTALL})
|
||||
+option(SPIRV_HEADERS_SKIP_INSTALL "Skip install" ON)
|
||||
|
||||
if(NOT ${SPIRV_HEADERS_SKIP_EXAMPLES})
|
||||
set(SPIRV_HEADERS_ENABLE_EXAMPLES ON)
|
||||
14
third_party/spirv-tools/.github/workflows/wasm.yml
vendored
Normal file
14
third_party/spirv-tools/.github/workflows/wasm.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Wasm Build
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build web
|
||||
run: docker-compose up
|
||||
- name: Run tests
|
||||
run: node test/wasm/test.js
|
||||
2
third_party/spirv-tools/.gitignore
vendored
2
third_party/spirv-tools/.gitignore
vendored
@@ -5,8 +5,6 @@ compile_commands.json
|
||||
/build*/
|
||||
/buildtools/
|
||||
/external/googletest
|
||||
/external/SPIRV-Headers
|
||||
/external/spirv-headers
|
||||
/external/effcee
|
||||
/external/re2
|
||||
/external/protobuf
|
||||
|
||||
6
third_party/spirv-tools/CMakeLists.txt
vendored
6
third_party/spirv-tools/CMakeLists.txt
vendored
@@ -24,8 +24,6 @@ if (POLICY CMP0054)
|
||||
endif()
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
|
||||
set(SPIRV-Headers_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../spirv-headers)
|
||||
|
||||
if (APPLE)
|
||||
set(CMAKE_MACOSX_RPATH ON)
|
||||
endif (APPLE)
|
||||
@@ -108,7 +106,6 @@ set(SPIRV_LIB_FUZZING_ENGINE_LINK_OPTIONS "" CACHE STRING "Used by OSS-Fuzz to c
|
||||
option(SPIRV_BUILD_LIBFUZZER_TARGETS "Build libFuzzer targets" OFF)
|
||||
|
||||
set(SPIRV_WERROR OFF)
|
||||
|
||||
if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") AND (NOT CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")))
|
||||
set(COMPILER_IS_LIKE_GNU TRUE)
|
||||
endif()
|
||||
@@ -316,6 +313,8 @@ option(SPIRV_SKIP_EXECUTABLES
|
||||
${SPIRV_SKIP_EXECUTABLES_OPTION})
|
||||
option(SPIRV_SKIP_TESTS
|
||||
"Skip building tests along with the library" ${SPIRV_SKIP_TESTS_OPTION})
|
||||
|
||||
|
||||
if ("${SPIRV_SKIP_EXECUTABLES}")
|
||||
set(SPIRV_SKIP_TESTS ON)
|
||||
endif()
|
||||
@@ -379,3 +378,4 @@ endif()
|
||||
|
||||
set(SPIRV_LIBRARIES "-lSPIRV-Tools-opt -lSPIRV-Tools -lSPIRV-Tools-link")
|
||||
set(SPIRV_SHARED_LIBRARIES "-lSPIRV-Tools-shared")
|
||||
|
||||
|
||||
36
third_party/spirv-tools/FILAMENT_README.md
vendored
36
third_party/spirv-tools/FILAMENT_README.md
vendored
@@ -1,20 +1,34 @@
|
||||
When updating SPIRV-Tools to a new version, run the following from *this* directory
|
||||
When updating spirv-tools to a new version there are several `CMakeLists.txt` files that need
|
||||
to patched with Filament specific changes. This can be done by running the following command at
|
||||
Filament's root:
|
||||
|
||||
```
|
||||
bash filament-update.sh [git commit hash]
|
||||
git apply third_party/spirv-tools/filament-specific-changes.patch
|
||||
```
|
||||
|
||||
This will pull in the updated source for SPRIV-Tools and pull in the right version of SPIRV-Headers.
|
||||
The script will also try to apply Filament specific changes to the `CMakeLists.txt`. It could be
|
||||
that the diff application will fail, in which case, the updater will need to resolve the difference
|
||||
manually and update `filament-specific-changes.patch`.
|
||||
|
||||
The above script will bring in the changes, but you would still need to add it to a git commit
|
||||
(i.e. pull request) by doing
|
||||
The following procedure can be used to update spirv-tools. Note that there is a secondary repository
|
||||
that needs to be downloaded (spirv-headers).
|
||||
|
||||
```
|
||||
git add -u third_party/spriv-tools third_party/spirv-headers
|
||||
curl -L https://github.com/KhronosGroup/spirv-tools/archive/master.zip > master.zip
|
||||
unzip master.zip
|
||||
rsync -r SPIRV-Tools-master/ spirv-tools/ --delete
|
||||
rm -rf SPIRV-Tools-master master.zip
|
||||
curl -L https://github.com/KhronosGroup/spirv-headers/archive/master.zip > master.zip
|
||||
unzip master.zip
|
||||
mv SPIRV-Headers-master/ spirv-tools/external/spirv-headers
|
||||
rm master.zip
|
||||
|
||||
git add spirv-tools
|
||||
```
|
||||
|
||||
from the Filament source root.
|
||||
The patch file also edits the `.gitignore` to allow `spirv-headers` to be committed.
|
||||
|
||||
Finally, remember to bring back the Filament-specific changes in CMakeLists.
|
||||
|
||||
To restore this file and the patch file do:
|
||||
|
||||
```
|
||||
git checkout main spirv-tools/FILAMENT_README.md
|
||||
git checkout main spirv-tools/filament-specific-changes.patch
|
||||
```
|
||||
|
||||
@@ -45,8 +45,6 @@ if (IS_DIRECTORY ${SPIRV_HEADER_DIR})
|
||||
# Do this so enclosing projects can use SPIRV-Headers_SOURCE_DIR to find
|
||||
# headers to include.
|
||||
if (NOT DEFINED SPIRV-Headers_SOURCE_DIR)
|
||||
set(SPIRV_HEADERS_SKIP_INSTALL ON)
|
||||
set(SPIRV_HEADERS_SKIP_EXAMPLES ON)
|
||||
add_subdirectory(${SPIRV_HEADER_DIR})
|
||||
endif()
|
||||
else()
|
||||
|
||||
37
third_party/spirv-tools/external/spirv-headers/.github/workflows/presubmit.yml
vendored
Normal file
37
third_party/spirv-tools/external/spirv-headers/.github/workflows/presubmit.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
name: Presubmit
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Ubuntu packages
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt install -y dos2unix
|
||||
- name: Install macOS packages
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: brew install dos2unix
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_INSTALL_PREFIX=install ..
|
||||
cmake --build . --target install
|
||||
- name: Build spec tools
|
||||
run: |
|
||||
cd tools/buildHeaders
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . --target install
|
||||
- name: Build headers
|
||||
run: |
|
||||
cd tools/buildHeaders
|
||||
./bin/makeHeaders
|
||||
- name: Check generated headers
|
||||
run: git diff --exit-code
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user