Merge branch 'rc/1.51.0' into release
This commit is contained in:
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.50.6'
|
||||
implementation 'com.google.android.filament:filament-android:1.51.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.50.6'
|
||||
pod 'Filament', '~> 1.51.0'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -7,6 +7,10 @@ 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.51.0
|
||||
|
||||
- materials: add support for post-lighting mix factor (b/328498606) [⚠️ **New Material Version**]
|
||||
|
||||
## v1.50.6
|
||||
|
||||
- Add new API `SwapChain::getFrameScheduledCallback`
|
||||
|
||||
@@ -654,7 +654,7 @@ public class Engine {
|
||||
*/
|
||||
@NonNull
|
||||
public SwapChain createSwapChain(@NonNull Object surface) {
|
||||
return createSwapChain(surface, SwapChain.CONFIG_DEFAULT);
|
||||
return createSwapChain(surface, SwapChainFlags.CONFIG_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -662,15 +662,15 @@ public class Engine {
|
||||
*
|
||||
* @param surface on Android, <b>must be</b> an instance of {@link android.view.Surface}
|
||||
*
|
||||
* @param flags configuration flags, see {@link SwapChain}
|
||||
* @param flags configuration flags, see {@link SwapChainFlags}
|
||||
*
|
||||
* @return a newly created {@link SwapChain} object
|
||||
*
|
||||
* @exception IllegalStateException can be thrown if the SwapChain couldn't be created
|
||||
*
|
||||
* @see SwapChain#CONFIG_DEFAULT
|
||||
* @see SwapChain#CONFIG_TRANSPARENT
|
||||
* @see SwapChain#CONFIG_READABLE
|
||||
* @see SwapChainFlags#CONFIG_DEFAULT
|
||||
* @see SwapChainFlags#CONFIG_TRANSPARENT
|
||||
* @see SwapChainFlags#CONFIG_READABLE
|
||||
*
|
||||
*/
|
||||
@NonNull
|
||||
@@ -688,21 +688,22 @@ public class Engine {
|
||||
*
|
||||
* @param width width of the rendering buffer
|
||||
* @param height height of the rendering buffer
|
||||
* @param flags configuration flags, see {@link SwapChain}
|
||||
* @param flags configuration flags, see {@link SwapChainFlags}
|
||||
*
|
||||
* @return a newly created {@link SwapChain} object
|
||||
*
|
||||
* @exception IllegalStateException can be thrown if the SwapChain couldn't be created
|
||||
*
|
||||
* @see SwapChain#CONFIG_DEFAULT
|
||||
* @see SwapChain#CONFIG_TRANSPARENT
|
||||
* @see SwapChain#CONFIG_READABLE
|
||||
* @see SwapChainFlags#CONFIG_DEFAULT
|
||||
* @see SwapChainFlags#CONFIG_TRANSPARENT
|
||||
* @see SwapChainFlags#CONFIG_READABLE
|
||||
*
|
||||
*/
|
||||
@NonNull
|
||||
public SwapChain createSwapChain(int width, int height, long flags) {
|
||||
if (width >= 0 && height >= 0) {
|
||||
long nativeSwapChain = nCreateSwapChainHeadless(getNativeObject(), width, height, flags);
|
||||
long nativeSwapChain =
|
||||
nCreateSwapChainHeadless(getNativeObject(), width, height, flags);
|
||||
if (nativeSwapChain == 0) throw new IllegalStateException("Couldn't create SwapChain");
|
||||
return new SwapChain(nativeSwapChain, null);
|
||||
}
|
||||
@@ -714,11 +715,12 @@ public class Engine {
|
||||
*
|
||||
* @param surface a properly initialized {@link NativeSurface}
|
||||
*
|
||||
* @param flags configuration flags, see {@link SwapChain}
|
||||
* @param flags configuration flags, see {@link SwapChainFlags}
|
||||
*
|
||||
* @return a newly created {@link SwapChain} object
|
||||
*
|
||||
* @exception IllegalStateException can be thrown if the {@link SwapChain} couldn't be created
|
||||
* @exception IllegalStateException can be thrown if the {@link SwapChainFlags} couldn't be
|
||||
* created
|
||||
*/
|
||||
@NonNull
|
||||
public SwapChain createSwapChainFromNativeSurface(@NonNull NativeSurface surface, long flags) {
|
||||
|
||||
@@ -68,72 +68,6 @@ public class SwapChain {
|
||||
private final Object mSurface;
|
||||
private long mNativeObject;
|
||||
|
||||
public static final long CONFIG_DEFAULT = 0x0;
|
||||
|
||||
/**
|
||||
* This flag indicates that the <code>SwapChain</code> must be allocated with an
|
||||
* alpha-channel.
|
||||
*/
|
||||
public static final long CONFIG_TRANSPARENT = 0x1;
|
||||
|
||||
/**
|
||||
* This flag indicates that the <code>SwapChain</code> may be used as a source surface
|
||||
* for reading back render results. This config must be set when creating
|
||||
* any <code>SwapChain</code> that will be used as the source for a blit operation.
|
||||
*
|
||||
* @see Renderer#copyFrame
|
||||
*/
|
||||
public static final long CONFIG_READABLE = 0x2;
|
||||
|
||||
/**
|
||||
* Indicates that the native X11 window is an XCB window rather than an XLIB window.
|
||||
* This is ignored on non-Linux platforms and in builds that support only one X11 API.
|
||||
*/
|
||||
public static final long CONFIG_ENABLE_XCB = 0x4;
|
||||
|
||||
/**
|
||||
* Indicates that the SwapChain must automatically perform linear to sRGB encoding.
|
||||
*
|
||||
* This flag is ignored if isSRGBSwapChainSupported() is false.
|
||||
*
|
||||
* When using this flag, post-processing should be disabled.
|
||||
*
|
||||
* @see SwapChain#isSRGBSwapChainSupported
|
||||
* @see View#setPostProcessingEnabled
|
||||
*/
|
||||
public static final long CONFIG_SRGB_COLORSPACE = 0x10;
|
||||
|
||||
/**
|
||||
* Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer.
|
||||
*
|
||||
* This flag is necessary when using View::setStencilBufferEnabled and rendering directly into
|
||||
* the SwapChain (when post-processing is disabled).
|
||||
*
|
||||
* The specific format of the stencil buffer depends on platform support. The following pixel
|
||||
* formats are tried, in order of preference:
|
||||
*
|
||||
* Depth only (without CONFIG_HAS_STENCIL_BUFFER):
|
||||
* - DEPTH32F
|
||||
* - DEPTH24
|
||||
*
|
||||
* Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER):
|
||||
* - DEPTH32F_STENCIL8
|
||||
* - DEPTH24F_STENCIL8
|
||||
*
|
||||
* Note that enabling the stencil buffer may hinder depth precision and should only be used if
|
||||
* necessary.
|
||||
*
|
||||
* @see View#setStencilBufferEnabled
|
||||
* @see View#setPostProcessingEnabled
|
||||
*/
|
||||
public static final long CONFIG_HAS_STENCIL_BUFFER = 0x20;
|
||||
|
||||
/**
|
||||
* The SwapChain contains protected content. Only supported when isProtectedContentSupported()
|
||||
* is true.
|
||||
*/
|
||||
public static final long CONFIG_PROTECTED_CONTENT = 0x40;
|
||||
|
||||
SwapChain(long nativeSwapChain, Object surface) {
|
||||
mNativeObject = nativeSwapChain;
|
||||
mSurface = surface;
|
||||
@@ -145,17 +79,19 @@ public class SwapChain {
|
||||
*
|
||||
* @param engine A reference to the filament Engine
|
||||
* @return true if CONFIG_PROTECTED_CONTENT is supported, false otherwise.
|
||||
* @see SwapChainFlags#CONFIG_PROTECTED_CONTENT
|
||||
*/
|
||||
public static boolean isProtectedContentSupported(@NonNull Engine engine) {
|
||||
return nIsProtectedContentSupported(engine.getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether createSwapChain supports the SWAP_CHAIN_CONFIG_SRGB_COLORSPACE flag.
|
||||
* Return whether createSwapChain supports the CONFIG_SRGB_COLORSPACE flag.
|
||||
* The default implementation returns false.
|
||||
*
|
||||
* @param engine A reference to the filament Engine
|
||||
* @return true if SWAP_CHAIN_CONFIG_SRGB_COLORSPACE is supported, false otherwise.
|
||||
* @return true if CONFIG_SRGB_COLORSPACE is supported, false otherwise.
|
||||
* @see SwapChainFlags#CONFIG_SRGB_COLORSPACE
|
||||
*/
|
||||
public static boolean isSRGBSwapChainSupported(@NonNull Engine engine) {
|
||||
return nIsSRGBSwapChainSupported(engine.getNativeObject());
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.
|
||||
*/
|
||||
|
||||
package com.google.android.filament;
|
||||
|
||||
// Note: SwapChainFlags is kept separate from SwapChain so that UiHelper does not need to depend
|
||||
// on SwapChain. This allows clients to use UiHelper without requiring all of Filament's Java
|
||||
// classes.
|
||||
|
||||
/**
|
||||
* Flags that a <code>SwapChain</code> can be created with to control behavior.
|
||||
*
|
||||
* @see Engine#createSwapChain
|
||||
* @see Engine#createSwapChainFromNativeSurface
|
||||
*/
|
||||
public final class SwapChainFlags {
|
||||
|
||||
public static final long CONFIG_DEFAULT = 0x0;
|
||||
|
||||
/**
|
||||
* This flag indicates that the <code>SwapChain</code> must be allocated with an
|
||||
* alpha-channel.
|
||||
*/
|
||||
public static final long CONFIG_TRANSPARENT = 0x1;
|
||||
|
||||
/**
|
||||
* This flag indicates that the <code>SwapChain</code> may be used as a source surface
|
||||
* for reading back render results. This config must be set when creating
|
||||
* any <code>SwapChain</code> that will be used as the source for a blit operation.
|
||||
*
|
||||
* @see Renderer#copyFrame
|
||||
*/
|
||||
public static final long CONFIG_READABLE = 0x2;
|
||||
|
||||
/**
|
||||
* Indicates that the native X11 window is an XCB window rather than an XLIB window.
|
||||
* This is ignored on non-Linux platforms and in builds that support only one X11 API.
|
||||
*/
|
||||
public static final long CONFIG_ENABLE_XCB = 0x4;
|
||||
|
||||
/**
|
||||
* Indicates that the SwapChain must automatically perform linear to sRGB encoding.
|
||||
*
|
||||
* This flag is ignored if isSRGBSwapChainSupported() is false.
|
||||
*
|
||||
* When using this flag, post-processing should be disabled.
|
||||
*
|
||||
* @see SwapChain#isSRGBSwapChainSupported
|
||||
* @see View#setPostProcessingEnabled
|
||||
*/
|
||||
public static final long CONFIG_SRGB_COLORSPACE = 0x10;
|
||||
|
||||
/**
|
||||
* Indicates that this SwapChain should allocate a stencil buffer in addition to a depth buffer.
|
||||
*
|
||||
* This flag is necessary when using View::setStencilBufferEnabled and rendering directly into
|
||||
* the SwapChain (when post-processing is disabled).
|
||||
*
|
||||
* The specific format of the stencil buffer depends on platform support. The following pixel
|
||||
* formats are tried, in order of preference:
|
||||
*
|
||||
* Depth only (without CONFIG_HAS_STENCIL_BUFFER):
|
||||
* - DEPTH32F
|
||||
* - DEPTH24
|
||||
*
|
||||
* Depth + stencil (with CONFIG_HAS_STENCIL_BUFFER):
|
||||
* - DEPTH32F_STENCIL8
|
||||
* - DEPTH24F_STENCIL8
|
||||
*
|
||||
* Note that enabling the stencil buffer may hinder depth precision and should only be used if
|
||||
* necessary.
|
||||
*
|
||||
* @see View#setStencilBufferEnabled
|
||||
* @see View#setPostProcessingEnabled
|
||||
*/
|
||||
public static final long CONFIG_HAS_STENCIL_BUFFER = 0x20;
|
||||
|
||||
/**
|
||||
* The SwapChain contains protected content. Only supported when isProtectedContentSupported()
|
||||
* is true.
|
||||
*/
|
||||
public static final long CONFIG_PROTECTED_CONTENT = 0x40;
|
||||
}
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.TextureView;
|
||||
|
||||
import com.google.android.filament.SwapChain;
|
||||
import com.google.android.filament.SwapChainFlags;
|
||||
|
||||
/**
|
||||
* UiHelper is a simple class that can manage either a SurfaceView, TextureView, or a SurfaceHolder
|
||||
@@ -538,7 +538,7 @@ public class UiHelper {
|
||||
* the options set on this UiHelper.
|
||||
*/
|
||||
public long getSwapChainFlags() {
|
||||
return isOpaque() ? SwapChain.CONFIG_DEFAULT : SwapChain.CONFIG_TRANSPARENT;
|
||||
return isOpaque() ? SwapChainFlags.CONFIG_DEFAULT : SwapChainFlags.CONFIG_TRANSPARENT;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.50.6
|
||||
VERSION_NAME=1.51.0
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
+20
-22
@@ -252,8 +252,8 @@ set(MATERIAL_SRCS
|
||||
)
|
||||
|
||||
set(MATERIAL_FL0_SRCS
|
||||
src/materials/defaultMaterial0.mat
|
||||
src/materials/skybox0.mat
|
||||
src/materials/defaultMaterial.mat
|
||||
src/materials/skybox.mat
|
||||
)
|
||||
|
||||
# Embed the binary resource blob for materials.
|
||||
@@ -316,32 +316,30 @@ foreach (mat_src ${MATERIAL_SRCS})
|
||||
get_filename_component(fullname "${mat_src}" ABSOLUTE)
|
||||
set(output_path "${MATERIAL_DIR}/${localname}.filamat")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${output_path}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname}
|
||||
MAIN_DEPENDENCY ${fullname}
|
||||
DEPENDS matc
|
||||
COMMENT "Compiling material ${mat_src} to ${output_path}"
|
||||
)
|
||||
list(APPEND MATERIAL_BINS ${output_path})
|
||||
endforeach()
|
||||
|
||||
if (FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
foreach (mat_src ${MATERIAL_FL0_SRCS})
|
||||
get_filename_component(localname "${mat_src}" NAME_WE)
|
||||
get_filename_component(fullname "${mat_src}" ABSOLUTE)
|
||||
set(output_path "${MATERIAL_DIR}/${localname}.filamat")
|
||||
|
||||
list(FIND MATERIAL_FL0_SRCS ${mat_src} index)
|
||||
if (${index} GREATER -1 AND FILAMENT_ENABLE_FEATURE_LEVEL_0)
|
||||
string(REGEX REPLACE "[.]filamat$" "0.filamat" output_path0 ${output_path})
|
||||
add_custom_command(
|
||||
OUTPUT ${output_path} ${output_path0}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -PfeatureLevel=0 -o ${output_path0} ${fullname}
|
||||
MAIN_DEPENDENCY ${fullname}
|
||||
DEPENDS matc
|
||||
COMMENT "Compiling material ${fullname} to ${output_path} and ${output_path0}"
|
||||
)
|
||||
list(APPEND MATERIAL_BINS ${output_path0})
|
||||
else ()
|
||||
add_custom_command(
|
||||
OUTPUT ${output_path}
|
||||
COMMAND matc ${MATC_BASE_FLAGS} -o ${output_path} ${fullname}
|
||||
MAIN_DEPENDENCY ${fullname}
|
||||
DEPENDS matc
|
||||
COMMENT "Compiling material ${mat_src} to ${output_path}"
|
||||
COMMENT "Compiling material ${fullname} to ${output_path}"
|
||||
)
|
||||
list(APPEND MATERIAL_BINS ${output_path})
|
||||
endforeach ()
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
list(APPEND MATERIAL_BINS ${output_path})
|
||||
endforeach()
|
||||
|
||||
# Additional dependencies on included files for materials
|
||||
|
||||
|
||||
@@ -1186,11 +1186,27 @@ struct StencilState {
|
||||
|
||||
//! Stencil operations for front-facing polygons
|
||||
StencilOperations front = {
|
||||
.stencilFunc = StencilFunction::A, .ref = 0, .readMask = 0xff, .writeMask = 0xff };
|
||||
.stencilFunc = StencilFunction::A,
|
||||
.stencilOpStencilFail = StencilOperation::KEEP,
|
||||
.padding0 = 0,
|
||||
.stencilOpDepthFail = StencilOperation::KEEP,
|
||||
.stencilOpDepthStencilPass = StencilOperation::KEEP,
|
||||
.padding1 = 0,
|
||||
.ref = 0,
|
||||
.readMask = 0xff,
|
||||
.writeMask = 0xff };
|
||||
|
||||
//! Stencil operations for back-facing polygons
|
||||
StencilOperations back = {
|
||||
.stencilFunc = StencilFunction::A, .ref = 0, .readMask = 0xff, .writeMask = 0xff };
|
||||
.stencilFunc = StencilFunction::A,
|
||||
.stencilOpStencilFail = StencilOperation::KEEP,
|
||||
.padding0 = 0,
|
||||
.stencilOpDepthFail = StencilOperation::KEEP,
|
||||
.stencilOpDepthStencilPass = StencilOperation::KEEP,
|
||||
.padding1 = 0,
|
||||
.ref = 0,
|
||||
.readMask = 0xff,
|
||||
.writeMask = 0xff };
|
||||
|
||||
//! Whether stencil-buffer writes are enabled
|
||||
bool stencilWrite = false;
|
||||
|
||||
@@ -29,10 +29,13 @@ namespace filament::backend {
|
||||
//! \privatesection
|
||||
|
||||
struct PipelineState {
|
||||
Handle<HwProgram> program;
|
||||
RasterState rasterState;
|
||||
StencilState stencilState;
|
||||
PolygonOffset polygonOffset;
|
||||
Handle<HwProgram> program; // 4
|
||||
Handle<HwVertexBufferInfo> vertexBufferInfo; // 4
|
||||
RasterState rasterState; // 4
|
||||
StencilState stencilState; // 12
|
||||
PolygonOffset polygonOffset; // 8
|
||||
PrimitiveType primitiveType = PrimitiveType::TRIANGLES; // 1
|
||||
uint8_t padding[3] = {}; // 3
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -56,7 +56,7 @@ public:
|
||||
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL backend.
|
||||
* Currently only honored by the GL and Metal backends.
|
||||
*/
|
||||
bool disableParallelShaderCompile = false;
|
||||
};
|
||||
|
||||
@@ -493,6 +493,17 @@ DECL_DRIVER_API_N(blit,
|
||||
math::uint2, srcOrigin,
|
||||
math::uint2, size)
|
||||
|
||||
DECL_DRIVER_API_N(bindPipeline,
|
||||
backend::PipelineState, state)
|
||||
|
||||
DECL_DRIVER_API_N(bindRenderPrimitive,
|
||||
backend::RenderPrimitiveHandle, rph)
|
||||
|
||||
DECL_DRIVER_API_N(draw2,
|
||||
uint32_t, indexOffset,
|
||||
uint32_t, indexCount,
|
||||
uint32_t, instanceCount)
|
||||
|
||||
DECL_DRIVER_API_N(draw,
|
||||
backend::PipelineState, state,
|
||||
backend::RenderPrimitiveHandle, rph,
|
||||
@@ -500,6 +511,7 @@ DECL_DRIVER_API_N(draw,
|
||||
uint32_t, indexCount,
|
||||
uint32_t, instanceCount)
|
||||
|
||||
|
||||
DECL_DRIVER_API_N(dispatchCompute,
|
||||
backend::ProgramHandle, program,
|
||||
math::uint3, workGroupCount)
|
||||
|
||||
@@ -37,9 +37,9 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define HandleAllocatorGL HandleAllocator<24, 64, 136> // ~3640 / pool / MiB
|
||||
#define HandleAllocatorVK HandleAllocator<80, 176, 320> // ~1820 / pool / MiB
|
||||
#define HandleAllocatorMTL HandleAllocator<48, 160, 592> // ~1310 / pool / MiB
|
||||
#define HandleAllocatorGL HandleAllocator<32, 64, 136> // ~4520 / pool / MiB
|
||||
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
|
||||
#define HandleAllocatorMTL HandleAllocator<32, 48, 552> // ~1660 / pool / MiB
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ struct MetalContext {
|
||||
std::array<BufferState, MAX_SSBO_COUNT> ssboState;
|
||||
CullModeStateTracker cullModeState;
|
||||
WindingStateTracker windingState;
|
||||
Handle<HwRenderPrimitive> currentRenderPrimitive;
|
||||
|
||||
// State caches.
|
||||
DepthStencilStateCache depthStencilStateCache;
|
||||
|
||||
@@ -48,24 +48,26 @@ Driver* MetalDriverFactory::create(MetalPlatform* const platform, const Platform
|
||||
// For reference on a 64-bits machine in Release mode:
|
||||
// MetalTimerQuery : 16 few
|
||||
// HwStream : 24 few
|
||||
// MetalRenderPrimitive : 24 many
|
||||
// MetalVertexBuffer : 32 moderate
|
||||
// -- less than or equal 32 bytes
|
||||
// MetalIndexBuffer : 40 moderate
|
||||
// MetalFence : 48 few
|
||||
// MetalBufferObject : 48 many
|
||||
// -- less than or equal 48 bytes
|
||||
// MetalSamplerGroup : 112 few
|
||||
// MetalProgram : 144 moderate
|
||||
// MetalProgram : 152 moderate
|
||||
// MetalTexture : 152 moderate
|
||||
// MetalVertexBuffer : 152 moderate
|
||||
// -- less than or equal 160 bytes
|
||||
// MetalSwapChain : 184 few
|
||||
// MetalRenderTarget : 272 few
|
||||
// MetalRenderPrimitive : 584 many
|
||||
// -- less than or equal to 592 bytes
|
||||
// MetalVertexBufferInfo : 552 moderate
|
||||
// -- less than or equal to 552 bytes
|
||||
|
||||
utils::slog.d
|
||||
<< "\nMetalSwapChain: " << sizeof(MetalSwapChain)
|
||||
<< "\nMetalBufferObject: " << sizeof(MetalBufferObject)
|
||||
<< "\nMetalVertexBuffer: " << sizeof(MetalVertexBuffer)
|
||||
<< "\nMetalVertexBufferInfo: " << sizeof(MetalVertexBufferInfo)
|
||||
<< "\nMetalIndexBuffer: " << sizeof(MetalIndexBuffer)
|
||||
<< "\nMetalSamplerGroup: " << sizeof(MetalSamplerGroup)
|
||||
<< "\nMetalRenderPrimitive: " << sizeof(MetalRenderPrimitive)
|
||||
@@ -178,7 +180,10 @@ MetalDriver::MetalDriver(MetalPlatform* platform, const Platform::DriverConfig&
|
||||
mContext->eventListener = [[MTLSharedEventListener alloc] initWithDispatchQueue:queue];
|
||||
}
|
||||
|
||||
mContext->shaderCompiler = new MetalShaderCompiler(mContext->device, *this);
|
||||
const MetalShaderCompiler::Mode compilerMode = driverConfig.disableParallelShaderCompile
|
||||
? MetalShaderCompiler::Mode::SYNCHRONOUS
|
||||
: MetalShaderCompiler::Mode::ASYNCHRONOUS;
|
||||
mContext->shaderCompiler = new MetalShaderCompiler(mContext->device, *this, compilerMode);
|
||||
mContext->shaderCompiler->init();
|
||||
|
||||
#if defined(FILAMENT_METAL_PROFILING)
|
||||
@@ -786,7 +791,7 @@ bool MetalDriver::isStereoSupported(backend::StereoscopicType stereoscopicType)
|
||||
}
|
||||
|
||||
bool MetalDriver::isParallelShaderCompileSupported() {
|
||||
return true;
|
||||
return mContext->shaderCompiler->isParallelShaderCompileSupported();
|
||||
}
|
||||
|
||||
bool MetalDriver::isDepthStencilResolveSupported() {
|
||||
@@ -1602,11 +1607,13 @@ void MetalDriver::finalizeSamplerGroup(MetalSamplerGroup* samplerGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) {
|
||||
void MetalDriver::bindPipeline(PipelineState ps) {
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
|
||||
"Attempted to draw without a valid command encoder.");
|
||||
auto primitive = handle_cast<MetalRenderPrimitive>(rph);
|
||||
"bindPipeline() without a valid command encoder.");
|
||||
|
||||
MetalVertexBufferInfo const* const vbi =
|
||||
handle_cast<MetalVertexBufferInfo>(ps.vertexBufferInfo);
|
||||
|
||||
auto program = handle_cast<MetalProgram>(ps.program);
|
||||
const auto& rs = ps.rasterState;
|
||||
|
||||
@@ -1620,7 +1627,7 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph,
|
||||
return;
|
||||
}
|
||||
|
||||
functions.validate();
|
||||
ASSERT_PRECONDITION(bool(functions), "Attempting to bind an invalid Metal program.");
|
||||
|
||||
auto [fragment, vertex] = functions.getRasterFunctions();
|
||||
|
||||
@@ -1647,7 +1654,7 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph,
|
||||
MetalPipelineState const pipelineState {
|
||||
.vertexFunction = vertex,
|
||||
.fragmentFunction = fragment,
|
||||
.vertexDescription = primitive->vertexDescription,
|
||||
.vertexDescription = vbi->vertexDescription,
|
||||
.colorAttachmentPixelFormat = {
|
||||
colorPixelFormat[0],
|
||||
colorPixelFormat[1],
|
||||
@@ -1741,20 +1748,6 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph,
|
||||
mContext->currentPolygonOffset = ps.polygonOffset;
|
||||
}
|
||||
|
||||
// Bind uniform buffers.
|
||||
MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil };
|
||||
NSUInteger offsets[Program::UNIFORM_BINDING_COUNT] = { 0 };
|
||||
|
||||
enumerateBoundBuffers(BufferObjectBinding::UNIFORM,
|
||||
[&uniformsToBind, &offsets](const BufferState& state, MetalBuffer* buffer,
|
||||
uint32_t index) {
|
||||
uniformsToBind[index] = buffer;
|
||||
offsets[index] = state.offset;
|
||||
});
|
||||
MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
|
||||
UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT,
|
||||
uniformsToBind, offsets, Program::UNIFORM_BINDING_COUNT);
|
||||
|
||||
// Bind sampler groups (argument buffers).
|
||||
for (size_t s = 0; s < Program::SAMPLER_BINDING_COUNT; s++) {
|
||||
MetalSamplerGroup* const samplerGroup = mContext->samplerBindings[s];
|
||||
@@ -1785,19 +1778,29 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph,
|
||||
atIndex:(SAMPLER_GROUP_BINDING_START + s)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetalDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
|
||||
"bindRenderPrimitive() without a valid command encoder.");
|
||||
|
||||
// Bind the user vertex buffers.
|
||||
|
||||
MetalBuffer* vertexBuffers[MAX_VERTEX_BUFFER_COUNT] = {};
|
||||
size_t vertexBufferOffsets[MAX_VERTEX_BUFFER_COUNT] = {};
|
||||
size_t maxBufferIndex = 0;
|
||||
|
||||
MetalRenderPrimitive const* const primitive = handle_cast<MetalRenderPrimitive>(rph);
|
||||
MetalVertexBufferInfo const* const vbi =
|
||||
handle_cast<MetalVertexBufferInfo>(primitive->vertexBuffer->vbih);
|
||||
|
||||
mContext->currentRenderPrimitive = rph;
|
||||
|
||||
auto vb = primitive->vertexBuffer;
|
||||
for (auto m : primitive->bufferMapping) {
|
||||
for (auto m : vbi->bufferMapping) {
|
||||
assert_invariant(
|
||||
m.bufferArgumentIndex >= USER_VERTEX_BUFFER_BINDING_START &&
|
||||
m.bufferArgumentIndex < USER_VERTEX_BUFFER_BINDING_START + MAX_VERTEX_BUFFER_COUNT);
|
||||
size_t vertexBufferIndex = m.bufferArgumentIndex - USER_VERTEX_BUFFER_BINDING_START;
|
||||
size_t const vertexBufferIndex = m.bufferArgumentIndex - USER_VERTEX_BUFFER_BINDING_START;
|
||||
vertexBuffers[vertexBufferIndex] = vb->buffers[m.sourceBufferIndex];
|
||||
maxBufferIndex = std::max(maxBufferIndex, vertexBufferIndex);
|
||||
}
|
||||
@@ -1812,6 +1815,27 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph,
|
||||
[mContext->currentRenderPassEncoder setVertexBytes:bytes
|
||||
length:16
|
||||
atIndex:ZERO_VERTEX_BUFFER_BINDING];
|
||||
}
|
||||
|
||||
void MetalDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
ASSERT_PRECONDITION(mContext->currentRenderPassEncoder != nullptr,
|
||||
"draw() without a valid command encoder.");
|
||||
|
||||
// Bind uniform buffers.
|
||||
MetalBuffer* uniformsToBind[Program::UNIFORM_BINDING_COUNT] = { nil };
|
||||
NSUInteger offsets[Program::UNIFORM_BINDING_COUNT] = { 0 };
|
||||
|
||||
enumerateBoundBuffers(BufferObjectBinding::UNIFORM,
|
||||
[&uniformsToBind, &offsets](const BufferState& state, MetalBuffer* buffer,
|
||||
uint32_t index) {
|
||||
uniformsToBind[index] = buffer;
|
||||
offsets[index] = state.offset;
|
||||
});
|
||||
MetalBuffer::bindBuffers(getPendingCommandBuffer(mContext), mContext->currentRenderPassEncoder,
|
||||
UNIFORM_BUFFER_BINDING_START, MetalBuffer::Stage::VERTEX | MetalBuffer::Stage::FRAGMENT,
|
||||
uniformsToBind, offsets, Program::UNIFORM_BINDING_COUNT);
|
||||
|
||||
auto primitive = handle_cast<MetalRenderPrimitive>(mContext->currentRenderPrimitive);
|
||||
|
||||
MetalIndexBuffer* indexBuffer = primitive->indexBuffer;
|
||||
|
||||
@@ -1825,6 +1849,16 @@ void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph,
|
||||
instanceCount:instanceCount];
|
||||
}
|
||||
|
||||
void MetalDriver::draw(PipelineState ps, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) {
|
||||
MetalRenderPrimitive const* const rp = handle_cast<MetalRenderPrimitive>(rph);
|
||||
ps.primitiveType = rp->type;
|
||||
ps.vertexBufferInfo = rp->vertexBuffer->vbih;
|
||||
bindPipeline(ps);
|
||||
bindRenderPrimitive(rph);
|
||||
draw2(indexOffset, indexCount, instanceCount);
|
||||
}
|
||||
|
||||
void MetalDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
ASSERT_PRECONDITION(!isInRenderPass(mContext),
|
||||
"dispatchCompute must be called outside of a render pass.");
|
||||
|
||||
@@ -149,7 +149,21 @@ struct MetalVertexBufferInfo : public HwVertexBufferInfo {
|
||||
MetalVertexBufferInfo(MetalContext& context,
|
||||
uint8_t bufferCount, uint8_t attributeCount, AttributeArray const& attributes);
|
||||
|
||||
AttributeArray attributes;
|
||||
// This struct is used to create the pipeline description to describe vertex assembly.
|
||||
VertexDescription vertexDescription = {};
|
||||
|
||||
struct Entry {
|
||||
uint8_t sourceBufferIndex = 0;
|
||||
uint8_t stride = 0;
|
||||
// maps to ->
|
||||
uint8_t bufferArgumentIndex = 0;
|
||||
|
||||
Entry(uint8_t sourceBufferIndex, uint8_t stride, uint8_t bufferArgumentIndex)
|
||||
: sourceBufferIndex(sourceBufferIndex),
|
||||
stride(stride),
|
||||
bufferArgumentIndex(bufferArgumentIndex) {}
|
||||
};
|
||||
utils::FixedCapacityVector<Entry> bufferMapping;
|
||||
};
|
||||
|
||||
struct MetalVertexBuffer : public HwVertexBuffer {
|
||||
@@ -176,22 +190,6 @@ struct MetalRenderPrimitive : public HwRenderPrimitive {
|
||||
|
||||
MetalVertexBuffer* vertexBuffer = nullptr;
|
||||
MetalIndexBuffer* indexBuffer = nullptr;
|
||||
|
||||
// This struct is used to create the pipeline description to describe vertex assembly.
|
||||
VertexDescription vertexDescription = {};
|
||||
|
||||
struct Entry {
|
||||
uint8_t sourceBufferIndex = 0;
|
||||
uint8_t stride = 0;
|
||||
// maps to ->
|
||||
uint8_t bufferArgumentIndex = 0;
|
||||
|
||||
Entry(uint8_t sourceBufferIndex, uint8_t stride, uint8_t bufferArgumentIndex)
|
||||
: sourceBufferIndex(sourceBufferIndex),
|
||||
stride(stride),
|
||||
bufferArgumentIndex(bufferArgumentIndex) {}
|
||||
};
|
||||
utils::FixedCapacityVector<Entry> bufferMapping;
|
||||
};
|
||||
|
||||
class MetalProgram : public HwProgram {
|
||||
|
||||
@@ -343,27 +343,9 @@ void MetalBufferObject::updateBufferUnsynchronized(void* data, size_t size, uint
|
||||
MetalVertexBufferInfo::MetalVertexBufferInfo(MetalContext& context, uint8_t bufferCount,
|
||||
uint8_t attributeCount, AttributeArray const& attributes)
|
||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||
attributes(attributes) {
|
||||
}
|
||||
bufferMapping(utils::FixedCapacityVector<Entry>::with_capacity(MAX_VERTEX_BUFFER_COUNT)) {
|
||||
|
||||
MetalVertexBuffer::MetalVertexBuffer(MetalContext& context,
|
||||
uint32_t vertexCount, uint32_t bufferCount, Handle<HwVertexBufferInfo> vbih)
|
||||
: HwVertexBuffer(vertexCount), vbih(vbih), buffers(bufferCount, nullptr) {
|
||||
}
|
||||
|
||||
MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uint8_t elementSize,
|
||||
uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount),
|
||||
buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { }
|
||||
|
||||
MetalRenderPrimitive::MetalRenderPrimitive()
|
||||
: bufferMapping(utils::FixedCapacityVector<Entry>::with_capacity(MAX_VERTEX_BUFFER_COUNT)) {}
|
||||
|
||||
void MetalRenderPrimitive::setBuffers(MetalVertexBufferInfo const* const vbi,
|
||||
MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer) {
|
||||
this->vertexBuffer = vertexBuffer;
|
||||
this->indexBuffer = indexBuffer;
|
||||
|
||||
const size_t attributeCount = vbi->attributes.size();
|
||||
const size_t maxAttributeCount = attributes.size();
|
||||
|
||||
auto& mapping = bufferMapping;
|
||||
mapping.clear();
|
||||
@@ -403,8 +385,8 @@ void MetalRenderPrimitive::setBuffers(MetalVertexBufferInfo const* const vbi,
|
||||
}
|
||||
};
|
||||
|
||||
for (uint32_t attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) {
|
||||
const auto& attribute = vbi->attributes[attributeIndex];
|
||||
for (uint32_t attributeIndex = 0; attributeIndex < maxAttributeCount; attributeIndex++) {
|
||||
const auto& attribute = attributes[attributeIndex];
|
||||
|
||||
// If the attribute is unused, bind it to the zero buffer. It's a Metal error for a shader
|
||||
// to read from missing vertex attributes.
|
||||
@@ -431,6 +413,24 @@ void MetalRenderPrimitive::setBuffers(MetalVertexBufferInfo const* const vbi,
|
||||
}
|
||||
}
|
||||
|
||||
MetalVertexBuffer::MetalVertexBuffer(MetalContext& context,
|
||||
uint32_t vertexCount, uint32_t bufferCount, Handle<HwVertexBufferInfo> vbih)
|
||||
: HwVertexBuffer(vertexCount), vbih(vbih), buffers(bufferCount, nullptr) {
|
||||
}
|
||||
|
||||
MetalIndexBuffer::MetalIndexBuffer(MetalContext& context, BufferUsage usage, uint8_t elementSize,
|
||||
uint32_t indexCount) : HwIndexBuffer(elementSize, indexCount),
|
||||
buffer(context, BufferObjectBinding::VERTEX, usage, elementSize * indexCount, true) { }
|
||||
|
||||
MetalRenderPrimitive::MetalRenderPrimitive() {
|
||||
}
|
||||
|
||||
void MetalRenderPrimitive::setBuffers(MetalVertexBufferInfo const* const vbi,
|
||||
MetalVertexBuffer* vertexBuffer, MetalIndexBuffer* indexBuffer) {
|
||||
this->vertexBuffer = vertexBuffer;
|
||||
this->indexBuffer = indexBuffer;
|
||||
}
|
||||
|
||||
MetalProgram::MetalProgram(MetalContext& context, Program&& program) noexcept
|
||||
: HwProgram(program.getName()), mContext(context) {
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@ class MetalShaderCompiler {
|
||||
struct MetalProgramToken;
|
||||
|
||||
public:
|
||||
enum class Mode {
|
||||
SYNCHRONOUS, // synchronous shader compilation
|
||||
ASYNCHRONOUS // asynchronous shader compilation
|
||||
};
|
||||
|
||||
class MetalFunctionBundle {
|
||||
public:
|
||||
using Raster = std::tuple<id<MTLFunction>, id<MTLFunction>>;
|
||||
@@ -110,7 +115,7 @@ public:
|
||||
|
||||
using program_token_t = std::shared_ptr<MetalProgramToken>;
|
||||
|
||||
explicit MetalShaderCompiler(id<MTLDevice> device, MetalDriver& driver);
|
||||
explicit MetalShaderCompiler(id<MTLDevice> device, MetalDriver& driver, Mode mode);
|
||||
|
||||
MetalShaderCompiler(MetalShaderCompiler const& rhs) = delete;
|
||||
MetalShaderCompiler(MetalShaderCompiler&& rhs) = delete;
|
||||
@@ -120,15 +125,15 @@ public:
|
||||
void init() noexcept;
|
||||
void terminate() noexcept;
|
||||
|
||||
// Creates a program asynchronously
|
||||
bool isParallelShaderCompileSupported() const noexcept;
|
||||
|
||||
// Creates a program, either synchronously or asynchronously, depending on the Mode
|
||||
// MetalShaderCompiler was constructed with.
|
||||
program_token_t createProgram(utils::CString const& name, Program&& program);
|
||||
|
||||
// Returns the functions, blocking if necessary. The Token is destroyed and becomes invalid.
|
||||
MetalFunctionBundle getProgram(program_token_t& token);
|
||||
|
||||
// Destroys a valid token and all associated resources. Used to "cancel" a program compilation.
|
||||
static void terminate(program_token_t& token);
|
||||
|
||||
void notifyWhenAllProgramsAreReady(
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user);
|
||||
|
||||
@@ -138,6 +143,7 @@ private:
|
||||
CompilerThreadPool mCompilerThreadPool;
|
||||
id<MTLDevice> mDevice;
|
||||
CallbackManager mCallbackManager;
|
||||
Mode mMode;
|
||||
};
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -70,22 +70,31 @@ struct MetalShaderCompiler::MetalProgramToken : ProgramToken {
|
||||
|
||||
MetalShaderCompiler::MetalProgramToken::~MetalProgramToken() = default;
|
||||
|
||||
MetalShaderCompiler::MetalShaderCompiler(id<MTLDevice> device, MetalDriver& driver)
|
||||
MetalShaderCompiler::MetalShaderCompiler(id<MTLDevice> device, MetalDriver& driver, Mode mode)
|
||||
: mDevice(device),
|
||||
mCallbackManager(driver) {
|
||||
mCallbackManager(driver),
|
||||
mMode(mode) {
|
||||
|
||||
}
|
||||
|
||||
void MetalShaderCompiler::init() noexcept {
|
||||
const uint32_t poolSize = 1;
|
||||
mCompilerThreadPool.init(poolSize, []() {}, []() {});
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
mCompilerThreadPool.init(poolSize, []() {}, []() {});
|
||||
}
|
||||
}
|
||||
|
||||
void MetalShaderCompiler::terminate() noexcept {
|
||||
mCompilerThreadPool.terminate();
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
mCompilerThreadPool.terminate();
|
||||
}
|
||||
mCallbackManager.terminate();
|
||||
}
|
||||
|
||||
bool MetalShaderCompiler::isParallelShaderCompileSupported() const noexcept {
|
||||
return mMode == Mode::ASYNCHRONOUS;
|
||||
}
|
||||
|
||||
/* static */ MetalShaderCompiler::MetalFunctionBundle MetalShaderCompiler::compileProgram(
|
||||
const Program& program, id<MTLDevice> device) {
|
||||
std::array<id<MTLFunction>, Program::SHADER_TYPE_COUNT> functions = { nil };
|
||||
@@ -180,14 +189,26 @@ MetalShaderCompiler::program_token_t MetalShaderCompiler::createProgram(
|
||||
|
||||
token->handle = mCallbackManager.get();
|
||||
|
||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, name, device = mDevice, program = std::move(program), token]() {
|
||||
MetalFunctionBundle compiledProgram = compileProgram(program, device);
|
||||
switch (mMode) {
|
||||
case Mode::ASYNCHRONOUS: {
|
||||
CompilerPriorityQueue const priorityQueue = program.getPriorityQueue();
|
||||
mCompilerThreadPool.queue(priorityQueue, token,
|
||||
[this, name, device = mDevice, program = std::move(program), token]() {
|
||||
MetalFunctionBundle compiledProgram = compileProgram(program, device);
|
||||
token->set(compiledProgram);
|
||||
mCallbackManager.put(token->handle);
|
||||
});
|
||||
|
||||
token->set(compiledProgram);
|
||||
mCallbackManager.put(token->handle);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::SYNCHRONOUS: {
|
||||
MetalFunctionBundle compiledProgram = compileProgram(program, mDevice);
|
||||
token->set(compiledProgram);
|
||||
mCallbackManager.put(token->handle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
@@ -195,35 +216,23 @@ MetalShaderCompiler::program_token_t MetalShaderCompiler::createProgram(
|
||||
MetalShaderCompiler::MetalFunctionBundle MetalShaderCompiler::getProgram(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
if (!token->isReady()) {
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
job();
|
||||
if (mMode == Mode::ASYNCHRONOUS) {
|
||||
if (!token->isReady()) {
|
||||
auto job = mCompilerThreadPool.dequeue(token);
|
||||
if (job) {
|
||||
job();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The job isn't guaranteed to have finished yet. We may have failed to dequeue it above,
|
||||
// which means it's currently running. In that case get() will block until it finishes.
|
||||
|
||||
MetalShaderCompiler::MetalFunctionBundle program = token->get();
|
||||
|
||||
token = nullptr;
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
/* static */ void MetalShaderCompiler::terminate(program_token_t& token) {
|
||||
assert_invariant(token);
|
||||
|
||||
auto job = token->compiler.mCompilerThreadPool.dequeue(token);
|
||||
if (!job) {
|
||||
// The job is being executed right now (or has already executed).
|
||||
token->wait();
|
||||
} else {
|
||||
// The job has not executed yet.
|
||||
token->compiler.mCallbackManager.put(token->handle);
|
||||
}
|
||||
|
||||
token.reset();
|
||||
}
|
||||
|
||||
void MetalShaderCompiler::notifyWhenAllProgramsAreReady(
|
||||
CallbackHandler* handler, CallbackHandler::Callback callback, void* user) {
|
||||
mCallbackManager.setCallback(handler, callback, user);
|
||||
|
||||
@@ -350,6 +350,15 @@ void NoopDriver::blit(
|
||||
math::uint2 size) {
|
||||
}
|
||||
|
||||
void NoopDriver::bindPipeline(PipelineState pipelineState) {
|
||||
}
|
||||
|
||||
void NoopDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
}
|
||||
|
||||
void NoopDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
}
|
||||
|
||||
void NoopDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
}
|
||||
|
||||
@@ -94,10 +94,10 @@ Driver* OpenGLDriver::create(OpenGLPlatform* const platform,
|
||||
// GLSamplerGroup : 16 few
|
||||
// GLSwapChain : 16 few
|
||||
// GLTimerQuery : 16 few
|
||||
// GLRenderPrimitive : 20 many
|
||||
// GLFence : 24 few
|
||||
// -- less than or equal 24 bytes
|
||||
// GLRenderPrimitive : 32 many
|
||||
// GLBufferObject : 32 many
|
||||
// -- less than or equal 32 bytes
|
||||
// OpenGLProgram : 56 moderate
|
||||
// GLTexture : 64 moderate
|
||||
// -- less than or equal 64 bytes
|
||||
@@ -518,10 +518,12 @@ void OpenGLDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||
GLIndexBuffer const* const ib = handle_cast<const GLIndexBuffer*>(ibh);
|
||||
assert_invariant(ib->elementSize == 2 || ib->elementSize == 4);
|
||||
|
||||
GLRenderPrimitive* rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
GLVertexBuffer* const vb = handle_cast<GLVertexBuffer*>(vbh);
|
||||
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
rp->gl.indicesSize = (ib->elementSize == 4u) ? 4u : 2u;
|
||||
rp->gl.vertexBufferWithObjects = vbh;
|
||||
rp->type = pt;
|
||||
rp->vbih = vb->vbih;
|
||||
|
||||
// create a name for this VAO in the current context
|
||||
gl.procs.genVertexArrays(1, &rp->gl.vao[gl.contextIndex]);
|
||||
@@ -3784,39 +3786,42 @@ void OpenGLDriver::updateTextureLodRange(GLTexture* texture, int8_t targetLevel)
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) {
|
||||
void OpenGLDriver::bindPipeline(PipelineState state) {
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
|
||||
setRasterState(state.rasterState);
|
||||
setStencilState(state.stencilState);
|
||||
gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant);
|
||||
OpenGLProgram* const p = handle_cast<OpenGLProgram*>(state.program);
|
||||
mValidProgram = useProgram(p);
|
||||
}
|
||||
|
||||
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.
|
||||
return;
|
||||
}
|
||||
void OpenGLDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
DEBUG_MARKER()
|
||||
auto& gl = mContext;
|
||||
|
||||
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
|
||||
// Gracefully do nothing if the render primitive has not been set up.
|
||||
VertexBufferHandle vb = rp->gl.vertexBufferWithObjects;
|
||||
if (UTILS_UNLIKELY(!vb)) {
|
||||
mBoundRenderPrimitive = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
gl.bindVertexArray(&rp->gl);
|
||||
|
||||
// If necessary, mutate the bindings in the VAO.
|
||||
gl.bindVertexArray(&rp->gl);
|
||||
GLVertexBuffer const* const glvb = handle_cast<GLVertexBuffer*>(vb);
|
||||
updateVertexArrayObject(rp, glvb);
|
||||
|
||||
setRasterState(state.rasterState);
|
||||
setStencilState(state.stencilState);
|
||||
mBoundRenderPrimitive = rp;
|
||||
}
|
||||
|
||||
gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant);
|
||||
void OpenGLDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
GLRenderPrimitive const* const rp = mBoundRenderPrimitive;
|
||||
if (UTILS_UNLIKELY(!rp || !mValidProgram)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(instanceCount <= 1)) {
|
||||
glDrawElements(GLenum(rp->type), (GLsizei)indexCount, rp->gl.getIndicesType(),
|
||||
@@ -3842,6 +3847,17 @@ void OpenGLDriver::scissor(Viewport scissor) {
|
||||
setScissor(scissor);
|
||||
}
|
||||
|
||||
void OpenGLDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) {
|
||||
DEBUG_MARKER()
|
||||
GLRenderPrimitive* const rp = handle_cast<GLRenderPrimitive*>(rph);
|
||||
state.primitiveType = rp->type;
|
||||
state.vertexBufferInfo = rp->vbih;
|
||||
bindPipeline(state);
|
||||
bindRenderPrimitive(rph);
|
||||
draw2(indexOffset, indexCount, instanceCount);
|
||||
}
|
||||
|
||||
void OpenGLDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
getShaderCompilerService().tick();
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ public:
|
||||
struct GLRenderPrimitive : public HwRenderPrimitive {
|
||||
using HwRenderPrimitive::HwRenderPrimitive;
|
||||
OpenGLContext::RenderPrimitive gl;
|
||||
Handle<HwVertexBufferInfo> vbih;
|
||||
};
|
||||
|
||||
struct GLTexture : public HwTexture {
|
||||
@@ -366,6 +367,10 @@ private:
|
||||
GLboolean mRenderPassDepthWrite{};
|
||||
GLboolean mRenderPassStencilWrite{};
|
||||
|
||||
GLRenderPrimitive const* mBoundRenderPrimitive = nullptr;
|
||||
bool mValidProgram = false;
|
||||
|
||||
|
||||
void clearWithRasterPipe(TargetBufferFlags clearFlags,
|
||||
math::float4 const& linearColor, GLfloat depth, GLint stencil) noexcept;
|
||||
|
||||
|
||||
@@ -14,21 +14,34 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <backend/AcquiredImage.h>
|
||||
#include <backend/Platform.h>
|
||||
#include <backend/platforms/PlatformEGL.h>
|
||||
#include <backend/platforms/PlatformEGLAndroid.h>
|
||||
|
||||
#include "opengl/GLUtils.h"
|
||||
#include "ExternalStreamManagerAndroid.h"
|
||||
|
||||
#include <android/api-level.h>
|
||||
#include <android/hardware_buffer.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <new>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// We require filament to be built with an API 19 toolchain, before that, OpenGLES 3.0 didn't exist
|
||||
// Actually, OpenGL ES 3.0 was added to API 18, but API 19 is the better target and
|
||||
// the minimum for Jetpack at the time of this comment.
|
||||
@@ -165,14 +178,28 @@ int PlatformEGLAndroid::getOSVersion() const noexcept {
|
||||
|
||||
AcquiredImage PlatformEGLAndroid::transformAcquiredImage(AcquiredImage source) noexcept {
|
||||
// Convert the AHardwareBuffer to EGLImage.
|
||||
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID((const AHardwareBuffer*)source.image);
|
||||
AHardwareBuffer const* const pHardwareBuffer = (const AHardwareBuffer*)source.image;
|
||||
|
||||
EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(pHardwareBuffer);
|
||||
if (!clientBuffer) {
|
||||
slog.e << "Unable to get EGLClientBuffer from AHardwareBuffer." << io::endl;
|
||||
return {};
|
||||
}
|
||||
// Note that this cannot be used to stream protected video (for now) because we do not set EGL_PROTECTED_CONTENT_EXT.
|
||||
EGLint attrs[] = { EGL_NONE, EGL_NONE };
|
||||
EGLImageKHR eglImage = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs);
|
||||
|
||||
PlatformEGL::Config attributes;
|
||||
|
||||
if (__builtin_available(android 26, *)) {
|
||||
AHardwareBuffer_Desc desc;
|
||||
AHardwareBuffer_describe(pHardwareBuffer, &desc);
|
||||
bool const isProtectedContent =
|
||||
desc.usage & AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT;
|
||||
if (isProtectedContent) {
|
||||
attributes[EGL_PROTECTED_CONTENT_EXT] = EGL_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
EGLImageKHR eglImage = eglCreateImageKHR(mEGLDisplay,
|
||||
EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attributes.data());
|
||||
if (eglImage == EGL_NO_IMAGE_KHR) {
|
||||
slog.e << "eglCreateImageKHR returned no image." << io::endl;
|
||||
return {};
|
||||
|
||||
@@ -435,8 +435,8 @@ void VulkanCommands::popGroupMarker() {
|
||||
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;
|
||||
utils::slog.d << "<---- " << marker << " elapsed: " << (diff.count() * 1000) << " ms"
|
||||
<< utils::io::endl;
|
||||
#else
|
||||
mGroupMarkers->pop();
|
||||
#endif
|
||||
|
||||
@@ -63,11 +63,12 @@
|
||||
// Enable the debug utils extension if it is available.
|
||||
#define FVK_DEBUG_DEBUG_UTILS 0x00008000
|
||||
|
||||
#define FVK_DEBUG_RESOURCE_LEAK 0x00010000
|
||||
|
||||
// Usefaul default combinations
|
||||
#define FVK_DEBUG_EVERYTHING 0xFFFFFFFF
|
||||
#define FVK_DEBUG_PERFORMANCE \
|
||||
FVK_DEBUG_SYSTRACE | \
|
||||
FVK_DEBUG_GROUP_MARKERS
|
||||
FVK_DEBUG_SYSTRACE
|
||||
|
||||
#define FVK_DEBUG_CORRECTNESS \
|
||||
FVK_DEBUG_VALIDATION | \
|
||||
@@ -80,14 +81,12 @@
|
||||
FVK_DEBUG_PRINT_GROUP_MARKERS
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE | FVK_DEBUG_DEBUG_UTILS | FVK_DEBUG_VALIDATION)
|
||||
#define FVK_DEBUG_FLAGS (FVK_DEBUG_PERFORMANCE)
|
||||
#else
|
||||
#define FVK_DEBUG_FLAGS 0
|
||||
#endif
|
||||
|
||||
#define FVK_ENABLED(flags) ((FVK_DEBUG_FLAGS) & (flags))
|
||||
#define FVK_ENABLED_BOOL(flags) ((bool) FVK_ENABLED(flags))
|
||||
|
||||
#define FVK_ENABLED(flags) (((FVK_DEBUG_FLAGS) & (flags)) == (flags))
|
||||
|
||||
// Group marker only works only if validation or debug utils is enabled since it uses
|
||||
// vkCmd(Begin/End)DebugUtilsLabelEXT or vkCmdDebugMarker(Begin/End)EXT
|
||||
@@ -107,6 +106,15 @@ static_assert(FVK_ENABLED(FVK_DEBUG_VALIDATION));
|
||||
|
||||
// end dependcy checks
|
||||
|
||||
// Shorthand for combination of enabled debug flags
|
||||
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS) || FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
#define FVK_ENABLED_DEBUG_SAMPLER_NAME 1
|
||||
#else
|
||||
#define FVK_ENABLED_DEBUG_SAMPLER_NAME 0
|
||||
#endif
|
||||
|
||||
// end shorthands
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SYSTRACE)
|
||||
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
@@ -264,26 +264,28 @@ Driver* VulkanDriver::create(VulkanPlatform* platform, VulkanContext const& cont
|
||||
#if 0
|
||||
// this is useful for development, but too verbose even for debug builds
|
||||
// For reference on a 64-bits machine in Release mode:
|
||||
// VulkanSamplerGroup : 24 few
|
||||
// VulkanSamplerGroup : 16 few
|
||||
// HwStream : 24 few
|
||||
// VulkanFence : 40 few
|
||||
// VulkanProgram : 40 moderate
|
||||
// VulkanIndexBuffer : 72 moderate
|
||||
// VulkanBufferObject : 72 many
|
||||
// -- less than or equal 80 bytes
|
||||
// VulkanRenderPrimitive : 104 many
|
||||
// VulkanSwapChain : 112 few
|
||||
// VulkanTimerQuery : 168 few
|
||||
// -- less than or equal 176 bytes
|
||||
// VulkanTexture : 232 moderate
|
||||
// VulkanVertexBuffer : 312 moderate
|
||||
// VulkanRenderTarget : 320 few
|
||||
// -- less than or equal to 320 bytes
|
||||
// VulkanFence : 32 few
|
||||
// VulkanProgram : 32 moderate
|
||||
// VulkanIndexBuffer : 64 moderate
|
||||
// VulkanBufferObject : 64 many
|
||||
// -- less than or equal 64 bytes
|
||||
// VulkanRenderPrimitive : 72 many
|
||||
// VulkanVertexBufferInfo : 96 moderate
|
||||
// VulkanSwapChain : 104 few
|
||||
// VulkanTimerQuery : 160 few
|
||||
// -- less than or equal 160 bytes
|
||||
// VulkanVertexBuffer : 192 moderate
|
||||
// VulkanTexture : 224 moderate
|
||||
// VulkanRenderTarget : 312 few
|
||||
// -- less than or equal to 312 bytes
|
||||
|
||||
utils::slog.d
|
||||
<< "\nVulkanSwapChain: " << sizeof(VulkanSwapChain)
|
||||
<< "\nVulkanBufferObject: " << sizeof(VulkanBufferObject)
|
||||
<< "\nVulkanVertexBuffer: " << sizeof(VulkanVertexBuffer)
|
||||
<< "\nVulkanVertexBufferInfo: " << sizeof(VulkanVertexBufferInfo)
|
||||
<< "\nVulkanIndexBuffer: " << sizeof(VulkanIndexBuffer)
|
||||
<< "\nVulkanSamplerGroup: " << sizeof(VulkanSamplerGroup)
|
||||
<< "\nVulkanRenderPrimitive: " << sizeof(VulkanRenderPrimitive)
|
||||
@@ -359,6 +361,11 @@ void VulkanDriver::collectGarbage() {
|
||||
mCommands->gc();
|
||||
mStagePool.gc();
|
||||
mFramebufferCache.gc();
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
mResourceAllocator.print();
|
||||
#endif
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
void VulkanDriver::beginFrame(int64_t monotonic_clock_ns, uint32_t frameId) {
|
||||
@@ -411,9 +418,9 @@ void VulkanDriver::createSamplerGroupR(Handle<HwSamplerGroup> sbh, uint32_t coun
|
||||
void VulkanDriver::createRenderPrimitiveR(Handle<HwRenderPrimitive> rph,
|
||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh,
|
||||
PrimitiveType pt) {
|
||||
auto rp = mResourceAllocator.construct<VulkanRenderPrimitive>(rph, &mResourceAllocator);
|
||||
auto rp = mResourceAllocator.construct<VulkanRenderPrimitive>(rph,
|
||||
&mResourceAllocator, pt, vbh, ibh);
|
||||
mResourceManager.acquire(rp);
|
||||
VulkanDriver::setRenderPrimitiveBuffer(rph, pt, vbh, ibh);
|
||||
}
|
||||
|
||||
void VulkanDriver::destroyRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
@@ -982,7 +989,6 @@ void VulkanDriver::setVertexBufferObject(Handle<HwVertexBuffer> vbh, uint32_t in
|
||||
auto vb = mResourceAllocator.handle_cast<VulkanVertexBuffer*>(vbh);
|
||||
auto bo = mResourceAllocator.handle_cast<VulkanBufferObject*>(boh);
|
||||
assert_invariant(bo->bindingType == BufferObjectBinding::VERTEX);
|
||||
|
||||
vb->setBuffer(mResourceAllocator, bo, index);
|
||||
}
|
||||
|
||||
@@ -1460,14 +1466,6 @@ void VulkanDriver::nextSubpass(int) {
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanDriver::setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
|
||||
Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh) {
|
||||
auto primitive = mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
primitive->setBuffers(mResourceAllocator.handle_cast<VulkanVertexBuffer*>(vbh),
|
||||
mResourceAllocator.handle_cast<VulkanIndexBuffer*>(ibh));
|
||||
primitive->setPrimitiveType(pt);
|
||||
}
|
||||
|
||||
void VulkanDriver::makeCurrent(Handle<HwSwapChain> drawSch, Handle<HwSwapChain> readSch) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("makeCurrent");
|
||||
@@ -1713,14 +1711,13 @@ void VulkanDriver::blitDEPRECATED(TargetBufferFlags buffers,
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) {
|
||||
void VulkanDriver::bindPipeline(PipelineState pipelineState) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("draw");
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
const VulkanVertexBufferInfo& vbi =
|
||||
*mResourceAllocator.handle_cast<VulkanVertexBufferInfo*>(pipelineState.vertexBufferInfo);
|
||||
|
||||
Handle<HwProgram> programHandle = pipelineState.program;
|
||||
RasterState rasterState = pipelineState.rasterState;
|
||||
@@ -1728,15 +1725,6 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
|
||||
auto* program = mResourceAllocator.handle_cast<VulkanProgram*>(programHandle);
|
||||
commands->acquire(program);
|
||||
commands->acquire(prim.indexBuffer);
|
||||
commands->acquire(prim.vertexBuffer);
|
||||
|
||||
// If this is a debug build, validate the current shader.
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
if (program->bundle.vertex == VK_NULL_HANDLE || program->bundle.fragment == VK_NULL_HANDLE) {
|
||||
utils::slog.e << "Binding missing shader: " << program->name.c_str() << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Update the VK raster state.
|
||||
const VulkanRenderTarget* rt = mCurrentRenderPass.renderTarget;
|
||||
@@ -1762,20 +1750,19 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
.depthBiasSlopeFactor = depthOffset.slope
|
||||
};
|
||||
|
||||
// unfortunately in Vulkan the topology is per pipeline
|
||||
VkPrimitiveTopology const topology =
|
||||
VulkanPipelineCache::getPrimitiveTopology(pipelineState.primitiveType);
|
||||
|
||||
// Declare fixed-size arrays that get passed to the pipeCache and to vkCmdBindVertexBuffers.
|
||||
VulkanVertexBufferInfo const* const vbi =
|
||||
mResourceAllocator.handle_cast<VulkanVertexBufferInfo*>(prim.vertexBuffer->vbih);
|
||||
uint32_t const bufferCount = vbi->attributes.size();
|
||||
VkVertexInputAttributeDescription const* attribDesc = prim.vertexBuffer->getAttribDescriptions();
|
||||
VkVertexInputBindingDescription const* bufferDesc = prim.vertexBuffer->getBufferDescriptions();
|
||||
VkBuffer const* buffers = prim.vertexBuffer->getVkBuffers();
|
||||
VkDeviceSize const* offsets = prim.vertexBuffer->getOffsets();
|
||||
VkVertexInputAttributeDescription const* attribDesc = vbi.getAttribDescriptions();
|
||||
VkVertexInputBindingDescription const* bufferDesc = vbi.getBufferDescriptions();
|
||||
|
||||
// Push state changes to the VulkanPipelineCache instance. This is fast and does not make VK calls.
|
||||
mPipelineCache.bindProgram(program);
|
||||
mPipelineCache.bindRasterState(vulkanRasterState);
|
||||
mPipelineCache.bindPrimitiveTopology(prim.primitiveTopology);
|
||||
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, bufferCount);
|
||||
mPipelineCache.bindPrimitiveTopology(topology);
|
||||
mPipelineCache.bindVertexArray(attribDesc, bufferDesc, vbi.getAttributeCount());
|
||||
|
||||
// Query the program for the mapping from (SamplerGroupBinding,Offset) to (SamplerBinding),
|
||||
// where "SamplerBinding" is the integer in the GLSL, and SamplerGroupBinding is the abstract
|
||||
@@ -1787,6 +1774,10 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
auto const& bindingToSamplerIndex = program->getBindingToSamplerIndex();
|
||||
VulkanPipelineCache::UsageFlags usage = program->getUsage();
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
auto const& bindingToName = program->getBindingToName();
|
||||
#endif
|
||||
|
||||
UTILS_NOUNROLL
|
||||
for (uint8_t binding = 0; binding < VulkanPipelineCache::SAMPLER_BINDING_COUNT; binding++) {
|
||||
uint16_t const indexPair = bindingToSamplerIndex[binding];
|
||||
@@ -1819,15 +1810,21 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
// matching characteristics. (e.g. if the missing texture is a 3D texture)
|
||||
if (UTILS_UNLIKELY(texture->getPrimaryImageLayout() == VulkanLayout::UNDEFINED)) {
|
||||
#if FVK_ENABLED(FVK_DEBUG_TEXTURE)
|
||||
utils::slog.w << "Uninitialized texture bound to '" << sampler.name.c_str() << "'";
|
||||
utils::slog.w << "Uninitialized texture bound to '" << bindingToName[binding] << "'";
|
||||
utils::slog.w << " in material '" << program->name.c_str() << "'";
|
||||
utils::slog.w << " at binding point " << +sampler.binding << utils::io::endl;
|
||||
utils::slog.w << " at binding point " << +binding << utils::io::endl;
|
||||
#endif
|
||||
texture = mEmptyTexture.get();
|
||||
}
|
||||
|
||||
SamplerParams const& samplerParams = boundSampler->s;
|
||||
VkSampler const vksampler = mSamplerCache.getSampler(samplerParams);
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
VulkanDriver::DebugUtils::setName(VK_OBJECT_TYPE_SAMPLER,
|
||||
reinterpret_cast<uint64_t>(vksampler), bindingToName[binding].c_str());
|
||||
#endif
|
||||
|
||||
VkImageView imageView = VK_NULL_HANDLE;
|
||||
VkImageSubresourceRange const range = texture->getPrimaryViewRange();
|
||||
if (any(texture->usage & TextureUsage::DEPTH_ATTACHMENT) &&
|
||||
@@ -1851,19 +1848,33 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
|
||||
mPipelineCache.bindSamplers(samplerInfo, samplerTextures, usage);
|
||||
|
||||
// Bind new descriptor sets if they need to change.
|
||||
// If descriptor set allocation failed, skip the draw call and bail. No need to emit an error
|
||||
// message since the validation layers already do so.
|
||||
if (!mPipelineCache.bindDescriptors(cmdbuffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind a new pipeline if the pipeline state changed.
|
||||
// If allocation failed, skip the draw call and bail. We do not emit an error since the
|
||||
// validation layer will already do so.
|
||||
if (!mPipelineCache.bindPipeline(commands)) {
|
||||
return;
|
||||
}
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::bindRenderPrimitive(Handle<HwRenderPrimitive> rph) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("bindRenderPrimitive");
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
const VulkanRenderPrimitive& prim = *mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
commands->acquire(prim.indexBuffer);
|
||||
commands->acquire(prim.vertexBuffer);
|
||||
|
||||
// This *must* match the VulkanVertexBufferInfo that was bound in bindPipeline(). But we want
|
||||
// to allow to call this before bindPipeline(), so the validation can only happen in draw()
|
||||
VulkanVertexBufferInfo const* const vbi =
|
||||
mResourceAllocator.handle_cast<VulkanVertexBufferInfo*>(prim.vertexBuffer->vbih);
|
||||
|
||||
uint32_t const bufferCount = vbi->getAttributeCount();
|
||||
VkDeviceSize const* offsets = vbi->getOffsets();
|
||||
VkBuffer const* buffers = prim.vertexBuffer->getVkBuffers();
|
||||
|
||||
// Next bind the vertex buffers and index buffer. One potential performance improvement is to
|
||||
// avoid rebinding these if they are already bound, but since we do not (yet) support subranges
|
||||
@@ -1872,15 +1883,43 @@ void VulkanDriver::draw(PipelineState pipelineState, Handle<HwRenderPrimitive> r
|
||||
vkCmdBindIndexBuffer(cmdbuffer, prim.indexBuffer->buffer.getGpuBuffer(), 0,
|
||||
prim.indexBuffer->indexType);
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::draw2(uint32_t indexOffset, uint32_t indexCount, uint32_t instanceCount) {
|
||||
FVK_SYSTRACE_CONTEXT();
|
||||
FVK_SYSTRACE_START("draw2");
|
||||
|
||||
VulkanCommandBuffer* commands = &mCommands->get();
|
||||
VkCommandBuffer cmdbuffer = commands->buffer();
|
||||
|
||||
// Bind new descriptor sets if they need to change.
|
||||
// If descriptor set allocation failed, skip the draw call and bail. No need to emit an error
|
||||
// message since the validation layers already do so.
|
||||
if (!mPipelineCache.bindDescriptors(cmdbuffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Finally, make the actual draw call. TODO: support subranges
|
||||
const uint32_t firstIndex = indexOffset;
|
||||
const int32_t vertexOffset = 0;
|
||||
const uint32_t firstInstId = 0;
|
||||
|
||||
vkCmdDrawIndexed(cmdbuffer, indexCount, instanceCount, firstIndex, vertexOffset, firstInstId);
|
||||
|
||||
FVK_SYSTRACE_END();
|
||||
}
|
||||
|
||||
void VulkanDriver::draw(PipelineState state, Handle<HwRenderPrimitive> rph,
|
||||
uint32_t const indexOffset, uint32_t const indexCount, uint32_t const instanceCount) {
|
||||
VulkanRenderPrimitive* const rp = mResourceAllocator.handle_cast<VulkanRenderPrimitive*>(rph);
|
||||
state.primitiveType = rp->type;
|
||||
state.vertexBufferInfo = rp->vertexBuffer->vbih;
|
||||
bindPipeline(state);
|
||||
bindRenderPrimitive(rph);
|
||||
draw2(indexOffset, indexCount, instanceCount);
|
||||
}
|
||||
|
||||
void VulkanDriver::dispatchCompute(Handle<HwProgram> program, math::uint3 workGroupCount) {
|
||||
// FIXME: implement me
|
||||
}
|
||||
|
||||
@@ -101,10 +101,6 @@ private:
|
||||
VulkanDriver& operator=(VulkanDriver const&) = delete;
|
||||
|
||||
private:
|
||||
inline void setRenderPrimitiveBuffer(Handle<HwRenderPrimitive> rph, PrimitiveType pt,
|
||||
Handle<HwVertexBuffer> vbh,
|
||||
Handle<HwIndexBuffer> ibh);
|
||||
|
||||
void collectGarbage();
|
||||
|
||||
VulkanPlatform* mPlatform = nullptr;
|
||||
|
||||
@@ -99,6 +99,10 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
auto& bindingToName = mInfo->bindingToName;
|
||||
#endif
|
||||
|
||||
auto& groupInfo = builder.getSamplerGroupInfo();
|
||||
auto& bindingToSamplerIndex = mInfo->bindingToSamplerIndex;
|
||||
auto& usage = mInfo->usage;
|
||||
@@ -109,12 +113,16 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
|
||||
uint32_t const binding = samplers[i].binding;
|
||||
bindingToSamplerIndex[binding] = (groupInd << 8) | (0xff & i);
|
||||
usage = VulkanPipelineCache::getUsageFlags(binding, group.stageFlags, usage);
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
bindingToName[binding] = samplers[i].name.c_str();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << bundle.vertex
|
||||
<< ", " << bundle.fragment << ")" << utils::io::endl;
|
||||
utils::slog.d << "Created VulkanProgram " << builder << ", shaders = (" << modules[0]
|
||||
<< ", " << modules[1] << ")" << utils::io::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -281,32 +289,21 @@ uint8_t VulkanRenderTarget::getColorTargetCount(const VulkanRenderPass& pass) co
|
||||
return count;
|
||||
}
|
||||
|
||||
VulkanVertexBufferInfo::VulkanVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
||||
AttributeArray const& attributes)
|
||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||
VulkanResource(VulkanResourceType::VERTEX_BUFFER_INFO),
|
||||
attributes(attributes) {
|
||||
}
|
||||
VulkanVertexBufferInfo::VulkanVertexBufferInfo(
|
||||
uint8_t bufferCount, uint8_t attributeCount, AttributeArray const& attributes)
|
||||
: HwVertexBufferInfo(bufferCount, attributeCount),
|
||||
VulkanResource(VulkanResourceType::VERTEX_BUFFER_INFO),
|
||||
mInfo(attributes.size()) {
|
||||
|
||||
VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||
VulkanResourceAllocator* allocator, uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih)
|
||||
: HwVertexBuffer(vertexCount),
|
||||
VulkanResource(VulkanResourceType::VERTEX_BUFFER),
|
||||
vbih(vbih),
|
||||
mResources(allocator) {
|
||||
auto attribDesc = mInfo.mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
|
||||
auto bufferDesc = mInfo.mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
|
||||
auto offsets = mInfo.mSoa.data<PipelineInfo::OFFSETS>();
|
||||
auto attribToBufferIndex = mInfo.mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
|
||||
std::fill(mInfo.mSoa.begin<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(),
|
||||
mInfo.mSoa.end<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(), -1);
|
||||
|
||||
VulkanVertexBufferInfo const* const vbi = allocator->handle_cast<VulkanVertexBufferInfo*>(vbih);
|
||||
mInfo = new PipelineInfo(vbi->attributes.size());
|
||||
|
||||
auto attribDesc = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
|
||||
auto bufferDesc = mInfo->mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
|
||||
auto offsets = mInfo->mSoa.data<PipelineInfo::OFFSETS>();
|
||||
auto attribToBufferIndex = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
|
||||
std::fill(mInfo->mSoa.begin<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(),
|
||||
mInfo->mSoa.end<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>(), -1);
|
||||
|
||||
for (uint32_t attribIndex = 0; attribIndex < vbi->attributes.size(); attribIndex++) {
|
||||
Attribute attrib = vbi->attributes[attribIndex];
|
||||
for (uint32_t attribIndex = 0; attribIndex < attributes.size(); attribIndex++) {
|
||||
Attribute attrib = attributes[attribIndex];
|
||||
bool const isInteger = attrib.flags & Attribute::FLAG_INTEGER_TARGET;
|
||||
bool const isNormalized = attrib.flags & Attribute::FLAG_NORMALIZED;
|
||||
VkFormat vkformat = getVkFormat(attrib.type, isNormalized, isInteger);
|
||||
@@ -318,7 +315,7 @@ VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool&
|
||||
// expects to receive floats or ints.
|
||||
if (attrib.buffer == Attribute::BUFFER_UNUSED) {
|
||||
vkformat = isInteger ? VK_FORMAT_R8G8B8A8_UINT : VK_FORMAT_R8G8B8A8_SNORM;
|
||||
attrib = vbi->attributes[0];
|
||||
attrib = attributes[0];
|
||||
}
|
||||
offsets[attribIndex] = attrib.offset;
|
||||
attribDesc[attribIndex] = {
|
||||
@@ -334,18 +331,23 @@ VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool&
|
||||
}
|
||||
}
|
||||
|
||||
VulkanVertexBuffer::~VulkanVertexBuffer() {
|
||||
delete mInfo;
|
||||
VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool,
|
||||
VulkanResourceAllocator* allocator,
|
||||
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih)
|
||||
: HwVertexBuffer(vertexCount),
|
||||
VulkanResource(VulkanResourceType::VERTEX_BUFFER),
|
||||
vbih(vbih),
|
||||
mBuffers(MAX_VERTEX_BUFFER_COUNT), // TODO: can we do better here?
|
||||
mResources(allocator) {
|
||||
}
|
||||
|
||||
void VulkanVertexBuffer::setBuffer(VulkanResourceAllocator const& allocator,
|
||||
VulkanBufferObject* bufferObject, uint32_t index) {
|
||||
|
||||
VulkanVertexBufferInfo const* const vbi =
|
||||
const_cast<VulkanResourceAllocator&>(allocator).handle_cast<VulkanVertexBufferInfo*>(vbih);
|
||||
size_t count = vbi->attributes.size();
|
||||
auto vkbuffers = mInfo->mSoa.data<PipelineInfo::VK_BUFFER>();
|
||||
auto attribToBuffer = mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
|
||||
size_t const count = vbi->getAttributeCount();
|
||||
VkBuffer* const vkbuffers = getVkBuffers();
|
||||
int8_t const* const attribToBuffer = vbi->getAttributeToBuffer();
|
||||
for (uint8_t attribIndex = 0; attribIndex < count; attribIndex++) {
|
||||
if (attribToBuffer[attribIndex] == static_cast<int8_t>(index)) {
|
||||
vkbuffers[attribIndex] = bufferObject->buffer.getGpuBuffer();
|
||||
@@ -361,35 +363,6 @@ VulkanBufferObject::VulkanBufferObject(VmaAllocator allocator, VulkanStagePool&
|
||||
buffer(allocator, stagePool, getBufferObjectUsage(bindingType), byteCount),
|
||||
bindingType(bindingType) {}
|
||||
|
||||
void VulkanRenderPrimitive::setPrimitiveType(PrimitiveType pt) {
|
||||
this->type = pt;
|
||||
switch (pt) {
|
||||
case PrimitiveType::POINTS:
|
||||
primitiveTopology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
break;
|
||||
case PrimitiveType::LINES:
|
||||
primitiveTopology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
break;
|
||||
case PrimitiveType::LINE_STRIP:
|
||||
primitiveTopology = VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
break;
|
||||
case PrimitiveType::TRIANGLES:
|
||||
primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
break;
|
||||
case PrimitiveType::TRIANGLE_STRIP:
|
||||
primitiveTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanRenderPrimitive::setBuffers(VulkanVertexBuffer* vertexBuffer,
|
||||
VulkanIndexBuffer* indexBuffer) {
|
||||
this->vertexBuffer = vertexBuffer;
|
||||
this->indexBuffer = indexBuffer;
|
||||
mResources.acquire(vertexBuffer);
|
||||
mResources.acquire(indexBuffer);
|
||||
}
|
||||
|
||||
VulkanTimerQuery::VulkanTimerQuery(std::tuple<uint32_t, uint32_t> indices)
|
||||
: VulkanThreadSafeResource(VulkanResourceType::TIMER_QUERY),
|
||||
mStartingQueryIndex(std::get<0>(indices)),
|
||||
@@ -421,4 +394,15 @@ bool VulkanTimerQuery::isCompleted() noexcept {
|
||||
|
||||
VulkanTimerQuery::~VulkanTimerQuery() = default;
|
||||
|
||||
VulkanRenderPrimitive::VulkanRenderPrimitive(VulkanResourceAllocator* resourceAllocator,
|
||||
PrimitiveType pt, Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh)
|
||||
: VulkanResource(VulkanResourceType::RENDER_PRIMITIVE),
|
||||
mResources(resourceAllocator) {
|
||||
type = pt;
|
||||
vertexBuffer = resourceAllocator->handle_cast<VulkanVertexBuffer*>(vbh);
|
||||
indexBuffer = resourceAllocator->handle_cast<VulkanIndexBuffer*>(ibh);
|
||||
mResources.acquire(vertexBuffer);
|
||||
mResources.acquire(indexBuffer);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -64,6 +64,12 @@ struct VulkanProgram : public HwProgram, VulkanResource {
|
||||
return mInfo->bindingToSamplerIndex;
|
||||
}
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
inline utils::FixedCapacityVector<std::string> const& getBindingToName() const {
|
||||
return mInfo->bindingToName;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
// TODO: handle compute shaders.
|
||||
// The expected order of shaders - from frontend to backend - is vertex, fragment, compute.
|
||||
@@ -80,6 +86,12 @@ 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 };
|
||||
|
||||
#if FVK_ENABLED_DEBUG_SAMPLER_NAME
|
||||
// We store the sampler name mapped from binding index (only for debug purposes).
|
||||
utils::FixedCapacityVector<std::string> bindingToName;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
PipelineInfo* mInfo;
|
||||
@@ -132,7 +144,48 @@ struct VulkanBufferObject;
|
||||
struct VulkanVertexBufferInfo : public HwVertexBufferInfo, VulkanResource {
|
||||
VulkanVertexBufferInfo(uint8_t bufferCount, uint8_t attributeCount,
|
||||
AttributeArray const& attributes);
|
||||
AttributeArray attributes;
|
||||
|
||||
inline VkVertexInputAttributeDescription const* getAttribDescriptions() const {
|
||||
return mInfo.mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
|
||||
}
|
||||
|
||||
inline VkVertexInputBindingDescription const* getBufferDescriptions() const {
|
||||
return mInfo.mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
|
||||
}
|
||||
|
||||
inline int8_t const* getAttributeToBuffer() const {
|
||||
return mInfo.mSoa.data<PipelineInfo::ATTRIBUTE_TO_BUFFER_INDEX>();
|
||||
}
|
||||
|
||||
inline VkDeviceSize const* getOffsets() const {
|
||||
return mInfo.mSoa.data<PipelineInfo::OFFSETS>();
|
||||
}
|
||||
|
||||
size_t getAttributeCount() const noexcept {
|
||||
return mInfo.mSoa.size();
|
||||
}
|
||||
|
||||
private:
|
||||
struct PipelineInfo {
|
||||
PipelineInfo(size_t size) : mSoa(size /* capacity */) {
|
||||
mSoa.resize(size);
|
||||
}
|
||||
|
||||
// These correspond to the index of the element in the SoA
|
||||
static constexpr uint8_t ATTRIBUTE_DESCRIPTION = 0;
|
||||
static constexpr uint8_t BUFFER_DESCRIPTION = 1;
|
||||
static constexpr uint8_t OFFSETS = 2;
|
||||
static constexpr uint8_t ATTRIBUTE_TO_BUFFER_INDEX = 3;
|
||||
|
||||
utils::StructureOfArrays<
|
||||
VkVertexInputAttributeDescription,
|
||||
VkVertexInputBindingDescription,
|
||||
VkDeviceSize,
|
||||
int8_t
|
||||
> mSoa;
|
||||
};
|
||||
|
||||
PipelineInfo mInfo;
|
||||
};
|
||||
|
||||
struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
|
||||
@@ -140,53 +193,20 @@ struct VulkanVertexBuffer : public HwVertexBuffer, VulkanResource {
|
||||
VulkanResourceAllocator* allocator,
|
||||
uint32_t vertexCount, Handle<HwVertexBufferInfo> vbih);
|
||||
|
||||
~VulkanVertexBuffer();
|
||||
|
||||
void setBuffer(VulkanResourceAllocator const& allocator,
|
||||
VulkanBufferObject* bufferObject, uint32_t index);
|
||||
|
||||
inline VkVertexInputAttributeDescription const* getAttribDescriptions() {
|
||||
return mInfo->mSoa.data<PipelineInfo::ATTRIBUTE_DESCRIPTION>();
|
||||
}
|
||||
|
||||
inline VkVertexInputBindingDescription const* getBufferDescriptions() {
|
||||
return mInfo->mSoa.data<PipelineInfo::BUFFER_DESCRIPTION>();
|
||||
}
|
||||
|
||||
inline VkBuffer const* getVkBuffers() const {
|
||||
return mInfo->mSoa.data<PipelineInfo::VK_BUFFER>();
|
||||
return mBuffers.data();
|
||||
}
|
||||
|
||||
inline VkDeviceSize const* getOffsets() const {
|
||||
return mInfo->mSoa.data<PipelineInfo::OFFSETS>();
|
||||
inline VkBuffer* getVkBuffers() {
|
||||
return mBuffers.data();
|
||||
}
|
||||
|
||||
Handle<HwVertexBufferInfo> vbih;
|
||||
|
||||
private:
|
||||
struct PipelineInfo {
|
||||
PipelineInfo(size_t size)
|
||||
: mSoa(size /* capacity */) {
|
||||
mSoa.resize(size);
|
||||
}
|
||||
|
||||
// These correspond to the index of the element in the SoA
|
||||
static constexpr uint8_t ATTRIBUTE_DESCRIPTION = 0;
|
||||
static constexpr uint8_t BUFFER_DESCRIPTION = 1;
|
||||
static constexpr uint8_t VK_BUFFER = 2;
|
||||
static constexpr uint8_t OFFSETS = 3;
|
||||
static constexpr uint8_t ATTRIBUTE_TO_BUFFER_INDEX = 4;
|
||||
|
||||
utils::StructureOfArrays<
|
||||
VkVertexInputAttributeDescription,
|
||||
VkVertexInputBindingDescription,
|
||||
VkBuffer,
|
||||
VkDeviceSize,
|
||||
int8_t
|
||||
> mSoa;
|
||||
};
|
||||
|
||||
PipelineInfo* mInfo;
|
||||
utils::FixedCapacityVector<VkBuffer> mBuffers;
|
||||
FixedSizeVulkanResourceManager<MAX_VERTEX_BUFFER_COUNT> mResources;
|
||||
};
|
||||
|
||||
@@ -219,19 +239,15 @@ struct VulkanSamplerGroup : public HwSamplerGroup, VulkanResource {
|
||||
};
|
||||
|
||||
struct VulkanRenderPrimitive : public HwRenderPrimitive, VulkanResource {
|
||||
VulkanRenderPrimitive(VulkanResourceAllocator* allocator)
|
||||
: VulkanResource(VulkanResourceType::RENDER_PRIMITIVE),
|
||||
mResources(allocator) {}
|
||||
VulkanRenderPrimitive(VulkanResourceAllocator* resourceAllocator,
|
||||
PrimitiveType pt, Handle<HwVertexBuffer> vbh, Handle<HwIndexBuffer> ibh);
|
||||
|
||||
~VulkanRenderPrimitive() {
|
||||
mResources.clear();
|
||||
}
|
||||
|
||||
void setPrimitiveType(PrimitiveType pt);
|
||||
void setBuffers(VulkanVertexBuffer* vertexBuffer, VulkanIndexBuffer* indexBuffer);
|
||||
VulkanVertexBuffer* vertexBuffer = nullptr;
|
||||
VulkanIndexBuffer* indexBuffer = nullptr;
|
||||
VkPrimitiveTopology primitiveTopology;
|
||||
|
||||
private:
|
||||
// Keep references to the vertex buffer and the index buffer.
|
||||
|
||||
@@ -563,6 +563,14 @@ VulkanPipelineCache::PipelineLayoutCacheEntry* VulkanPipelineCache::getOrCreateP
|
||||
void VulkanPipelineCache::bindProgram(VulkanProgram* program) noexcept {
|
||||
mPipelineRequirements.shaders[0] = program->getVertexShader();
|
||||
mPipelineRequirements.shaders[1] = program->getFragmentShader();
|
||||
|
||||
// If this is a debug build, validate the current shader.
|
||||
#if FVK_ENABLED(FVK_DEBUG_SHADER_MODULE)
|
||||
if (mPipelineRequirements.shaders[0] == VK_NULL_HANDLE ||
|
||||
mPipelineRequirements.shaders[1] == VK_NULL_HANDLE) {
|
||||
utils::slog.e << "Binding missing shader: " << program->name.c_str() << utils::io::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void VulkanPipelineCache::bindRasterState(const RasterState& rasterState) noexcept {
|
||||
|
||||
@@ -191,10 +191,19 @@ public:
|
||||
mDummyTargetInfo.imageView = imageView;
|
||||
}
|
||||
|
||||
// Acquires a resource to be bound to the current pipeline. The ownership of the resource
|
||||
// will be transferred to the corresponding pipeline when pipeline is bound.
|
||||
void acquireResource(VulkanResource* resource) {
|
||||
mPipelineBoundResources.acquire(resource);
|
||||
static VkPrimitiveTopology getPrimitiveTopology(PrimitiveType pt) noexcept {
|
||||
switch (pt) {
|
||||
case PrimitiveType::POINTS:
|
||||
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
|
||||
case PrimitiveType::LINES:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
|
||||
case PrimitiveType::LINE_STRIP:
|
||||
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
|
||||
case PrimitiveType::TRIANGLES:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
case PrimitiveType::TRIANGLE_STRIP:
|
||||
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H
|
||||
#define TNT_FILAMENT_BACKEND_VULKANRESOURCEALLOCATOR_H
|
||||
|
||||
#include "VulkanConstants.h"
|
||||
#include "VulkanHandles.h"
|
||||
|
||||
#include <private/backend/HandleAllocator.h>
|
||||
@@ -29,18 +30,17 @@
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
// RESOURCE_TYPE_COUNT matches the count of enum VulkanResourceType.
|
||||
#define RESOURCE_TYPE_COUNT 12
|
||||
#define DEBUG_RESOURCE_LEAKS 0
|
||||
#define RESOURCE_TYPE_COUNT (static_cast<int>(VulkanResourceType::END_TYPE))
|
||||
#define DEBUG_RESOURCE_LEAKS FVK_ENABLED(FVK_DEBUG_RESOURCE_LEAK)
|
||||
|
||||
#if DEBUG_RESOURCE_LEAKS
|
||||
#define TRACK_INCREMENT() \
|
||||
if (!IS_MANAGED_TYPE(obj->mType)) { \
|
||||
mDebugOnlyResourceCount[static_cast<size_t>(obj->mType)]++; \
|
||||
#define TRACK_INCREMENT() \
|
||||
if (!IS_HEAP_ALLOC_TYPE(obj->getType())) { \
|
||||
mDebugOnlyResourceCount[static_cast<size_t>(obj->getType())]++; \
|
||||
}
|
||||
#define TRACK_DECREMENT() \
|
||||
if (!IS_MANAGED_TYPE(obj->mType)) { \
|
||||
mDebugOnlyResourceCount[static_cast<size_t>(obj->mType)]--; \
|
||||
#define TRACK_DECREMENT() \
|
||||
if (!IS_HEAP_ALLOC_TYPE(obj->getType())) { \
|
||||
mDebugOnlyResourceCount[static_cast<size_t>(obj->getType())]--; \
|
||||
}
|
||||
#else
|
||||
// No-op
|
||||
|
||||
@@ -70,6 +70,7 @@ void deallocateResource(VulkanResourceAllocator* allocator, VulkanResourceType t
|
||||
// destruction.
|
||||
case VulkanResourceType::FENCE:
|
||||
case VulkanResourceType::HEAP_ALLOCATED:
|
||||
case VulkanResourceType::END_TYPE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ enum class VulkanResourceType : uint8_t {
|
||||
// Below are resources that are managed manually (i.e. not ref counted).
|
||||
FENCE,
|
||||
HEAP_ALLOCATED,
|
||||
|
||||
END_TYPE, // A placeholder
|
||||
};
|
||||
|
||||
#define IS_HEAP_ALLOC_TYPE(f) \
|
||||
|
||||
@@ -159,7 +159,7 @@ VulkanTexture::VulkanTexture(VkDevice device, VkPhysicalDevice physicalDevice,
|
||||
imageInfo.samples = (VkSampleCountFlagBits) samples;
|
||||
|
||||
VkResult error = vkCreateImage(mDevice, &imageInfo, VKALLOC, &mTextureImage);
|
||||
if (error || FVK_ENABLED_BOOL(FVK_DEBUG_TEXTURE)) {
|
||||
if (error || FVK_ENABLED(FVK_DEBUG_TEXTURE)) {
|
||||
utils::slog.d << "vkCreateImage: "
|
||||
<< "image = " << mTextureImage << ", "
|
||||
<< "result = " << error << ", "
|
||||
|
||||
@@ -300,7 +300,7 @@ public:
|
||||
|
||||
/**
|
||||
* Set to `true` to forcibly disable parallel shader compilation in the backend.
|
||||
* Currently only honored by the GL backend.
|
||||
* Currently only honored by the GL and Metal backends.
|
||||
*/
|
||||
bool disableParallelShaderCompile = false;
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "details/Material.h"
|
||||
#include "details/MaterialInstance.h"
|
||||
#include "details/Texture.h"
|
||||
#include "details/VertexBuffer.h"
|
||||
|
||||
#include "generated/resources/materials.h"
|
||||
|
||||
@@ -190,8 +191,9 @@ std::pair<backend::PipelineState, backend::Viewport>
|
||||
FMaterial* const material = getMaterial(engine);
|
||||
material->prepareProgram(Variant{ variantKey });
|
||||
return {{
|
||||
.program = material->getProgram(Variant{ variantKey }),
|
||||
.rasterState = material->getRasterState() },
|
||||
.program = material->getProgram(Variant{ variantKey }),
|
||||
.vertexBufferInfo = engine.getFullScreenVertexBuffer()->getVertexBufferInfoHandle(),
|
||||
.rasterState = material->getRasterState() },
|
||||
material->getDefaultInstance()->getScissor() };
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace utils;
|
||||
using namespace filament::math;
|
||||
@@ -866,6 +867,10 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
.polygonOffset = mPolygonOffset,
|
||||
};
|
||||
|
||||
PipelineState currentPipeline{};
|
||||
Handle<HwRenderPrimitive> currentPrimitiveHandle{};
|
||||
bool rebindPipeline = true;
|
||||
|
||||
FMaterialInstance const* UTILS_RESTRICT mi = nullptr;
|
||||
FMaterial const* UTILS_RESTRICT ma = nullptr;
|
||||
auto const* UTILS_RESTRICT pCustomCommands = mCustomCommands.data();
|
||||
@@ -918,7 +923,9 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
// per-renderable uniform
|
||||
PrimitiveInfo const info = first->primitive;
|
||||
pipeline.rasterState = info.rasterState;
|
||||
//pipeline.vertexBufferInfo = info.vertexBufferInfo;
|
||||
pipeline.vertexBufferInfo = info.primitive->getVertexBufferInfoHandle();
|
||||
pipeline.primitiveType = info.primitive->getPrimitiveType();
|
||||
assert_invariant(pipeline.vertexBufferInfo);
|
||||
|
||||
if (UTILS_UNLIKELY(mi != info.primitive->getMaterialInstance())) {
|
||||
// this is always taken the first time
|
||||
@@ -940,6 +947,11 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
}
|
||||
pipeline.stencilState = mi->getStencilState();
|
||||
mi->use(driver);
|
||||
|
||||
// FIXME: MaterialInstance changed (not necessarily the program though),
|
||||
// however, texture bindings may have changed and currently we need to
|
||||
// rebind the pipeline when that happens.
|
||||
rebindPipeline = true;
|
||||
}
|
||||
|
||||
assert_invariant(ma);
|
||||
@@ -989,6 +1001,10 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
// note: even if only skinning is enabled, binding morphTargetBuffer is needed.
|
||||
driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_MORPHING,
|
||||
info.morphTargetBuffer);
|
||||
|
||||
// FIXME: Currently we need to rebind the PipelineState when texture or
|
||||
// UBO binding change.
|
||||
rebindPipeline = true;
|
||||
}
|
||||
|
||||
if (UTILS_UNLIKELY(info.morphWeightBuffer)) {
|
||||
@@ -1001,10 +1017,27 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
// note: even if only morphing is enabled, binding skinningTexture is needed.
|
||||
driver.bindSamplers(+SamplerBindingPoints::PER_RENDERABLE_SKINNING,
|
||||
info.skinningTexture);
|
||||
|
||||
// FIXME: Currently we need to rebind the PipelineState when texture or
|
||||
// UBO binding change.
|
||||
rebindPipeline = true;
|
||||
}
|
||||
|
||||
driver.draw(pipeline, info.primitive->getHwHandle(),
|
||||
info.primitive->getIndexOffset(), info.primitive->getIndexCount(),
|
||||
if (rebindPipeline ||
|
||||
(memcmp(&pipeline, ¤tPipeline, sizeof(PipelineState)) != 0)) {
|
||||
rebindPipeline = false;
|
||||
currentPipeline = pipeline;
|
||||
driver.bindPipeline(pipeline);
|
||||
}
|
||||
|
||||
if (info.primitive->getHwHandle() != currentPrimitiveHandle) {
|
||||
currentPrimitiveHandle = info.primitive->getHwHandle();
|
||||
driver.bindRenderPrimitive(info.primitive->getHwHandle());
|
||||
}
|
||||
|
||||
driver.draw2(
|
||||
info.primitive->getIndexOffset(),
|
||||
info.primitive->getIndexCount(),
|
||||
instanceCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,18 @@
|
||||
|
||||
#include "RenderPrimitive.h"
|
||||
|
||||
#include "details/Engine.h"
|
||||
#include <filament/RenderableManager.h>
|
||||
#include <filament/MaterialEnums.h>
|
||||
|
||||
#include "details/IndexBuffer.h"
|
||||
#include "details/Material.h"
|
||||
#include "details/MaterialInstance.h"
|
||||
#include "details/VertexBuffer.h"
|
||||
|
||||
#include <private/backend/CommandStream.h>
|
||||
#include <backend/DriverApiForward.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace filament {
|
||||
@@ -39,18 +43,7 @@ void FRenderPrimitive::init(HwRenderPrimitiveFactory& factory, backend::DriverAp
|
||||
if (entry.indices && entry.vertices) {
|
||||
FVertexBuffer* vertexBuffer = downcast(entry.vertices);
|
||||
FIndexBuffer* indexBuffer = downcast(entry.indices);
|
||||
|
||||
AttributeBitset const enabledAttributes = vertexBuffer->getDeclaredAttributes();
|
||||
|
||||
auto const& ebh = vertexBuffer->getHwHandle();
|
||||
auto const& ibh = indexBuffer->getHwHandle();
|
||||
|
||||
mHandle = factory.create(driver, ebh, ibh, entry.type);
|
||||
|
||||
mPrimitiveType = entry.type;
|
||||
mIndexOffset = entry.offset;
|
||||
mIndexCount = entry.count;
|
||||
mEnabledAttributes = enabledAttributes;
|
||||
set(factory, driver, entry.type, vertexBuffer, indexBuffer, entry.offset, entry.count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,17 +55,19 @@ void FRenderPrimitive::terminate(HwRenderPrimitiveFactory& factory, backend::Dri
|
||||
|
||||
void FRenderPrimitive::set(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver,
|
||||
RenderableManager::PrimitiveType type,
|
||||
FVertexBuffer* vertices, FIndexBuffer* indices, size_t offset, size_t count) noexcept {
|
||||
FVertexBuffer* vertexBuffer, FIndexBuffer* indexBuffer,
|
||||
size_t offset, size_t count) noexcept {
|
||||
if (mHandle) {
|
||||
factory.destroy(driver, mHandle);
|
||||
}
|
||||
|
||||
AttributeBitset const enabledAttributes = vertices->getDeclaredAttributes();
|
||||
AttributeBitset const enabledAttributes = vertexBuffer->getDeclaredAttributes();
|
||||
|
||||
auto const& ebh = vertices->getHwHandle();
|
||||
auto const& ibh = indices->getHwHandle();
|
||||
auto const& ebh = vertexBuffer->getHwHandle();
|
||||
auto const& ibh = indexBuffer->getHwHandle();
|
||||
|
||||
mHandle = factory.create(driver, ebh, ibh, type);
|
||||
mVertexBufferInfoHandle = vertexBuffer->getVertexBufferInfoHandle();
|
||||
|
||||
mPrimitiveType = type;
|
||||
mIndexOffset = offset;
|
||||
|
||||
@@ -45,14 +45,15 @@ public:
|
||||
|
||||
void set(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver,
|
||||
RenderableManager::PrimitiveType type,
|
||||
FVertexBuffer* vertices, FIndexBuffer* indices, size_t offset,
|
||||
FVertexBuffer* vertexBuffer, FIndexBuffer* indexBuffer, size_t offset,
|
||||
size_t count) noexcept;
|
||||
|
||||
// frees driver resources, object becomes invalid
|
||||
void terminate(HwRenderPrimitiveFactory& factory, backend::DriverApi& driver);
|
||||
|
||||
const FMaterialInstance* getMaterialInstance() const noexcept { return mMaterialInstance; }
|
||||
backend::Handle<backend::HwRenderPrimitive> getHwHandle() const noexcept { return mHandle; }
|
||||
backend::RenderPrimitiveHandle getHwHandle() const noexcept { return mHandle; }
|
||||
backend::VertexBufferInfoHandle getVertexBufferInfoHandle() const { return mVertexBufferInfoHandle; }
|
||||
uint32_t getIndexOffset() const noexcept { return mIndexOffset; }
|
||||
uint32_t getIndexCount() const noexcept { return mIndexCount; }
|
||||
|
||||
@@ -76,7 +77,7 @@ private:
|
||||
struct {
|
||||
FMaterialInstance const* mMaterialInstance = nullptr;
|
||||
backend::Handle<backend::HwRenderPrimitive> mHandle = {};
|
||||
UTILS_UNUSED uint8_t padding[4]= {};
|
||||
backend::Handle<backend::HwVertexBufferInfo> mVertexBufferInfoHandle = {};
|
||||
uint32_t mIndexOffset = 0;
|
||||
uint32_t mIndexCount = 0;
|
||||
};
|
||||
|
||||
@@ -52,6 +52,8 @@ public:
|
||||
|
||||
VertexBufferHandle getHwHandle() const noexcept { return mHandle; }
|
||||
|
||||
VertexBufferInfoHandle getVertexBufferInfoHandle() const { return mVertexBufferInfoHandle; }
|
||||
|
||||
size_t getVertexCount() const noexcept;
|
||||
|
||||
AttributeBitset getDeclaredAttributes() const noexcept {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
material {
|
||||
name : "Filament Default Material",
|
||||
shadingModel : unlit,
|
||||
featureLevel: 0
|
||||
}
|
||||
|
||||
fragment {
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
material.baseColor.rgb = vec3(0.8);
|
||||
}
|
||||
}
|
||||
@@ -35,10 +35,14 @@ fragment {
|
||||
if (materialParams.constantColor != 0) {
|
||||
sky = materialParams.color;
|
||||
} else {
|
||||
#if MATERIAL_FEATURE_LEVEL == 0
|
||||
sky = vec4(textureCube(materialParams_skybox, variable_eyeDirection.xyz).rgb, 1.0);
|
||||
#else
|
||||
sky = vec4(textureLod(materialParams_skybox, variable_eyeDirection.xyz, 0.0).rgb, 1.0);
|
||||
#endif
|
||||
sky.rgb *= frameUniforms.iblLuminance;
|
||||
}
|
||||
if (materialParams.showSun != 0 && frameUniforms.sun.w >= 0.0f) {
|
||||
if (materialParams.showSun != 0 && frameUniforms.sun.w >= 0.0) {
|
||||
vec3 direction = normalize(variable_eyeDirection.xyz);
|
||||
// Assume the sun is a sphere
|
||||
vec3 sun = frameUniforms.lightColorIntensity.rgb *
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
material {
|
||||
name : Skybox,
|
||||
parameters : [
|
||||
{
|
||||
type : int,
|
||||
name : showSun
|
||||
},
|
||||
{
|
||||
type : int,
|
||||
name : constantColor
|
||||
},
|
||||
{
|
||||
type : samplerCubemap,
|
||||
name : skybox
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : color
|
||||
}
|
||||
],
|
||||
variables : [
|
||||
eyeDirection
|
||||
],
|
||||
vertexDomain : device,
|
||||
depthWrite : false,
|
||||
shadingModel : unlit,
|
||||
variantFilter : [ skinning, shadowReceiver, vsm ],
|
||||
culling: none,
|
||||
featureLevel: 0
|
||||
}
|
||||
|
||||
fragment {
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
vec4 sky;
|
||||
if (materialParams.constantColor != 0) {
|
||||
sky = materialParams.color;
|
||||
} else {
|
||||
sky = vec4(textureCube(materialParams_skybox, variable_eyeDirection.xyz).rgb, 1.0);
|
||||
sky.rgb *= frameUniforms.iblLuminance;
|
||||
}
|
||||
if (materialParams.showSun != 0 && frameUniforms.sun.w >= 0.0) {
|
||||
vec3 direction = normalize(variable_eyeDirection.xyz);
|
||||
// Assume the sun is a sphere
|
||||
vec3 sun = frameUniforms.lightColorIntensity.rgb *
|
||||
(frameUniforms.lightColorIntensity.a * (4.0 * PI));
|
||||
float cosAngle = dot(direction, frameUniforms.lightDirection);
|
||||
float x = (cosAngle - frameUniforms.sun.x) * frameUniforms.sun.z;
|
||||
float gradient = pow(1.0 - saturate(x), frameUniforms.sun.w);
|
||||
sky.rgb = sky.rgb + gradient * sun;
|
||||
}
|
||||
material.baseColor = sky;
|
||||
}
|
||||
}
|
||||
|
||||
vertex {
|
||||
void materialVertex(inout MaterialVertexInputs material) {
|
||||
material.eyeDirection.xyz = material.worldPosition.xyz;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.50.6"
|
||||
spec.version = "1.51.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.50.6/filament-v1.50.6-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.51.0/filament-v1.51.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 = 50;
|
||||
static constexpr size_t MATERIAL_VERSION = 51;
|
||||
|
||||
/**
|
||||
* Supported shading models
|
||||
@@ -201,7 +201,7 @@ enum class ReflectionMode : uint8_t {
|
||||
// can't really use std::underlying_type<AttributeIndex>::type because the driver takes a uint32_t
|
||||
using AttributeBitset = utils::bitset32;
|
||||
|
||||
static constexpr size_t MATERIAL_PROPERTIES_COUNT = 26;
|
||||
static constexpr size_t MATERIAL_PROPERTIES_COUNT = 27;
|
||||
enum class Property : uint8_t {
|
||||
BASE_COLOR, //!< float4, all shading models
|
||||
ROUGHNESS, //!< float, lit shading models only
|
||||
@@ -223,6 +223,7 @@ enum class Property : uint8_t {
|
||||
EMISSIVE, //!< float4, all shading models
|
||||
NORMAL, //!< float3, all shading models only, except unlit
|
||||
POST_LIGHTING_COLOR, //!< float4, all shading models
|
||||
POST_LIGHTING_MIX_FACTOR,//!< float, all shading models
|
||||
CLIP_SPACE_TRANSFORM, //!< mat4, vertex shader only
|
||||
ABSORPTION, //!< float3, how much light is absorbed by the material
|
||||
TRANSMISSION, //!< float, how much light is refracted through the material
|
||||
|
||||
@@ -410,7 +410,8 @@ public:
|
||||
/**
|
||||
* Set the blending mode of the post-lighting color for this material.
|
||||
* Only OPAQUE, TRANSPARENT and ADD are supported, the default is TRANSPARENT.
|
||||
* This setting requires the material property "postLightingColor" to be set.
|
||||
* This setting requires the material properties "postLightingColor" and
|
||||
* "postLightingMixFactor" to be set.
|
||||
*/
|
||||
MaterialBuilder& postLightingBlending(BlendingMode blending) noexcept;
|
||||
|
||||
|
||||
+27
-26
@@ -21,32 +21,33 @@
|
||||
namespace filamat {
|
||||
|
||||
std::unordered_map<std::string, Property> Enums::mStringToProperty = {
|
||||
{ "baseColor", Property::BASE_COLOR },
|
||||
{ "roughness", Property::ROUGHNESS },
|
||||
{ "metallic", Property::METALLIC },
|
||||
{ "reflectance", Property::REFLECTANCE },
|
||||
{ "ambientOcclusion", Property::AMBIENT_OCCLUSION },
|
||||
{ "clearCoat", Property::CLEAR_COAT },
|
||||
{ "clearCoatRoughness", Property::CLEAR_COAT_ROUGHNESS },
|
||||
{ "clearCoatNormal", Property::CLEAR_COAT_NORMAL },
|
||||
{ "anisotropy", Property::ANISOTROPY },
|
||||
{ "anisotropyDirection", Property::ANISOTROPY_DIRECTION },
|
||||
{ "thickness", Property::THICKNESS },
|
||||
{ "subsurfacePower", Property::SUBSURFACE_POWER },
|
||||
{ "subsurfaceColor", Property::SUBSURFACE_COLOR },
|
||||
{ "sheenColor", Property::SHEEN_COLOR },
|
||||
{ "sheenRoughness", Property::SHEEN_ROUGHNESS },
|
||||
{ "glossiness", Property::GLOSSINESS },
|
||||
{ "specularColor", Property::SPECULAR_COLOR },
|
||||
{ "emissive", Property::EMISSIVE },
|
||||
{ "normal", Property::NORMAL },
|
||||
{ "postLightingColor", Property::POST_LIGHTING_COLOR },
|
||||
{ "clipSpaceTransform", Property::CLIP_SPACE_TRANSFORM },
|
||||
{ "absorption", Property::ABSORPTION },
|
||||
{ "transmission", Property::TRANSMISSION },
|
||||
{ "ior", Property::IOR },
|
||||
{ "microThickness", Property::MICRO_THICKNESS },
|
||||
{ "bentNormal", Property::BENT_NORMAL },
|
||||
{ "baseColor", Property::BASE_COLOR },
|
||||
{ "roughness", Property::ROUGHNESS },
|
||||
{ "metallic", Property::METALLIC },
|
||||
{ "reflectance", Property::REFLECTANCE },
|
||||
{ "ambientOcclusion", Property::AMBIENT_OCCLUSION },
|
||||
{ "clearCoat", Property::CLEAR_COAT },
|
||||
{ "clearCoatRoughness", Property::CLEAR_COAT_ROUGHNESS },
|
||||
{ "clearCoatNormal", Property::CLEAR_COAT_NORMAL },
|
||||
{ "anisotropy", Property::ANISOTROPY },
|
||||
{ "anisotropyDirection", Property::ANISOTROPY_DIRECTION },
|
||||
{ "thickness", Property::THICKNESS },
|
||||
{ "subsurfacePower", Property::SUBSURFACE_POWER },
|
||||
{ "subsurfaceColor", Property::SUBSURFACE_COLOR },
|
||||
{ "sheenColor", Property::SHEEN_COLOR },
|
||||
{ "sheenRoughness", Property::SHEEN_ROUGHNESS },
|
||||
{ "glossiness", Property::GLOSSINESS },
|
||||
{ "specularColor", Property::SPECULAR_COLOR },
|
||||
{ "emissive", Property::EMISSIVE },
|
||||
{ "normal", Property::NORMAL },
|
||||
{ "postLightingColor", Property::POST_LIGHTING_COLOR },
|
||||
{ "postLightingMixFactor", Property::POST_LIGHTING_MIX_FACTOR },
|
||||
{ "clipSpaceTransform", Property::CLIP_SPACE_TRANSFORM },
|
||||
{ "absorption", Property::ABSORPTION },
|
||||
{ "transmission", Property::TRANSMISSION },
|
||||
{ "ior", Property::IOR },
|
||||
{ "microThickness", Property::MICRO_THICKNESS },
|
||||
{ "bentNormal", Property::BENT_NORMAL },
|
||||
};
|
||||
|
||||
template <>
|
||||
|
||||
@@ -1115,32 +1115,33 @@ io::sstream& CodeGenerator::generateShaderReflections(utils::io::sstream& out, S
|
||||
char const* CodeGenerator::getConstantName(MaterialBuilder::Property property) noexcept {
|
||||
using Property = MaterialBuilder::Property;
|
||||
switch (property) {
|
||||
case Property::BASE_COLOR: return "BASE_COLOR";
|
||||
case Property::ROUGHNESS: return "ROUGHNESS";
|
||||
case Property::METALLIC: return "METALLIC";
|
||||
case Property::REFLECTANCE: return "REFLECTANCE";
|
||||
case Property::AMBIENT_OCCLUSION: return "AMBIENT_OCCLUSION";
|
||||
case Property::CLEAR_COAT: return "CLEAR_COAT";
|
||||
case Property::CLEAR_COAT_ROUGHNESS: return "CLEAR_COAT_ROUGHNESS";
|
||||
case Property::CLEAR_COAT_NORMAL: return "CLEAR_COAT_NORMAL";
|
||||
case Property::ANISOTROPY: return "ANISOTROPY";
|
||||
case Property::ANISOTROPY_DIRECTION: return "ANISOTROPY_DIRECTION";
|
||||
case Property::THICKNESS: return "THICKNESS";
|
||||
case Property::SUBSURFACE_POWER: return "SUBSURFACE_POWER";
|
||||
case Property::SUBSURFACE_COLOR: return "SUBSURFACE_COLOR";
|
||||
case Property::SHEEN_COLOR: return "SHEEN_COLOR";
|
||||
case Property::SHEEN_ROUGHNESS: return "SHEEN_ROUGHNESS";
|
||||
case Property::GLOSSINESS: return "GLOSSINESS";
|
||||
case Property::SPECULAR_COLOR: return "SPECULAR_COLOR";
|
||||
case Property::EMISSIVE: return "EMISSIVE";
|
||||
case Property::NORMAL: return "NORMAL";
|
||||
case Property::POST_LIGHTING_COLOR: return "POST_LIGHTING_COLOR";
|
||||
case Property::CLIP_SPACE_TRANSFORM: return "CLIP_SPACE_TRANSFORM";
|
||||
case Property::ABSORPTION: return "ABSORPTION";
|
||||
case Property::TRANSMISSION: return "TRANSMISSION";
|
||||
case Property::IOR: return "IOR";
|
||||
case Property::MICRO_THICKNESS: return "MICRO_THICKNESS";
|
||||
case Property::BENT_NORMAL: return "BENT_NORMAL";
|
||||
case Property::BASE_COLOR: return "BASE_COLOR";
|
||||
case Property::ROUGHNESS: return "ROUGHNESS";
|
||||
case Property::METALLIC: return "METALLIC";
|
||||
case Property::REFLECTANCE: return "REFLECTANCE";
|
||||
case Property::AMBIENT_OCCLUSION: return "AMBIENT_OCCLUSION";
|
||||
case Property::CLEAR_COAT: return "CLEAR_COAT";
|
||||
case Property::CLEAR_COAT_ROUGHNESS: return "CLEAR_COAT_ROUGHNESS";
|
||||
case Property::CLEAR_COAT_NORMAL: return "CLEAR_COAT_NORMAL";
|
||||
case Property::ANISOTROPY: return "ANISOTROPY";
|
||||
case Property::ANISOTROPY_DIRECTION: return "ANISOTROPY_DIRECTION";
|
||||
case Property::THICKNESS: return "THICKNESS";
|
||||
case Property::SUBSURFACE_POWER: return "SUBSURFACE_POWER";
|
||||
case Property::SUBSURFACE_COLOR: return "SUBSURFACE_COLOR";
|
||||
case Property::SHEEN_COLOR: return "SHEEN_COLOR";
|
||||
case Property::SHEEN_ROUGHNESS: return "SHEEN_ROUGHNESS";
|
||||
case Property::GLOSSINESS: return "GLOSSINESS";
|
||||
case Property::SPECULAR_COLOR: return "SPECULAR_COLOR";
|
||||
case Property::EMISSIVE: return "EMISSIVE";
|
||||
case Property::NORMAL: return "NORMAL";
|
||||
case Property::POST_LIGHTING_COLOR: return "POST_LIGHTING_COLOR";
|
||||
case Property::POST_LIGHTING_MIX_FACTOR: return "POST_LIGHTING_MIX_FACTOR";
|
||||
case Property::CLIP_SPACE_TRANSFORM: return "CLIP_SPACE_TRANSFORM";
|
||||
case Property::ABSORPTION: return "ABSORPTION";
|
||||
case Property::TRANSMISSION: return "TRANSMISSION";
|
||||
case Property::IOR: return "IOR";
|
||||
case Property::MICRO_THICKNESS: return "MICRO_THICKNESS";
|
||||
case Property::BENT_NORMAL: return "BENT_NORMAL";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -701,6 +701,7 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerOutputFactor) {
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
material.postLightingColor = vec4(1.0);
|
||||
material.postLightingMixFactor = 0.5;
|
||||
}
|
||||
)");
|
||||
|
||||
@@ -712,6 +713,7 @@ TEST_F(MaterialCompiler, StaticCodeAnalyzerOutputFactor) {
|
||||
glslTools.findProperties(ShaderStage::FRAGMENT, shaderCode, properties);
|
||||
MaterialBuilder::PropertyList expected{ false };
|
||||
expected[size_t(filamat::MaterialBuilder::Property::POST_LIGHTING_COLOR)] = true;
|
||||
expected[size_t(filamat::MaterialBuilder::Property::POST_LIGHTING_MIX_FACTOR)] = true;
|
||||
EXPECT_TRUE(PropertyListsMatch(expected, properties));
|
||||
}
|
||||
|
||||
|
||||
@@ -88,12 +88,23 @@ public:
|
||||
*/
|
||||
class EquirectangularToCubemap {
|
||||
public:
|
||||
|
||||
struct Config {
|
||||
bool mirror = true; //!< mirror the source horizontally
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a EquirectangularToCubemap processor.
|
||||
* Creates a EquirectangularToCubemap processor using the default Config
|
||||
* @param context IBLPrefilterContext to use
|
||||
*/
|
||||
explicit EquirectangularToCubemap(IBLPrefilterContext& context);
|
||||
|
||||
/**
|
||||
* Creates a EquirectangularToCubemap processor using the provided Config
|
||||
* @param context IBLPrefilterContext to use
|
||||
*/
|
||||
EquirectangularToCubemap(IBLPrefilterContext& context, Config const& config);
|
||||
|
||||
/**
|
||||
* Destroys all GPU resources created during initialization.
|
||||
*/
|
||||
@@ -125,6 +136,7 @@ public:
|
||||
private:
|
||||
IBLPrefilterContext& mContext;
|
||||
filament::Material* mEquirectMaterial = nullptr;
|
||||
Config mConfig{};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/IndexBuffer.h>
|
||||
#include <filament/Material.h>
|
||||
#include <filament/MaterialEnums.h>
|
||||
#include <filament/RenderTarget.h>
|
||||
#include <filament/RenderableManager.h>
|
||||
#include <filament/Renderer.h>
|
||||
@@ -29,12 +30,22 @@
|
||||
#include <filament/View.h>
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
#include <utils/Panic.h>
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/EntityManager.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <math/mat3.h>
|
||||
#include <math/vec3.h>
|
||||
#include <math/scalar.h>
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "generated/resources/iblprefilter_materials.h"
|
||||
|
||||
@@ -167,13 +178,19 @@ IBLPrefilterContext& IBLPrefilterContext::operator=(IBLPrefilterContext&& rhs) n
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
IBLPrefilterContext::EquirectangularToCubemap::EquirectangularToCubemap(
|
||||
IBLPrefilterContext& context) : mContext(context) {
|
||||
IBLPrefilterContext& context,
|
||||
IBLPrefilterContext::EquirectangularToCubemap::Config const& config)
|
||||
: mContext(context), mConfig(config) {
|
||||
Engine& engine = mContext.mEngine;
|
||||
mEquirectMaterial = Material::Builder().package(
|
||||
IBLPREFILTER_MATERIALS_EQUIRECTTOCUBE_DATA,
|
||||
IBLPREFILTER_MATERIALS_EQUIRECTTOCUBE_SIZE).build(engine);
|
||||
}
|
||||
|
||||
IBLPrefilterContext::EquirectangularToCubemap::EquirectangularToCubemap(
|
||||
IBLPrefilterContext& context) : EquirectangularToCubemap(context, {}) {
|
||||
}
|
||||
|
||||
IBLPrefilterContext::EquirectangularToCubemap::~EquirectangularToCubemap() noexcept {
|
||||
Engine& engine = mContext.mEngine;
|
||||
engine.destroy(mEquirectMaterial);
|
||||
@@ -257,6 +274,8 @@ Texture* IBLPrefilterContext::EquirectangularToCubemap::operator()(
|
||||
.texture(RenderTarget::AttachmentPoint::COLOR1, outCube)
|
||||
.texture(RenderTarget::AttachmentPoint::COLOR2, outCube);
|
||||
|
||||
mi->setParameter("mirror", mConfig.mirror ? -1.0f : 1.0f);
|
||||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
mi->setParameter("side", i == 0 ? 1.0f : -1.0f);
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ material {
|
||||
type : float,
|
||||
name : side,
|
||||
precision: medium
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : mirror,
|
||||
precision: medium
|
||||
}
|
||||
],
|
||||
outputs : [
|
||||
@@ -51,7 +56,7 @@ highp vec2 toEquirect(const highp vec3 s) {
|
||||
highp float xf = atan(s.x, s.z) * (1.0 / PI); // range [-1.0, 1.0]
|
||||
highp float yf = asin(s.y) * (2.0 / PI); // range [-1.0, 1.0]
|
||||
xf = (xf + 1.0) * 0.5; // range [0, 1.0]
|
||||
yf = (1.0 - yf) * 0.5; // range [0, 1.0]
|
||||
yf = (yf + 1.0) * 0.5; // range [0, 1.0]
|
||||
return vec2(xf, yf);
|
||||
}
|
||||
|
||||
@@ -62,18 +67,16 @@ mediump vec3 sampleEquirect(mediump sampler2D equirect, const highp vec3 r) {
|
||||
|
||||
void postProcess(inout PostProcessInputs postProcess) {
|
||||
highp vec2 uv = variable_vertex.xy; // interpolated at pixel's center
|
||||
highp vec2 p = vec2(
|
||||
uv.x * 2.0 - 1.0,
|
||||
1.0 - uv.y * 2.0);
|
||||
highp vec2 p = uv * 2.0 - 1.0;
|
||||
|
||||
float side = materialParams.side;
|
||||
float mirror = materialParams.mirror;
|
||||
highp float l = inversesqrt(p.x * p.x + p.y * p.y + 1.0);
|
||||
|
||||
// compute the view (and normal, since v = n) direction for each face
|
||||
highp vec3 rx = vec3( side, p.y, side * -p.x);
|
||||
highp vec3 ry = vec3( p.x, side, side * -p.y);
|
||||
highp vec3 rz = vec3(side * p.x, p.y, side);
|
||||
|
||||
highp vec3 rx = vec3( side * mirror, p.y, side * -p.x);
|
||||
highp vec3 ry = vec3( p.x * mirror, side, side * -p.y);
|
||||
highp vec3 rz = vec3(side * p.x * mirror, p.y, side);
|
||||
|
||||
postProcess.outx = sampleEquirect(materialParams_equirect, rx * l);
|
||||
postProcess.outy = sampleEquirect(materialParams_equirect, ry * l);
|
||||
|
||||
@@ -122,9 +122,10 @@ void postProcess(inout PostProcessInputs postProcess) {
|
||||
float side = materialParams.side;
|
||||
|
||||
// compute the view (and normal, since v = n) direction for each face
|
||||
vec3 rx = normalize(vec3( side, -p.y, side * -p.x));
|
||||
vec3 ry = normalize(vec3( p.x, side, side * p.y));
|
||||
vec3 rz = normalize(vec3(side * p.x, -p.y, side));
|
||||
float l = inversesqrt(p.x * p.x + p.y * p.y + 1.0);
|
||||
vec3 rx = vec3( side, p.y, side * -p.x) * l;
|
||||
vec3 ry = vec3( p.x, side, side * -p.y) * l;
|
||||
vec3 rz = vec3(side * p.x, p.y, side) * l;
|
||||
|
||||
// random rotation around r
|
||||
mediump float a = 2.0 * PI * random(gl_FragCoord.xy);
|
||||
|
||||
@@ -222,6 +222,12 @@ function(add_demo NAME)
|
||||
target_link_libraries(${NAME} PRIVATE sample-resources filamentapp)
|
||||
target_compile_options(${NAME} PRIVATE ${COMPILER_FLAGS})
|
||||
set_target_properties(${NAME} PROPERTIES FOLDER Samples)
|
||||
|
||||
# This is needed after XCode 15.3
|
||||
if (APPLE)
|
||||
set_target_properties(${NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set_target_properties(${NAME} PROPERTIES INSTALL_RPATH /usr/local/lib)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
if (NOT ANDROID)
|
||||
|
||||
+7
-5
@@ -6,17 +6,19 @@ layout(location = 0) out vec4 fragColor;
|
||||
|
||||
#if defined(MATERIAL_HAS_POST_LIGHTING_COLOR)
|
||||
void blendPostLightingColor(const MaterialInputs material, inout vec4 color) {
|
||||
vec4 blend = color;
|
||||
#if defined(POST_LIGHTING_BLEND_MODE_OPAQUE)
|
||||
color = material.postLightingColor;
|
||||
blend = material.postLightingColor;
|
||||
#elif defined(POST_LIGHTING_BLEND_MODE_TRANSPARENT)
|
||||
color = material.postLightingColor + color * (1.0 - material.postLightingColor.a);
|
||||
blend = material.postLightingColor + color * (1.0 - material.postLightingColor.a);
|
||||
#elif defined(POST_LIGHTING_BLEND_MODE_ADD)
|
||||
color += material.postLightingColor;
|
||||
blend += material.postLightingColor;
|
||||
#elif defined(POST_LIGHTING_BLEND_MODE_MULTIPLY)
|
||||
color *= material.postLightingColor;
|
||||
blend *= material.postLightingColor;
|
||||
#elif defined(POST_LIGHTING_BLEND_MODE_SCREEN)
|
||||
color += material.postLightingColor * (1.0 - color);
|
||||
blend += material.postLightingColor * (1.0 - color);
|
||||
#endif
|
||||
color = mix(color, blend, material.postLightingMixFactor);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ struct MaterialInputs {
|
||||
|
||||
#if defined(MATERIAL_HAS_POST_LIGHTING_COLOR)
|
||||
vec4 postLightingColor;
|
||||
float postLightingMixFactor;
|
||||
#endif
|
||||
|
||||
#if !defined(SHADING_MODEL_CLOTH) && !defined(SHADING_MODEL_SUBSURFACE) && !defined(SHADING_MODEL_UNLIT)
|
||||
@@ -153,6 +154,7 @@ void initMaterial(out MaterialInputs material) {
|
||||
|
||||
#if defined(MATERIAL_HAS_POST_LIGHTING_COLOR)
|
||||
material.postLightingColor = vec4(0.0);
|
||||
material.postLightingMixFactor = 1.0;
|
||||
#endif
|
||||
|
||||
#if !defined(SHADING_MODEL_CLOTH) && !defined(SHADING_MODEL_SUBSURFACE) && !defined(SHADING_MODEL_UNLIT)
|
||||
|
||||
@@ -1283,6 +1283,11 @@ bool ParametersProcessor::process(filamat::MaterialBuilder& builder, const std::
|
||||
case JsonishValue::Type::STRING:
|
||||
var = std::make_unique<JsonishString>(value);
|
||||
break;
|
||||
default:
|
||||
std::cerr << "Unsupported type: \""
|
||||
<< JsonishValue::typeToString(mParameters.at(key).rootAssert)
|
||||
<< "\"" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto fPointer = mParameters[key].callback;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "filament",
|
||||
"version": "1.50.6",
|
||||
"version": "1.51.0",
|
||||
"description": "Real-time physically based rendering engine",
|
||||
"main": "filament.js",
|
||||
"module": "filament.js",
|
||||
|
||||
Reference in New Issue
Block a user