Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5aded33477 |
@@ -31,7 +31,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.android.filament:filament-android:1.53.4'
|
||||
implementation 'com.google.android.filament:filament-android:1.53.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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.53.4'
|
||||
pod 'Filament', '~> 1.53.1'
|
||||
```
|
||||
|
||||
### Snapshots
|
||||
|
||||
@@ -7,13 +7,6 @@ A new header is inserted each time a *tag* is created.
|
||||
Instead, if you are authoring a PR for the main branch, add your release note to
|
||||
[NEW_RELEASE_NOTES.md](./NEW_RELEASE_NOTES.md).
|
||||
|
||||
## v1.53.4
|
||||
|
||||
|
||||
## v1.53.3
|
||||
|
||||
- Add drag and drop support for IBL files for desktop gltf_viewer.
|
||||
|
||||
## v1.53.2
|
||||
|
||||
|
||||
|
||||
@@ -253,10 +253,16 @@ Java_com_google_android_filament_RenderableManager_nBuilderMorphingStandard(JNIE
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferOffsetAt(JNIEnv*, jclass,
|
||||
jlong nativeBuilder, int level, int primitiveIndex, int offset) {
|
||||
Java_com_google_android_filament_RenderableManager_nBuilderSetMorphTargetBufferAt(JNIEnv*, jclass,
|
||||
jlong nativeBuilder, int level, int primitiveIndex, jlong nativeMorphTargetBuffer,
|
||||
int offset, int count) {
|
||||
RenderableManager::Builder *builder = (RenderableManager::Builder *) nativeBuilder;
|
||||
builder->morphing(level, primitiveIndex, offset);
|
||||
if (nativeMorphTargetBuffer) {
|
||||
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
|
||||
builder->morphing(level, primitiveIndex, morphTargetBuffer, offset, count);
|
||||
} else {
|
||||
builder->morphing(level, primitiveIndex, offset, count);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
@@ -328,12 +334,18 @@ Java_com_google_android_filament_RenderableManager_nSetMorphWeights(JNIEnv* env,
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferOffsetAt(JNIEnv*,
|
||||
Java_com_google_android_filament_RenderableManager_nSetMorphTargetBufferAt(JNIEnv*,
|
||||
jclass, jlong nativeRenderableManager, jint i, int level, jint primitiveIndex,
|
||||
jlong, jint offset) {
|
||||
jlong nativeMorphTargetBuffer, jint offset, jint count) {
|
||||
RenderableManager *rm = (RenderableManager *) nativeRenderableManager;
|
||||
rm->setMorphTargetBufferOffsetAt((RenderableManager::Instance) i, (uint8_t) level,
|
||||
(size_t) primitiveIndex, (size_t) offset);
|
||||
if (nativeMorphTargetBuffer) {
|
||||
MorphTargetBuffer *morphTargetBuffer = (MorphTargetBuffer *) nativeMorphTargetBuffer;
|
||||
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
|
||||
(size_t) primitiveIndex, morphTargetBuffer, (size_t) offset, (size_t) count);
|
||||
} else {
|
||||
rm->setMorphTargetBufferAt((RenderableManager::Instance) i, (uint8_t) level,
|
||||
(size_t) primitiveIndex, (size_t) offset, (size_t) count);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jint JNICALL
|
||||
|
||||
@@ -28,14 +28,6 @@
|
||||
using namespace filament;
|
||||
using namespace backend;
|
||||
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_Renderer_nSkipFrame(JNIEnv *, jclass, jlong nativeRenderer,
|
||||
jlong vsyncSteadyClockTimeNano) {
|
||||
Renderer *renderer = (Renderer *) nativeRenderer;
|
||||
renderer->skipFrame(uint64_t(vsyncSteadyClockTimeNano));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jboolean JNICALL
|
||||
Java_com_google_android_filament_Renderer_nBeginFrame(JNIEnv *, jclass, jlong nativeRenderer,
|
||||
jlong nativeSwapChain, jlong frameTimeNanos) {
|
||||
|
||||
@@ -531,12 +531,3 @@ Java_com_google_android_filament_View_nGetFogEntity(JNIEnv *env, jclass clazz,
|
||||
View *view = (View *) nativeView;
|
||||
return (jint)view->getFogEntity().getId();
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_google_android_filament_View_nClearFrameHistory(JNIEnv *env, jclass clazz,
|
||||
jlong nativeView, jlong nativeEngine) {
|
||||
View *view = (View *) nativeView;
|
||||
Engine *engine = (Engine *) nativeEngine;
|
||||
view->clearFrameHistory(*engine);
|
||||
}
|
||||
|
||||
@@ -425,12 +425,9 @@ public class Engine {
|
||||
public long resourceAllocatorCacheSizeMB = 64;
|
||||
|
||||
/*
|
||||
* This value determines how many frames texture entries are kept for in the cache. This
|
||||
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
|
||||
* Typically only one texture is evicted per frame.
|
||||
* The default is 1.
|
||||
* This value determines for how many frames are texture entries kept in the cache.
|
||||
*/
|
||||
public long resourceAllocatorCacheMaxAge = 1;
|
||||
public long resourceAllocatorCacheMaxAge = 2;
|
||||
|
||||
/*
|
||||
* Disable backend handles use-after-free checks.
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ public class MorphTargetBuffer {
|
||||
*
|
||||
* @exception IllegalStateException if the MorphTargetBuffer could not be created
|
||||
*
|
||||
* @see #setMorphTargetBufferOffsetAt
|
||||
* @see #setMorphTargetBufferAt
|
||||
*/
|
||||
@NonNull
|
||||
public MorphTargetBuffer build(@NonNull Engine engine) {
|
||||
|
||||
+57
-7
@@ -569,13 +569,40 @@ public class RenderableManager {
|
||||
*
|
||||
* @param level the level of detail (lod), only 0 can be specified
|
||||
* @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor
|
||||
* @param morphTargetBuffer specifies the morph target buffer
|
||||
* @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices)
|
||||
* @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3)
|
||||
*/
|
||||
@NonNull
|
||||
public Builder morphing(@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@IntRange(from = 0) int offset) {
|
||||
nBuilderSetMorphTargetBufferOffsetAt(mNativeBuilder, level, primitiveIndex, offset);
|
||||
@IntRange(from = 0) int offset,
|
||||
@IntRange(from = 0) int count) {
|
||||
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex, 0, offset, count);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Builder morphing(@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@NonNull MorphTargetBuffer morphTargetBuffer,
|
||||
@IntRange(from = 0) int offset,
|
||||
@IntRange(from = 0) int count) {
|
||||
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex,
|
||||
morphTargetBuffer.getNativeObject(), offset, count);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
@NonNull
|
||||
public Builder morphing(@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@NonNull MorphTargetBuffer morphTargetBuffer) {
|
||||
nBuilderSetMorphTargetBufferAt(mNativeBuilder, level, primitiveIndex,
|
||||
morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount());
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -678,11 +705,34 @@ public class RenderableManager {
|
||||
*
|
||||
* @see Builder#morphing
|
||||
*/
|
||||
public void setMorphTargetBufferOffsetAt(@EntityInstance int i,
|
||||
public void setMorphTargetBufferAt(@EntityInstance int i,
|
||||
@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@IntRange(from = 0) int offset) {
|
||||
nSetMorphTargetBufferOffsetAt(mNativeObject, i, level, primitiveIndex, 0, offset);
|
||||
@IntRange(from = 0) int offset,
|
||||
@IntRange(from = 0) int count) {
|
||||
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex, 0, offset, count);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
public void setMorphTargetBufferAt(@EntityInstance int i,
|
||||
@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@NonNull MorphTargetBuffer morphTargetBuffer,
|
||||
@IntRange(from = 0) int offset,
|
||||
@IntRange(from = 0) int count) {
|
||||
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex,
|
||||
morphTargetBuffer.getNativeObject(), offset, count);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Deprecated
|
||||
public void setMorphTargetBufferAt(@EntityInstance int i,
|
||||
@IntRange(from = 0) int level,
|
||||
@IntRange(from = 0) int primitiveIndex,
|
||||
@NonNull MorphTargetBuffer morphTargetBuffer) {
|
||||
nSetMorphTargetBufferAt(mNativeObject, i, level, primitiveIndex,
|
||||
morphTargetBuffer.getNativeObject(), 0, morphTargetBuffer.getVertexCount());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -983,7 +1033,7 @@ public class RenderableManager {
|
||||
private static native void nBuilderSkinningBuffer(long nativeBuilder, long nativeSkinningBuffer, int boneCount, int offset);
|
||||
private static native void nBuilderMorphing(long nativeBuilder, int targetCount);
|
||||
private static native void nBuilderMorphingStandard(long nativeBuilder, long nativeMorphTargetBuffer);
|
||||
private static native void nBuilderSetMorphTargetBufferOffsetAt(long nativeBuilder, int level, int primitiveIndex, int offset);
|
||||
private static native void nBuilderSetMorphTargetBufferAt(long nativeBuilder, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count);
|
||||
private static native void nBuilderEnableSkinningBuffers(long nativeBuilder, boolean enabled);
|
||||
private static native void nBuilderFog(long nativeBuilder, boolean enabled);
|
||||
private static native void nBuilderLightChannel(long nativeRenderableManager, int channel, boolean enable);
|
||||
@@ -993,7 +1043,7 @@ public class RenderableManager {
|
||||
private static native int nSetBonesAsMatrices(long nativeObject, int i, Buffer matrices, int remaining, int boneCount, int offset);
|
||||
private static native int nSetBonesAsQuaternions(long nativeObject, int i, Buffer quaternions, int remaining, int boneCount, int offset);
|
||||
private static native void nSetMorphWeights(long nativeObject, int instance, float[] weights, int offset);
|
||||
private static native void nSetMorphTargetBufferOffsetAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset);
|
||||
private static native void nSetMorphTargetBufferAt(long nativeObject, int i, int level, int primitiveIndex, long nativeMorphTargetBuffer, int offset, int count);
|
||||
private static native int nGetMorphTargetCount(long nativeObject, int i);
|
||||
private static native void nSetAxisAlignedBoundingBox(long nativeRenderableManager, int i, float cx, float cy, float cz, float ex, float ey, float ez);
|
||||
private static native void nSetLayerMask(long nativeRenderableManager, int i, int select, int value);
|
||||
|
||||
@@ -297,20 +297,6 @@ public class Renderer {
|
||||
nSetVsyncTime(getNativeObject(), steadyClockTimeNano);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call skipFrame when momentarily skipping frames, for instance if the content of the
|
||||
* scene doesn't change.
|
||||
*
|
||||
* @param vsyncSteadyClockTimeNano The time in nanoseconds when the frame started being rendered,
|
||||
* in the {@link System#nanoTime()} timebase. Divide this value by 1000000 to
|
||||
* convert it to the {@link android.os.SystemClock#uptimeMillis()}
|
||||
* time base. This typically comes from
|
||||
* {@link android.view.Choreographer.FrameCallback}.
|
||||
*/
|
||||
public void skipFrame(long vsyncSteadyClockTimeNano) {
|
||||
nSkipFrame(getNativeObject(), vsyncSteadyClockTimeNano);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a frame for this <code>Renderer</code>.
|
||||
* <p><code>beginFrame</code> manages frame pacing, and returns whether or not a frame should be
|
||||
@@ -730,7 +716,6 @@ public class Renderer {
|
||||
|
||||
private static native void nSetPresentationTime(long nativeObject, long monotonicClockNanos);
|
||||
private static native void nSetVsyncTime(long nativeObject, long steadyClockTimeNano);
|
||||
private static native void nSkipFrame(long nativeObject, long vsyncSteadyClockTimeNano);
|
||||
private static native boolean nBeginFrame(long nativeRenderer, long nativeSwapChain, long frameTimeNanos);
|
||||
private static native void nEndFrame(long nativeRenderer);
|
||||
private static native void nRender(long nativeRenderer, long nativeView);
|
||||
|
||||
@@ -1233,18 +1233,6 @@ public class View {
|
||||
return nGetFogEntity(getNativeObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view
|
||||
* keeps a history of previous frame renders associated with the Renderer the view was last
|
||||
* used with. When switching Renderer, it may be necessary to clear that history by calling
|
||||
* this method. Similarly, if the whole content of the screen change, like when a cut-scene
|
||||
* starts, clearing the history might be needed to avoid artifacts due to the previous frame
|
||||
* being very different.
|
||||
*/
|
||||
public void clearFrameHistory(Engine engine) {
|
||||
nClearFrameHistory(getNativeObject(), engine.getNativeObject());
|
||||
}
|
||||
|
||||
public long getNativeObject() {
|
||||
if (mNativeObject == 0) {
|
||||
throw new IllegalStateException("Calling method on destroyed View");
|
||||
@@ -1306,7 +1294,7 @@ public class View {
|
||||
private static native void nSetMaterialGlobal(long nativeView, int index, float x, float y, float z, float w);
|
||||
private static native void nGetMaterialGlobal(long nativeView, int index, float[] out);
|
||||
private static native int nGetFogEntity(long nativeView);
|
||||
private static native void nClearFrameHistory(long nativeView, long nativeEngine);
|
||||
|
||||
|
||||
/**
|
||||
* List of available ambient occlusion techniques.
|
||||
|
||||
@@ -125,11 +125,6 @@ extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBu
|
||||
builder->groundPlane(a, b, c, d);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void Java_com_google_android_filament_utils_Manipulator_nBuilderPanning(JNIEnv*, jclass, jlong nativeBuilder, jboolean enabled) {
|
||||
Builder* builder = (Builder*) nativeBuilder;
|
||||
builder->panning(enabled);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT long Java_com_google_android_filament_utils_Manipulator_nBuilderBuild(JNIEnv*, jclass, jlong nativeBuilder, jint mode) {
|
||||
Builder* builder = (Builder*) nativeBuilder;
|
||||
return (jlong) builder->build((Mode) mode);
|
||||
|
||||
-12
@@ -274,17 +274,6 @@ public class Manipulator {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether panning is enabled in the manipulator.
|
||||
*
|
||||
* @return this <code>Builder</code> object for chaining calls
|
||||
*/
|
||||
@NonNull
|
||||
public Builder panning(Boolean enabled) {
|
||||
nBuilderPanning(mNativeBuilder, enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns the <code>Manipulator</code> object.
|
||||
*
|
||||
@@ -494,7 +483,6 @@ public class Manipulator {
|
||||
private static native void nBuilderFlightPanSpeed(long nativeBuilder, float x, float y);
|
||||
private static native void nBuilderFlightMoveDamping(long nativeBuilder, float damping);
|
||||
private static native void nBuilderGroundPlane(long nativeBuilder, float a, float b, float c, float d);
|
||||
private static native void nBuilderPanning(long nativeBuilder, Boolean enabled);
|
||||
private static native long nBuilderBuild(long nativeBuilder, int mode);
|
||||
|
||||
private static native void nDestroyManipulator(long nativeManip);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
GROUP=com.google.android.filament
|
||||
VERSION_NAME=1.53.4
|
||||
VERSION_NAME=1.53.1
|
||||
|
||||
POM_DESCRIPTION=Real-time physically based rendering engine for Android.
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
#define HandleAllocatorGL HandleAllocator<32, 64, 136> // ~4520 / pool / MiB
|
||||
#define HandleAllocatorVK HandleAllocator<64, 160, 312> // ~1820 / pool / MiB
|
||||
#define HandleAllocatorMTL HandleAllocator<32, 64, 552> // ~1660 / pool / MiB
|
||||
#define HandleAllocatorMTL HandleAllocator<32, 48, 552> // ~1660 / pool / MiB
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
|
||||
@@ -32,11 +32,10 @@
|
||||
# define HAS_MMAP 0
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
using namespace utils;
|
||||
|
||||
@@ -82,9 +81,6 @@ void* CircularBuffer::alloc(size_t size) noexcept {
|
||||
// map the circular buffer once...
|
||||
vaddr = mmap(reserve_vaddr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||
if (vaddr != MAP_FAILED) {
|
||||
// populate the address space with pages (because this is a circular buffer,
|
||||
// all the pages will be allocated eventually, might as well do it now)
|
||||
memset(vaddr, 0, size);
|
||||
// and map the circular buffer again, behind the previous copy...
|
||||
vaddr_shadow = mmap((char*)vaddr + size, size,
|
||||
PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
|
||||
@@ -105,7 +101,7 @@ void* CircularBuffer::alloc(size_t size) noexcept {
|
||||
if (UTILS_UNLIKELY(mAshmemFd < 0)) {
|
||||
// ashmem failed
|
||||
if (vaddr_guard != MAP_FAILED) {
|
||||
munmap(vaddr_guard, BLOCK_SIZE);
|
||||
munmap(vaddr_guard, size);
|
||||
}
|
||||
|
||||
if (vaddr_shadow != MAP_FAILED) {
|
||||
@@ -123,11 +119,12 @@ void* CircularBuffer::alloc(size_t size) noexcept {
|
||||
data = mmap(nullptr, size * 2 + BLOCK_SIZE,
|
||||
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(data != MAP_FAILED) <<
|
||||
FILAMENT_CHECK_POSTCONDITION(data) <<
|
||||
"couldn't allocate " << (size * 2 / 1024) <<
|
||||
" KiB of virtual address space for the command buffer";
|
||||
|
||||
slog.w << "Using 'soft' CircularBuffer (" << (size * 2 / 1024) << " KiB)" << io::endl;
|
||||
slog.d << "WARNING: Using soft CircularBuffer (" << (size * 2 / 1024) << " KiB)"
|
||||
<< io::endl;
|
||||
|
||||
// guard page at the end
|
||||
void* guard = (void*)(uintptr_t(data) + size * 2);
|
||||
|
||||
@@ -145,7 +145,6 @@ std::pair<id<MTLBuffer>, size_t> MetalBumpAllocator::allocateStagingArea(size_t
|
||||
// We're finished with the current allocation.
|
||||
mCurrentUploadBuffer = { [mDevice newBufferWithLength:mCapacity options:MTLStorageModeShared],
|
||||
TrackedMetalBuffer::Type::STAGING };
|
||||
mHead = size;
|
||||
|
||||
return { mCurrentUploadBuffer.get(), 0 };
|
||||
}
|
||||
|
||||
@@ -53,14 +53,14 @@ Driver* MetalDriverFactory::create(MetalPlatform* const platform, const Platform
|
||||
// MetalRenderPrimitive : 24 many
|
||||
// MetalVertexBuffer : 32 moderate
|
||||
// -- less than or equal 32 bytes
|
||||
// MetalIndexBuffer : 40 moderate
|
||||
// MetalFence : 48 few
|
||||
// MetalIndexBuffer : 56 moderate
|
||||
// MetalBufferObject : 64 many
|
||||
// -- less than or equal 64 bytes
|
||||
// MetalBufferObject : 48 many
|
||||
// -- less than or equal 48 bytes
|
||||
// MetalSamplerGroup : 112 few
|
||||
// MetalProgram : 152 moderate
|
||||
// MetalTexture : 152 moderate
|
||||
// MetalSwapChain : 208 few
|
||||
// MetalSwapChain : 184 few
|
||||
// MetalRenderTarget : 272 few
|
||||
// MetalVertexBufferInfo : 552 moderate
|
||||
// -- less than or equal to 552 bytes
|
||||
|
||||
@@ -556,6 +556,8 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
descriptor.usage = getMetalTextureUsage(usage);
|
||||
descriptor.storageMode = MTLStorageModePrivate;
|
||||
texture = [context.device newTextureWithDescriptor:descriptor];
|
||||
FILAMENT_CHECK_POSTCONDITION(texture != nil)
|
||||
<< "Could not create Metal texture. Out of memory?";
|
||||
break;
|
||||
case SamplerType::SAMPLER_CUBEMAP:
|
||||
case SamplerType::SAMPLER_CUBEMAP_ARRAY:
|
||||
@@ -570,6 +572,8 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
descriptor.usage = getMetalTextureUsage(usage);
|
||||
descriptor.storageMode = MTLStorageModePrivate;
|
||||
texture = [context.device newTextureWithDescriptor:descriptor];
|
||||
FILAMENT_CHECK_POSTCONDITION(texture != nil)
|
||||
<< "Could not create Metal texture. Out of memory?";
|
||||
break;
|
||||
case SamplerType::SAMPLER_3D:
|
||||
descriptor = [MTLTextureDescriptor new];
|
||||
@@ -582,6 +586,8 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
descriptor.usage = getMetalTextureUsage(usage);
|
||||
descriptor.storageMode = MTLStorageModePrivate;
|
||||
texture = [context.device newTextureWithDescriptor:descriptor];
|
||||
FILAMENT_CHECK_POSTCONDITION(texture != nil)
|
||||
<< "Could not create Metal texture. Out of memory?";
|
||||
break;
|
||||
case SamplerType::SAMPLER_EXTERNAL:
|
||||
// If we're using external textures (CVPixelBufferRefs), we don't need to make any
|
||||
@@ -590,12 +596,6 @@ MetalTexture::MetalTexture(MetalContext& context, SamplerType target, uint8_t le
|
||||
break;
|
||||
}
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(target == SamplerType::SAMPLER_EXTERNAL || texture != nil)
|
||||
<< "Could not create Metal texture (SamplerType = " << int(target)
|
||||
<< ", levels = " << int(levels) << ", MTLPixelFormat = " << int(devicePixelFormat)
|
||||
<< ", width = " << width << ", height = " << height << ", depth = " << depth
|
||||
<< "). Out of memory?";
|
||||
|
||||
// If swizzling is set, set up a swizzled texture view that we'll use when sampling this texture.
|
||||
const bool isDefaultSwizzle =
|
||||
r == TextureSwizzle::CHANNEL_0 &&
|
||||
|
||||
@@ -143,25 +143,21 @@ void TimerQueryNativeFactory::endTimeElapsedQuery(OpenGLDriver& driver, GLTimerQ
|
||||
|
||||
driver.runEveryNowAndThen([&context = mContext, weak]() -> bool {
|
||||
auto state = weak.lock();
|
||||
if (!state) {
|
||||
// The timer query state has been destroyed on the way, very likely due to the IBL
|
||||
// prefilter context destruction. We still return true to get this element removed from
|
||||
// the query list.
|
||||
return true;
|
||||
if (state) {
|
||||
GLuint available = 0;
|
||||
context.procs.getQueryObjectuiv(state->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
if (!available) {
|
||||
// we need to try this one again later
|
||||
return false;
|
||||
}
|
||||
GLuint64 elapsedTime = 0;
|
||||
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
|
||||
context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime);
|
||||
state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed);
|
||||
} else {
|
||||
state->elapsed.store(int64_t(TimerQueryResult::ERROR), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
GLuint available = 0;
|
||||
context.procs.getQueryObjectuiv(state->gl.query, GL_QUERY_RESULT_AVAILABLE, &available);
|
||||
CHECK_GL_ERROR(utils::slog.e)
|
||||
if (!available) {
|
||||
// we need to try this one again later
|
||||
return false;
|
||||
}
|
||||
GLuint64 elapsedTime = 0;
|
||||
// we won't end-up here if we're on ES and don't have GL_EXT_disjoint_timer_query
|
||||
context.procs.getQueryObjectui64v(state->gl.query, GL_QUERY_RESULT, &elapsedTime);
|
||||
state->elapsed.store((int64_t)elapsedTime, std::memory_order_relaxed);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -572,16 +572,23 @@ void ShaderCompilerService::compileShaders(OpenGLContext& context,
|
||||
|
||||
// split shader source, so we can insert the specialization constants and the packing
|
||||
// functions
|
||||
auto const [prolog, body] = splitShaderSource({ shader_src, shader_len });
|
||||
auto [version, prolog, body] = splitShaderSource({ shader_src, shader_len });
|
||||
|
||||
const std::array<const char*, 4> sources = {
|
||||
// enable ESSL 3.10 if available
|
||||
if (context.isAtLeastGLES<3, 1>()) {
|
||||
version = "#version 310 es\n";
|
||||
}
|
||||
|
||||
const std::array<const char*, 5> sources = {
|
||||
version.data(),
|
||||
prolog.data(),
|
||||
specializationConstantString.c_str(),
|
||||
packingFunctions.data(),
|
||||
body.data()
|
||||
};
|
||||
|
||||
const std::array<GLint, 4> lengths = {
|
||||
const std::array<GLint, 5> lengths = {
|
||||
(GLint)version.length(),
|
||||
(GLint)prolog.length(),
|
||||
(GLint)specializationConstantString.length(),
|
||||
(GLint)packingFunctions.length(),
|
||||
@@ -661,6 +668,7 @@ void ShaderCompilerService::process_OVR_multiview2(OpenGLContext& context,
|
||||
|
||||
// Tragically, OpenGL 4.1 doesn't support unpackHalf2x16 (appeared in 4.2) and
|
||||
// macOS doesn't support GL_ARB_shading_language_packing
|
||||
// Also GLES3.0 didn't have the full set of packing/unpacking functions
|
||||
std::string_view ShaderCompilerService::process_ARB_shading_language_packing(OpenGLContext& context) noexcept {
|
||||
using namespace std::literals;
|
||||
#ifdef BACKEND_OPENGL_VERSION_GL
|
||||
@@ -700,21 +708,92 @@ highp uint packHalf2x16(vec2 v) {
|
||||
highp uint y = fp32tou16(v.y);
|
||||
return (y << 16u) | x;
|
||||
}
|
||||
highp uint packUnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, 0.0, 1.0) * 255.0);
|
||||
highp uint a = uint(v.x);
|
||||
highp uint b = uint(v.y) << 8;
|
||||
highp uint c = uint(v.z) << 16;
|
||||
highp uint d = uint(v.w) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
highp uint packSnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, -1.0, 1.0) * 127.0);
|
||||
highp uint a = uint((int(v.x) & 0xff));
|
||||
highp uint b = uint((int(v.y) & 0xff)) << 8;
|
||||
highp uint c = uint((int(v.z) & 0xff)) << 16;
|
||||
highp uint d = uint((int(v.w) & 0xff)) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
mediump vec4 unpackUnorm4x8(highp uint v) {
|
||||
return vec4(float((v & 0x000000ffu) ),
|
||||
float((v & 0x0000ff00u) >> 8),
|
||||
float((v & 0x00ff0000u) >> 16),
|
||||
float((v & 0xff000000u) >> 24)) / 255.0;
|
||||
}
|
||||
mediump vec4 unpackSnorm4x8(highp uint v) {
|
||||
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
|
||||
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
|
||||
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
|
||||
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
|
||||
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
|
||||
}
|
||||
)"sv;
|
||||
}
|
||||
#endif // BACKEND_OPENGL_VERSION_GL
|
||||
|
||||
#ifdef BACKEND_OPENGL_VERSION_GLES
|
||||
if (!context.isES2() && !context.isAtLeastGLES<3, 1>()) {
|
||||
return R"(
|
||||
|
||||
highp uint packUnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, 0.0, 1.0) * 255.0);
|
||||
highp uint a = uint(v.x);
|
||||
highp uint b = uint(v.y) << 8;
|
||||
highp uint c = uint(v.z) << 16;
|
||||
highp uint d = uint(v.w) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
highp uint packSnorm4x8(mediump vec4 v) {
|
||||
v = round(clamp(v, -1.0, 1.0) * 127.0);
|
||||
highp uint a = uint((int(v.x) & 0xff));
|
||||
highp uint b = uint((int(v.y) & 0xff)) << 8;
|
||||
highp uint c = uint((int(v.z) & 0xff)) << 16;
|
||||
highp uint d = uint((int(v.w) & 0xff)) << 24;
|
||||
return (a|b|c|d);
|
||||
}
|
||||
mediump vec4 unpackUnorm4x8(highp uint v) {
|
||||
return vec4(float((v & 0x000000ffu) ),
|
||||
float((v & 0x0000ff00u) >> 8),
|
||||
float((v & 0x00ff0000u) >> 16),
|
||||
float((v & 0xff000000u) >> 24)) / 255.0;
|
||||
}
|
||||
mediump vec4 unpackSnorm4x8(highp uint v) {
|
||||
int a = int(((v ) & 0xffu) << 24u) >> 24 ;
|
||||
int b = int(((v >> 8u) & 0xffu) << 24u) >> 24 ;
|
||||
int c = int(((v >> 16u) & 0xffu) << 24u) >> 24 ;
|
||||
int d = int(((v >> 24u) & 0xffu) << 24u) >> 24 ;
|
||||
return clamp(vec4(float(a), float(b), float(c), float(d)) / 127.0, -1.0, 1.0);
|
||||
}
|
||||
)"sv;
|
||||
}
|
||||
#endif // BACKEND_OPENGL_VERSION_GLES
|
||||
return ""sv;
|
||||
}
|
||||
|
||||
// split shader source code in two, the first section goes from the start to the line after the
|
||||
// last #extension, and the 2nd part goes from there to the end.
|
||||
std::array<std::string_view, 2> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
|
||||
// split shader source code in three:
|
||||
// - the version line
|
||||
// - extensions
|
||||
// - everything else
|
||||
std::array<std::string_view, 3> ShaderCompilerService::splitShaderSource(std::string_view source) noexcept {
|
||||
auto start = source.find("#version");
|
||||
assert_invariant(start != std::string_view::npos);
|
||||
|
||||
auto version_eol = source.find('\n', start) + 1;
|
||||
assert_invariant(version_eol != std::string_view::npos);
|
||||
|
||||
auto pos = source.rfind("\n#extension");
|
||||
if (pos == std::string_view::npos) {
|
||||
pos = start;
|
||||
pos = version_eol;
|
||||
} else {
|
||||
++pos;
|
||||
}
|
||||
@@ -722,9 +801,10 @@ std::array<std::string_view, 2> ShaderCompilerService::splitShaderSource(std::st
|
||||
auto eol = source.find('\n', pos) + 1;
|
||||
assert_invariant(eol != std::string_view::npos);
|
||||
|
||||
std::string_view const version = source.substr(start, eol - start);
|
||||
std::string_view const body = source.substr(version.length(), source.length() - version.length());
|
||||
return { version, body };
|
||||
std::string_view const version = source.substr(start, version_eol - start);
|
||||
std::string_view const prolog = source.substr(version_eol, eol - version_eol);
|
||||
std::string_view const body = source.substr(eol, source.length() - eol);
|
||||
return { version, prolog, body };
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -146,7 +146,7 @@ private:
|
||||
|
||||
static std::string_view process_ARB_shading_language_packing(OpenGLContext& context) noexcept;
|
||||
|
||||
static std::array<std::string_view, 2> splitShaderSource(std::string_view source) noexcept;
|
||||
static std::array<std::string_view, 3> splitShaderSource(std::string_view source) noexcept;
|
||||
|
||||
static GLuint linkProgram(OpenGLContext& context,
|
||||
std::array<GLuint, Program::SHADER_TYPE_COUNT> shaders,
|
||||
|
||||
@@ -28,7 +28,6 @@ VulkanBuffer::VulkanBuffer(VmaAllocator allocator, VulkanStagePool& stagePool,
|
||||
: mAllocator(allocator),
|
||||
mStagePool(stagePool),
|
||||
mUsage(usage),
|
||||
mUpdatedOffset(0),
|
||||
mUpdatedBytes(0) {
|
||||
// for now make sure that only 1 bit is set in usage
|
||||
// (because loadFromCpu() assumes that somewhat)
|
||||
@@ -81,7 +80,6 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.buffer = mGpuBuffer,
|
||||
.offset = mUpdatedOffset,
|
||||
.size = mUpdatedBytes,
|
||||
};
|
||||
vkCmdPipelineBarrier(cmdbuf, srcStage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1,
|
||||
@@ -95,7 +93,6 @@ void VulkanBuffer::loadFromCpu(VkCommandBuffer cmdbuf, const void* cpuData, uint
|
||||
};
|
||||
vkCmdCopyBuffer(cmdbuf, stage->buffer, mGpuBuffer, 1, ®ion);
|
||||
|
||||
mUpdatedOffset = byteOffset;
|
||||
mUpdatedBytes = numBytes;
|
||||
|
||||
// Firstly, ensure that the copy finishes before the next draw call.
|
||||
|
||||
@@ -42,7 +42,6 @@ private:
|
||||
VmaAllocation mGpuMemory = VK_NULL_HANDLE;
|
||||
VkBuffer mGpuBuffer = VK_NULL_HANDLE;
|
||||
VkBufferUsageFlags mUsage = {};
|
||||
uint32_t mUpdatedOffset = 0;
|
||||
uint32_t mUpdatedBytes = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -59,11 +59,7 @@ VkExtent2D VulkanAttachment::getExtent2D() const {
|
||||
|
||||
VkImageView VulkanAttachment::getImageView() {
|
||||
assert_invariant(texture);
|
||||
VkImageSubresourceRange range = getSubresourceRange();
|
||||
if (range.layerCount > 1) {
|
||||
return texture->getMultiviewAttachmentView(range);
|
||||
}
|
||||
return texture->getAttachmentView(range);
|
||||
return texture->getAttachmentView(getSubresourceRange());
|
||||
}
|
||||
|
||||
bool VulkanAttachment::isDepth() const {
|
||||
@@ -77,7 +73,7 @@ VkImageSubresourceRange VulkanAttachment::getSubresourceRange() const {
|
||||
.baseMipLevel = uint32_t(level),
|
||||
.levelCount = 1,
|
||||
.baseArrayLayer = uint32_t(layer),
|
||||
.layerCount = layerCount,
|
||||
.layerCount = 1,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -43,8 +43,6 @@ struct VulkanCommandBuffer;
|
||||
struct VulkanAttachment {
|
||||
VulkanTexture* texture = nullptr;
|
||||
uint8_t level = 0;
|
||||
uint8_t baseViewIndex = 0;
|
||||
uint8_t layerCount = 1;
|
||||
uint16_t layer = 0;
|
||||
|
||||
bool isDepth() const;
|
||||
|
||||
@@ -595,8 +595,6 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
colorTargets[i] = {
|
||||
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(color[i].handle),
|
||||
.level = color[i].level,
|
||||
.baseViewIndex = color[i].baseViewIndex,
|
||||
.layerCount = layerCount,
|
||||
.layer = color[i].layer,
|
||||
};
|
||||
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = colorTargets[i].getExtent2D();
|
||||
@@ -611,8 +609,6 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
depthStencil[0] = {
|
||||
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(depth.handle),
|
||||
.level = depth.level,
|
||||
.baseViewIndex = depth.baseViewIndex,
|
||||
.layerCount = layerCount,
|
||||
.layer = depth.layer,
|
||||
};
|
||||
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[0].getExtent2D();
|
||||
@@ -625,8 +621,6 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
depthStencil[1] = {
|
||||
.texture = mResourceAllocator.handle_cast<VulkanTexture*>(stencil.handle),
|
||||
.level = stencil.level,
|
||||
.baseViewIndex = stencil.baseViewIndex,
|
||||
.layerCount = layerCount,
|
||||
.layer = stencil.layer,
|
||||
};
|
||||
UTILS_UNUSED_IN_RELEASE VkExtent2D extent = depthStencil[1].getExtent2D();
|
||||
@@ -643,7 +637,7 @@ void VulkanDriver::createRenderTargetR(Handle<HwRenderTarget> rth,
|
||||
|
||||
auto renderTarget = mResourceAllocator.construct<VulkanRenderTarget>(rth, mPlatform->getDevice(),
|
||||
mPlatform->getPhysicalDevice(), mContext, mAllocator, &mCommands, width, height,
|
||||
samples, colorTargets, depthStencil, mStagePool, layerCount);
|
||||
samples, colorTargets, depthStencil, mStagePool);
|
||||
mResourceManager.acquire(renderTarget);
|
||||
}
|
||||
|
||||
@@ -992,15 +986,15 @@ FeatureLevel VulkanDriver::getFeatureLevel() {
|
||||
|
||||
// If the max sampler counts do not meet FL2 standards, then this is an FL1 device.
|
||||
const auto& fl2 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_2];
|
||||
if (limits.maxPerStageDescriptorSamplers < fl2.MAX_VERTEX_SAMPLER_COUNT ||
|
||||
limits.maxPerStageDescriptorSamplers < fl2.MAX_FRAGMENT_SAMPLER_COUNT) {
|
||||
if (fl2.MAX_VERTEX_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers ||
|
||||
fl2.MAX_FRAGMENT_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers) {
|
||||
return FeatureLevel::FEATURE_LEVEL_1;
|
||||
}
|
||||
|
||||
// If the max sampler counts do not meet FL3 standards, then this is an FL2 device.
|
||||
const auto& fl3 = FEATURE_LEVEL_CAPS[+FeatureLevel::FEATURE_LEVEL_3];
|
||||
if (limits.maxPerStageDescriptorSamplers < fl3.MAX_VERTEX_SAMPLER_COUNT||
|
||||
limits.maxPerStageDescriptorSamplers < fl3.MAX_FRAGMENT_SAMPLER_COUNT) {
|
||||
if (fl3.MAX_VERTEX_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers ||
|
||||
fl3.MAX_FRAGMENT_SAMPLER_COUNT < limits.maxPerStageDescriptorSamplers) {
|
||||
return FeatureLevel::FEATURE_LEVEL_2;
|
||||
}
|
||||
|
||||
@@ -1273,8 +1267,6 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t const renderTargetLayerCount = rt->getLayerCount();
|
||||
|
||||
// Create the VkRenderPass or fetch it from cache.
|
||||
VulkanFboCache::RenderPassKey rpkey = {
|
||||
.initialColorLayoutMask = 0,
|
||||
@@ -1285,12 +1277,10 @@ void VulkanDriver::beginRenderPass(Handle<HwRenderTarget> rth, const RenderPassP
|
||||
.discardEnd = discardEndVal,
|
||||
.samples = rt->getSamples(),
|
||||
.subpassMask = uint8_t(params.subpassMask),
|
||||
.viewCount = renderTargetLayerCount,
|
||||
};
|
||||
for (int i = 0; i < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; i++) {
|
||||
const VulkanAttachment& info = rt->getColor(i);
|
||||
if (info.texture) {
|
||||
assert_invariant(info.layerCount == renderTargetLayerCount);
|
||||
rpkey.initialColorLayoutMask |= 1 << i;
|
||||
rpkey.colorFormat[i] = info.getFormat();
|
||||
if (rpkey.samples > 1 && info.texture->samples == 1) {
|
||||
|
||||
@@ -43,7 +43,6 @@ bool VulkanFboCache::RenderPassEq::operator()(const RenderPassKey& k1,
|
||||
if (k1.samples != k2.samples) return false;
|
||||
if (k1.needsResolveMask != k2.needsResolveMask) return false;
|
||||
if (k1.subpassMask != k2.subpassMask) return false;
|
||||
if (k1.viewCount != k2.viewCount) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -187,25 +186,6 @@ VkRenderPass VulkanFboCache::getRenderPass(RenderPassKey config) noexcept {
|
||||
.pDependencies = dependencies
|
||||
};
|
||||
|
||||
VkRenderPassMultiviewCreateInfo multiviewCreateInfo = {};
|
||||
uint32_t const subpassViewMask = (1 << config.viewCount) - 1;
|
||||
// Prepare a view mask array for the maximum number of subpasses. All subpasses have all views
|
||||
// activated.
|
||||
uint32_t const viewMasks[2] = {subpassViewMask, subpassViewMask};
|
||||
if (config.viewCount > 1) {
|
||||
// Fill the multiview create info.
|
||||
multiviewCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO;
|
||||
multiviewCreateInfo.pNext = nullptr;
|
||||
multiviewCreateInfo.subpassCount = hasSubpasses ? 2u : 1u;
|
||||
multiviewCreateInfo.pViewMasks = viewMasks;
|
||||
multiviewCreateInfo.dependencyCount = 0;
|
||||
multiviewCreateInfo.pViewOffsets = nullptr;
|
||||
multiviewCreateInfo.correlationMaskCount = 1;
|
||||
multiviewCreateInfo.pCorrelationMasks = &subpassViewMask;
|
||||
|
||||
renderPassInfo.pNext = &multiviewCreateInfo;
|
||||
}
|
||||
|
||||
int attachmentIndex = 0;
|
||||
|
||||
// Populate the Color Attachments.
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
uint8_t samples; // 1 byte
|
||||
uint8_t needsResolveMask; // 1 byte
|
||||
uint8_t subpassMask; // 1 byte
|
||||
uint8_t viewCount; // 1 byte
|
||||
uint8_t padding2; // 1 byte
|
||||
};
|
||||
struct RenderPassVal {
|
||||
VkRenderPass handle;
|
||||
|
||||
@@ -323,12 +323,11 @@ VulkanRenderTarget::VulkanRenderTarget(VkDevice device, VkPhysicalDevice physica
|
||||
VulkanContext const& context, VmaAllocator allocator, VulkanCommands* commands,
|
||||
uint32_t width, uint32_t height, uint8_t samples,
|
||||
VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount)
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool)
|
||||
: HwRenderTarget(width, height),
|
||||
VulkanResource(VulkanResourceType::RENDER_TARGET),
|
||||
mOffscreen(true),
|
||||
mSamples(samples),
|
||||
mLayerCount(layerCount) {
|
||||
mSamples(samples) {
|
||||
for (int index = 0; index < MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT; index++) {
|
||||
mColor[index] = color[index];
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
|
||||
VulkanContext const& context, VmaAllocator allocator,
|
||||
VulkanCommands* commands, uint32_t width, uint32_t height,
|
||||
uint8_t samples, VulkanAttachment color[MRT::MAX_SUPPORTED_RENDER_TARGET_COUNT],
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool, uint8_t layerCount);
|
||||
VulkanAttachment depthStencil[2], VulkanStagePool& stagePool);
|
||||
|
||||
// Creates a special "default" render target (i.e. associated with the swap chain)
|
||||
explicit VulkanRenderTarget();
|
||||
@@ -319,7 +319,6 @@ struct VulkanRenderTarget : private HwRenderTarget, VulkanResource {
|
||||
VulkanAttachment& getMsaaDepth();
|
||||
uint8_t getColorTargetCount(const VulkanRenderPass& pass) const;
|
||||
uint8_t getSamples() const { return mSamples; }
|
||||
uint8_t getLayerCount() const { return mLayerCount; }
|
||||
bool hasDepth() const { return mDepth.texture; }
|
||||
bool isSwapChain() const { return !mOffscreen; }
|
||||
void bindToSwapChain(VulkanSwapChain& surf);
|
||||
@@ -331,7 +330,6 @@ private:
|
||||
VulkanAttachment mMsaaDepthAttachment = {};
|
||||
const bool mOffscreen : 1;
|
||||
uint8_t mSamples : 7;
|
||||
uint8_t mLayerCount = 1;
|
||||
};
|
||||
|
||||
struct VulkanBufferObject;
|
||||
|
||||
@@ -387,15 +387,12 @@ void VulkanTexture::setPrimaryRange(uint32_t minMiplevel, uint32_t maxMiplevel)
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getAttachmentView(VkImageSubresourceRange range) {
|
||||
// Attachments should only have one mipmap level and one layer.
|
||||
range.levelCount = 1;
|
||||
range.layerCount = 1;
|
||||
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D, {});
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getMultiviewAttachmentView(VkImageSubresourceRange range) {
|
||||
return getImageView(range, VK_IMAGE_VIEW_TYPE_2D_ARRAY, {});
|
||||
}
|
||||
|
||||
VkImageView VulkanTexture::getViewForType(VkImageSubresourceRange const& range, VkImageViewType type) {
|
||||
return getImageView(range, type, mSwizzle);
|
||||
}
|
||||
|
||||
@@ -73,10 +73,6 @@ struct VulkanTexture : public HwTexture, VulkanResource {
|
||||
// and the identity swizzle.
|
||||
VkImageView getAttachmentView(VkImageSubresourceRange range);
|
||||
|
||||
// Gets or creates a cached VkImageView for a single subresource that can be used as a render
|
||||
// target attachment when rendering with multiview.
|
||||
VkImageView getMultiviewAttachmentView(VkImageSubresourceRange range);
|
||||
|
||||
// This is a workaround for the first few frames where we're waiting for the texture to actually
|
||||
// be uploaded. In that case, we bind the sampler to an empty texture, but the corresponding
|
||||
// imageView needs to be of the right type. Hence, we provide an option to indicate the
|
||||
|
||||
@@ -33,11 +33,11 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
|
||||
// build the push constant layout key
|
||||
uint32_t pushConstantRangeCount = program->getPushConstantRangeCount();
|
||||
auto const& pushConstantRanges = program->getPushConstantRanges();
|
||||
auto const& pushCostantRanges = program->getPushConstantRanges();
|
||||
if (pushConstantRangeCount > 0) {
|
||||
assert_invariant(pushConstantRangeCount <= Program::SHADER_TYPE_COUNT);
|
||||
for (uint8_t i = 0; i < pushConstantRangeCount; ++i) {
|
||||
auto const& range = pushConstantRanges[i];
|
||||
auto const& range = pushCostantRanges[i];
|
||||
auto& pushConstant = key.pushConstant[i];
|
||||
if (range.stageFlags & VK_SHADER_STAGE_VERTEX_BIT) {
|
||||
pushConstant.stage = static_cast<uint8_t>(ShaderStage::VERTEX);
|
||||
@@ -52,8 +52,9 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
}
|
||||
}
|
||||
|
||||
if (PipelineLayoutMap::iterator iter = mPipelineLayouts.find(key); iter != mPipelineLayouts.end()) {
|
||||
PipelineLayoutCacheEntry& entry = iter.value();
|
||||
auto iter = mPipelineLayouts.find(key);
|
||||
if (iter != mPipelineLayouts.end()) {
|
||||
PipelineLayoutCacheEntry& entry = mPipelineLayouts[key];
|
||||
entry.lastUsed = mTimestamp++;
|
||||
return entry.handle;
|
||||
}
|
||||
@@ -64,7 +65,7 @@ VkPipelineLayout VulkanPipelineLayoutCache::getLayout(
|
||||
.setLayoutCount = (uint32_t) descSetLayoutCount,
|
||||
.pSetLayouts = key.descSetLayouts.data(),
|
||||
.pushConstantRangeCount = pushConstantRangeCount,
|
||||
.pPushConstantRanges = pushConstantRanges,
|
||||
.pPushConstantRanges = pushCostantRanges,
|
||||
};
|
||||
|
||||
VkPipelineLayout layout;
|
||||
|
||||
@@ -64,15 +64,14 @@ First determine the Filament material and variant that causes the problem.
|
||||
What we want is the "raw" GLSL version of the shader, before any optimizations / cross-compilation
|
||||
happens.
|
||||
|
||||
We can use the `--save-raw-variants` debug flag in matc to export each GLSL
|
||||
shader to a file. For example:
|
||||
The easiest way I've found to do this is by adding a `std::cout` statement inside `MaterialBuilder`,
|
||||
right after `sg.createFragmentProgram` or `sg.createVertexProgram` is called. For example,
|
||||
|
||||
```
|
||||
matc --save-raw-variants --optimize-size --variant-filter fog,ssr,vsm,stereo \
|
||||
-a all -p all -o mymaterial.filamat mymaterial.mat
|
||||
if (v.variant.key == 8u) { std::cout << shader << std::endl; }
|
||||
```
|
||||
|
||||
Files will be named like `mymaterial_0x05.frag` or `mymaterial_0x05.vert`.
|
||||
Then, I invoke `matc` and redirect stdout to a .frag or .vert file.
|
||||
|
||||
Note that gltfio material "templates" first go through a build step. After building gltfio, the
|
||||
gltfio Filament materials are output to:
|
||||
|
||||
@@ -350,12 +350,9 @@ public:
|
||||
uint32_t resourceAllocatorCacheSizeMB = 64;
|
||||
|
||||
/*
|
||||
* This value determines how many frames texture entries are kept for in the cache. This
|
||||
* is a soft limit, meaning some texture older than this are allowed to stay in the cache.
|
||||
* Typically only one texture is evicted per frame.
|
||||
* The default is 1.
|
||||
* This value determines for how many frames are texture entries kept in the cache.
|
||||
*/
|
||||
uint32_t resourceAllocatorCacheMaxAge = 1;
|
||||
uint32_t resourceAllocatorCacheMaxAge = 2;
|
||||
|
||||
/*
|
||||
* Disable backend handles use-after-free checks.
|
||||
|
||||
@@ -489,20 +489,43 @@ public:
|
||||
* For standard morphing, A MorphTargetBuffer must be provided.
|
||||
* Standard morphing supports up to \c CONFIG_MAX_MORPH_TARGET_COUNT morph targets.
|
||||
*
|
||||
* For legacy morphing, the attached VertexBuffer must provide data in the
|
||||
* appropriate VertexAttribute slots (\c MORPH_POSITION_0 etc). Legacy morphing only
|
||||
* supports up to 4 morph targets and will be deprecated in the future. Legacy morphing must
|
||||
* be enabled on the material definition: either via the legacyMorphing material attribute
|
||||
* or by calling filamat::MaterialBuilder::useLegacyMorphing().
|
||||
*
|
||||
* See also RenderableManager::setMorphWeights(), which can be called on a per-frame basis
|
||||
* to advance the animation.
|
||||
*/
|
||||
Builder& morphing(MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept;
|
||||
|
||||
/**
|
||||
* @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead
|
||||
*/
|
||||
Builder& morphing(uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer,
|
||||
size_t offset, size_t count) noexcept;
|
||||
|
||||
/**
|
||||
* @deprecated Use morphing(uint8_t level, size_t primitiveIndex, size_t offset, size_t count) instead
|
||||
*/
|
||||
inline Builder& morphing(uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) noexcept {
|
||||
return morphing(level, primitiveIndex, morphTargetBuffer, 0,
|
||||
morphTargetBuffer->getVertexCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the the range of the MorphTargetBuffer to use with this primitive.
|
||||
*
|
||||
* @param level the level of detail (lod), only 0 can be specified
|
||||
* @param primitiveIndex zero-based index of the primitive, must be less than the count passed to Builder constructor
|
||||
* @param offset specifies where in the morph target buffer to start reading (expressed as a number of vertices)
|
||||
* @param count number of vertices in the morph target buffer to read, must equal the geometry's count (for triangles, this should be a multiple of 3)
|
||||
*/
|
||||
RenderableManager::Builder& morphing(uint8_t level,
|
||||
size_t primitiveIndex, size_t offset) noexcept;
|
||||
Builder& morphing(uint8_t level, size_t primitiveIndex,
|
||||
size_t offset, size_t count) noexcept;
|
||||
|
||||
|
||||
/**
|
||||
@@ -763,13 +786,25 @@ public:
|
||||
/**
|
||||
* Associates a MorphTargetBuffer to the given primitive.
|
||||
*/
|
||||
void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
size_t offset);
|
||||
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
size_t offset, size_t count);
|
||||
|
||||
/** @deprecated */
|
||||
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer, size_t offset, size_t count);
|
||||
|
||||
/** @deprecated */
|
||||
inline void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* UTILS_NONNULL morphTargetBuffer) {
|
||||
setMorphTargetBufferAt(instance, level, primitiveIndex, morphTargetBuffer, 0,
|
||||
morphTargetBuffer->getVertexCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a MorphTargetBuffer to the given renderable or null if it doesn't exist.
|
||||
* Get a MorphTargetBuffer to the given primitive or null if it doesn't exist.
|
||||
*/
|
||||
MorphTargetBuffer* UTILS_NULLABLE getMorphTargetBuffer(Instance instance) const noexcept;
|
||||
MorphTargetBuffer* UTILS_NULLABLE getMorphTargetBufferAt(Instance instance,
|
||||
uint8_t level, size_t primitiveIndex) const noexcept;
|
||||
|
||||
/**
|
||||
* Gets the number of morphing in the given entity.
|
||||
|
||||
@@ -22,11 +22,9 @@
|
||||
#include <filament/FilamentAPI.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
@@ -83,37 +81,6 @@ public:
|
||||
UTILS_DEPRECATED uint64_t vsyncOffsetNanos = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Timing information about a frame
|
||||
* @see getFrameInfoHistory()
|
||||
*/
|
||||
struct FrameInfo {
|
||||
using time_point_ns = int64_t;
|
||||
using duration_ns = int64_t;
|
||||
uint32_t frameId; //!< monotonically increasing frame identifier
|
||||
duration_ns frameTime; //!< frame duration on the GPU in nanosecond [ns]
|
||||
duration_ns denoisedFrameTime; //!< denoised frame duration on the GPU in [ns]
|
||||
time_point_ns beginFrame; //!< Renderer::beginFrame() time since epoch [ns]
|
||||
time_point_ns endFrame; //!< Renderer::endFrame() time since epoch [ns]
|
||||
time_point_ns backendBeginFrame; //!< Backend thread time of frame start since epoch [ns]
|
||||
time_point_ns backendEndFrame; //!< Backend thread time of frame end since epoch [ns]
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve an historic of frame timing information. The maximum frame history size is
|
||||
* given by getMaxFrameHistorySize().
|
||||
* @param historySize requested history size. The returned vector could be smaller.
|
||||
* @return A vector of FrameInfo.
|
||||
*/
|
||||
utils::FixedCapacityVector<Renderer::FrameInfo> getFrameInfoHistory(
|
||||
size_t historySize = 1) const noexcept;
|
||||
|
||||
/**
|
||||
* @return the maximum supported frame history size.
|
||||
* @see getFrameInfoHistory()
|
||||
*/
|
||||
size_t getMaxFrameHistorySize() const noexcept;
|
||||
|
||||
/**
|
||||
* Use FrameRateOptions to set the desired frame rate and control how quickly the system
|
||||
* reacts to GPU load changes.
|
||||
@@ -271,14 +238,6 @@ public:
|
||||
*/
|
||||
void setVsyncTime(uint64_t steadyClockTimeNano) noexcept;
|
||||
|
||||
/**
|
||||
* Call skipFrame when momentarily skipping frames, for instance if the content of the
|
||||
* scene doesn't change.
|
||||
*
|
||||
* @param vsyncSteadyClockTimeNano
|
||||
*/
|
||||
void skipFrame(uint64_t vsyncSteadyClockTimeNano = 0u);
|
||||
|
||||
/**
|
||||
* Set-up a frame for this Renderer.
|
||||
*
|
||||
|
||||
@@ -40,7 +40,6 @@ class CallbackHandler;
|
||||
|
||||
class Camera;
|
||||
class ColorGrading;
|
||||
class Engine;
|
||||
class MaterialInstance;
|
||||
class RenderTarget;
|
||||
class Scene;
|
||||
@@ -879,17 +878,6 @@ public:
|
||||
*/
|
||||
utils::Entity getFogEntity() const noexcept;
|
||||
|
||||
|
||||
/**
|
||||
* When certain temporal features are used (e.g.: TAA or Screen-space reflections), the view
|
||||
* keeps a history of previous frame renders associated with the Renderer the view was last
|
||||
* used with. When switching Renderer, it may be necessary to clear that history by calling
|
||||
* this method. Similarly, if the whole content of the screen change, like when a cut-scene
|
||||
* starts, clearing the history might be needed to avoid artifacts due to the previous frame
|
||||
* being very different.
|
||||
*/
|
||||
void clearFrameHistory(Engine& engine) noexcept;
|
||||
|
||||
/**
|
||||
* List of available ambient occlusion techniques
|
||||
* @deprecated use AmbientOcclusionOptions::enabled instead
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
#include <fg/FrameGraphTexture.h>
|
||||
|
||||
#include <math/mat4.h>
|
||||
#include <math/vec2.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
|
||||
+45
-132
@@ -16,33 +16,30 @@
|
||||
|
||||
#include "FrameInfo.h"
|
||||
|
||||
#include <filament/Renderer.h>
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <ratio>
|
||||
#include <math/scalar.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <cmath>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace utils;
|
||||
using namespace backend;
|
||||
|
||||
// this is to avoid a call to memmove
|
||||
template<class InputIterator, class OutputIterator>
|
||||
static inline
|
||||
void move_backward(InputIterator first, InputIterator last, OutputIterator result) {
|
||||
while (first != last) {
|
||||
*--result = *--last;
|
||||
}
|
||||
}
|
||||
|
||||
FrameInfoManager::FrameInfoManager(DriverApi& driver) noexcept {
|
||||
for (auto& query : mQueries) {
|
||||
query.handle = driver.createTimerQuery();
|
||||
query = driver.createTimerQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,36 +47,14 @@ FrameInfoManager::~FrameInfoManager() noexcept = default;
|
||||
|
||||
void FrameInfoManager::terminate(DriverApi& driver) noexcept {
|
||||
for (auto& query : mQueries) {
|
||||
driver.destroyTimerQuery(query.handle);
|
||||
driver.destroyTimerQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
void FrameInfoManager::beginFrame(DriverApi& driver, Config const& config, uint32_t frameId) noexcept {
|
||||
auto& history = mFrameTimeHistory;
|
||||
// don't exceed the capacity, drop the oldest entry
|
||||
if (UTILS_LIKELY(history.size() == history.capacity())) {
|
||||
history.pop_back();
|
||||
}
|
||||
|
||||
// create a new entry
|
||||
auto& front = history.emplace_front(frameId);
|
||||
|
||||
// store the current time
|
||||
front.beginFrame = std::chrono::steady_clock::now();
|
||||
|
||||
// references are not invalidated by CircularQueue<>, so we can associate a reference to
|
||||
// the slot we created to the timer query used to find the frame time.
|
||||
mQueries[mIndex].pInfo = std::addressof(front);
|
||||
// issue the timer query
|
||||
driver.beginTimerQuery(mQueries[mIndex].handle);
|
||||
// issue the custom backend command to get the backend time
|
||||
driver.queueCommand([&front](){
|
||||
front.backendBeginFrame = std::chrono::steady_clock::now();
|
||||
});
|
||||
|
||||
// now is a good time to check the oldest active query
|
||||
void FrameInfoManager::beginFrame(DriverApi& driver,Config const& config, uint32_t) noexcept {
|
||||
driver.beginTimerQuery(mQueries[mIndex]);
|
||||
uint64_t elapsed = 0;
|
||||
TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast].handle, &elapsed);
|
||||
TimerQueryResult const result = driver.getTimerQueryValue(mQueries[mLast], &elapsed);
|
||||
switch (result) {
|
||||
case TimerQueryResult::NOT_READY:
|
||||
// nothing to do
|
||||
@@ -88,111 +63,49 @@ void FrameInfoManager::beginFrame(DriverApi& driver, Config const& config, uint3
|
||||
mLast = (mLast + 1) % POOL_COUNT;
|
||||
break;
|
||||
case TimerQueryResult::AVAILABLE:
|
||||
// conversion to our duration happens here
|
||||
pFront = mQueries[mLast].pInfo;
|
||||
pFront->frameTime = std::chrono::duration<uint64_t, std::nano>(elapsed);
|
||||
mLast = (mLast + 1) % POOL_COUNT;
|
||||
denoiseFrameTime(config);
|
||||
// conversion to our duration happens here
|
||||
mFrameTime = std::chrono::duration<uint64_t, std::nano>(elapsed);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// keep this just for debugging
|
||||
if constexpr (false) {
|
||||
using namespace utils;
|
||||
auto h = getFrameInfoHistory(1);
|
||||
if (!h.empty()) {
|
||||
slog.d << frameId << ": "
|
||||
<< h[0].frameId << " (" << frameId - h[0].frameId << ")"
|
||||
<< ", Dm=" << h[0].endFrame - h[0].beginFrame
|
||||
<< ", L =" << h[0].backendBeginFrame - h[0].beginFrame
|
||||
<< ", Db=" << h[0].backendEndFrame - h[0].backendBeginFrame
|
||||
<< ", T =" << h[0].frameTime
|
||||
<< io::endl;
|
||||
}
|
||||
}
|
||||
update(config, mFrameTime);
|
||||
}
|
||||
|
||||
void FrameInfoManager::endFrame(DriverApi& driver) noexcept {
|
||||
auto& front = mFrameTimeHistory.front();
|
||||
// close the timer query
|
||||
driver.endTimerQuery(mQueries[mIndex].handle);
|
||||
// queue custom backend command to query the current time
|
||||
driver.queueCommand([&front](){
|
||||
// backend frame end-time
|
||||
front.backendEndFrame = std::chrono::steady_clock::now();
|
||||
// signal that the data is available
|
||||
front.ready.store(true, std::memory_order_release);
|
||||
});
|
||||
// and finally acquire the time on the main thread
|
||||
front.endFrame = std::chrono::steady_clock::now();
|
||||
driver.endTimerQuery(mQueries[mIndex]);
|
||||
mIndex = (mIndex + 1) % POOL_COUNT;
|
||||
}
|
||||
|
||||
void FrameInfoManager::denoiseFrameTime(Config const& config) noexcept {
|
||||
void FrameInfoManager::update(Config const& config,
|
||||
FrameInfoManager::duration lastFrameTime) noexcept {
|
||||
// keep an history of frame times
|
||||
auto& history = mFrameTimeHistory;
|
||||
assert_invariant(!history.empty());
|
||||
|
||||
// find the first slot that has a valid frame duration
|
||||
size_t first = history.size();
|
||||
for (size_t i = 0, c = history.size(); i < c; ++i) {
|
||||
if (history[i].frameTime != duration(0)) {
|
||||
assert_invariant(std::addressof(history[i]) == pFront);
|
||||
first = i;
|
||||
break;
|
||||
}
|
||||
// this is like doing { pop_back(); push_front(); }
|
||||
filament::move_backward(history.begin(), history.end() - 1, history.end());
|
||||
history[0].frameTime = lastFrameTime;
|
||||
|
||||
mFrameTimeHistorySize = std::min(++mFrameTimeHistorySize, uint32_t(MAX_FRAMETIME_HISTORY));
|
||||
if (UTILS_UNLIKELY(mFrameTimeHistorySize < 3)) {
|
||||
// not enough history to do anything useful
|
||||
history[0].valid = false;
|
||||
return;
|
||||
}
|
||||
assert_invariant(first != history.size());
|
||||
|
||||
// we need at least 3 valid frame time to calculate the median
|
||||
if (history.size() >= first + 3) {
|
||||
// apply a median filter to get a good representation of the frame time of the last
|
||||
// N frames.
|
||||
std::array<duration, MAX_FRAMETIME_HISTORY> median; // NOLINT -- it's initialized below
|
||||
size_t const size = std::min({
|
||||
history.size() - first,
|
||||
median.size(),
|
||||
size_t(config.historySize) });
|
||||
// apply a median filter to get a good representation of the frame time of the last
|
||||
// N frames.
|
||||
std::array<duration, MAX_FRAMETIME_HISTORY> median; // NOLINT -- it's initialized below
|
||||
size_t const size = std::min(mFrameTimeHistorySize,
|
||||
std::min(config.historySize, (uint32_t)median.size()));
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
median[i] = history[i].frameTime;
|
||||
}
|
||||
std::sort(median.begin(), median.begin() + size);
|
||||
duration const denoisedFrameTime = median[size / 2];
|
||||
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
median[i] = history[first + i].frameTime;
|
||||
}
|
||||
std::sort(median.begin(), median.begin() + size);
|
||||
duration const denoisedFrameTime = median[size / 2];
|
||||
|
||||
history[first].denoisedFrameTime = denoisedFrameTime;
|
||||
history[first].valid = true;
|
||||
}
|
||||
history[0].denoisedFrameTime = denoisedFrameTime;
|
||||
history[0].valid = true;
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<Renderer::FrameInfo> FrameInfoManager::getFrameInfoHistory(
|
||||
size_t historySize) const noexcept {
|
||||
auto result = utils::FixedCapacityVector<Renderer::FrameInfo>::with_capacity(MAX_FRAMETIME_HISTORY);
|
||||
auto const& history = mFrameTimeHistory;
|
||||
size_t i = 0;
|
||||
size_t const c = history.size();
|
||||
for (; i < c; ++i) {
|
||||
auto const& entry = history[i];
|
||||
if (entry.ready.load(std::memory_order_acquire) && entry.valid) {
|
||||
// once we found an entry ready,
|
||||
// we know by construction that all following ones are too
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (; i < c && historySize; ++i, --historySize) {
|
||||
auto const& entry = history[i];
|
||||
using namespace std::chrono;
|
||||
result.push_back({
|
||||
entry.frameId,
|
||||
duration_cast<nanoseconds>(entry.frameTime).count(),
|
||||
duration_cast<nanoseconds>(entry.denoisedFrameTime).count(),
|
||||
duration_cast<nanoseconds>(entry.beginFrame.time_since_epoch()).count(),
|
||||
duration_cast<nanoseconds>(entry.endFrame.time_since_epoch()).count(),
|
||||
duration_cast<nanoseconds>(entry.backendBeginFrame.time_since_epoch()).count(),
|
||||
duration_cast<nanoseconds>(entry.backendEndFrame.time_since_epoch()).count()
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
+17
-124
@@ -17,136 +17,30 @@
|
||||
#ifndef TNT_FILAMENT_FRAMEINFO_H
|
||||
#define TNT_FILAMENT_FRAMEINFO_H
|
||||
|
||||
#include <filament/Renderer.h>
|
||||
|
||||
#include <backend/Handle.h>
|
||||
|
||||
#include "backend/Handle.h"
|
||||
#include <private/backend/DriverApi.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
#include <type_traits>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace filament {
|
||||
class FEngine;
|
||||
|
||||
namespace details {
|
||||
struct FrameInfo {
|
||||
using duration = std::chrono::duration<float, std::milli>;
|
||||
duration frameTime{}; // frame period
|
||||
duration denoisedFrameTime{}; // frame period (median filter)
|
||||
bool valid = false; // true if the data of the structure is valid
|
||||
};
|
||||
} // namespace details
|
||||
|
||||
struct FrameInfoImpl : public details::FrameInfo {
|
||||
using clock = std::chrono::steady_clock;
|
||||
using time_point = clock::time_point;
|
||||
uint32_t const frameId;
|
||||
time_point beginFrame; // main thread beginFrame time
|
||||
time_point endFrame; // main thread endFrame time
|
||||
time_point backendBeginFrame; // backend thread beginFrame time (makeCurrent time)
|
||||
time_point backendEndFrame; // backend thread endFrame time (present time)
|
||||
std::atomic_bool ready{}; // true once backend thread has populated its data
|
||||
explicit FrameInfoImpl(uint32_t frameId) noexcept
|
||||
: frameId(frameId) {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, size_t CAPACITY>
|
||||
class CircularQueue {
|
||||
public:
|
||||
using value_type = T;
|
||||
using reference = value_type&;
|
||||
using const_reference = value_type const&;
|
||||
|
||||
size_t capacity() const {
|
||||
return CAPACITY;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
bool empty() const noexcept {
|
||||
return !size();
|
||||
}
|
||||
|
||||
void pop_back() noexcept {
|
||||
assert_invariant(!empty());
|
||||
--mSize;
|
||||
std::destroy_at(&mStorage[(mFront - mSize) % CAPACITY]);
|
||||
}
|
||||
|
||||
void push_front(T const& v) noexcept {
|
||||
assert_invariant(size() < CAPACITY);
|
||||
mFront = advance(mFront);
|
||||
new(&mStorage[mFront]) T(v);
|
||||
++mSize;
|
||||
}
|
||||
|
||||
void push_front(T&& v) noexcept {
|
||||
assert_invariant(size() < CAPACITY);
|
||||
mFront = advance(mFront);
|
||||
new(&mStorage[mFront]) T(std::move(v));
|
||||
++mSize;
|
||||
}
|
||||
|
||||
template<typename ...Args>
|
||||
T& emplace_front(Args&&... args) noexcept {
|
||||
assert_invariant(size() < CAPACITY);
|
||||
mFront = advance(mFront);
|
||||
new(&mStorage[mFront]) T(std::forward<Args>(args)...);
|
||||
++mSize;
|
||||
return front();
|
||||
}
|
||||
|
||||
T& operator[](size_t pos) noexcept {
|
||||
assert_invariant(pos < size());
|
||||
size_t const index = (mFront + CAPACITY - pos) % CAPACITY;
|
||||
return *std::launder(reinterpret_cast<T*>(&mStorage[index]));
|
||||
}
|
||||
|
||||
T const& operator[](size_t pos) const noexcept {
|
||||
return const_cast<CircularQueue&>(*this)[pos];
|
||||
}
|
||||
|
||||
T const& front() const noexcept {
|
||||
assert_invariant(!empty());
|
||||
return operator[](0);
|
||||
}
|
||||
|
||||
T& front() noexcept {
|
||||
assert_invariant(!empty());
|
||||
return operator[](0);
|
||||
}
|
||||
|
||||
private:
|
||||
using Storage = std::aligned_storage_t<sizeof(T), alignof(T)>;
|
||||
Storage mStorage[CAPACITY];
|
||||
uint32_t mFront = 0; // always index 0
|
||||
uint32_t mSize = 0;
|
||||
[[nodiscard]] inline uint32_t advance(uint32_t v) noexcept {
|
||||
return (v + 1) % CAPACITY;
|
||||
}
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
class FrameInfoManager {
|
||||
static constexpr size_t POOL_COUNT = 4;
|
||||
static constexpr size_t MAX_FRAMETIME_HISTORY = 16u;
|
||||
static constexpr size_t MAX_FRAMETIME_HISTORY = 31u;
|
||||
|
||||
public:
|
||||
using duration = FrameInfoImpl::duration;
|
||||
using clock = FrameInfoImpl::clock;
|
||||
using duration = FrameInfo::duration;
|
||||
|
||||
struct Config {
|
||||
uint32_t historySize;
|
||||
@@ -163,24 +57,23 @@ public:
|
||||
// call this immediately before "swap buffers"
|
||||
void endFrame(backend::DriverApi& driver) noexcept;
|
||||
|
||||
details::FrameInfo getLastFrameInfo() const noexcept {
|
||||
// if pFront is not set yet, return FrameInfo(). But the `valid` field will be false in this case.
|
||||
return pFront ? *pFront : details::FrameInfo{};
|
||||
FrameInfo const& getLastFrameInfo() const noexcept {
|
||||
return mFrameTimeHistory[0];
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<Renderer::FrameInfo> getFrameInfoHistory(size_t historySize) const noexcept;
|
||||
duration getLastFrameTime() const noexcept {
|
||||
return getLastFrameInfo().frameTime;
|
||||
}
|
||||
|
||||
private:
|
||||
void denoiseFrameTime(Config const& config) noexcept;
|
||||
struct Query {
|
||||
backend::Handle<backend::HwTimerQuery> handle{};
|
||||
FrameInfoImpl* pInfo = nullptr;
|
||||
};
|
||||
std::array<Query, POOL_COUNT> mQueries;
|
||||
uint32_t mIndex = 0; // index of current query
|
||||
uint32_t mLast = 0; // index of oldest query still active
|
||||
FrameInfoImpl* pFront = nullptr; // the most recent slot with a valid frame time
|
||||
CircularQueue<FrameInfoImpl, MAX_FRAMETIME_HISTORY> mFrameTimeHistory;
|
||||
void update(Config const& config, duration lastFrameTime) noexcept;
|
||||
backend::Handle<backend::HwTimerQuery> mQueries[POOL_COUNT];
|
||||
duration mFrameTime{};
|
||||
uint32_t mIndex = 0;
|
||||
uint32_t mLast = 0;
|
||||
|
||||
std::array<FrameInfo, MAX_FRAMETIME_HISTORY> mFrameTimeHistory;
|
||||
uint32_t mFrameTimeHistorySize = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <filament/Viewport.h>
|
||||
|
||||
#include <utils/BinaryTreeArray.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/Systrace.h>
|
||||
#include <utils/debug.h>
|
||||
|
||||
|
||||
@@ -1965,8 +1965,8 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
// - visible bloom size changes with dynamic resolution in non-homogenous mode
|
||||
// This allows us to use the 9 sample downsampling filter (instead of 13)
|
||||
// for at least 4 levels.
|
||||
uint32_t width = std::max(16u, uint32_t(std::floor(bloomWidth)));
|
||||
uint32_t height = std::max(16u, uint32_t(std::floor(bloomHeight)));
|
||||
uint32_t width = std::max(1u, uint32_t(std::floor(bloomWidth)));
|
||||
uint32_t height = std::max(1u, uint32_t(std::floor(bloomHeight)));
|
||||
width &= ~((1 << 4) - 1); // at least 4 levels
|
||||
height &= ~((1 << 4) - 1);
|
||||
bloomWidth = float(width);
|
||||
@@ -1978,8 +1978,6 @@ PostProcessManager::BloomPassOutput PostProcessManager::bloom(FrameGraph& fg,
|
||||
// we don't need to do the fireflies reduction if we have TAA (it already does it)
|
||||
bool fireflies = threshold && !taaOptions.enabled;
|
||||
|
||||
assert_invariant(bloomWidth && bloomHeight);
|
||||
|
||||
while (2 * bloomWidth < float(desc.width) || 2 * bloomHeight < float(desc.height)) {
|
||||
if (inoutBloomOptions.quality == QualityLevel::LOW ||
|
||||
inoutBloomOptions.quality == QualityLevel::MEDIUM) {
|
||||
|
||||
+11
-28
@@ -38,7 +38,6 @@
|
||||
#include <backend/PipelineState.h>
|
||||
|
||||
#include "private/backend/CircularBuffer.h"
|
||||
#include "private/backend/CommandStream.h"
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
@@ -154,7 +153,7 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
|
||||
sortCommands(builder.mArena);
|
||||
|
||||
if (engine.isAutomaticInstancingEnabled()) {
|
||||
int32_t stereoscopicEyeCount = 1;
|
||||
uint32_t stereoscopicEyeCount = 1;
|
||||
if (builder.mFlags & IS_INSTANCED_STEREOSCOPIC) {
|
||||
stereoscopicEyeCount *= engine.getConfig().stereoscopicEyeCount;
|
||||
}
|
||||
@@ -163,7 +162,8 @@ RenderPass::RenderPass(FEngine& engine, RenderPassBuilder const& builder) noexce
|
||||
}
|
||||
|
||||
// this destructor is actually heavy because it inlines ~vector<>
|
||||
RenderPass::~RenderPass() noexcept = default;
|
||||
RenderPass::~RenderPass() noexcept {
|
||||
}
|
||||
|
||||
void RenderPass::resize(Arena& arena, size_t count) noexcept {
|
||||
if (mCommandBegin) {
|
||||
@@ -221,7 +221,7 @@ void RenderPass::appendCommands(FEngine& engine,
|
||||
work(vr.first, vr.size());
|
||||
} else {
|
||||
auto* jobCommandsParallel = jobs::parallel_for(js, nullptr, vr.first, (uint32_t)vr.size(),
|
||||
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT>());
|
||||
std::cref(work), jobs::CountSplitter<JOBS_PARALLEL_FOR_COMMANDS_COUNT, 5>());
|
||||
js.runAndWait(jobCommandsParallel);
|
||||
}
|
||||
|
||||
@@ -671,6 +671,7 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
*/
|
||||
for (size_t pi = 0, c = primitives.size(); pi < c; ++pi) {
|
||||
auto const& primitive = primitives[pi];
|
||||
auto const& morphTargets = morphing.targets[pi];
|
||||
FMaterialInstance const* const mi = primitive.getMaterialInstance();
|
||||
FMaterial const* const ma = mi->getMaterial();
|
||||
|
||||
@@ -683,9 +684,8 @@ RenderPass::Command* RenderPass::generateCommandsImpl(RenderPass::CommandTypeFla
|
||||
cmd.info.indexOffset = primitive.getIndexOffset();
|
||||
cmd.info.indexCount = primitive.getIndexCount();
|
||||
cmd.info.type = primitive.getPrimitiveType();
|
||||
cmd.info.morphTargetBuffer = morphing.morphTargetBuffer ?
|
||||
morphing.morphTargetBuffer->getHwHandle() : SamplerGroupHandle{};
|
||||
cmd.info.morphingOffset = primitive.getMorphingBufferOffset();
|
||||
cmd.info.morphTargetBuffer = morphTargets.buffer->getHwHandle();
|
||||
cmd.info.morphingOffset = morphTargets.offset;
|
||||
|
||||
if constexpr (isColorPass) {
|
||||
RenderPass::setupColorCommand(cmd, renderableVariant, mi, inverseFrontFaces);
|
||||
@@ -885,31 +885,14 @@ void RenderPass::Executor::execute(FEngine& engine,
|
||||
|
||||
// Maximum space occupied in the CircularBuffer by a single `Command`. This must be
|
||||
// reevaluated when the inner loop below adds DriverApi commands or when we change the
|
||||
// CommandStream protocol. Currently, the maximum is 320 bytes.
|
||||
// The batch size is calculated by adding the size of all commands that can possibly be
|
||||
// emitted per draw call:
|
||||
constexpr size_t const maxCommandSizeInBytes =
|
||||
sizeof(CustomCommand) +
|
||||
sizeof(COMMAND_TYPE(scissor)) +
|
||||
sizeof(COMMAND_TYPE(bindUniformBuffer)) +
|
||||
sizeof(COMMAND_TYPE(bindSamplers)) +
|
||||
sizeof(COMMAND_TYPE(bindBufferRange)) +
|
||||
sizeof(COMMAND_TYPE(bindBufferRange)) +
|
||||
sizeof(COMMAND_TYPE(bindSamplers)) +
|
||||
sizeof(COMMAND_TYPE(bindSamplers)) +
|
||||
sizeof(COMMAND_TYPE(bindUniformBuffer)) +
|
||||
sizeof(COMMAND_TYPE(bindSamplers)) +
|
||||
sizeof(COMMAND_TYPE(bindSamplers)) +
|
||||
sizeof(COMMAND_TYPE(bindPipeline)) +
|
||||
sizeof(COMMAND_TYPE(setPushConstant)) +
|
||||
sizeof(COMMAND_TYPE(bindRenderPrimitive)) +
|
||||
sizeof(COMMAND_TYPE(draw2));
|
||||
|
||||
// CommandStream protocol. Currently, the maximum is 240 bytes, and we use 256 to be on
|
||||
// the safer side.
|
||||
size_t const maxCommandSizeInBytes = 256;
|
||||
|
||||
// Number of Commands that can be issued and guaranteed to fit in the current
|
||||
// CircularBuffer allocation. In practice, we'll have tons of headroom especially if
|
||||
// skinning and morphing aren't used. With a 2 MiB buffer (the default) a batch is
|
||||
// 6553 commands (i.e. draw calls).
|
||||
// 8192 commands (i.e. draw calls).
|
||||
size_t const batchCommandCount = capacity / maxCommandSizeInBytes;
|
||||
while(first != last) {
|
||||
Command const* const batchLast = std::min(first + batchCommandCount, last);
|
||||
|
||||
@@ -417,7 +417,8 @@ private:
|
||||
void instanceify(FEngine& engine, Arena& arena, int32_t eyeCount) noexcept;
|
||||
|
||||
// We choose the command count per job to minimize JobSystem overhead.
|
||||
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 128;
|
||||
// On a Pixel 4, 2048 commands is about half a millisecond of processing.
|
||||
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_COUNT = 2048;
|
||||
static constexpr size_t JOBS_PARALLEL_FOR_COMMANDS_SIZE =
|
||||
sizeof(Command) * JOBS_PARALLEL_FOR_COMMANDS_COUNT;
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ public:
|
||||
backend::VertexBufferInfoHandle getVertexBufferInfoHandle() const { return mVertexBufferInfoHandle; }
|
||||
uint32_t getIndexOffset() const noexcept { return mIndexOffset; }
|
||||
uint32_t getIndexCount() const noexcept { return mIndexCount; }
|
||||
uint32_t getMorphingBufferOffset() const noexcept { return mMorphingBufferOffset; }
|
||||
|
||||
backend::PrimitiveType getPrimitiveType() const noexcept { return mPrimitiveType; }
|
||||
AttributeBitset getEnabledAttributes() const noexcept { return mEnabledAttributes; }
|
||||
@@ -73,10 +72,6 @@ public:
|
||||
mGlobalBlendOrderEnabled = enabled;
|
||||
}
|
||||
|
||||
void setMorphingBufferOffset(uint32_t offset) noexcept {
|
||||
mMorphingBufferOffset = offset;
|
||||
}
|
||||
|
||||
private:
|
||||
// These first fields are dereferences from PrimitiveInfo, keep them together
|
||||
struct {
|
||||
@@ -85,7 +80,6 @@ private:
|
||||
backend::Handle<backend::HwVertexBufferInfo> mVertexBufferInfoHandle = {};
|
||||
uint32_t mIndexOffset = 0;
|
||||
uint32_t mIndexCount = 0;
|
||||
uint32_t mMorphingBufferOffset = 0;
|
||||
};
|
||||
|
||||
AttributeBitset mEnabledAttributes = {};
|
||||
|
||||
@@ -158,14 +158,20 @@ void RenderableManager::setMorphWeights(Instance instance, float const* weights,
|
||||
downcast(this)->setMorphWeights(instance, weights, count, offset);
|
||||
}
|
||||
|
||||
void RenderableManager::setMorphTargetBufferOffsetAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex,
|
||||
size_t offset) {
|
||||
downcast(this)->setMorphTargetBufferOffsetAt(instance, level, primitiveIndex, offset);
|
||||
void RenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
MorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) {
|
||||
downcast(this)->setMorphTargetBufferAt(instance, level, primitiveIndex,
|
||||
downcast(morphTargetBuffer), offset, count);
|
||||
}
|
||||
|
||||
MorphTargetBuffer* RenderableManager::getMorphTargetBuffer(Instance instance) const noexcept {
|
||||
return downcast(this)->getMorphTargetBuffer(instance);
|
||||
void RenderableManager::setMorphTargetBufferAt(
|
||||
Instance instance, uint8_t level, size_t primitiveIndex, size_t offset, size_t count) {
|
||||
downcast(this)->setMorphTargetBufferAt(instance, level, primitiveIndex, offset, count);
|
||||
}
|
||||
|
||||
MorphTargetBuffer* RenderableManager::getMorphTargetBufferAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex) const noexcept {
|
||||
return downcast(this)->getMorphTargetBufferAt(instance, level, primitiveIndex);
|
||||
}
|
||||
|
||||
size_t RenderableManager::getMorphTargetCount(Instance instance) const noexcept {
|
||||
|
||||
@@ -14,20 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <filament/Renderer.h>
|
||||
|
||||
#include "details/Renderer.h"
|
||||
|
||||
#include "details/Engine.h"
|
||||
#include "details/View.h"
|
||||
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
using namespace math;
|
||||
@@ -45,10 +36,6 @@ void Renderer::setPresentationTime(int64_t monotonic_clock_ns) {
|
||||
downcast(this)->setPresentationTime(monotonic_clock_ns);
|
||||
}
|
||||
|
||||
void Renderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
|
||||
downcast(this)->skipFrame(vsyncSteadyClockTimeNano);
|
||||
}
|
||||
|
||||
bool Renderer::beginFrame(SwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano) {
|
||||
return downcast(this)->beginFrame(downcast(swapChain), vsyncSteadyClockTimeNano);
|
||||
}
|
||||
@@ -106,12 +93,4 @@ void Renderer::setVsyncTime(uint64_t steadyClockTimeNano) noexcept {
|
||||
downcast(this)->setVsyncTime(steadyClockTimeNano);
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<Renderer::FrameInfo> Renderer::getFrameInfoHistory(size_t historySize) const noexcept {
|
||||
return downcast(this)->getFrameInfoHistory(historySize);
|
||||
}
|
||||
|
||||
size_t Renderer::getMaxFrameHistorySize() const noexcept {
|
||||
return downcast(this)->getMaxFrameHistorySize();
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -75,7 +75,7 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
|
||||
TargetBufferFlags const clearColorFlags = config.clearFlags & TargetBufferFlags::COLOR;
|
||||
TargetBufferFlags clearDepthFlags = config.clearFlags & TargetBufferFlags::DEPTH;
|
||||
TargetBufferFlags clearStencilFlags = config.clearFlags & TargetBufferFlags::STENCIL;
|
||||
uint8_t layerCount = 1;
|
||||
uint8_t layerCount = 0;
|
||||
|
||||
data.shadows = blackboard.get<FrameGraphTexture>("shadows");
|
||||
data.ssao = blackboard.get<FrameGraphTexture>("ssao");
|
||||
@@ -172,7 +172,7 @@ FrameGraphId<FrameGraphTexture> RendererUtils::colorPass(
|
||||
|
||||
data.color = builder.write(data.color, FrameGraphTexture::Usage::COLOR_ATTACHMENT);
|
||||
data.depth = builder.write(data.depth, FrameGraphTexture::Usage::DEPTH_ATTACHMENT);
|
||||
if (view.hasStereo() && engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) {
|
||||
if (engine.getConfig().stereoscopicType == StereoscopicType::MULTIVIEW) {
|
||||
layerCount = engine.getConfig().stereoscopicEyeCount;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,18 +27,15 @@
|
||||
|
||||
#include "private/backend/DriverApi.h"
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/bitset.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/ostream.h>
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -92,15 +89,8 @@ void ResourceAllocator::AssociativeContainer<K, V, H>::emplace(ARGS&& ... args)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
ResourceAllocatorInterface::~ResourceAllocatorInterface() = default;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
ResourceAllocatorDisposerInterface::~ResourceAllocatorDisposerInterface() = default;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
size_t ResourceAllocator::TextureKey::getSize() const noexcept {
|
||||
size_t const pixelCount = width * height * depth;
|
||||
size_t size = pixelCount * FTexture::getFormatSize(format);
|
||||
@@ -120,22 +110,16 @@ size_t ResourceAllocator::TextureKey::getSize() const noexcept {
|
||||
|
||||
ResourceAllocator::ResourceAllocator(Engine::Config const& config, DriverApi& driverApi) noexcept
|
||||
: mCacheMaxAge(config.resourceAllocatorCacheMaxAge),
|
||||
mBackend(driverApi),
|
||||
mDisposer(std::make_shared<ResourceAllocatorDisposer>(driverApi)) {
|
||||
}
|
||||
|
||||
ResourceAllocator::ResourceAllocator(std::shared_ptr<ResourceAllocatorDisposer> disposer,
|
||||
Engine::Config const& config, DriverApi& driverApi) noexcept
|
||||
: mCacheMaxAge(config.resourceAllocatorCacheMaxAge),
|
||||
mBackend(driverApi),
|
||||
mDisposer(std::move(disposer)) {
|
||||
mBackend(driverApi) {
|
||||
}
|
||||
|
||||
ResourceAllocator::~ResourceAllocator() noexcept {
|
||||
assert_invariant(mTextureCache.empty());
|
||||
assert_invariant(!mTextureCache.size());
|
||||
assert_invariant(!mInUseTextures.size());
|
||||
}
|
||||
|
||||
void ResourceAllocator::terminate() noexcept {
|
||||
assert_invariant(!mInUseTextures.size());
|
||||
auto& textureCache = mTextureCache;
|
||||
for (auto it = textureCache.begin(); it != textureCache.end();) {
|
||||
mBackend.destroyTexture(it->second.handle);
|
||||
@@ -190,7 +174,7 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name,
|
||||
swizzle[0], swizzle[1], swizzle[2], swizzle[3]);
|
||||
}
|
||||
}
|
||||
mDisposer->checkout(handle, key);
|
||||
mInUseTextures.emplace(handle, key);
|
||||
} else {
|
||||
if (swizzle == defaultSwizzle) {
|
||||
handle = mBackend.createTexture(
|
||||
@@ -206,99 +190,58 @@ backend::TextureHandle ResourceAllocator::createTexture(const char* name,
|
||||
|
||||
void ResourceAllocator::destroyTexture(TextureHandle h) noexcept {
|
||||
if constexpr (mEnabled) {
|
||||
auto const key = mDisposer->checkin(h);
|
||||
if (UTILS_LIKELY(key.has_value())) {
|
||||
uint32_t const size = key.value().getSize();
|
||||
mTextureCache.emplace(key.value(), TextureCachePayload{ h, mAge, size });
|
||||
mCacheSize += size;
|
||||
mCacheSizeHiWaterMark = std::max(mCacheSizeHiWaterMark, mCacheSize);
|
||||
}
|
||||
// find the texture in the in-use list (it must be there!)
|
||||
auto it = mInUseTextures.find(h);
|
||||
assert_invariant(it != mInUseTextures.end());
|
||||
|
||||
// move it to the cache
|
||||
const TextureKey key = it->second;
|
||||
uint32_t const size = key.getSize();
|
||||
|
||||
mTextureCache.emplace(key, TextureCachePayload{ h, mAge, size });
|
||||
mCacheSize += size;
|
||||
|
||||
// remove it from the in-use list
|
||||
mInUseTextures.erase(it);
|
||||
} else {
|
||||
mBackend.destroyTexture(h);
|
||||
}
|
||||
}
|
||||
|
||||
ResourceAllocatorDisposerInterface& ResourceAllocator::getDisposer() noexcept {
|
||||
return *mDisposer;
|
||||
}
|
||||
void ResourceAllocator::gc() noexcept {
|
||||
// this is called regularly -- usually once per frame of each Renderer
|
||||
|
||||
void ResourceAllocator::gc(bool skippedFrame) noexcept {
|
||||
// this is called regularly -- usually once per frame
|
||||
|
||||
// increase our age at each (non-skipped) frame
|
||||
const size_t age = mAge;
|
||||
if (!skippedFrame) {
|
||||
mAge++;
|
||||
}
|
||||
// increase our age
|
||||
const size_t age = mAge++;
|
||||
|
||||
// Purging strategy:
|
||||
// - remove all entries older than MAX_AGE_SKIPPED_FRAME when skipping a frame
|
||||
// - remove entries older than mCacheMaxAgeSoft
|
||||
// - remove only MAX_EVICTION_COUNT entry per gc(),
|
||||
// - look for the number of unique resource ages present in the cache (this basically gives
|
||||
// us how many buckets of resources we have corresponding to previous frames.
|
||||
// - remove all resources that have an age older than the MAX_UNIQUE_AGE_COUNT'th bucket
|
||||
// - remove entries that are older than a certain age
|
||||
// - remove only one entry per gc(),
|
||||
|
||||
auto& textureCache = mTextureCache;
|
||||
|
||||
// when skipping a frame, the maximum age to keep in the cache
|
||||
constexpr size_t MAX_AGE_SKIPPED_FRAME = 1;
|
||||
|
||||
// maximum entry count to evict per GC, under the mCacheMaxAgeSoft limit
|
||||
constexpr size_t MAX_EVICTION_COUNT = 1;
|
||||
|
||||
// maximum number of unique ages in the cache
|
||||
constexpr size_t MAX_UNIQUE_AGE_COUNT = 3;
|
||||
|
||||
utils::bitset32 ages;
|
||||
uint32_t evictedCount = 0;
|
||||
for (auto it = textureCache.begin(); it != textureCache.end();) {
|
||||
size_t const ageDiff = age - it->second.age;
|
||||
if ((ageDiff >= MAX_AGE_SKIPPED_FRAME && skippedFrame) ||
|
||||
(ageDiff >= mCacheMaxAge && evictedCount < MAX_EVICTION_COUNT)) {
|
||||
evictedCount++;
|
||||
const size_t ageDiff = age - it->second.age;
|
||||
if (ageDiff >= mCacheMaxAge) {
|
||||
purge(it);
|
||||
// only purge a single entry per gc
|
||||
break;
|
||||
} else {
|
||||
// build the set of ages present in the cache after eviction
|
||||
ages.set(std::min(size_t(31), ageDiff));
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have MAX_UNIQUE_AGE_COUNT ages or more, we evict all the resources that
|
||||
// are older than the MAX_UNIQUE_AGE_COUNT'th age.
|
||||
if (!skippedFrame && ages.count() >= MAX_UNIQUE_AGE_COUNT) {
|
||||
uint32_t bits = ages.getValue();
|
||||
// remove from the set the ages we keep
|
||||
for (size_t i = 0; i < MAX_UNIQUE_AGE_COUNT - 1; i++) {
|
||||
bits &= ~(1 << utils::ctz(bits));
|
||||
}
|
||||
size_t const maxAge = utils::ctz(bits);
|
||||
for (auto it = textureCache.begin(); it != textureCache.end();) {
|
||||
const size_t ageDiff = age - it->second.age;
|
||||
if (ageDiff >= maxAge) {
|
||||
purge(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void ResourceAllocator::dump(bool brief) const noexcept {
|
||||
constexpr float MiB = 1.0f / float(1u << 20u);
|
||||
slog.d << "# entries=" << mTextureCache.size()
|
||||
<< ", sz=" << (float)mCacheSize * MiB << " MiB"
|
||||
<< ", max=" << (float)mCacheSizeHiWaterMark * MiB << " MiB"
|
||||
<< io::endl;
|
||||
slog.d << "# entries=" << mTextureCache.size() << ", sz=" << mCacheSize / float(1u << 20u)
|
||||
<< " MiB" << io::endl;
|
||||
if (!brief) {
|
||||
for (auto const& it : mTextureCache) {
|
||||
auto w = it.first.width;
|
||||
auto h = it.first.height;
|
||||
auto f = FTexture::getFormatSize(it.first.format);
|
||||
slog.d << it.first.name << ": w=" << w << ", h=" << h << ", f=" << f << ", sz="
|
||||
<< (float)it.second.size * MiB << io::endl;
|
||||
<< it.second.size / float(1u << 20u) << io::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,46 +254,4 @@ void ResourceAllocator::purge(
|
||||
mTextureCache.erase(pos);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
ResourceAllocatorDisposer::ResourceAllocatorDisposer(DriverApi& driverApi) noexcept
|
||||
: mBackend(driverApi) {
|
||||
}
|
||||
|
||||
ResourceAllocatorDisposer::~ResourceAllocatorDisposer() noexcept {
|
||||
assert_invariant(mInUseTextures.empty());
|
||||
}
|
||||
|
||||
void ResourceAllocatorDisposer::terminate() noexcept {
|
||||
assert_invariant(mInUseTextures.empty());
|
||||
}
|
||||
|
||||
void ResourceAllocatorDisposer::destroy(backend::TextureHandle handle) noexcept {
|
||||
if (handle) {
|
||||
auto r = checkin(handle);
|
||||
if (r.has_value()) {
|
||||
mBackend.destroyTexture(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceAllocatorDisposer::checkout(backend::TextureHandle handle,
|
||||
ResourceAllocator::TextureKey key) {
|
||||
mInUseTextures.emplace(handle, key);
|
||||
}
|
||||
|
||||
std::optional<ResourceAllocator::TextureKey> ResourceAllocatorDisposer::checkin(
|
||||
backend::TextureHandle handle) {
|
||||
// find the texture in the in-use list (it must be there!)
|
||||
auto it = mInUseTextures.find(handle);
|
||||
assert_invariant(it != mInUseTextures.end());
|
||||
if (it == mInUseTextures.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
TextureKey const key = it->second;
|
||||
// remove it from the in-use list
|
||||
mInUseTextures.erase(it);
|
||||
return key;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -28,29 +28,16 @@
|
||||
#include <utils/Hash.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include <cstddef>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace filament {
|
||||
|
||||
class ResourceAllocatorDisposer;
|
||||
|
||||
// The only reason we use an interface here is for unit-tests, so we can mock this allocator.
|
||||
// This is not too time-critical, so that's okay.
|
||||
|
||||
class ResourceAllocatorDisposerInterface {
|
||||
public:
|
||||
virtual void destroy(backend::TextureHandle handle) noexcept = 0;
|
||||
protected:
|
||||
virtual ~ResourceAllocatorDisposerInterface();
|
||||
};
|
||||
|
||||
class ResourceAllocatorInterface {
|
||||
public:
|
||||
virtual backend::RenderTargetHandle createRenderTarget(const char* name,
|
||||
@@ -73,20 +60,15 @@ public:
|
||||
|
||||
virtual void destroyTexture(backend::TextureHandle h) noexcept = 0;
|
||||
|
||||
virtual ResourceAllocatorDisposerInterface& getDisposer() noexcept = 0;
|
||||
|
||||
protected:
|
||||
virtual ~ResourceAllocatorInterface();
|
||||
};
|
||||
|
||||
|
||||
class ResourceAllocator final : public ResourceAllocatorInterface {
|
||||
public:
|
||||
explicit ResourceAllocator(std::shared_ptr<ResourceAllocatorDisposer> disposer,
|
||||
Engine::Config const& config, backend::DriverApi& driverApi) noexcept;
|
||||
|
||||
explicit ResourceAllocator(
|
||||
Engine::Config const& config, backend::DriverApi& driverApi) noexcept;
|
||||
|
||||
~ResourceAllocator() noexcept override;
|
||||
|
||||
void terminate() noexcept;
|
||||
@@ -111,9 +93,7 @@ public:
|
||||
|
||||
void destroyTexture(backend::TextureHandle h) noexcept override;
|
||||
|
||||
ResourceAllocatorDisposerInterface& getDisposer() noexcept override;
|
||||
|
||||
void gc(bool skippedFrame = false) noexcept;
|
||||
void gc() noexcept;
|
||||
|
||||
private:
|
||||
size_t const mCacheMaxAge;
|
||||
@@ -201,7 +181,6 @@ private:
|
||||
using value_type = typename Container::value_type::second_type;
|
||||
|
||||
size_t size() const { return mContainer.size(); }
|
||||
bool empty() const { return size() == 0; }
|
||||
iterator begin() { return mContainer.begin(); }
|
||||
const_iterator begin() const { return mContainer.begin(); }
|
||||
iterator end() { return mContainer.end(); }
|
||||
@@ -214,36 +193,16 @@ private:
|
||||
};
|
||||
|
||||
using CacheContainer = AssociativeContainer<TextureKey, TextureCachePayload>;
|
||||
using InUseContainer = AssociativeContainer<backend::TextureHandle, TextureKey>;
|
||||
|
||||
void purge(ResourceAllocator::CacheContainer::iterator const& pos);
|
||||
|
||||
backend::DriverApi& mBackend;
|
||||
std::shared_ptr<ResourceAllocatorDisposer> mDisposer;
|
||||
CacheContainer mTextureCache;
|
||||
InUseContainer mInUseTextures;
|
||||
size_t mAge = 0;
|
||||
uint32_t mCacheSize = 0;
|
||||
uint32_t mCacheSizeHiWaterMark = 0;
|
||||
static constexpr bool mEnabled = true;
|
||||
|
||||
friend class ResourceAllocatorDisposer;
|
||||
};
|
||||
|
||||
class ResourceAllocatorDisposer final : public ResourceAllocatorDisposerInterface {
|
||||
using TextureKey = ResourceAllocator::TextureKey;
|
||||
public:
|
||||
explicit ResourceAllocatorDisposer(backend::DriverApi& driverApi) noexcept;
|
||||
~ResourceAllocatorDisposer() noexcept override;
|
||||
void terminate() noexcept;
|
||||
void destroy(backend::TextureHandle handle) noexcept override;
|
||||
|
||||
private:
|
||||
friend class ResourceAllocator;
|
||||
void checkout(backend::TextureHandle handle, TextureKey key);
|
||||
std::optional<TextureKey> checkin(backend::TextureHandle handle);
|
||||
|
||||
using InUseContainer = ResourceAllocator::AssociativeContainer<backend::TextureHandle, TextureKey>;
|
||||
backend::DriverApi& mBackend;
|
||||
InUseContainer mInUseTextures;
|
||||
};
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
|
||||
#include "details/View.h"
|
||||
#include "filament/View.h"
|
||||
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -314,8 +312,4 @@ utils::Entity View::getFogEntity() const noexcept {
|
||||
return downcast(this)->getFogEntity();
|
||||
}
|
||||
|
||||
void View::clearFrameHistory(Engine& engine) noexcept {
|
||||
downcast(this)->clearFrameHistory(downcast(engine));
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
@@ -20,14 +20,13 @@
|
||||
|
||||
#include "details/Engine.h"
|
||||
|
||||
#include <filament/LightManager.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <math/fast.h>
|
||||
#include <math/scalar.h>
|
||||
|
||||
#include <utils/debug.h>
|
||||
#include <filament/LightManager.h>
|
||||
|
||||
|
||||
using namespace filament::math;
|
||||
using namespace utils;
|
||||
|
||||
|
||||
@@ -281,13 +281,19 @@ RenderableManager::Builder& RenderableManager::Builder::morphing(
|
||||
return *this;
|
||||
}
|
||||
|
||||
RenderableManager::Builder& RenderableManager::Builder::morphing(uint8_t level,
|
||||
size_t primitiveIndex, size_t offset) noexcept {
|
||||
// the last parameter "count" is unused, because it must be equal to the primitive's vertex count
|
||||
std::vector<BuilderDetails::Entry>& entries = mImpl->mEntries;
|
||||
RenderableManager::Builder& RenderableManager::Builder::morphing(
|
||||
uint8_t level, size_t primitiveIndex, size_t offset, size_t count) noexcept {
|
||||
return morphing(level, primitiveIndex, mImpl->mMorphTargetBuffer, offset, count);
|
||||
}
|
||||
|
||||
RenderableManager::Builder& RenderableManager::Builder::morphing(uint8_t, size_t primitiveIndex,
|
||||
MorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) noexcept {
|
||||
std::vector<Entry>& entries = mImpl->mEntries;
|
||||
if (primitiveIndex < entries.size()) {
|
||||
auto& morphing = entries[primitiveIndex].morphing;
|
||||
morphing.offset = uint32_t(offset);
|
||||
morphing.buffer = morphTargetBuffer;
|
||||
morphing.offset = offset;
|
||||
morphing.count = count;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -443,10 +449,10 @@ RenderableManager::Builder::Result RenderableManager::Builder::build(Engine& eng
|
||||
<< ") instances when supplying transforms via an InstanceBuffer.";
|
||||
|
||||
if (mImpl->mGeometryType == GeometryType::STATIC) {
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mSkinningBoneCount == 0)
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mSkinningBoneCount > 0)
|
||||
<< "Skinning can't be used with STATIC geometry";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mMorphTargetCount == 0)
|
||||
FILAMENT_CHECK_PRECONDITION(mImpl->mMorphTargetCount > 0)
|
||||
<< "Morphing can't be used with STATIC geometry";
|
||||
}
|
||||
|
||||
@@ -662,6 +668,13 @@ void FRenderableManager::create(
|
||||
if (morphTargetBuffer == nullptr) {
|
||||
morphTargetBuffer = mEngine.getDummyMorphTargetBuffer();
|
||||
}
|
||||
MorphTargets* const morphTargets = new MorphTargets[entryCount];
|
||||
std::generate_n(morphTargets, entryCount,
|
||||
[morphTargetBuffer]() -> MorphTargets {
|
||||
return { morphTargetBuffer, 0, 0 };
|
||||
});
|
||||
|
||||
mManager[ci].morphTargets = { morphTargets, size_type(entryCount) };
|
||||
|
||||
// Always create skinning and morphing resources if one of them is enabled because
|
||||
// the shader always handles both. See Variant::SKINNING_OR_MORPHING.
|
||||
@@ -687,13 +700,13 @@ void FRenderableManager::create(
|
||||
backend::BufferUsage::DYNAMIC),
|
||||
.count = targetCount };
|
||||
|
||||
Slice<FRenderPrimitive>& primitives = mManager[ci].primitives;
|
||||
mManager[ci].morphTargetBuffer = morphTargetBuffer;
|
||||
if (builder->mMorphTargetBuffer) {
|
||||
for (size_t i = 0; i < entryCount; ++i) {
|
||||
const auto& morphing = builder->mEntries[i].morphing;
|
||||
primitives[i].setMorphingBufferOffset(morphing.offset);
|
||||
for (size_t i = 0; i < entryCount; ++i) {
|
||||
const auto& morphing = builder->mEntries[i].morphing;
|
||||
if (!morphing.buffer) {
|
||||
continue;
|
||||
}
|
||||
morphTargets[i] = { downcast(morphing.buffer), (uint32_t)morphing.offset,
|
||||
(uint32_t)morphing.count };
|
||||
}
|
||||
|
||||
// When targetCount equal 0, boneCount>0 in this case, do an initialization for the
|
||||
@@ -749,6 +762,7 @@ void FRenderableManager::destroyComponent(Instance ci) noexcept {
|
||||
|
||||
// See create(RenderableManager::Builder&, Entity)
|
||||
destroyComponentPrimitives(mHwRenderPrimitiveFactory, driver, manager[ci].primitives);
|
||||
destroyComponentMorphTargets(engine, manager[ci].morphTargets);
|
||||
|
||||
// destroy the bones structures if any
|
||||
Bones const& bones = manager[ci].bones;
|
||||
@@ -781,6 +795,11 @@ void FRenderableManager::destroyComponentPrimitives(
|
||||
delete[] primitives.data();
|
||||
}
|
||||
|
||||
void FRenderableManager::destroyComponentMorphTargets(FEngine&,
|
||||
utils::Slice<MorphTargets>& morphTargets) noexcept {
|
||||
delete[] morphTargets.data();
|
||||
}
|
||||
|
||||
void FRenderableManager::setMaterialInstanceAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex, FMaterialInstance const* mi) {
|
||||
if (instance) {
|
||||
@@ -949,21 +968,42 @@ void FRenderableManager::setMorphWeights(Instance instance, float const* weights
|
||||
}
|
||||
}
|
||||
|
||||
void FRenderableManager::setMorphTargetBufferOffsetAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex,
|
||||
size_t offset) {
|
||||
void FRenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex, FMorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count) {
|
||||
if (instance) {
|
||||
assert_invariant(mManager[instance].morphTargetBuffer);
|
||||
Slice<FRenderPrimitive>& primitives = mManager[instance].primitives;
|
||||
if (primitiveIndex < primitives.size()) {
|
||||
primitives[primitiveIndex].setMorphingBufferOffset(offset);
|
||||
assert_invariant(morphTargetBuffer);
|
||||
|
||||
MorphWeights const& morphWeights = mManager[instance].morphWeights;
|
||||
FILAMENT_CHECK_PRECONDITION(morphWeights.count == count)
|
||||
<< "Only " << morphWeights.count << " morph targets can be set (count=" << count
|
||||
<< ")";
|
||||
|
||||
Slice<MorphTargets>& morphTargets = getMorphTargets(instance, level);
|
||||
if (primitiveIndex < morphTargets.size()) {
|
||||
morphTargets[primitiveIndex] = { morphTargetBuffer, (uint32_t)offset,
|
||||
(uint32_t)count };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MorphTargetBuffer* FRenderableManager::getMorphTargetBuffer(Instance instance) const noexcept {
|
||||
void FRenderableManager::setMorphTargetBufferAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex, size_t offset, size_t count) {
|
||||
if (instance) {
|
||||
return mManager[instance].morphTargetBuffer;
|
||||
Slice<MorphTargets>& morphTargets = getMorphTargets(instance, level);
|
||||
if (primitiveIndex < morphTargets.size()) {
|
||||
setMorphTargetBufferAt(instance, level,
|
||||
primitiveIndex, morphTargets[primitiveIndex].buffer, offset, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MorphTargetBuffer* FRenderableManager::getMorphTargetBufferAt(Instance instance, uint8_t level,
|
||||
size_t primitiveIndex) const noexcept {
|
||||
if (instance) {
|
||||
const Slice<MorphTargets>& morphTargets = getMorphTargets(instance, level);
|
||||
if (primitiveIndex < morphTargets.size()) {
|
||||
return morphTargets[primitiveIndex].buffer;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,12 @@ public:
|
||||
|
||||
static_assert(sizeof(Visibility) == sizeof(uint16_t), "Visibility should be 16 bits");
|
||||
|
||||
struct MorphTargets {
|
||||
FMorphTargetBuffer* buffer = nullptr;
|
||||
uint32_t offset = 0;
|
||||
uint32_t count = 0;
|
||||
};
|
||||
|
||||
explicit FRenderableManager(FEngine& engine) noexcept;
|
||||
~FRenderableManager();
|
||||
|
||||
@@ -149,9 +155,11 @@ public:
|
||||
|
||||
inline void setMorphing(Instance instance, bool enable);
|
||||
void setMorphWeights(Instance instance, float const* weights, size_t count, size_t offset);
|
||||
void setMorphTargetBufferOffsetAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
size_t offset);
|
||||
MorphTargetBuffer* getMorphTargetBuffer(Instance instance) const noexcept;
|
||||
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
FMorphTargetBuffer* morphTargetBuffer, size_t offset, size_t count);
|
||||
void setMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex,
|
||||
size_t offset, size_t count);
|
||||
MorphTargetBuffer* getMorphTargetBufferAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept;
|
||||
size_t getMorphTargetCount(Instance instance) const noexcept;
|
||||
|
||||
void setLightChannel(Instance instance, unsigned int channel, bool enable) noexcept;
|
||||
@@ -181,7 +189,7 @@ public:
|
||||
struct MorphingBindingInfo {
|
||||
backend::Handle<backend::HwBufferObject> handle;
|
||||
uint32_t count;
|
||||
FMorphTargetBuffer const* morphTargetBuffer;
|
||||
MorphTargets const* targets; // Pointer to Slice<MorphTargets> at a renderable.
|
||||
};
|
||||
inline MorphingBindingInfo getMorphingBufferInfo(Instance instance) const noexcept;
|
||||
|
||||
@@ -210,12 +218,16 @@ public:
|
||||
AttributeBitset getEnabledAttributesAt(Instance instance, uint8_t level, size_t primitiveIndex) const noexcept;
|
||||
inline utils::Slice<FRenderPrimitive> const& getRenderPrimitives(Instance instance, uint8_t level) const noexcept;
|
||||
inline utils::Slice<FRenderPrimitive>& getRenderPrimitives(Instance instance, uint8_t level) noexcept;
|
||||
inline utils::Slice<MorphTargets> const& getMorphTargets(Instance instance, uint8_t level) const noexcept;
|
||||
inline utils::Slice<MorphTargets>& getMorphTargets(Instance instance, uint8_t level) noexcept;
|
||||
|
||||
private:
|
||||
void destroyComponent(Instance ci) noexcept;
|
||||
static void destroyComponentPrimitives(
|
||||
HwRenderPrimitiveFactory& factory, backend::DriverApi& driver,
|
||||
utils::Slice<FRenderPrimitive>& primitives) noexcept;
|
||||
static void destroyComponentMorphTargets(FEngine& engine,
|
||||
utils::Slice<MorphTargets>& morphTargets) noexcept;
|
||||
|
||||
struct Bones {
|
||||
backend::Handle<backend::HwBufferObject> handle;
|
||||
@@ -242,7 +254,7 @@ private:
|
||||
VISIBILITY, // user data
|
||||
PRIMITIVES, // user data
|
||||
BONES, // filament data, UBO storing a pointer to the bones information
|
||||
MORPHTARGET_BUFFER // morphtarget buffer for the component
|
||||
MORPH_TARGETS
|
||||
};
|
||||
|
||||
using Base = utils::SingleInstanceComponentManager<
|
||||
@@ -254,7 +266,7 @@ private:
|
||||
Visibility, // VISIBILITY
|
||||
utils::Slice<FRenderPrimitive>, // PRIMITIVES
|
||||
Bones, // BONES
|
||||
FMorphTargetBuffer* // MORPHTARGET_BUFFER
|
||||
utils::Slice<MorphTargets> // MORPH_TARGETS
|
||||
>;
|
||||
|
||||
struct Sim : public Base {
|
||||
@@ -277,7 +289,7 @@ private:
|
||||
Field<VISIBILITY> visibility;
|
||||
Field<PRIMITIVES> primitives;
|
||||
Field<BONES> bones;
|
||||
Field<MORPHTARGET_BUFFER> morphTargetBuffer;
|
||||
Field<MORPH_TARGETS> morphTargets;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -449,8 +461,8 @@ inline uint32_t FRenderableManager::getBoneCount(Instance instance) const noexce
|
||||
FRenderableManager::MorphingBindingInfo
|
||||
FRenderableManager::getMorphingBufferInfo(Instance instance) const noexcept {
|
||||
MorphWeights const& morphWeights = mManager[instance].morphWeights;
|
||||
FMorphTargetBuffer const* const buffer = mManager[instance].morphTargetBuffer;
|
||||
return { morphWeights.handle, morphWeights.count, buffer };
|
||||
utils::Slice<MorphTargets> const& morphTargets = getMorphTargets(instance, 0);
|
||||
return { morphWeights.handle, morphWeights.count, morphTargets.data() };
|
||||
}
|
||||
|
||||
FRenderableManager::InstancesInfo
|
||||
@@ -468,6 +480,16 @@ utils::Slice<FRenderPrimitive>& FRenderableManager::getRenderPrimitives(
|
||||
return mManager[instance].primitives;
|
||||
}
|
||||
|
||||
utils::Slice<FRenderableManager::MorphTargets> const& FRenderableManager::getMorphTargets(
|
||||
Instance instance, UTILS_UNUSED uint8_t level) const noexcept {
|
||||
return mManager[instance].morphTargets;
|
||||
}
|
||||
|
||||
utils::Slice<FRenderableManager::MorphTargets>& FRenderableManager::getMorphTargets(
|
||||
Instance instance, UTILS_UNUSED uint8_t level) noexcept {
|
||||
return mManager[instance].morphTargets;
|
||||
}
|
||||
|
||||
} // namespace filament
|
||||
|
||||
#endif // TNT_FILAMENT_COMPONENTS_RENDERABLEMANAGER_H
|
||||
|
||||
@@ -163,7 +163,8 @@ FEngine* FEngine::getEngine(void* token) {
|
||||
FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(instance->mMainThreadId))
|
||||
<< "Engine::createAsync() and Engine::getEngine() must be called on the same thread.";
|
||||
|
||||
if (!instance->mInitialized) {
|
||||
// we use mResourceAllocator as a proxy for "am I already initialized"
|
||||
if (!instance->mResourceAllocator) {
|
||||
if (UTILS_UNLIKELY(!instance->mDriver)) {
|
||||
// something went horribly wrong during driver initialization
|
||||
instance->mDriverThread.join();
|
||||
@@ -261,7 +262,7 @@ void FEngine::init() {
|
||||
slog.i << "FEngine feature level: " << int(mActiveFeatureLevel) << io::endl;
|
||||
|
||||
|
||||
mResourceAllocatorDisposer = std::make_shared<ResourceAllocatorDisposer>(driverApi);
|
||||
mResourceAllocator = new ResourceAllocator(mConfig, driverApi);
|
||||
|
||||
mFullScreenTriangleVb = downcast(VertexBuffer::Builder()
|
||||
.vertexCount(3)
|
||||
@@ -424,13 +425,11 @@ void FEngine::init() {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
FEngine::~FEngine() noexcept {
|
||||
SYSTRACE_CALL();
|
||||
assert_invariant(!mResourceAllocatorDisposer);
|
||||
delete mResourceAllocator;
|
||||
delete mDriver;
|
||||
if (mOwnPlatform) {
|
||||
PlatformFactory::destroy(&mPlatform);
|
||||
@@ -441,7 +440,7 @@ void FEngine::shutdown() {
|
||||
SYSTRACE_CALL();
|
||||
|
||||
// by construction this should never be nullptr
|
||||
assert_invariant(mResourceAllocatorDisposer);
|
||||
assert_invariant(mResourceAllocator);
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(ThreadUtils::isThisThread(mMainThreadId))
|
||||
<< "Engine::shutdown() called from the wrong thread!";
|
||||
@@ -461,8 +460,7 @@ void FEngine::shutdown() {
|
||||
*/
|
||||
|
||||
mPostProcessManager.terminate(driver); // free-up post-process manager resources
|
||||
mResourceAllocatorDisposer->terminate();
|
||||
mResourceAllocatorDisposer.reset();
|
||||
mResourceAllocator->terminate();
|
||||
mDFG.terminate(*this); // free-up the DFG
|
||||
mRenderableManager.terminate(); // free-up all renderables
|
||||
mLightManager.terminate(); // free-up all lights
|
||||
|
||||
@@ -86,7 +86,6 @@ namespace filament {
|
||||
|
||||
class Renderer;
|
||||
class MaterialParser;
|
||||
class ResourceAllocatorDisposer;
|
||||
|
||||
namespace backend {
|
||||
class Driver;
|
||||
@@ -255,13 +254,9 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
ResourceAllocatorDisposer& getResourceAllocatorDisposer() noexcept {
|
||||
assert_invariant(mResourceAllocatorDisposer);
|
||||
return *mResourceAllocatorDisposer;
|
||||
}
|
||||
|
||||
std::shared_ptr<ResourceAllocatorDisposer> const& getSharedResourceAllocatorDisposer() noexcept {
|
||||
return mResourceAllocatorDisposer;
|
||||
ResourceAllocator& getResourceAllocator() noexcept {
|
||||
assert_invariant(mResourceAllocator);
|
||||
return *mResourceAllocator;
|
||||
}
|
||||
|
||||
void* streamAlloc(size_t size, size_t alignment) noexcept;
|
||||
@@ -495,7 +490,7 @@ private:
|
||||
FTransformManager mTransformManager;
|
||||
FLightManager mLightManager;
|
||||
FCameraManager mCameraManager;
|
||||
std::shared_ptr<ResourceAllocatorDisposer> mResourceAllocatorDisposer;
|
||||
ResourceAllocator* mResourceAllocator = nullptr;
|
||||
HwVertexBufferInfoFactory mHwVertexBufferInfoFactory;
|
||||
|
||||
ResourceList<FBufferObject> mBufferObjects{ "BufferObject" };
|
||||
@@ -567,8 +562,6 @@ private:
|
||||
|
||||
std::thread::id mMainThreadId{};
|
||||
|
||||
bool mInitialized = false;
|
||||
|
||||
// Creation parameters
|
||||
Config mConfig;
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@
|
||||
|
||||
#include <utils/Allocator.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
namespace filament {
|
||||
|
||||
@@ -60,21 +59,16 @@ public:
|
||||
inline size_t getVertexCount() const noexcept { return mVertexCount; }
|
||||
inline size_t getCount() const noexcept { return mCount; }
|
||||
|
||||
backend::TextureHandle getPositionsHandle() const noexcept {
|
||||
return mPbHandle;
|
||||
}
|
||||
|
||||
backend::TextureHandle getTangentsHandle() const noexcept {
|
||||
return mTbHandle;
|
||||
}
|
||||
|
||||
inline backend::Handle<backend::HwSamplerGroup> getHwHandle() const noexcept { return mSbHandle; }
|
||||
|
||||
private:
|
||||
friend class FView;
|
||||
friend class RenderPass;
|
||||
|
||||
void updateDataAt(backend::DriverApi& driver, backend::Handle <backend::HwTexture> handle,
|
||||
backend::PixelDataFormat format, backend::PixelDataType type, const char* out,
|
||||
size_t elementSize, size_t targetIndex, size_t count, size_t offset);
|
||||
|
||||
inline backend::Handle<backend::HwSamplerGroup> getHwHandle() const noexcept { return mSbHandle; }
|
||||
|
||||
backend::Handle<backend::HwSamplerGroup> mSbHandle;
|
||||
backend::Handle<backend::HwTexture> mPbHandle;
|
||||
backend::Handle<backend::HwTexture> mTbHandle;
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -89,11 +88,7 @@ FRenderer::FRenderer(FEngine& engine) :
|
||||
mHdrQualityMedium(TextureFormat::R11F_G11F_B10F),
|
||||
mHdrQualityHigh(TextureFormat::RGB16F),
|
||||
mIsRGB8Supported(false),
|
||||
mUserEpoch(engine.getEngineEpoch()),
|
||||
mResourceAllocator(std::make_unique<ResourceAllocator>(
|
||||
engine.getSharedResourceAllocatorDisposer(),
|
||||
engine.getConfig(),
|
||||
engine.getDriverApi()))
|
||||
mUserEpoch(engine.getEngineEpoch())
|
||||
{
|
||||
FDebugRegistry& debugRegistry = engine.getDebugRegistry();
|
||||
debugRegistry.registerProperty("d.renderer.doFrameCapture",
|
||||
@@ -181,7 +176,6 @@ void FRenderer::terminate(FEngine& engine) {
|
||||
}
|
||||
mFrameInfoManager.terminate(driver);
|
||||
mFrameSkipper.terminate(driver);
|
||||
mResourceAllocator->terminate();
|
||||
}
|
||||
|
||||
void FRenderer::resetUserTime() {
|
||||
@@ -242,39 +236,6 @@ void FRenderer::setVsyncTime(uint64_t steadyClockTimeNano) noexcept {
|
||||
mVsyncSteadyClockTimeNano = steadyClockTimeNano;
|
||||
}
|
||||
|
||||
void FRenderer::skipFrame(uint64_t vsyncSteadyClockTimeNano) {
|
||||
SYSTRACE_CALL();
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!mSwapChain) <<
|
||||
"skipFrame() can't be called between beginFrame() and endFrame()";
|
||||
|
||||
if (!vsyncSteadyClockTimeNano) {
|
||||
vsyncSteadyClockTimeNano = mVsyncSteadyClockTimeNano;
|
||||
mVsyncSteadyClockTimeNano = 0;
|
||||
}
|
||||
|
||||
FEngine& engine = mEngine;
|
||||
FEngine::DriverApi& driver = engine.getDriverApi();
|
||||
|
||||
// Gives the backend a chance to execute periodic tasks. This must be called before
|
||||
// the frame skipper.
|
||||
driver.tick();
|
||||
|
||||
// do this before engine.flush()
|
||||
mResourceAllocator->gc(true);
|
||||
|
||||
// Run the component managers' GC in parallel
|
||||
// WARNING: while doing this we can't access any component manager
|
||||
auto& js = engine.getJobSystem();
|
||||
|
||||
auto *job = js.runAndRetain(jobs::createJob(js, nullptr, &FEngine::gc, &engine)); // gc all managers
|
||||
|
||||
engine.flush(); // flush command stream
|
||||
|
||||
// make sure we're done with the gcs
|
||||
js.waitAndRelease(job);
|
||||
}
|
||||
|
||||
bool FRenderer::beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano) {
|
||||
assert_invariant(swapChain);
|
||||
|
||||
@@ -419,7 +380,7 @@ void FRenderer::endFrame() {
|
||||
}
|
||||
|
||||
// do this before engine.flush()
|
||||
mResourceAllocator->gc();
|
||||
engine.getResourceAllocator().gc();
|
||||
|
||||
// Run the component managers' GC in parallel
|
||||
// WARNING: while doing this we can't access any component manager
|
||||
@@ -507,9 +468,6 @@ void FRenderer::renderStandaloneView(FView const* view) {
|
||||
FILAMENT_CHECK_PRECONDITION(view->getRenderTarget())
|
||||
<< "View \"" << view->getName() << "\" must have a RenderTarget associated";
|
||||
|
||||
FILAMENT_CHECK_PRECONDITION(!mSwapChain)
|
||||
<< "renderStandaloneView() must be called outside of beginFrame() / endFrame()";
|
||||
|
||||
if (UTILS_LIKELY(view->getScene())) {
|
||||
mPreviousRenderTargets.clear();
|
||||
mFrameId++;
|
||||
@@ -789,7 +747,7 @@ void FRenderer::renderJob(RootArenaScope& rootArenaScope, FView& view) {
|
||||
* Frame graph
|
||||
*/
|
||||
|
||||
FrameGraph fg(*mResourceAllocator,
|
||||
FrameGraph fg(engine.getResourceAllocator(),
|
||||
isProtectedContent ? FrameGraph::Mode::PROTECTED : FrameGraph::Mode::UNPROTECTED);
|
||||
auto& blackboard = fg.getBlackboard();
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Allocator.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
|
||||
#include <math/vec4.h>
|
||||
|
||||
@@ -46,7 +45,6 @@
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -54,8 +52,6 @@
|
||||
|
||||
namespace filament {
|
||||
|
||||
class ResourceAllocator;
|
||||
|
||||
namespace backend {
|
||||
class Driver;
|
||||
} // namespace backend
|
||||
@@ -91,9 +87,6 @@ public:
|
||||
|
||||
void setVsyncTime(uint64_t steadyClockTimeNano) noexcept;
|
||||
|
||||
// skip a frame
|
||||
void skipFrame(uint64_t vsyncSteadyClockTimeNano);
|
||||
|
||||
// start a frame
|
||||
bool beginFrame(FSwapChain* swapChain, uint64_t vsyncSteadyClockTimeNano);
|
||||
|
||||
@@ -147,14 +140,6 @@ public:
|
||||
return mClearOptions;
|
||||
}
|
||||
|
||||
utils::FixedCapacityVector<Renderer::FrameInfo> getFrameInfoHistory(size_t historySize) const noexcept {
|
||||
return mFrameInfoManager.getFrameInfoHistory(historySize);
|
||||
}
|
||||
|
||||
size_t getMaxFrameHistorySize() const noexcept {
|
||||
return MAX_FRAMETIME_HISTORY;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Renderer;
|
||||
using Command = RenderPass::Command;
|
||||
@@ -212,7 +197,6 @@ private:
|
||||
tsl::robin_set<FRenderTarget*> mPreviousRenderTargets;
|
||||
std::function<void()> mBeginFrameInternal;
|
||||
uint64_t mVsyncSteadyClockTimeNano = 0;
|
||||
std::unique_ptr<ResourceAllocator> mResourceAllocator{};
|
||||
};
|
||||
|
||||
FILAMENT_DOWNCAST(Renderer)
|
||||
|
||||
@@ -248,7 +248,7 @@ void FScene::prepare(utils::JobSystem& js,
|
||||
|
||||
auto* renderableJob = jobs::parallel_for(js, rootJob,
|
||||
renderableInstances.data(), renderableInstances.size(),
|
||||
std::cref(renderableWork), jobs::CountSplitter<64>());
|
||||
std::cref(renderableWork), jobs::CountSplitter<128, 5>());
|
||||
|
||||
auto* lightJob = jobs::parallel_for(js, rootJob,
|
||||
lightInstances.data(), lightInstances.size(),
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "details/View.h"
|
||||
|
||||
#include "Culler.h"
|
||||
#include "FrameHistory.h"
|
||||
#include "Froxelizer.h"
|
||||
#include "RenderPrimitive.h"
|
||||
#include "ResourceAllocator.h"
|
||||
@@ -119,7 +118,7 @@ void FView::terminate(FEngine& engine) {
|
||||
DriverApi& driver = engine.getDriverApi();
|
||||
driver.destroyBufferObject(mLightUbh);
|
||||
driver.destroyBufferObject(mRenderableUbh);
|
||||
clearFrameHistory(engine);
|
||||
drainFrameHistory(engine);
|
||||
|
||||
ShadowMapManager::terminate(engine, mShadowMapManager);
|
||||
mPerViewUniforms.terminate(driver);
|
||||
@@ -163,7 +162,7 @@ void FView::setDynamicLightingOptions(float zLightNear, float zLightFar) noexcep
|
||||
}
|
||||
|
||||
float2 FView::updateScale(FEngine& engine,
|
||||
filament::details::FrameInfo const& info,
|
||||
FrameInfo const& info,
|
||||
Renderer::FrameRateOptions const& frameRateOptions,
|
||||
Renderer::DisplayInfo const& displayInfo) noexcept {
|
||||
|
||||
@@ -1008,29 +1007,20 @@ FrameGraphId<FrameGraphTexture> FView::renderShadowMaps(FEngine& engine, FrameGr
|
||||
|
||||
void FView::commitFrameHistory(FEngine& engine) noexcept {
|
||||
// Here we need to destroy resources in mFrameHistory.back()
|
||||
auto& disposer = engine.getResourceAllocatorDisposer();
|
||||
auto& frameHistory = mFrameHistory;
|
||||
|
||||
FrameHistoryEntry& last = frameHistory.back();
|
||||
disposer.destroy(last.taa.color.handle);
|
||||
disposer.destroy(last.ssr.color.handle);
|
||||
last.taa.color.handle.clear();
|
||||
last.ssr.color.handle.clear();
|
||||
last.taa.color.destroy(engine.getResourceAllocator());
|
||||
last.ssr.color.destroy(engine.getResourceAllocator());
|
||||
|
||||
// and then push the new history entry to the history stack
|
||||
frameHistory.commit();
|
||||
}
|
||||
|
||||
void FView::clearFrameHistory(FEngine& engine) noexcept {
|
||||
void FView::drainFrameHistory(FEngine& engine) noexcept {
|
||||
// make sure we free all resources in the history
|
||||
auto& disposer = engine.getResourceAllocatorDisposer();
|
||||
auto& frameHistory = mFrameHistory;
|
||||
for (size_t i = 0; i < frameHistory.size(); ++i) {
|
||||
FrameHistoryEntry& last = frameHistory[i];
|
||||
disposer.destroy(last.taa.color.handle);
|
||||
disposer.destroy(last.ssr.color.handle);
|
||||
last.taa.color.handle.clear();
|
||||
last.ssr.color.handle.clear();
|
||||
for (size_t i = 0; i < mFrameHistory.size(); ++i) {
|
||||
commitFrameHistory(engine);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ public:
|
||||
}
|
||||
|
||||
math::float2 updateScale(FEngine& engine,
|
||||
details::FrameInfo const& info,
|
||||
FrameInfo const& info,
|
||||
Renderer::FrameRateOptions const& frameRateOptions,
|
||||
Renderer::DisplayInfo const& displayInfo) noexcept;
|
||||
|
||||
@@ -422,10 +422,6 @@ public:
|
||||
// (e.g.: after the FrameGraph execution).
|
||||
void commitFrameHistory(FEngine& engine) noexcept;
|
||||
|
||||
// Clean-up the whole history, free all resources. This is typically called when the View is
|
||||
// being terminated. Or we're changing Renderer.
|
||||
void clearFrameHistory(FEngine& engine) noexcept;
|
||||
|
||||
// create the picking query
|
||||
View::PickingQuery& pick(uint32_t x, uint32_t y, backend::CallbackHandler* handler,
|
||||
View::PickingQueryResultCallback callback) noexcept;
|
||||
@@ -489,6 +485,10 @@ private:
|
||||
Culler::result_type* visibleMask,
|
||||
size_t count);
|
||||
|
||||
// Clean-up the whole history, free all resources. This is typically called when the View is
|
||||
// being terminated.
|
||||
void drainFrameHistory(FEngine& engine) noexcept;
|
||||
|
||||
// we don't inline this one, because the function is quite large and there is not much to
|
||||
// gain from inlining.
|
||||
static FScene::RenderableSoa::iterator partition(
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "FrameGraphPass.h"
|
||||
#include "FrameGraphRenderPass.h"
|
||||
#include "FrameGraphTexture.h"
|
||||
#include "ResourceAllocator.h"
|
||||
|
||||
#include "details/Engine.h"
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ struct FrameGraphRenderPass {
|
||||
Viewport viewport{};
|
||||
math::float4 clearColor{};
|
||||
uint8_t samples = 0; // # of samples (0 = unset, default)
|
||||
uint8_t layerCount = 1; // # of layer (# > 1 = multiview)
|
||||
uint8_t layerCount = 0; // # of layer (# > 1 = multiview)
|
||||
backend::TargetBufferFlags clearFlags{};
|
||||
backend::TargetBufferFlags discardStart{};
|
||||
};
|
||||
|
||||
@@ -34,10 +34,6 @@ using namespace backend;
|
||||
|
||||
class MockResourceAllocator : public ResourceAllocatorInterface {
|
||||
uint32_t handle = 0;
|
||||
struct MockDisposer : public ResourceAllocatorDisposerInterface {
|
||||
void destroy(backend::TextureHandle) noexcept override {}
|
||||
} disposer;
|
||||
|
||||
public:
|
||||
backend::RenderTargetHandle createRenderTarget(const char* name,
|
||||
backend::TargetBufferFlags targetBufferFlags,
|
||||
@@ -64,10 +60,6 @@ public:
|
||||
|
||||
void destroyTexture(backend::TextureHandle h) noexcept override {
|
||||
}
|
||||
|
||||
ResourceAllocatorDisposerInterface& getDisposer() noexcept override {
|
||||
return disposer;
|
||||
}
|
||||
};
|
||||
|
||||
class FrameGraphTest : public testing::Test {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = "Filament"
|
||||
spec.version = "1.53.4"
|
||||
spec.version = "1.53.1"
|
||||
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.53.4/filament-v1.53.4-ios.tgz" }
|
||||
spec.source = { :http => "https://github.com/google/filament/releases/download/v1.53.1/filament-v1.53.1-ios.tgz" }
|
||||
|
||||
# Fix linking error with Xcode 12; we do not yet support the simulator on Apple silicon.
|
||||
spec.pod_target_xcconfig = {
|
||||
|
||||
@@ -109,7 +109,6 @@ public:
|
||||
vec4 groundPlane;
|
||||
RayCallback raycastCallback;
|
||||
void* raycastUserdata;
|
||||
bool panning = true;
|
||||
};
|
||||
|
||||
struct Builder {
|
||||
@@ -144,8 +143,6 @@ public:
|
||||
Builder& groundPlane(FLOAT a, FLOAT b, FLOAT c, FLOAT d); //! Plane equation used as a raycast fallback
|
||||
Builder& raycastCallback(RayCallback cb, void* userdata); //! Raycast function for accurate grab-and-pan
|
||||
|
||||
Builder& panning(bool enabled); //! Sets whether panning is enabled
|
||||
|
||||
/**
|
||||
* Creates a new camera manipulator, either ORBIT, MAP, or FREE_FLIGHT.
|
||||
*
|
||||
|
||||
@@ -144,14 +144,6 @@ Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::raycastCallback(RayCal
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename FLOAT> typename
|
||||
Manipulator<FLOAT>::Builder& Manipulator<FLOAT>::Builder::panning(bool enabled) {
|
||||
details.panning = enabled;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename FLOAT>
|
||||
Manipulator<FLOAT>* Manipulator<FLOAT>::Builder::build(Mode mode) {
|
||||
switch (mode) {
|
||||
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
}
|
||||
|
||||
void grabBegin(int x, int y, bool strafe) override {
|
||||
mGrabState = strafe && Base::mProps.panning ? PANNING : ORBITING;
|
||||
mGrabState = strafe ? PANNING : ORBITING;
|
||||
mGrabPivot = mPivot;
|
||||
mGrabEye = Base::mEye;
|
||||
mGrabTarget = Base::mTarget;
|
||||
|
||||
@@ -119,7 +119,7 @@ static void PlotEx(ImGuiPlotType plot_type, const char* label, int series_count,
|
||||
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
|
||||
ImGui::ItemSize(total_bb, style.FramePadding.y);
|
||||
if (!ImGui::ItemAdd(total_bb, 0, &frame_bb)) return;
|
||||
const bool hovered = ImGui::ItemHoverable(inner_bb, 0, ImGuiHoveredFlags_None);
|
||||
const bool hovered = ImGui::ItemHoverable(inner_bb, 0);
|
||||
|
||||
// Determine scale from values if not specified
|
||||
if (scale_min == FLT_MAX || scale_max == FLT_MAX) {
|
||||
|
||||
@@ -134,7 +134,6 @@ protected:
|
||||
TargetApi mTargetApi = (TargetApi) 0;
|
||||
Optimization mOptimization = Optimization::PERFORMANCE;
|
||||
bool mPrintShaders = false;
|
||||
bool mSaveRawVariants = false;
|
||||
bool mGenerateDebugInfo = false;
|
||||
bool mIncludeEssl1 = true;
|
||||
utils::bitset32 mShaderModels;
|
||||
@@ -587,14 +586,6 @@ public:
|
||||
//! If true, will output the generated GLSL shader code to stdout.
|
||||
MaterialBuilder& printShaders(bool printShaders) noexcept;
|
||||
|
||||
/**
|
||||
* If true, this will write the raw generated GLSL for each variant to a text file in the
|
||||
* current directory. The file will be named after the material name and the variant name. Its
|
||||
* extension will be derived from the shader stage. For example, mymaterial_0x0e.frag,
|
||||
* mymaterial_0x18.vert, etc.
|
||||
*/
|
||||
MaterialBuilder& saveRawVariants(bool saveVariants) noexcept;
|
||||
|
||||
//! If true, will include debugging information in generated SPIRV.
|
||||
MaterialBuilder& generateDebugInfo(bool generateDebugInfo) noexcept;
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
namespace filamat {
|
||||
|
||||
@@ -547,11 +547,6 @@ MaterialBuilder& MaterialBuilder::printShaders(bool printShaders) noexcept {
|
||||
return *this;
|
||||
}
|
||||
|
||||
MaterialBuilder& MaterialBuilder::saveRawVariants(bool saveRawVariants) noexcept {
|
||||
mSaveRawVariants = saveRawVariants;
|
||||
return *this;
|
||||
}
|
||||
|
||||
MaterialBuilder& MaterialBuilder::generateDebugInfo(bool generateDebugInfo) noexcept {
|
||||
mGenerateDebugInfo = generateDebugInfo;
|
||||
return *this;
|
||||
@@ -923,30 +918,6 @@ bool MaterialBuilder::generateShaders(JobSystem& jobSystem, const std::vector<Va
|
||||
shaderModel, targetApi, targetLanguage, featureLevel, info);
|
||||
}
|
||||
|
||||
// Write the variant to a file.
|
||||
if (mSaveRawVariants) {
|
||||
int variantKey = v.variant.key;
|
||||
auto getExtension = [](backend::ShaderStage stage) {
|
||||
switch (stage) {
|
||||
case backend::ShaderStage::VERTEX:
|
||||
return "vert";
|
||||
case backend::ShaderStage::FRAGMENT:
|
||||
return "frag";
|
||||
case backend::ShaderStage::COMPUTE:
|
||||
return "comp";
|
||||
}
|
||||
};
|
||||
char filename[256];
|
||||
snprintf(filename, sizeof(filename), "%s_0x%02x.%s", mMaterialName.c_str_safe(),
|
||||
variantKey, getExtension(v.stage));
|
||||
printf("Writing variant 0x%02x to %s\n", variantKey, filename);
|
||||
std::ofstream file(filename);
|
||||
if (file.is_open()) {
|
||||
file << shader;
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
std::string* pGlsl = nullptr;
|
||||
if (targetApiNeedsGlsl) {
|
||||
pGlsl = &shader;
|
||||
|
||||
@@ -104,8 +104,6 @@ public:
|
||||
|
||||
size_t getSkippedFrameCount() const { return mSkippedFrames; }
|
||||
|
||||
void loadIBL(std::string_view path);
|
||||
|
||||
FilamentApp(const FilamentApp& rhs) = delete;
|
||||
FilamentApp(FilamentApp&& rhs) = delete;
|
||||
FilamentApp& operator=(const FilamentApp& rhs) = delete;
|
||||
|
||||
@@ -174,6 +174,11 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
|
||||
|
||||
loadDirt(config);
|
||||
loadIBL(config);
|
||||
if (mIBL != nullptr) {
|
||||
mIBL->getSkybox()->setLayerMask(0x7, 0x4);
|
||||
mScene->setSkybox(mIBL->getSkybox());
|
||||
mScene->setIndirectLight(mIBL->getIndirectLight());
|
||||
}
|
||||
|
||||
for (auto& view : window->mViews) {
|
||||
if (view.get() != window->mUiView) {
|
||||
@@ -191,7 +196,7 @@ void FilamentApp::run(const Config& config, SetupCallback setupCallback,
|
||||
SDL_SysWMinfo wmInfo;
|
||||
SDL_VERSION(&wmInfo.version);
|
||||
SDL_GetWindowWMInfo(window->getSDLWindow(), &wmInfo);
|
||||
ImGui::GetMainViewport()->PlatformHandleRaw = wmInfo.info.win.window;
|
||||
io.ImeWindowHandle = wmInfo.info.win.window;
|
||||
#endif
|
||||
io.KeyMap[ImGuiKey_Tab] = SDL_SCANCODE_TAB;
|
||||
io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT;
|
||||
@@ -514,41 +519,31 @@ const utils::Path& FilamentApp::getRootAssetsPath() {
|
||||
return root;
|
||||
}
|
||||
|
||||
void FilamentApp::loadIBL(std::string_view path) {
|
||||
Path iblPath(path);
|
||||
if (!iblPath.exists()) {
|
||||
std::cerr << "The specified IBL path does not exist: " << iblPath << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
mIBL = std::make_unique<IBL>(*mEngine);
|
||||
|
||||
if (!iblPath.isDirectory()) {
|
||||
if (!mIBL->loadFromEquirect(iblPath)) {
|
||||
std::cerr << "Could not load the specified IBL: " << iblPath << std::endl;
|
||||
mIBL.reset(nullptr);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!mIBL->loadFromDirectory(iblPath)) {
|
||||
std::cerr << "Could not load the specified IBL: " << iblPath << std::endl;
|
||||
mIBL.reset(nullptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mIBL != nullptr) {
|
||||
mIBL->getSkybox()->setLayerMask(0x7, 0x4);
|
||||
mScene->setSkybox(mIBL->getSkybox());
|
||||
mScene->setIndirectLight(mIBL->getIndirectLight());
|
||||
}
|
||||
}
|
||||
|
||||
void FilamentApp::loadIBL(const Config& config) {
|
||||
if (config.iblDirectory.empty()) {
|
||||
return;
|
||||
if (!config.iblDirectory.empty()) {
|
||||
Path iblPath(config.iblDirectory);
|
||||
|
||||
if (!iblPath.exists()) {
|
||||
std::cerr << "The specified IBL path does not exist: " << iblPath << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
mIBL = std::make_unique<IBL>(*mEngine);
|
||||
|
||||
if (!iblPath.isDirectory()) {
|
||||
if (!mIBL->loadFromEquirect(iblPath)) {
|
||||
std::cerr << "Could not load the specified IBL: " << iblPath << std::endl;
|
||||
mIBL.reset(nullptr);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!mIBL->loadFromDirectory(iblPath)) {
|
||||
std::cerr << "Could not load the specified IBL: " << iblPath << std::endl;
|
||||
mIBL.reset(nullptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
loadIBL(config.iblDirectory);
|
||||
}
|
||||
|
||||
void FilamentApp::loadDirt(const Config& config) {
|
||||
|
||||
+24
-135
@@ -58,8 +58,6 @@
|
||||
|
||||
#include "downcast.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
|
||||
using namespace filament;
|
||||
@@ -90,58 +88,12 @@ static constexpr cgltf_material kDefaultMat = {
|
||||
},
|
||||
};
|
||||
|
||||
static std::string getNodeName(cgltf_node const* node, char const* defaultNodeName) {
|
||||
auto const getNameImpl = [node, defaultNodeName]() -> char const* {
|
||||
if (node->name) return node->name;
|
||||
if (node->mesh && node->mesh->name) return node->mesh->name;
|
||||
if (node->light && node->light->name) return node->light->name;
|
||||
if (node->camera && node->camera->name) return node->camera->name;
|
||||
if (defaultNodeName) return defaultNodeName;
|
||||
return "<unknown>";
|
||||
};
|
||||
|
||||
std::string strOrig(getNameImpl());
|
||||
|
||||
// We handle the potential case of escaped characters in the JSON which should be properly
|
||||
// interpreted as unicode. See spec:
|
||||
// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#json-encoding
|
||||
// Also see spec for escaped strings in JSON (Section 2.5) https://www.ietf.org/rfc/rfc4627.txt
|
||||
|
||||
std::string strEscaped;
|
||||
size_t cur = 0, idx = 0;
|
||||
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> conv;
|
||||
|
||||
auto const addUnencodedSubstr = [&](size_t cursor, size_t nextPoint) {
|
||||
assert_invariant(nextPoint >= cursor);
|
||||
if (cursor == nextPoint) {
|
||||
return;
|
||||
}
|
||||
strEscaped += strOrig.substr(cursor, nextPoint - cursor);
|
||||
};
|
||||
|
||||
while ((idx = strOrig.find("\\u", cur)) != std::string::npos) {
|
||||
if (idx + 6 > strOrig.length()) {
|
||||
utils::slog.w << "gltfio: Unable to interpret node name=" << strOrig
|
||||
<< " as proper unicode encoding." << utils::io::endl;
|
||||
return strOrig;
|
||||
}
|
||||
|
||||
// Turns string of the form \u0062 to 0x0062
|
||||
std::string const hexStr = strOrig.substr(idx + 2, 4);
|
||||
if (hexStr.find_first_not_of("0123456789abcdefABCDEF") != std::string::npos) {
|
||||
utils::slog.w << "gltfio: Unable to interpret node name=" << strOrig
|
||||
<< " as proper unicode encoding." << utils::io::endl;
|
||||
return strOrig;
|
||||
}
|
||||
|
||||
addUnencodedSubstr(cur, idx);
|
||||
|
||||
strEscaped += conv.to_bytes((char32_t) std::stoul(hexStr, nullptr, 16));
|
||||
cur = idx + 6;
|
||||
}
|
||||
addUnencodedSubstr(cur, strOrig.length());
|
||||
|
||||
return strEscaped;
|
||||
static const char* getNodeName(const cgltf_node* node, const char* defaultNodeName) {
|
||||
if (node->name) return node->name;
|
||||
if (node->mesh && node->mesh->name) return node->mesh->name;
|
||||
if (node->light && node->light->name) return node->light->name;
|
||||
if (node->camera && node->camera->name) return node->camera->name;
|
||||
return defaultNodeName;
|
||||
}
|
||||
|
||||
static bool primitiveHasVertexColor(const cgltf_primitive& inPrim) {
|
||||
@@ -552,8 +504,7 @@ FFilamentAsset* FAssetLoader::createRootAsset(const cgltf_data* srcAsset) {
|
||||
}
|
||||
|
||||
void FAssetLoader::recursePrimitives(const cgltf_node* node, FFilamentAsset* fAsset) {
|
||||
auto nameStr = getNodeName(node, mDefaultNodeName);
|
||||
const char* name = nameStr.c_str();
|
||||
const char* name = getNodeName(node, mDefaultNodeName);
|
||||
name = name ? name : "node";
|
||||
|
||||
if (node->mesh) {
|
||||
@@ -622,8 +573,7 @@ void FAssetLoader::recurseEntities(const cgltf_node* node, SceneMask scenes, Ent
|
||||
instance->mEntities.push_back(entity);
|
||||
instance->mNodeMap[node - srcAsset->nodes] = entity;
|
||||
|
||||
auto nameStr = getNodeName(node, mDefaultNodeName);
|
||||
const char* name = nameStr.c_str();
|
||||
const char* name = getNodeName(node, mDefaultNodeName);
|
||||
|
||||
if (name) {
|
||||
fAsset->mNameToEntity[name].push_back(entity);
|
||||
@@ -700,6 +650,9 @@ void FAssetLoader::createPrimitives(const cgltf_node* node, const char* name,
|
||||
if (outputPrim.indices) {
|
||||
fAsset->mIndexBuffers.push_back(outputPrim.indices);
|
||||
}
|
||||
if (outputPrim.targets) {
|
||||
fAsset->mMorphTargetBuffers.push_back(outputPrim.targets);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create a Filament VertexBuffer and IndexBuffer for this prim if we haven't
|
||||
@@ -742,11 +695,11 @@ void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity, const
|
||||
// glTF spec says that all primitives must have the same number of morph targets.
|
||||
const cgltf_size numMorphTargets = inputPrim ? inputPrim->targets_count : 0;
|
||||
RenderableManager::Builder builder(primitiveCount);
|
||||
builder.morphing(numMorphTargets);
|
||||
|
||||
// For each prim, create a Filament VertexBuffer, IndexBuffer, and MaterialInstance.
|
||||
// The VertexBuffer and IndexBuffer objects are cached for possible re-use, but MaterialInstance
|
||||
// is not.
|
||||
size_t morphingVertexCount = 0;
|
||||
for (cgltf_size index = 0; index < primitiveCount; ++index, ++outputPrim, ++inputPrim) {
|
||||
RenderableManager::PrimitiveType primType;
|
||||
if (!getPrimitiveType(inputPrim->type, &primType)) {
|
||||
@@ -787,78 +740,8 @@ void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity, const
|
||||
builder.geometry(index, primType, outputPrim->vertices, outputPrim->indices);
|
||||
|
||||
if (numMorphTargets) {
|
||||
outputPrim->morphTargetOffset = morphingVertexCount; // FIXME: can I do that here?
|
||||
builder.morphing(0, index, morphingVertexCount);
|
||||
morphingVertexCount += outputPrim->vertices->getVertexCount();
|
||||
}
|
||||
}
|
||||
|
||||
if (numMorphTargets) {
|
||||
MorphTargetBuffer* morphTargetBuffer = MorphTargetBuffer::Builder()
|
||||
.count(numMorphTargets)
|
||||
.vertexCount(morphingVertexCount)
|
||||
.build(mEngine);
|
||||
|
||||
fAsset->mMorphTargetBuffers.push_back(morphTargetBuffer);
|
||||
|
||||
builder.morphing(morphTargetBuffer);
|
||||
|
||||
outputPrim = prims.data();
|
||||
inputPrim = &mesh->primitives[0];
|
||||
for (cgltf_size index = 0; index < primitiveCount; ++index, ++outputPrim, ++inputPrim) {
|
||||
outputPrim->morphTargetBuffer = morphTargetBuffer;
|
||||
|
||||
UTILS_UNUSED_IN_RELEASE cgltf_accessor const* previous = nullptr;
|
||||
for (int tindex = 0; tindex < numMorphTargets; ++tindex) {
|
||||
const cgltf_morph_target& inTarget = inputPrim->targets[tindex];
|
||||
for (cgltf_size aindex = 0; aindex < inTarget.attributes_count; ++aindex) {
|
||||
const cgltf_attribute& attribute = inTarget.attributes[aindex];
|
||||
const cgltf_accessor* accessor = attribute.data;
|
||||
const cgltf_attribute_type atype = attribute.type;
|
||||
if (atype == cgltf_attribute_type_position) {
|
||||
// All position attributes must have the same number of components.
|
||||
assert_invariant(!previous || previous->type == accessor->type);
|
||||
previous = accessor;
|
||||
|
||||
assert_invariant(outputPrim->morphTargetBuffer);
|
||||
|
||||
if (std::holds_alternative<FFilamentAsset::ResourceInfo>(
|
||||
fAsset->mResourceInfo)) {
|
||||
using BufferSlot = FFilamentAsset::ResourceInfo::BufferSlot;
|
||||
auto& slots = std::get<FFilamentAsset::ResourceInfo>(
|
||||
fAsset->mResourceInfo).mBufferSlots;
|
||||
BufferSlot& slot = slots[outputPrim->slotIndices[tindex]];
|
||||
|
||||
assert_invariant(!slot.vertexBuffer);
|
||||
assert_invariant(!slot.indexBuffer);
|
||||
|
||||
slot.morphTargetBuffer = outputPrim->morphTargetBuffer;
|
||||
slot.morphTargetOffset = outputPrim->morphTargetOffset;
|
||||
slot.morphTargetCount = outputPrim->vertices->getVertexCount();
|
||||
slot.bufferIndex = tindex;
|
||||
}
|
||||
else if (std::holds_alternative<FFilamentAsset::ResourceInfoExtended>(
|
||||
fAsset->mResourceInfo))
|
||||
{
|
||||
using BufferSlot = FFilamentAsset::ResourceInfoExtended::BufferSlot;
|
||||
auto& slots = std::get<FFilamentAsset::ResourceInfoExtended>(
|
||||
fAsset->mResourceInfo).slots;
|
||||
|
||||
BufferSlot& slot = slots[outputPrim->slotIndices[tindex]];
|
||||
|
||||
assert_invariant(slot.slot == tindex);
|
||||
assert_invariant(!slot.vertices);
|
||||
assert_invariant(!slot.indices);
|
||||
|
||||
slot.target = outputPrim->morphTargetBuffer;
|
||||
slot.offset = outputPrim->morphTargetOffset;
|
||||
slot.count = outputPrim->vertices->getVertexCount();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_invariant(outputPrim->targets);
|
||||
builder.morphing(0, index, outputPrim->targets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,7 +753,7 @@ void FAssetLoader::createRenderable(const cgltf_node* node, Entity entity, const
|
||||
nm.setMorphTargetNames(nm.getInstance(entity), std::move(morphTargetNames));
|
||||
|
||||
if (node->skin) {
|
||||
builder.skinning(node->skin->joints_count);
|
||||
builder.skinning(node->skin->joints_count);
|
||||
}
|
||||
|
||||
// Per the spec, glTF models must have valid mix / max annotations for position attributes.
|
||||
@@ -942,7 +825,7 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, const char* na
|
||||
// request from Google.
|
||||
|
||||
// Create a little lambda that appends to the asset's vertex buffer slots.
|
||||
auto* const slots = &std::get<FFilamentAsset::ResourceInfo>(fAsset->mResourceInfo).mBufferSlots;
|
||||
auto slots = &std::get<FFilamentAsset::ResourceInfo>(fAsset->mResourceInfo).mBufferSlots;
|
||||
auto addBufferSlot = [slots](FFilamentAsset::ResourceInfo::BufferSlot entry) {
|
||||
slots->push_back(entry);
|
||||
};
|
||||
@@ -1213,8 +1096,13 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, const char* na
|
||||
}
|
||||
|
||||
if (targetsCount > 0) {
|
||||
MorphTargetBuffer* targets = MorphTargetBuffer::Builder()
|
||||
.vertexCount(vertexCount)
|
||||
.count(targetsCount)
|
||||
.build(mEngine);
|
||||
outPrim->targets = targets;
|
||||
fAsset->mMorphTargetBuffers.push_back(targets);
|
||||
UTILS_UNUSED_IN_RELEASE cgltf_accessor const* previous = nullptr;
|
||||
outPrim->slotIndices.resize(targetsCount);
|
||||
for (int tindex = 0; tindex < targetsCount; ++tindex) {
|
||||
const cgltf_morph_target& inTarget = inPrim.targets[tindex];
|
||||
for (cgltf_size aindex = 0; aindex < inTarget.attributes_count; ++aindex) {
|
||||
@@ -1226,7 +1114,8 @@ bool FAssetLoader::createPrimitive(const cgltf_primitive& inPrim, const char* na
|
||||
assert_invariant(!previous || previous->type == accessor->type);
|
||||
previous = accessor;
|
||||
BufferSlot slot = { accessor };
|
||||
outPrim->slotIndices[tindex] = slots->size();
|
||||
slot.morphTargetBuffer = targets;
|
||||
slot.bufferIndex = tindex;
|
||||
addBufferSlot(slot);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -96,9 +96,7 @@ struct Primitive {
|
||||
IndexBuffer* indices = nullptr;
|
||||
Aabb aabb; // object-space bounding box
|
||||
UvMap uvmap; // mapping from each glTF UV set to either UV0 or UV1 (8 bytes)
|
||||
MorphTargetBuffer* morphTargetBuffer = nullptr;
|
||||
uint32_t morphTargetOffset;
|
||||
std::vector<int> slotIndices;
|
||||
MorphTargetBuffer* targets = nullptr;
|
||||
};
|
||||
using MeshCache = utils::FixedCapacityVector<utils::FixedCapacityVector<Primitive>>;
|
||||
|
||||
@@ -331,8 +329,6 @@ struct FFilamentAsset : public FilamentAsset {
|
||||
VertexBuffer* vertexBuffer;
|
||||
IndexBuffer* indexBuffer;
|
||||
MorphTargetBuffer* morphTargetBuffer;
|
||||
uint32_t morphTargetOffset;
|
||||
uint32_t morphTargetCount;
|
||||
};
|
||||
|
||||
std::vector<BufferSlot> mBufferSlots;
|
||||
@@ -347,8 +343,6 @@ struct FFilamentAsset : public FilamentAsset {
|
||||
VertexBuffer* vertices = nullptr;
|
||||
IndexBuffer* indices = nullptr;
|
||||
MorphTargetBuffer* target = nullptr;
|
||||
uint32_t offset = 0;
|
||||
uint32_t count = 0;
|
||||
int slot = -1;
|
||||
size_t sizeInBytes = 0;
|
||||
|
||||
|
||||
@@ -131,7 +131,6 @@ inline bool getPrimitiveType(cgltf_primitive_type in,
|
||||
case cgltf_primitive_type_triangle_strip:
|
||||
*out = filament::RenderableManager::PrimitiveType::TRIANGLE_STRIP;
|
||||
return true;
|
||||
case cgltf_primitive_type_invalid:
|
||||
case cgltf_primitive_type_line_loop:
|
||||
case cgltf_primitive_type_triangle_fan:
|
||||
case cgltf_primitive_type_max_enum:
|
||||
|
||||
@@ -293,13 +293,10 @@ inline void uploadBuffers(FFilamentAsset* asset, Engine& engine,
|
||||
cgltf_accessor_unpack_floats(accessor, floatsData, floatsCount);
|
||||
if (accessor->type == cgltf_type_vec3) {
|
||||
slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex,
|
||||
(const float3*) floatsData,
|
||||
slot.morphTargetCount,
|
||||
slot.morphTargetOffset);
|
||||
(const float3*) floatsData, slot.morphTargetBuffer->getVertexCount());
|
||||
} else {
|
||||
slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex,
|
||||
(const float4*) data, slot.morphTargetBuffer->getVertexCount(),
|
||||
slot.morphTargetOffset);
|
||||
(const float4*) data, slot.morphTargetBuffer->getVertexCount());
|
||||
}
|
||||
free(floatsData);
|
||||
continue;
|
||||
@@ -307,13 +304,11 @@ inline void uploadBuffers(FFilamentAsset* asset, Engine& engine,
|
||||
|
||||
if (accessor->type == cgltf_type_vec3) {
|
||||
slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex, (const float3*) data,
|
||||
slot.morphTargetCount,
|
||||
slot.morphTargetOffset);
|
||||
slot.morphTargetBuffer->getVertexCount());
|
||||
} else {
|
||||
assert_invariant(accessor->type == cgltf_type_vec4);
|
||||
slot.morphTargetBuffer->setPositionsAt(engine, slot.bufferIndex, (const float4*) data,
|
||||
slot.morphTargetCount,
|
||||
slot.morphTargetOffset);
|
||||
slot.morphTargetBuffer->getVertexCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -699,7 +694,7 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) {
|
||||
}
|
||||
auto iter = baseTangents.find(vb);
|
||||
if (iter != baseTangents.end()) {
|
||||
jobParams.emplace_back(Params {{ prim }, {vb, nullptr, 0, iter->second }});
|
||||
jobParams.emplace_back(Params {{ prim }, {vb, nullptr, iter->second }});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,8 +707,7 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) {
|
||||
}
|
||||
for (cgltf_size pindex = 0, pcount = mesh.primitives_count; pindex < pcount; ++pindex) {
|
||||
const cgltf_primitive& prim = mesh.primitives[pindex];
|
||||
MorphTargetBuffer* const tb = prims[pindex].morphTargetBuffer;
|
||||
uint32_t const morphTargetOffset = prims[pindex].morphTargetOffset;
|
||||
MorphTargetBuffer* tb = prims[pindex].targets;
|
||||
for (cgltf_size tindex = 0, tcount = prim.targets_count; tindex < tcount; ++tindex) {
|
||||
const cgltf_morph_target& target = prim.targets[tindex];
|
||||
bool hasNormals = false;
|
||||
@@ -725,13 +719,13 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) {
|
||||
}
|
||||
hasNormals = true;
|
||||
jobParams.emplace_back(Params { { &prim, (int) tindex },
|
||||
{ nullptr, tb, morphTargetOffset, (uint8_t) pindex } });
|
||||
{ nullptr, tb, (uint8_t) pindex } });
|
||||
break;
|
||||
}
|
||||
// Generate flat normals if necessary.
|
||||
if (!hasNormals && prim.material && !prim.material->unlit) {
|
||||
jobParams.emplace_back(Params { { &prim, (int) tindex },
|
||||
{ nullptr, tb, morphTargetOffset, (uint8_t) pindex } });
|
||||
{ nullptr, tb, (uint8_t) pindex } });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -758,7 +752,7 @@ void ResourceLoader::Impl::computeTangents(FFilamentAsset* asset) {
|
||||
} else {
|
||||
assert_invariant(params.context.tb);
|
||||
params.context.tb->setTangentsAt(*mEngine, params.in.morphTargetIndex,
|
||||
params.out.results, params.out.vertexCount, params.context.offset);
|
||||
params.out.results, params.out.vertexCount);
|
||||
free(params.out.results);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ struct TangentsJob {
|
||||
struct Context {
|
||||
VertexBuffer* const vb;
|
||||
MorphTargetBuffer* const tb;
|
||||
uint32_t const offset;
|
||||
const uint8_t slot;
|
||||
};
|
||||
|
||||
|
||||
@@ -288,12 +288,14 @@ std::vector<BufferSlot> computeGeometries(cgltf_primitive const* prim, uint8_t c
|
||||
}
|
||||
|
||||
if (!morphTargets.empty()) {
|
||||
UTILS_UNUSED_IN_RELEASE
|
||||
auto const vertexCount = morphTargetOuts[0].vertexCount;
|
||||
MorphTargetBuffer* buffer = MorphTargetBuffer::Builder()
|
||||
.count(morphTargets.size())
|
||||
.vertexCount(vertexCount)
|
||||
.build(*engine);
|
||||
for (auto target: morphTargetOuts) {
|
||||
assert_invariant(target.vertexCount == vertexCount);
|
||||
slots.push_back({
|
||||
.offset = 0xdeadbeef,
|
||||
slots.push_back({.target = buffer,
|
||||
.slot = target.morphTarget,
|
||||
.targetData = {
|
||||
.tbn = target.tbn,
|
||||
@@ -525,9 +527,8 @@ bool AssetLoaderExtended::createPrimitive(Input* input, Output* out,
|
||||
slotCount++};
|
||||
}
|
||||
|
||||
if (!mCgltfBuffersLoaded) {
|
||||
mCgltfBuffersLoaded = utility::loadCgltfBuffers(gltf, mGltfPath.c_str(), mUriDataCache);
|
||||
if (!mCgltfBuffersLoaded) return false;
|
||||
if (!utility::loadCgltfBuffers(gltf, mGltfPath.c_str(), mUriDataCache)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
utility::decodeDracoMeshes(gltf, prim, input->dracoCache);
|
||||
@@ -535,10 +536,7 @@ bool AssetLoaderExtended::createPrimitive(Input* input, Output* out,
|
||||
|
||||
auto slots = computeGeometries(prim, jobType, attributesMap, morphTargets, out->uvmap, mEngine);
|
||||
|
||||
out->slotIndices.resize(morphTargets.size());
|
||||
|
||||
for (size_t i = 0; i < slots.size(); i++) {
|
||||
auto& slot = slots[i];
|
||||
for (auto slot: slots) {
|
||||
if (slot.vertices) {
|
||||
assert_invariant(!out->vertices || out->vertices == slot.vertices);
|
||||
out->vertices = slot.vertices;
|
||||
@@ -547,10 +545,9 @@ bool AssetLoaderExtended::createPrimitive(Input* input, Output* out,
|
||||
assert_invariant(!out->indices || out->indices == slot.indices);
|
||||
out->indices = slot.indices;
|
||||
}
|
||||
if (slot.offset == 0xdeadbeef) {
|
||||
// we can't fill this here, unfortunately, so this is done in
|
||||
// FAssetLoader::createRenderable
|
||||
out->slotIndices[slot.slot] = outSlots.size() + i;
|
||||
if (slot.target) {
|
||||
assert_invariant(!out->targets || out->targets == slot.target);
|
||||
out->targets = slot.target;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,8 +70,7 @@ struct AssetLoaderExtended {
|
||||
: mEngine(engine),
|
||||
mGltfPath(config.gltfPath),
|
||||
mMaterials(materials),
|
||||
mUriDataCache(std::make_shared<UriDataCache>()),
|
||||
mCgltfBuffersLoaded(false) {}
|
||||
mUriDataCache(std::make_shared<UriDataCache>()) {}
|
||||
|
||||
~AssetLoaderExtended() = default;
|
||||
|
||||
@@ -84,7 +83,6 @@ private:
|
||||
std::string mGltfPath;
|
||||
MaterialProvider& mMaterials;
|
||||
UriDataCacheHandle mUriDataCache;
|
||||
bool mCgltfBuffersLoaded;
|
||||
};
|
||||
|
||||
} // namespace filament::gltfio
|
||||
|
||||
@@ -48,10 +48,9 @@ void ResourceLoaderExtended::loadResources(std::vector<BufferSlot> const& slots,
|
||||
if (slot.target) {
|
||||
assert_invariant(slot.targetData.positions && slot.targetData.tbn);
|
||||
slot.target->setPositionsAt(*engine, slot.slot,
|
||||
(float3 const*) slot.targetData.positions,
|
||||
slot.count, slot.offset);
|
||||
(float3 const*) slot.targetData.positions, slot.target->getVertexCount());
|
||||
slot.target->setTangentsAt(*engine, slot.slot, (short4 const*) slot.targetData.tbn,
|
||||
slot.count, slot.offset);
|
||||
slot.target->getVertexCount());
|
||||
|
||||
free(slot.targetData.positions);
|
||||
free(slot.targetData.tbn);
|
||||
|
||||
@@ -228,7 +228,7 @@ TEST_F(glTFIOTest, AnimatedMorphCubeRenderables) {
|
||||
EXPECT_EQ(renderableManager.getMorphTargetCount(inst), 2u);
|
||||
|
||||
// The 0-th MorphTargetBuffer holds both of the targets
|
||||
auto const morphTargetBuffer = renderableManager.getMorphTargetBuffer(inst);
|
||||
auto const morphTargetBuffer = renderableManager.getMorphTargetBufferAt(inst, 0, 0);
|
||||
EXPECT_EQ(morphTargetBuffer->getCount(), 2u);
|
||||
|
||||
// The number of vertices for the morph target should be the face vertices in a cube =>
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
#include "CubemapUtilsImpl.h"
|
||||
|
||||
#include <utils/Log.h>
|
||||
#include <utils/JobSystem.h>
|
||||
|
||||
#include <math/mat4.h>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#include <utils/architecture.h>
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Condition.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/memalign.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/Slice.h>
|
||||
@@ -44,23 +44,15 @@
|
||||
namespace utils {
|
||||
|
||||
class JobSystem {
|
||||
static constexpr size_t MAX_JOB_COUNT = 1 << 14; // 16384
|
||||
static constexpr uint32_t JOB_COUNT_MASK = MAX_JOB_COUNT - 1;
|
||||
static constexpr uint32_t WAITER_COUNT_SHIFT = 24;
|
||||
static constexpr size_t MAX_JOB_COUNT = 16384;
|
||||
static_assert(MAX_JOB_COUNT <= 0x7FFE, "MAX_JOB_COUNT must be <= 0x7FFE");
|
||||
using WorkQueue = WorkStealingDequeue<uint16_t, MAX_JOB_COUNT>;
|
||||
using Mutex = utils::Mutex;
|
||||
using Condition = utils::Condition;
|
||||
|
||||
public:
|
||||
class Job;
|
||||
|
||||
using ThreadId = uint8_t;
|
||||
|
||||
using JobFunc = void(*)(void*, JobSystem&, Job*);
|
||||
|
||||
static constexpr ThreadId invalidThreadId = 0xff;
|
||||
|
||||
class alignas(CACHELINE_SIZE) Job {
|
||||
public:
|
||||
Job() noexcept {} /* = default; */ /* clang bug */ // NOLINT(modernize-use-equals-default,cppcoreguidelines-pro-type-member-init)
|
||||
@@ -83,18 +75,12 @@ public:
|
||||
void* storage[JOB_STORAGE_SIZE_WORDS]; // 48 | 48
|
||||
JobFunc function; // 4 | 8
|
||||
uint16_t parent; // 2 | 2
|
||||
mutable ThreadId id = invalidThreadId; // 1 | 1
|
||||
mutable std::atomic<uint8_t> refCount = { 1 }; // 1 | 1
|
||||
std::atomic<uint32_t> runningJobCount = { 1 }; // 4 | 4
|
||||
// 4 | 0 (padding)
|
||||
std::atomic<uint16_t> runningJobCount = { 1 }; // 2 | 2
|
||||
mutable std::atomic<uint16_t> refCount = { 1 }; // 2 | 2
|
||||
// 6 | 2 (padding)
|
||||
// 64 | 64
|
||||
};
|
||||
|
||||
#ifndef WIN32
|
||||
// on windows std::function<void()> is bigger and forces the whole structure to be larger
|
||||
static_assert(sizeof(Job) == 64);
|
||||
#endif
|
||||
|
||||
explicit JobSystem(size_t threadCount = 0, size_t adoptableThreadsCount = 1) noexcept;
|
||||
|
||||
~JobSystem();
|
||||
@@ -213,21 +199,6 @@ public:
|
||||
return job;
|
||||
}
|
||||
|
||||
// creates a job from a KNOWN method pointer w/ object passed by value
|
||||
template<typename T, void(T::*method)(JobSystem&, Job*), typename ... ARGS>
|
||||
Job* emplaceJob(Job* parent, ARGS&& ... args) noexcept {
|
||||
static_assert(sizeof(T) <= sizeof(Job::storage), "user data too large");
|
||||
Job* job = create(parent, [](void* storage, JobSystem& js, Job* job) {
|
||||
T* const that = static_cast<T*>(storage);
|
||||
(that->*method)(js, job);
|
||||
that->~T();
|
||||
});
|
||||
if (job) {
|
||||
new(job->storage) T(std::forward<ARGS>(args)...);
|
||||
}
|
||||
return job;
|
||||
}
|
||||
|
||||
// creates a job from a functor passed by value
|
||||
template<typename T>
|
||||
Job* createJob(Job* parent, T functor) noexcept {
|
||||
@@ -243,21 +214,6 @@ public:
|
||||
return job;
|
||||
}
|
||||
|
||||
// creates a job from a functor passed by value
|
||||
template<typename T, typename ... ARGS>
|
||||
Job* emplaceJob(Job* parent, ARGS&& ... args) noexcept {
|
||||
static_assert(sizeof(T) <= sizeof(Job::storage), "functor too large");
|
||||
Job* job = create(parent, [](void* storage, JobSystem& js, Job* job){
|
||||
T* const that = static_cast<T*>(storage);
|
||||
that->operator()(js, job);
|
||||
that->~T();
|
||||
});
|
||||
if (job) {
|
||||
new(job->storage) T(std::forward<ARGS>(args)...);
|
||||
}
|
||||
return job;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Jobs are normally finished automatically, this can be used to cancel a job before it is run.
|
||||
@@ -288,7 +244,7 @@ public:
|
||||
}
|
||||
|
||||
/*
|
||||
* Add job to this thread's execution queue. Its reference will drop automatically.
|
||||
* Add job to this thread's execution queue. It's reference will drop automatically.
|
||||
* The current thread must be owned by JobSystem's thread pool. See adopt().
|
||||
*
|
||||
* The job can't be used after this call.
|
||||
@@ -299,19 +255,7 @@ public:
|
||||
run(p);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add job to this thread's execution queue. Its reference will drop automatically.
|
||||
* The current thread must be owned by JobSystem's thread pool. See adopt().
|
||||
* id must be the current thread id obtained with JobSystem::getThreadId(Job*). This
|
||||
* API is more efficient than the methods above.
|
||||
*
|
||||
* The job can't be used after this call.
|
||||
*/
|
||||
void run(Job*& job, ThreadId id) noexcept;
|
||||
void run(Job*&& job, ThreadId id) noexcept { // allows run(createJob(...));
|
||||
Job* p = job;
|
||||
run(p, id);
|
||||
}
|
||||
void signal() noexcept;
|
||||
|
||||
/*
|
||||
* Add job to this thread's execution queue and keep a reference to it.
|
||||
@@ -368,29 +312,12 @@ public:
|
||||
|
||||
size_t getThreadCount() const { return mThreadCount; }
|
||||
|
||||
// returns the current ThreadId, which can be used with run(). This method can only be
|
||||
// called from a job's function.
|
||||
static ThreadId getThreadId(Job const* job) noexcept {
|
||||
assert_invariant(job->id != invalidThreadId);
|
||||
return job->id;
|
||||
}
|
||||
|
||||
private:
|
||||
// this is just to avoid using std::default_random_engine, since we're in a public header.
|
||||
class default_random_engine {
|
||||
static constexpr uint32_t m = 0x7fffffffu;
|
||||
uint32_t mState; // must be 0 < seed < 0x7fffffff
|
||||
public:
|
||||
using result_type = uint32_t;
|
||||
|
||||
static constexpr result_type min() noexcept {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static constexpr result_type max() noexcept {
|
||||
return m - 1;
|
||||
}
|
||||
|
||||
inline constexpr explicit default_random_engine(uint32_t seed = 1u) noexcept
|
||||
: mState(((seed % m) == 0u) ? 1u : seed % m) {
|
||||
}
|
||||
@@ -435,15 +362,13 @@ private:
|
||||
Job* pop(WorkQueue& workQueue) noexcept;
|
||||
Job* steal(WorkQueue& workQueue) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
uint32_t wait(std::unique_lock<Mutex>& lock, Job* job) noexcept;
|
||||
void wait(std::unique_lock<Mutex>& lock) noexcept;
|
||||
void wait(std::unique_lock<Mutex>& lock, Job* job = nullptr) noexcept;
|
||||
void wake(size_t hint) noexcept;
|
||||
void wakeAll() noexcept;
|
||||
void wakeOne() noexcept;
|
||||
|
||||
// these have thread contention, keep them together
|
||||
Mutex mWaiterLock;
|
||||
Condition mWaiterCondition;
|
||||
utils::Mutex mWaiterLock;
|
||||
utils::Condition mWaiterCondition;
|
||||
|
||||
std::atomic<int32_t> mActiveJobs = { 0 };
|
||||
utils::Arena<utils::ThreadSafeObjectPoolAllocator<Job>, LockingPolicy::NoLock> mJobPool;
|
||||
@@ -466,7 +391,7 @@ private:
|
||||
uint8_t mParallelSplitCount = 0; // # of split allowable in parallel_for
|
||||
Job* mRootJob = nullptr;
|
||||
|
||||
Mutex mThreadMapLock; // this should have very little contention
|
||||
utils::Mutex mThreadMapLock; // this should have very little contention
|
||||
tsl::robin_map<std::thread::id, ThreadState *> mThreadMap;
|
||||
};
|
||||
|
||||
@@ -484,13 +409,12 @@ template<typename CALLABLE, typename ... ARGS>
|
||||
JobSystem::Job* createJob(JobSystem& js, JobSystem::Job* parent,
|
||||
CALLABLE&& func, ARGS&&... args) noexcept {
|
||||
struct Data {
|
||||
explicit Data(std::function<void()> f) noexcept: f(std::move(f)) {}
|
||||
std::function<void()> f;
|
||||
// Renaming the method below could cause an Arrested Development.
|
||||
void gob(JobSystem&, JobSystem::Job*) noexcept { f(); }
|
||||
};
|
||||
return js.emplaceJob<Data, &Data::gob>(parent,
|
||||
std::bind(std::forward<CALLABLE>(func), std::forward<ARGS>(args)...));
|
||||
} user{ std::bind(std::forward<CALLABLE>(func),
|
||||
std::forward<ARGS>(args)...) };
|
||||
return js.createJob<Data, &Data::gob>(parent, std::move(user));
|
||||
}
|
||||
|
||||
template<typename CALLABLE, typename T, typename ... ARGS,
|
||||
@@ -501,13 +425,12 @@ template<typename CALLABLE, typename T, typename ... ARGS,
|
||||
JobSystem::Job* createJob(JobSystem& js, JobSystem::Job* parent,
|
||||
CALLABLE&& func, T&& o, ARGS&&... args) noexcept {
|
||||
struct Data {
|
||||
explicit Data(std::function<void()> f) noexcept: f(std::move(f)) {}
|
||||
std::function<void()> f;
|
||||
// Renaming the method below could cause an Arrested Development.
|
||||
void gob(JobSystem&, JobSystem::Job*) noexcept { f(); }
|
||||
};
|
||||
return js.emplaceJob<Data, &Data::gob>(parent,
|
||||
std::bind(std::forward<CALLABLE>(func), std::forward<T>(o), std::forward<ARGS>(args)...));
|
||||
} user{ std::bind(std::forward<CALLABLE>(func), std::forward<T>(o),
|
||||
std::forward<ARGS>(args)...) };
|
||||
return js.createJob<Data, &Data::gob>(parent, std::move(user));
|
||||
}
|
||||
|
||||
|
||||
@@ -536,8 +459,8 @@ struct ParallelForJobData {
|
||||
right_side:
|
||||
if (splitter.split(splits, count)) {
|
||||
const size_type lc = count / 2;
|
||||
JobSystem::Job* l = js.emplaceJob<JobData, &JobData::parallelWithJobs>(parent,
|
||||
start, lc, splits + uint8_t(1), functor, splitter);
|
||||
JobData ld(start, lc, splits + uint8_t(1), functor, splitter);
|
||||
JobSystem::Job* l = js.createJob<JobData, &JobData::parallelWithJobs>(parent, std::move(ld));
|
||||
if (UTILS_UNLIKELY(l == nullptr)) {
|
||||
// couldn't create a job, just pretend we're done splitting
|
||||
goto execute;
|
||||
@@ -545,7 +468,7 @@ right_side:
|
||||
|
||||
// start the left side before attempting the right side, so we parallelize in case
|
||||
// of job creation failure -- rare, but still.
|
||||
js.run(l, JobSystem::getThreadId(parent));
|
||||
js.run(l);
|
||||
|
||||
// don't spawn a job for the right side, just reuse us -- spawning jobs is more
|
||||
// costly than we'd like.
|
||||
@@ -577,8 +500,8 @@ template<typename S, typename F>
|
||||
JobSystem::Job* parallel_for(JobSystem& js, JobSystem::Job* parent,
|
||||
uint32_t start, uint32_t count, F functor, const S& splitter) noexcept {
|
||||
using JobData = details::ParallelForJobData<S, F>;
|
||||
return js.emplaceJob<JobData, &JobData::parallelWithJobs>(parent,
|
||||
start, count, 0, std::move(functor), splitter);
|
||||
JobData jobData(start, count, 0, std::move(functor), splitter);
|
||||
return js.createJob<JobData, &JobData::parallelWithJobs>(parent, std::move(jobData));
|
||||
}
|
||||
|
||||
// parallel jobs with pointer/count
|
||||
@@ -589,8 +512,8 @@ JobSystem::Job* parallel_for(JobSystem& js, JobSystem::Job* parent,
|
||||
f(data + s, c);
|
||||
};
|
||||
using JobData = details::ParallelForJobData<S, decltype(user)>;
|
||||
return js.emplaceJob<JobData, &JobData::parallelWithJobs>(parent,
|
||||
0, count, 0, std::move(user), splitter);
|
||||
JobData jobData(0, count, 0, std::move(user), splitter);
|
||||
return js.createJob<JobData, &JobData::parallelWithJobs>(parent, std::move(jobData));
|
||||
}
|
||||
|
||||
// parallel jobs on a Slice<>
|
||||
@@ -601,7 +524,7 @@ JobSystem::Job* parallel_for(JobSystem& js, JobSystem::Job* parent,
|
||||
}
|
||||
|
||||
|
||||
template<size_t COUNT, size_t MAX_SPLITS = 12>
|
||||
template <size_t COUNT, size_t MAX_SPLITS = 12>
|
||||
class CountSplitter {
|
||||
public:
|
||||
bool split(size_t splits, size_t count) const noexcept {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#define TNT_UTILS_WORKSTEALINGDEQUEUE_H
|
||||
|
||||
#include <atomic>
|
||||
#include <type_traits>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
@@ -36,13 +35,7 @@ namespace utils {
|
||||
* steal() push(), pop()
|
||||
* any thread main thread
|
||||
*
|
||||
* References:
|
||||
* - This code is largely inspired from
|
||||
* https://blog.molecular-matters.com/2015/09/25/job-system-2-0-lock-free-work-stealing-part-3-going-lock-free/
|
||||
* - other implementations
|
||||
* https://github.com/ConorWilliams/ConcurrentDeque/blob/main/include/riften/deque.hpp
|
||||
* https://github.com/ssbl/concurrent-deque/blob/master/include/deque.hpp
|
||||
* https://github.com/taskflow/work-stealing-queue/blob/master/wsq.hpp
|
||||
*
|
||||
*/
|
||||
template <typename TYPE, size_t COUNT>
|
||||
class WorkStealingDequeue {
|
||||
@@ -124,7 +117,7 @@ TYPE WorkStealingDequeue<TYPE, COUNT>::pop() noexcept {
|
||||
index_t top = mTop.load(std::memory_order_seq_cst);
|
||||
|
||||
if (top < bottom) {
|
||||
// Queue isn't empty, and it's not the last item, just return it, this is the common case.
|
||||
// Queue isn't empty and it's not the last item, just return it, this is the common case.
|
||||
return getItemAt(bottom);
|
||||
}
|
||||
|
||||
@@ -139,13 +132,13 @@ TYPE WorkStealingDequeue<TYPE, COUNT>::pop() noexcept {
|
||||
if (mTop.compare_exchange_strong(top, top + 1,
|
||||
std::memory_order_seq_cst,
|
||||
std::memory_order_relaxed)) {
|
||||
// Success: we stole our last item from ourselves, meaning that a concurrent steal()
|
||||
// success: we stole our last item from ourself, meaning that a concurrent steal()
|
||||
// would have failed.
|
||||
// mTop now equals top + 1, we adjust top to make the queue empty.
|
||||
top++;
|
||||
} else {
|
||||
// Failure: mTop was not equal to top, which means the item was stolen under our feet.
|
||||
// `top` now equals to mTop. Simply discard the item we just popped.
|
||||
// failure: mTop was not equal to top, which means the item was stolen under our feet.
|
||||
// top now equals to mTop. Simply discard the item we just popped.
|
||||
// The queue is now empty.
|
||||
item = TYPE();
|
||||
}
|
||||
@@ -156,7 +149,7 @@ TYPE WorkStealingDequeue<TYPE, COUNT>::pop() noexcept {
|
||||
}
|
||||
|
||||
// std::memory_order_relaxed used because we're not publishing any data.
|
||||
// No concurrent writes to mBottom possible, it's always safe to write mBottom.
|
||||
// no concurrent writes to mBottom possible, it's always safe to write mBottom.
|
||||
mBottom.store(top, std::memory_order_relaxed);
|
||||
return item;
|
||||
}
|
||||
@@ -201,8 +194,6 @@ TYPE WorkStealingDequeue<TYPE, COUNT>::steal() noexcept {
|
||||
}
|
||||
// failure: the item we just tried to steal was pop()'ed under our feet,
|
||||
// simply discard it; nothing to do -- it's okay to try again.
|
||||
// However, item might be corrupted, so it must be trivially destructible
|
||||
static_assert(std::is_trivially_destructible_v<TYPE>);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+111
-116
@@ -23,18 +23,20 @@
|
||||
// when SYSTRACE_TAG_JOBSYSTEM is used, enables even heavier systraces
|
||||
#define HEAVY_SYSTRACE 0
|
||||
|
||||
// enable for catching hangs waiting on a job to finish
|
||||
static constexpr bool DEBUG_FINISH_HANGS = false;
|
||||
|
||||
#include <utils/JobSystem.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/debug.h>
|
||||
#include <utils/Log.h>
|
||||
#include <utils/memalign.h>
|
||||
#include <utils/ostream.h>
|
||||
#include <utils/Panic.h>
|
||||
#include <utils/Systrace.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <condition_variable>
|
||||
@@ -246,62 +248,68 @@ inline bool JobSystem::hasActiveJobs() const noexcept {
|
||||
}
|
||||
|
||||
inline bool JobSystem::hasJobCompleted(JobSystem::Job const* job) noexcept {
|
||||
return (job->runningJobCount.load(std::memory_order_acquire) & JOB_COUNT_MASK) == 0;
|
||||
return job->runningJobCount.load(std::memory_order_acquire) <= 0;
|
||||
}
|
||||
|
||||
inline void JobSystem::wait(std::unique_lock<Mutex>& lock) noexcept {
|
||||
HEAVY_SYSTRACE_CALL();
|
||||
mWaiterCondition.wait(lock);
|
||||
}
|
||||
|
||||
inline uint32_t JobSystem::wait(std::unique_lock<Mutex>& lock, Job* const job) noexcept {
|
||||
HEAVY_SYSTRACE_CALL();
|
||||
// signal we are waiting
|
||||
|
||||
if (hasActiveJobs() || exitRequested()) {
|
||||
return job->runningJobCount.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
uint32_t runningJobCount =
|
||||
job->runningJobCount.fetch_add(1 << WAITER_COUNT_SHIFT, std::memory_order_relaxed);
|
||||
|
||||
if (runningJobCount & JOB_COUNT_MASK) {
|
||||
void JobSystem::wait(std::unique_lock<Mutex>& lock, Job* job) noexcept {
|
||||
if constexpr (!DEBUG_FINISH_HANGS) {
|
||||
mWaiterCondition.wait(lock);
|
||||
} else {
|
||||
do {
|
||||
// we use a pretty long timeout (4s) so we're very confident that the system is hung
|
||||
// and nothing else is happening.
|
||||
std::cv_status status = mWaiterCondition.wait_for(lock,
|
||||
std::chrono::milliseconds(4000));
|
||||
if (status == std::cv_status::no_timeout) {
|
||||
break;
|
||||
}
|
||||
|
||||
// hang debugging...
|
||||
|
||||
// We check of we had active jobs or if the job we're waiting on had completed already.
|
||||
// There is the possibility of a race condition, but our long timeout gives us some
|
||||
// confidence that we're in an incorrect state.
|
||||
|
||||
size_t const id = std::distance(mThreadStates.data(), &getState());
|
||||
auto activeJobs = mActiveJobs.load();
|
||||
|
||||
if (job) {
|
||||
auto runningJobCount = job->runningJobCount.load();
|
||||
FILAMENT_CHECK_POSTCONDITION(runningJobCount > 0)
|
||||
<< "JobSystem(" << this << ", " << unsigned(id) << "): waiting while job "
|
||||
<< job << " has completed and " << activeJobs << " jobs are active!";
|
||||
}
|
||||
|
||||
FILAMENT_CHECK_POSTCONDITION(activeJobs <= 0)
|
||||
<< "JobSystem(" << this << ", " << unsigned(id) << "): waiting while "
|
||||
<< activeJobs << " jobs are active!";
|
||||
|
||||
} while (true);
|
||||
}
|
||||
|
||||
runningJobCount =
|
||||
job->runningJobCount.fetch_sub(1 << WAITER_COUNT_SHIFT, std::memory_order_acquire);
|
||||
|
||||
assert_invariant((runningJobCount >> WAITER_COUNT_SHIFT) >= 1);
|
||||
|
||||
return runningJobCount;
|
||||
}
|
||||
|
||||
UTILS_NOINLINE
|
||||
void JobSystem::wakeAll() noexcept {
|
||||
// wakeAll() is called when a job finishes (to wake up any thread that might be waiting on it)
|
||||
SYSTRACE_CALL();
|
||||
mWaiterLock.lock();
|
||||
HEAVY_SYSTRACE_CALL();
|
||||
std::lock_guard<Mutex> const lock(mWaiterLock);
|
||||
// this empty critical section is needed -- it guarantees that notify_all() happens
|
||||
// either before the condition is checked, or after the condition variable sleeps.
|
||||
mWaiterLock.unlock();
|
||||
// notify_all() can be pretty slow, and it doesn't need to be inside the lock.
|
||||
// after the condition's variables are set.
|
||||
mWaiterCondition.notify_all();
|
||||
}
|
||||
|
||||
void JobSystem::wakeOne() noexcept {
|
||||
// wakeOne() is called when a new job is added to a queue
|
||||
void JobSystem::wake(size_t hint) noexcept {
|
||||
HEAVY_SYSTRACE_CALL();
|
||||
mWaiterLock.lock();
|
||||
// this empty critical section is needed -- it guarantees that notify_one() happens
|
||||
// either before the condition is checked, or after the condition variable sleeps.
|
||||
mWaiterLock.unlock();
|
||||
// notify_one() can be pretty slow, and it doesn't need to be inside the lock.
|
||||
mWaiterCondition.notify_one();
|
||||
std::lock_guard<Mutex> const lock(mWaiterLock);
|
||||
// this empty critical section is needed -- it guarantees that notify_all() happens
|
||||
// after the condition's variables are set.
|
||||
if (hint == 1) {
|
||||
mWaiterCondition.notify_one();
|
||||
} else {
|
||||
mWaiterCondition.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
inline JobSystem::ThreadState& JobSystem::getState() noexcept {
|
||||
std::lock_guard<Mutex> const lock(mThreadMapLock);
|
||||
std::lock_guard<utils::Mutex> const lock(mThreadMapLock);
|
||||
auto iter = mThreadMap.find(std::this_thread::get_id());
|
||||
FILAMENT_CHECK_PRECONDITION(iter != mThreadMap.end()) << "This thread has not been adopted.";
|
||||
return *iter->second;
|
||||
@@ -316,36 +324,58 @@ void JobSystem::put(WorkQueue& workQueue, Job* job) noexcept {
|
||||
size_t const index = job - mJobStorageBase;
|
||||
assert(index >= 0 && index < MAX_JOB_COUNT);
|
||||
|
||||
// put the job into the queue
|
||||
// put the job into the queue first
|
||||
workQueue.push(uint16_t(index + 1));
|
||||
|
||||
// increase our active job count (the order in which we're doing this must not matter
|
||||
// because we're not using std::memory_order_seq_cst (here or in WorkQueue::push()).
|
||||
mActiveJobs.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
// Note: it's absolutely possible for mActiveJobs to be 0 here, because the job could have
|
||||
// been handled by a zealous worker already. In that case we could avoid calling wakeOne(),
|
||||
// but that is not the common case.
|
||||
|
||||
wakeOne();
|
||||
// then increase our active job count
|
||||
int32_t const oldActiveJobs = mActiveJobs.fetch_add(1, std::memory_order_relaxed);
|
||||
// But it's possible that the job has already been picked-up, so oldActiveJobs could be
|
||||
// negative for instance. We signal only if that's not the case.
|
||||
if (oldActiveJobs >= 0) {
|
||||
wake(oldActiveJobs + 1); // wake-up a thread if needed...
|
||||
}
|
||||
}
|
||||
|
||||
JobSystem::Job* JobSystem::pop(WorkQueue& workQueue) noexcept {
|
||||
// decrement mActiveJobs first, this is to ensure that if there is only a single job left
|
||||
// (and we're about to pick it up), other threads don't loop trying to do the same.
|
||||
mActiveJobs.fetch_sub(1, std::memory_order_relaxed);
|
||||
|
||||
size_t const index = workQueue.pop();
|
||||
assert(index <= MAX_JOB_COUNT);
|
||||
Job* const job = !index ? nullptr : &mJobStorageBase[index - 1];
|
||||
if (UTILS_LIKELY(job)) {
|
||||
mActiveJobs.fetch_sub(1, std::memory_order_relaxed);
|
||||
|
||||
// If our guess was wrong, i.e. we couldn't pick up a job (b/c our queue was empty), we
|
||||
// need to correct mActiveJobs.
|
||||
if (!job) {
|
||||
int32_t const oldActiveJobs = mActiveJobs.fetch_add(1, std::memory_order_relaxed);
|
||||
if (oldActiveJobs >= 0) {
|
||||
// And if there are some active jobs, then we need to wake someone up. We know it
|
||||
// can't be us, because we failed taking a job and we know another thread can't
|
||||
// have added one in our queue.
|
||||
wake(oldActiveJobs + 1); // wake-up a thread if needed...
|
||||
}
|
||||
}
|
||||
return job;
|
||||
}
|
||||
|
||||
JobSystem::Job* JobSystem::steal(WorkQueue& workQueue) noexcept {
|
||||
// decrement mActiveJobs first, this is to ensure that if there is only a single job left
|
||||
// (and we're about to pick it up), other threads don't loop trying to do the same.
|
||||
mActiveJobs.fetch_sub(1, std::memory_order_relaxed);
|
||||
|
||||
size_t const index = workQueue.steal();
|
||||
assert_invariant(index <= MAX_JOB_COUNT);
|
||||
assert(index <= MAX_JOB_COUNT);
|
||||
Job* const job = !index ? nullptr : &mJobStorageBase[index - 1];
|
||||
if (UTILS_LIKELY(job)) {
|
||||
mActiveJobs.fetch_sub(1, std::memory_order_relaxed);
|
||||
|
||||
// If we failed taking a job, we need to correct mActiveJobs.
|
||||
if (!job) {
|
||||
int32_t const oldActiveJobs = mActiveJobs.fetch_add(1, std::memory_order_relaxed);
|
||||
if (oldActiveJobs >= 0) {
|
||||
// And if there are some active jobs, then we need to wake someone up. We know it
|
||||
// can't be us, because we failed taking a job and we know another thread can't
|
||||
// have added one in our queue.
|
||||
wake(oldActiveJobs + 1); // wake-up a thread if needed...
|
||||
}
|
||||
}
|
||||
return job;
|
||||
}
|
||||
@@ -377,7 +407,7 @@ JobSystem::Job* JobSystem::steal(JobSystem::ThreadState& state) noexcept {
|
||||
Job* job = nullptr;
|
||||
do {
|
||||
ThreadState* const stateToStealFrom = getStateToStealFrom(state);
|
||||
if (stateToStealFrom) {
|
||||
if (UTILS_LIKELY(stateToStealFrom)) {
|
||||
job = steal(stateToStealFrom->workQueue);
|
||||
}
|
||||
// nullptr -> nothing to steal in that queue either, if there are active jobs,
|
||||
@@ -390,23 +420,17 @@ bool JobSystem::execute(JobSystem::ThreadState& state) noexcept {
|
||||
HEAVY_SYSTRACE_CALL();
|
||||
|
||||
Job* job = pop(state.workQueue);
|
||||
|
||||
// It is beneficial for some benchmarks to poll on steal() for a bit, because going back to
|
||||
// sleep and waking up is pretty expensive. However, it is unclear it helps in practice with
|
||||
// larger jobs or when parallel_for is used.
|
||||
constexpr size_t const STEAL_TRY_COUNT = 1;
|
||||
for (size_t i = 0; UTILS_UNLIKELY(!job && i < STEAL_TRY_COUNT); i++) {
|
||||
if (UTILS_UNLIKELY(job == nullptr)) {
|
||||
// our queue is empty, try to steal a job
|
||||
job = steal(state);
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(job)) {
|
||||
assert((job->runningJobCount.load(std::memory_order_relaxed) & JOB_COUNT_MASK) >= 1);
|
||||
if (job) {
|
||||
assert(job->runningJobCount.load(std::memory_order_relaxed) >= 1);
|
||||
|
||||
if (UTILS_LIKELY(job->function)) {
|
||||
HEAVY_SYSTRACE_NAME("job->function");
|
||||
job->id = std::distance(mThreadStates.data(), &state);
|
||||
job->function(job->storage, *this, job);
|
||||
job->id = invalidThreadId;
|
||||
}
|
||||
finish(job);
|
||||
}
|
||||
@@ -418,10 +442,9 @@ void JobSystem::loop(ThreadState* state) noexcept {
|
||||
setThreadPriority(Priority::DISPLAY);
|
||||
|
||||
// record our work queue
|
||||
std::unique_lock<Mutex> lock(mThreadMapLock);
|
||||
mThreadMapLock.lock();
|
||||
bool const inserted = mThreadMap.emplace(std::this_thread::get_id(), state).second;
|
||||
lock.unlock();
|
||||
|
||||
mThreadMapLock.unlock();
|
||||
FILAMENT_CHECK_PRECONDITION(inserted) << "This thread is already in a loop.";
|
||||
|
||||
// run our main loop...
|
||||
@@ -446,16 +469,11 @@ void JobSystem::finish(Job* job) noexcept {
|
||||
do {
|
||||
// std::memory_order_release here is needed to synchronize with JobSystem::wait()
|
||||
// which needs to "see" all changes that happened before the job terminated.
|
||||
uint32_t const v = job->runningJobCount.fetch_sub(1, std::memory_order_acq_rel);
|
||||
uint32_t const runningJobCount = v & JOB_COUNT_MASK;
|
||||
auto runningJobCount = job->runningJobCount.fetch_sub(1, std::memory_order_acq_rel);
|
||||
assert(runningJobCount > 0);
|
||||
|
||||
if (runningJobCount == 1) {
|
||||
// no more work, destroy this job and notify its parent
|
||||
uint32_t const waiters = v >> WAITER_COUNT_SHIFT;
|
||||
if (waiters) {
|
||||
notify = true;
|
||||
}
|
||||
notify = true;
|
||||
Job* const parent = job->parent == 0x7FFF ? nullptr : &storage[job->parent];
|
||||
decRef(job);
|
||||
job = parent;
|
||||
@@ -466,8 +484,7 @@ void JobSystem::finish(Job* job) noexcept {
|
||||
} while (job);
|
||||
|
||||
// wake-up all threads that could potentially be waiting on this job finishing
|
||||
if (UTILS_UNLIKELY(notify)) {
|
||||
// but avoid calling notify_all() at all cost, because it's always expensive
|
||||
if (notify) {
|
||||
wakeAll();
|
||||
}
|
||||
}
|
||||
@@ -485,11 +502,10 @@ JobSystem::Job* JobSystem::create(JobSystem::Job* parent, JobFunc func) noexcept
|
||||
// add a reference to the parent to make sure it can't be terminated.
|
||||
// memory_order_relaxed is safe because no action is taken at this point
|
||||
// (the job is not started yet).
|
||||
UTILS_UNUSED_IN_RELEASE auto const parentJobCount =
|
||||
parent->runningJobCount.fetch_add(1, std::memory_order_relaxed);
|
||||
auto parentJobCount = parent->runningJobCount.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
// can't create a child job of a terminated parent
|
||||
assert((parentJobCount & JOB_COUNT_MASK) > 0);
|
||||
assert(parentJobCount > 0);
|
||||
|
||||
index = parent - mJobStorageBase;
|
||||
assert(index < MAX_JOB_COUNT);
|
||||
@@ -516,6 +532,10 @@ void JobSystem::release(JobSystem::Job*& job) noexcept {
|
||||
job = nullptr;
|
||||
}
|
||||
|
||||
void JobSystem::signal() noexcept {
|
||||
wakeAll();
|
||||
}
|
||||
|
||||
void JobSystem::run(Job*& job) noexcept {
|
||||
HEAVY_SYSTRACE_CALL();
|
||||
|
||||
@@ -527,18 +547,6 @@ void JobSystem::run(Job*& job) noexcept {
|
||||
job = nullptr;
|
||||
}
|
||||
|
||||
void JobSystem::run(Job*& job, uint8_t id) noexcept {
|
||||
HEAVY_SYSTRACE_CALL();
|
||||
|
||||
ThreadState& state = mThreadStates[id];
|
||||
assert_invariant(&state == &getState());
|
||||
|
||||
put(state.workQueue, job);
|
||||
|
||||
// after run() returns, the job is virtually invalid (it'll die on its own)
|
||||
job = nullptr;
|
||||
}
|
||||
|
||||
JobSystem::Job* JobSystem::runAndRetain(Job* job) noexcept {
|
||||
JobSystem::Job* retained = retain(job);
|
||||
run(job);
|
||||
@@ -553,7 +561,7 @@ void JobSystem::waitAndRelease(Job*& job) noexcept {
|
||||
|
||||
ThreadState& state(getState());
|
||||
do {
|
||||
if (UTILS_UNLIKELY(!execute(state))) {
|
||||
if (!execute(state)) {
|
||||
// test if job has completed first, to possibly avoid taking the lock
|
||||
if (hasJobCompleted(job)) {
|
||||
break;
|
||||
@@ -569,23 +577,11 @@ void JobSystem::waitAndRelease(Job*& job) noexcept {
|
||||
// continue to handle more jobs, as they get added.
|
||||
|
||||
std::unique_lock<Mutex> lock(mWaiterLock);
|
||||
uint32_t const runningJobCount = wait(lock, job);
|
||||
// we could be waking up because either:
|
||||
// - the job we're waiting on has completed
|
||||
// - more jobs where added to the JobSystem
|
||||
// - we're asked to exit
|
||||
if ((runningJobCount & JOB_COUNT_MASK) == 0 || exitRequested()) {
|
||||
break;
|
||||
if (!hasJobCompleted(job) && !hasActiveJobs() && !exitRequested()) {
|
||||
wait(lock, job);
|
||||
}
|
||||
|
||||
// if we get here, it means that
|
||||
// - the job we're waiting on is still running, and
|
||||
// - we're not asked to exit, and
|
||||
// - there were some active jobs
|
||||
// So we try to handle one.
|
||||
continue;
|
||||
}
|
||||
} while (UTILS_LIKELY(!hasJobCompleted(job) && !exitRequested()));
|
||||
} while (!hasJobCompleted(job) && !exitRequested());
|
||||
|
||||
if (job == mRootJob) {
|
||||
mRootJob = nullptr;
|
||||
@@ -595,7 +591,6 @@ void JobSystem::waitAndRelease(Job*& job) noexcept {
|
||||
}
|
||||
|
||||
void JobSystem::runAndWait(JobSystem::Job*& job) noexcept {
|
||||
SYSTRACE_CALL();
|
||||
runAndRetain(job);
|
||||
waitAndRelease(job);
|
||||
}
|
||||
@@ -603,7 +598,7 @@ void JobSystem::runAndWait(JobSystem::Job*& job) noexcept {
|
||||
void JobSystem::adopt() {
|
||||
const auto tid = std::this_thread::get_id();
|
||||
|
||||
std::unique_lock<Mutex> lock(mThreadMapLock);
|
||||
std::unique_lock<utils::Mutex> lock(mThreadMapLock);
|
||||
auto iter = mThreadMap.find(tid);
|
||||
ThreadState* const state = iter == mThreadMap.end() ? nullptr : iter->second;
|
||||
lock.unlock();
|
||||
@@ -636,7 +631,7 @@ void JobSystem::adopt() {
|
||||
|
||||
void JobSystem::emancipate() {
|
||||
const auto tid = std::this_thread::get_id();
|
||||
std::unique_lock<Mutex> const lock(mThreadMapLock);
|
||||
std::lock_guard<utils::Mutex> const lock(mThreadMapLock);
|
||||
auto iter = mThreadMap.find(tid);
|
||||
ThreadState* const state = iter == mThreadMap.end() ? nullptr : iter->second;
|
||||
FILAMENT_CHECK_PRECONDITION(state) << "this thread is not an adopted thread";
|
||||
|
||||
+14
-48
@@ -58,7 +58,6 @@
|
||||
|
||||
#include <cgltf.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
@@ -509,55 +508,30 @@ static void onClick(App& app, View* view, ImVec2 pos) {
|
||||
});
|
||||
}
|
||||
|
||||
static utils::Path getPathForIBLAsset(std::string_view string) {
|
||||
auto isIBL = [] (utils::Path file) -> bool {
|
||||
return file.getExtension() == "ktx" || file.getExtension() == "hdr";
|
||||
};
|
||||
|
||||
static utils::Path getPathForAsset(std::string_view string) {
|
||||
utils::Path filename{ string };
|
||||
if (!filename.exists()) {
|
||||
std::cerr << "file " << filename << " not found!" << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (filename.isDirectory()) {
|
||||
std::vector<Path> files = filename.listContents();
|
||||
if (std::none_of(files.cbegin(), files.cend(), isIBL)) {
|
||||
auto files = filename.listContents();
|
||||
for (const auto& file: files) {
|
||||
if (file.getExtension() == "gltf" || file.getExtension() == "glb") {
|
||||
filename = file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (filename.isDirectory()) {
|
||||
std::cerr << "no glTF file found in " << filename << std::endl;
|
||||
return {};
|
||||
}
|
||||
} else if (!isIBL(filename)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
static utils::Path getPathForGLTFAsset(std::string_view string) {
|
||||
auto isGLTF = [] (utils::Path file) -> bool {
|
||||
return file.getExtension() == "gltf" || file.getExtension() == "glb";
|
||||
};
|
||||
|
||||
utils::Path filename{ string };
|
||||
if (!filename.exists()) {
|
||||
std::cerr << "file " << filename << " not found!" << std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (filename.isDirectory()) {
|
||||
std::vector<Path> files = filename.listContents();
|
||||
auto it = std::find_if(files.cbegin(), files.cend(), isGLTF);
|
||||
if (it == files.end()) {
|
||||
return {};
|
||||
}
|
||||
filename = *it;
|
||||
} else if (!isGLTF(filename)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
static bool checkGLTFAsset(const utils::Path& filename) {
|
||||
static bool checkAsset(const utils::Path& filename) {
|
||||
// Peek at the file size to allow pre-allocation.
|
||||
long const contentSize = static_cast<long>(getFileSize(filename.c_str()));
|
||||
if (contentSize <= 0) {
|
||||
@@ -597,9 +571,8 @@ int main(int argc, char** argv) {
|
||||
utils::Path filename;
|
||||
int const num_args = argc - optionIndex;
|
||||
if (num_args >= 1) {
|
||||
filename = getPathForGLTFAsset(argv[optionIndex]);
|
||||
filename = getPathForAsset(argv[optionIndex]);
|
||||
if (filename.isEmpty()) {
|
||||
std::cerr << "no glTF file found in " << filename << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1152,9 +1125,9 @@ int main(int argc, char** argv) {
|
||||
filamentApp.resize(resize);
|
||||
|
||||
filamentApp.setDropHandler([&](std::string_view path) {
|
||||
utils::Path filename = getPathForGLTFAsset(path);
|
||||
utils::Path const filename = getPathForAsset(path);
|
||||
if (!filename.isEmpty()) {
|
||||
if (checkGLTFAsset(filename)) {
|
||||
if (checkAsset(filename)) {
|
||||
app.resourceLoader->asyncCancelLoad();
|
||||
app.resourceLoader->evictResourceData();
|
||||
app.viewer->removeAsset();
|
||||
@@ -1163,13 +1136,6 @@ int main(int argc, char** argv) {
|
||||
loadResources(filename);
|
||||
app.viewer->setAsset(app.asset, app.instance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
filename = getPathForIBLAsset(path);
|
||||
if (!filename.isEmpty()) {
|
||||
FilamentApp::get().loadIBL(path);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -202,9 +202,9 @@ int main(int argc, char** argv) {
|
||||
.culling(false)
|
||||
.receiveShadows(false)
|
||||
.castShadows(false)
|
||||
.morphing(app.mt1)
|
||||
.morphing(0, 0, 0)
|
||||
.morphing(0, 1, 9)
|
||||
.morphing(3)
|
||||
.morphing(0,0,app.mt1, 0, app.mt1->getCount())
|
||||
.morphing(0,1,app.mt1, 9, app.mt1->getCount())
|
||||
.build(*engine, app.renderable);
|
||||
|
||||
scene->addEntity(app.renderable);
|
||||
|
||||
+85
-89
@@ -511,27 +511,26 @@ int main(int argc, char** argv) {
|
||||
// primitive 2 = triangle, no skinning, no morph target, buffer objects enabled
|
||||
// primitive 3 = triangle, no skinning, morph target, buffer objects enabled
|
||||
app.renderables[0] = EntityManager::get().create();
|
||||
RenderableManager::Builder(4)
|
||||
.boundingBox({{ -1, -1, -1 },
|
||||
{ 1, 1, 1 }})
|
||||
.material(0, app.mat->getDefaultInstance())
|
||||
.material(1, app.mat->getDefaultInstance())
|
||||
.material(2, app.mat->getDefaultInstance())
|
||||
.material(3, app.mat->getDefaultInstance())
|
||||
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[0], app.ib, 0, 3)
|
||||
.geometry(1, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[0], app.ib, 0, 3)
|
||||
.geometry(2, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[1], app.ib, 0, 3)
|
||||
.geometry(3, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[1], app.ib, 0, 3)
|
||||
.culling(false)
|
||||
.receiveShadows(false)
|
||||
.castShadows(false)
|
||||
.morphing(app.mt)
|
||||
.morphing(0, 1, 0)
|
||||
.morphing(0, 3, 0)
|
||||
RenderableManager::Builder(4)
|
||||
.boundingBox({{ -1, -1, -1}, { 1, 1, 1}})
|
||||
.material(0, app.mat->getDefaultInstance())
|
||||
.material(1, app.mat->getDefaultInstance())
|
||||
.material(2, app.mat->getDefaultInstance())
|
||||
.material(3, app.mat->getDefaultInstance())
|
||||
.geometry(0,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[0],app.ib,0,3)
|
||||
.geometry(1,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[0],app.ib,0,3)
|
||||
.geometry(2,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[1],app.ib,0,3)
|
||||
.geometry(3,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[1],app.ib,0,3)
|
||||
.culling(false)
|
||||
.receiveShadows(false)
|
||||
.castShadows(false)
|
||||
.morphing(3)
|
||||
.morphing(0,1,app.mt)
|
||||
.morphing(0,3,app.mt)
|
||||
.build(*engine, app.renderables[0]);
|
||||
|
||||
// renderable 1: attribute bone data definitions skinning
|
||||
@@ -540,29 +539,28 @@ int main(int argc, char** argv) {
|
||||
// primitive 3 = triangle with skinning, bone data defined as vertex attributes (buffer object)
|
||||
// primitive 2 = triangle with skinning and with morphing, bone data defined as vertex attributes
|
||||
app.renderables[1] = EntityManager::get().create();
|
||||
RenderableManager::Builder(4)
|
||||
.boundingBox({{ -1, -1, -1 },
|
||||
{ 1, 1, 1 }})
|
||||
.material(0, app.mat->getDefaultInstance())
|
||||
.material(1, app.mat->getDefaultInstance())
|
||||
.material(2, app.mat->getDefaultInstance())
|
||||
.material(3, app.mat->getDefaultInstance())
|
||||
.geometry(1, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[2], app.ib, 0, 3)
|
||||
.geometry(2, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[2], app.ib, 0, 3)
|
||||
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[3], app.ib, 0, 3)
|
||||
.geometry(3, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[3], app.ib, 0, 3)
|
||||
.culling(false)
|
||||
.receiveShadows(false)
|
||||
.castShadows(false)
|
||||
.enableSkinningBuffers(true)
|
||||
.skinning(app.sb, 9, 0)
|
||||
.morphing(app.mt)
|
||||
.morphing(0, 2, 0)
|
||||
.morphing(0, 0, 0)
|
||||
RenderableManager::Builder(4)
|
||||
.boundingBox({{ -1, -1, -1}, { 1, 1, 1}})
|
||||
.material(0, app.mat->getDefaultInstance())
|
||||
.material(1, app.mat->getDefaultInstance())
|
||||
.material(2, app.mat->getDefaultInstance())
|
||||
.material(3, app.mat->getDefaultInstance())
|
||||
.geometry(1,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[2],app.ib,0,3)
|
||||
.geometry(2,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[2],app.ib,0,3)
|
||||
.geometry(0,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[3],app.ib,0,3)
|
||||
.geometry(3,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[3],app.ib,0,3)
|
||||
.culling(false)
|
||||
.receiveShadows(false)
|
||||
.castShadows(false)
|
||||
.enableSkinningBuffers(true)
|
||||
.skinning(app.sb, 9, 0)
|
||||
.morphing(3)
|
||||
.morphing(0,2,app.mt)
|
||||
.morphing(0,0,app.mt)
|
||||
.build(*engine, app.renderables[1]);
|
||||
|
||||
// renderable 2: various ways of skinning definitions
|
||||
@@ -571,31 +569,30 @@ int main(int argc, char** argv) {
|
||||
// primitive 2 = triangle with skinning and with morphing, advanced bone data
|
||||
// defined as vector per primitive
|
||||
app.renderables[2] = EntityManager::get().create();
|
||||
RenderableManager::Builder(3)
|
||||
.boundingBox({{ -1, -1, -1 },
|
||||
{ 1, 1, 1 }})
|
||||
.material(0, app.mat->getDefaultInstance())
|
||||
.material(1, app.mat->getDefaultInstance())
|
||||
.material(2, app.mat->getDefaultInstance())
|
||||
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[4], app.ib, 0, 3)
|
||||
.geometry(1, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[5], app.ib, 0, 3)
|
||||
.geometry(2, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[6], app.ib, 0, 3)
|
||||
.culling(false)
|
||||
.receiveShadows(false)
|
||||
.castShadows(false)
|
||||
.enableSkinningBuffers(true)
|
||||
.skinning(app.sb, 9, 0)
|
||||
RenderableManager::Builder(3)
|
||||
.boundingBox({{ -1, -1, -1}, { 1, 1, 1}})
|
||||
.material(0, app.mat->getDefaultInstance())
|
||||
.material(1, app.mat->getDefaultInstance())
|
||||
.material(2, app.mat->getDefaultInstance())
|
||||
.geometry(0,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[4], app.ib, 0, 3)
|
||||
.geometry(1,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[5], app.ib, 0, 3)
|
||||
.geometry(2,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[6], app.ib, 0, 3)
|
||||
.culling(false)
|
||||
.receiveShadows(false)
|
||||
.castShadows(false)
|
||||
.enableSkinningBuffers(true)
|
||||
.skinning(app.sb, 9, 0)
|
||||
|
||||
.boneIndicesAndWeights(0, boneDataArray,
|
||||
3 * app.bonesPerVertex, app.bonesPerVertex)
|
||||
.boneIndicesAndWeights(1, app.boneDataPerPrimitive)
|
||||
.boneIndicesAndWeights(2, app.boneDataPerPrimitive)
|
||||
.boneIndicesAndWeights(0, boneDataArray,
|
||||
3 * app.bonesPerVertex, app.bonesPerVertex)
|
||||
.boneIndicesAndWeights(1, app.boneDataPerPrimitive)
|
||||
.boneIndicesAndWeights(2, app.boneDataPerPrimitive)
|
||||
|
||||
.morphing(app.mt)
|
||||
.morphing(0, 2, 0)
|
||||
.morphing(3)
|
||||
.morphing(0, 2, app.mt)
|
||||
.build(*engine, app.renderables[2]);
|
||||
|
||||
// renderable 3: combination attribute and advance bone data
|
||||
@@ -605,28 +602,27 @@ int main(int argc, char** argv) {
|
||||
// primitive 2 = triangle with skinning and morphing, advanced bone data defined
|
||||
// as vector per primitive
|
||||
app.renderables[3] = EntityManager::get().create();
|
||||
RenderableManager::Builder(3)
|
||||
.boundingBox({{ -1, -1, -1 },
|
||||
{ 1, 1, 1 }})
|
||||
.material(0, app.mat->getDefaultInstance())
|
||||
.material(1, app.mat->getDefaultInstance())
|
||||
.material(2, app.mat->getDefaultInstance())
|
||||
.geometry(0, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[2], app.ib, 0, 3)
|
||||
.geometry(1, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[7], app.ib2, 0, 6)
|
||||
.geometry(2, RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[8], app.ib, 0, 3)
|
||||
.culling(false)
|
||||
.receiveShadows(false)
|
||||
.castShadows(false)
|
||||
.enableSkinningBuffers(true)
|
||||
.skinning(app.sb, 9, 0)
|
||||
.boneIndicesAndWeights(1, app.boneDataPerPrimitiveMulti)
|
||||
.boneIndicesAndWeights(2, app.boneDataPerPrimitive)
|
||||
.morphing(app.mt)
|
||||
.morphing(0, 0, 0)
|
||||
.morphing(0, 2, 0)
|
||||
RenderableManager::Builder(3)
|
||||
.boundingBox({{ -1, -1, -1}, { 1, 1, 1}})
|
||||
.material(0, app.mat->getDefaultInstance())
|
||||
.material(1, app.mat->getDefaultInstance())
|
||||
.material(2, app.mat->getDefaultInstance())
|
||||
.geometry(0,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[2], app.ib, 0, 3)
|
||||
.geometry(1,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[7], app.ib2, 0, 6)
|
||||
.geometry(2,RenderableManager::PrimitiveType::TRIANGLES,
|
||||
app.vbs[8], app.ib, 0, 3)
|
||||
.culling(false)
|
||||
.receiveShadows(false)
|
||||
.castShadows(false)
|
||||
.enableSkinningBuffers(true)
|
||||
.skinning(app.sb, 9, 0)
|
||||
.boneIndicesAndWeights(1, app.boneDataPerPrimitiveMulti)
|
||||
.boneIndicesAndWeights(2, app.boneDataPerPrimitive)
|
||||
.morphing(3)
|
||||
.morphing(0,0,app.mt)
|
||||
.morphing(0,2,app.mt)
|
||||
.build(*engine, app.renderables[3]);
|
||||
|
||||
scene->addEntity(app.renderables[0]);
|
||||
|
||||
Vendored
-2
@@ -103,9 +103,7 @@ cgltf also supports some glTF extensions:
|
||||
- EXT_meshopt_compression
|
||||
- KHR_draco_mesh_compression (requires a library like [Google's Draco](https://github.com/google/draco) for decompression though)
|
||||
- KHR_lights_punctual
|
||||
- KHR_materials_anisotropy
|
||||
- KHR_materials_clearcoat
|
||||
- KHR_materials_dispersion
|
||||
- KHR_materials_emissive_strength
|
||||
- KHR_materials_ior
|
||||
- KHR_materials_iridescence
|
||||
|
||||
Vendored
+264
-562
File diff suppressed because it is too large
Load Diff
Vendored
+26
-154
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* cgltf_write - a single-file glTF 2.0 writer written in C99.
|
||||
*
|
||||
* Version: 1.14
|
||||
* Version: 1.13
|
||||
*
|
||||
* Website: https://github.com/jkuhlmann/cgltf
|
||||
*
|
||||
@@ -15,17 +15,19 @@
|
||||
*
|
||||
* Reference:
|
||||
* `cgltf_result cgltf_write_file(const cgltf_options* options, const char*
|
||||
* path, const cgltf_data* data)` writes a glTF data to the given file path.
|
||||
* If `options->type` is `cgltf_file_type_glb`, both JSON content and binary
|
||||
* buffer of the given glTF data will be written in a GLB format.
|
||||
* Otherwise, only the JSON part will be written.
|
||||
* External buffers and images are not written out. `data` is not deallocated.
|
||||
* path, const cgltf_data* data)` writes JSON to the given file path. Buffer
|
||||
* files and external images are not written out. `data` is not deallocated.
|
||||
*
|
||||
* `cgltf_size cgltf_write(const cgltf_options* options, char* buffer,
|
||||
* cgltf_size size, const cgltf_data* data)` writes JSON into the given memory
|
||||
* buffer. Returns the number of bytes written to `buffer`, including a null
|
||||
* terminator. If buffer is null, returns the number of bytes that would have
|
||||
* been written. `data` is not deallocated.
|
||||
*
|
||||
* To write custom JSON into the `extras` field, aggregate all the custom JSON
|
||||
* into a single buffer, then set `file_data` to this buffer. By supplying
|
||||
* start_offset and end_offset values for various objects, you can select a
|
||||
* range of characters within the aggregated buffer.
|
||||
*/
|
||||
#ifndef CGLTF_WRITE_H_INCLUDED__
|
||||
#define CGLTF_WRITE_H_INCLUDED__
|
||||
@@ -85,8 +87,6 @@ cgltf_size cgltf_write(const cgltf_options* options, char* buffer, cgltf_size si
|
||||
#define CGLTF_EXTENSION_FLAG_MATERIALS_EMISSIVE_STRENGTH (1 << 13)
|
||||
#define CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING (1 << 14)
|
||||
#define CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE (1 << 15)
|
||||
#define CGLTF_EXTENSION_FLAG_MATERIALS_ANISOTROPY (1 << 16)
|
||||
#define CGLTF_EXTENSION_FLAG_MATERIALS_DISPERSION (1 << 17)
|
||||
|
||||
typedef struct {
|
||||
char* buffer;
|
||||
@@ -147,16 +147,6 @@ typedef struct {
|
||||
context->needs_comma = 1; }
|
||||
|
||||
#define CGLTF_WRITE_TEXTURE_INFO(label, info) if (info.texture) { \
|
||||
cgltf_write_line(context, "\"" label "\": {"); \
|
||||
CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \
|
||||
cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \
|
||||
if (info.has_transform) { \
|
||||
context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \
|
||||
cgltf_write_texture_transform(context, &info.transform); \
|
||||
} \
|
||||
cgltf_write_line(context, "}"); }
|
||||
|
||||
#define CGLTF_WRITE_NORMAL_TEXTURE_INFO(label, info) if (info.texture) { \
|
||||
cgltf_write_line(context, "\"" label "\": {"); \
|
||||
CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \
|
||||
cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \
|
||||
@@ -165,29 +155,9 @@ typedef struct {
|
||||
context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \
|
||||
cgltf_write_texture_transform(context, &info.transform); \
|
||||
} \
|
||||
cgltf_write_extras(context, &info.extras); \
|
||||
cgltf_write_line(context, "}"); }
|
||||
|
||||
#define CGLTF_WRITE_OCCLUSION_TEXTURE_INFO(label, info) if (info.texture) { \
|
||||
cgltf_write_line(context, "\"" label "\": {"); \
|
||||
CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \
|
||||
cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \
|
||||
cgltf_write_floatprop(context, "strength", info.scale, 1.0f); \
|
||||
if (info.has_transform) { \
|
||||
context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \
|
||||
cgltf_write_texture_transform(context, &info.transform); \
|
||||
} \
|
||||
cgltf_write_line(context, "}"); }
|
||||
|
||||
#ifndef CGLTF_CONSTS
|
||||
#define GlbHeaderSize 12
|
||||
#define GlbChunkHeaderSize 8
|
||||
static const uint32_t GlbVersion = 2;
|
||||
static const uint32_t GlbMagic = 0x46546C67;
|
||||
static const uint32_t GlbMagicJsonChunk = 0x4E4F534A;
|
||||
static const uint32_t GlbMagicBinChunk = 0x004E4942;
|
||||
#define CGLTF_CONSTS
|
||||
#endif
|
||||
|
||||
static void cgltf_write_indent(cgltf_write_context* context)
|
||||
{
|
||||
if (context->needs_comma)
|
||||
@@ -238,24 +208,15 @@ static void cgltf_write_strprop(cgltf_write_context* context, const char* label,
|
||||
|
||||
static void cgltf_write_extras(cgltf_write_context* context, const cgltf_extras* extras)
|
||||
{
|
||||
if (extras->data)
|
||||
cgltf_size length = extras->end_offset - extras->start_offset;
|
||||
if (length > 0 && context->data->file_data)
|
||||
{
|
||||
char* json_string = ((char*) context->data->file_data) + extras->start_offset;
|
||||
cgltf_write_indent(context);
|
||||
CGLTF_SPRINTF("\"extras\": %s", extras->data);
|
||||
CGLTF_SPRINTF("%s", "\"extras\": ");
|
||||
CGLTF_SNPRINTF(length, "%.*s", (int)(extras->end_offset - extras->start_offset), json_string);
|
||||
context->needs_comma = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
cgltf_size length = extras->end_offset - extras->start_offset;
|
||||
if (length > 0 && context->data->json)
|
||||
{
|
||||
char* json_string = ((char*) context->data->json) + extras->start_offset;
|
||||
cgltf_write_indent(context);
|
||||
CGLTF_SPRINTF("%s", "\"extras\": ");
|
||||
CGLTF_SNPRINTF(length, "%.*s", (int)(extras->end_offset - extras->start_offset), json_string);
|
||||
context->needs_comma = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cgltf_write_stritem(cgltf_write_context* context, const char* item)
|
||||
@@ -359,21 +320,6 @@ static int cgltf_int_from_component_type(cgltf_component_type ctype)
|
||||
}
|
||||
}
|
||||
|
||||
static int cgltf_int_from_primitive_type(cgltf_primitive_type ctype)
|
||||
{
|
||||
switch (ctype)
|
||||
{
|
||||
case cgltf_primitive_type_points: return 0;
|
||||
case cgltf_primitive_type_lines: return 1;
|
||||
case cgltf_primitive_type_line_loop: return 2;
|
||||
case cgltf_primitive_type_line_strip: return 3;
|
||||
case cgltf_primitive_type_triangles: return 4;
|
||||
case cgltf_primitive_type_triangle_strip: return 5;
|
||||
case cgltf_primitive_type_triangle_fan: return 6;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* cgltf_str_from_alpha_mode(cgltf_alpha_mode alpha_mode)
|
||||
{
|
||||
switch (alpha_mode)
|
||||
@@ -469,7 +415,7 @@ static void cgltf_write_asset(cgltf_write_context* context, const cgltf_asset* a
|
||||
|
||||
static void cgltf_write_primitive(cgltf_write_context* context, const cgltf_primitive* prim)
|
||||
{
|
||||
cgltf_write_intprop(context, "mode", cgltf_int_from_primitive_type(prim->type), 4);
|
||||
cgltf_write_intprop(context, "mode", (int) prim->type, 4);
|
||||
CGLTF_WRITE_IDXPROP("indices", prim->indices, context->data->accessors);
|
||||
CGLTF_WRITE_IDXPROP("material", prim->material, context->data->materials);
|
||||
cgltf_write_line(context, "\"attributes\": {");
|
||||
@@ -655,16 +601,6 @@ static void cgltf_write_material(cgltf_write_context* context, const cgltf_mater
|
||||
context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE;
|
||||
}
|
||||
|
||||
if (material->has_anisotropy)
|
||||
{
|
||||
context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_ANISOTROPY;
|
||||
}
|
||||
|
||||
if (material->has_dispersion)
|
||||
{
|
||||
context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_DISPERSION;
|
||||
}
|
||||
|
||||
if (material->has_pbr_metallic_roughness)
|
||||
{
|
||||
const cgltf_pbr_metallic_roughness* params = &material->pbr_metallic_roughness;
|
||||
@@ -677,10 +613,11 @@ static void cgltf_write_material(cgltf_write_context* context, const cgltf_mater
|
||||
{
|
||||
cgltf_write_floatarrayprop(context, "baseColorFactor", params->base_color_factor, 4);
|
||||
}
|
||||
cgltf_write_extras(context, ¶ms->extras);
|
||||
cgltf_write_line(context, "}");
|
||||
}
|
||||
|
||||
if (material->unlit || material->has_pbr_specular_glossiness || material->has_clearcoat || material->has_ior || material->has_specular || material->has_transmission || material->has_sheen || material->has_volume || material->has_emissive_strength || material->has_iridescence || material->has_anisotropy || material->has_dispersion)
|
||||
if (material->unlit || material->has_pbr_specular_glossiness || material->has_clearcoat || material->has_ior || material->has_specular || material->has_transmission || material->has_sheen || material->has_volume || material->has_emissive_strength || material->has_iridescence)
|
||||
{
|
||||
cgltf_write_line(context, "\"extensions\": {");
|
||||
if (material->has_clearcoat)
|
||||
@@ -689,7 +626,7 @@ static void cgltf_write_material(cgltf_write_context* context, const cgltf_mater
|
||||
cgltf_write_line(context, "\"KHR_materials_clearcoat\": {");
|
||||
CGLTF_WRITE_TEXTURE_INFO("clearcoatTexture", params->clearcoat_texture);
|
||||
CGLTF_WRITE_TEXTURE_INFO("clearcoatRoughnessTexture", params->clearcoat_roughness_texture);
|
||||
CGLTF_WRITE_NORMAL_TEXTURE_INFO("clearcoatNormalTexture", params->clearcoat_normal_texture);
|
||||
CGLTF_WRITE_TEXTURE_INFO("clearcoatNormalTexture", params->clearcoat_normal_texture);
|
||||
cgltf_write_floatprop(context, "clearcoatFactor", params->clearcoat_factor, 0.0f);
|
||||
cgltf_write_floatprop(context, "clearcoatRoughnessFactor", params->clearcoat_roughness_factor, 0.0f);
|
||||
cgltf_write_line(context, "}");
|
||||
@@ -791,27 +728,11 @@ static void cgltf_write_material(cgltf_write_context* context, const cgltf_mater
|
||||
CGLTF_WRITE_TEXTURE_INFO("iridescenceThicknessTexture", params->iridescence_thickness_texture);
|
||||
cgltf_write_line(context, "}");
|
||||
}
|
||||
if (material->has_anisotropy)
|
||||
{
|
||||
cgltf_write_line(context, "\"KHR_materials_anisotropy\": {");
|
||||
const cgltf_anisotropy* params = &material->anisotropy;
|
||||
cgltf_write_floatprop(context, "anisotropyFactor", params->anisotropy_strength, 0.f);
|
||||
cgltf_write_floatprop(context, "anisotropyRotation", params->anisotropy_rotation, 0.f);
|
||||
CGLTF_WRITE_TEXTURE_INFO("anisotropyTexture", params->anisotropy_texture);
|
||||
cgltf_write_line(context, "}");
|
||||
}
|
||||
if (material->has_dispersion)
|
||||
{
|
||||
cgltf_write_line(context, "\"KHR_materials_dispersion\": {");
|
||||
const cgltf_dispersion* params = &material->dispersion;
|
||||
cgltf_write_floatprop(context, "dispersion", params->dispersion, 0.f);
|
||||
cgltf_write_line(context, "}");
|
||||
}
|
||||
cgltf_write_line(context, "}");
|
||||
}
|
||||
|
||||
CGLTF_WRITE_NORMAL_TEXTURE_INFO("normalTexture", material->normal_texture);
|
||||
CGLTF_WRITE_OCCLUSION_TEXTURE_INFO("occlusionTexture", material->occlusion_texture);
|
||||
CGLTF_WRITE_TEXTURE_INFO("normalTexture", material->normal_texture);
|
||||
CGLTF_WRITE_TEXTURE_INFO("occlusionTexture", material->occlusion_texture);
|
||||
CGLTF_WRITE_TEXTURE_INFO("emissiveTexture", material->emissive_texture);
|
||||
if (cgltf_check_floatarray(material->emissive_factor, 3, 0.0f))
|
||||
{
|
||||
@@ -1017,6 +938,7 @@ static void cgltf_write_node(cgltf_write_context* context, const cgltf_node* nod
|
||||
|
||||
cgltf_write_line(context, "\"EXT_mesh_gpu_instancing\": {");
|
||||
{
|
||||
CGLTF_WRITE_IDXPROP("bufferView", node->mesh_gpu_instancing.buffer_view, context->data->buffer_views);
|
||||
cgltf_write_line(context, "\"attributes\": {");
|
||||
{
|
||||
for (cgltf_size i = 0; i < node->mesh_gpu_instancing.attributes_count; ++i)
|
||||
@@ -1083,11 +1005,14 @@ static void cgltf_write_accessor(cgltf_write_context* context, const cgltf_acces
|
||||
cgltf_write_sizeprop(context, "byteOffset", (int)accessor->sparse.indices_byte_offset, 0);
|
||||
CGLTF_WRITE_IDXPROP("bufferView", accessor->sparse.indices_buffer_view, context->data->buffer_views);
|
||||
cgltf_write_intprop(context, "componentType", cgltf_int_from_component_type(accessor->sparse.indices_component_type), 0);
|
||||
cgltf_write_extras(context, &accessor->sparse.indices_extras);
|
||||
cgltf_write_line(context, "}");
|
||||
cgltf_write_line(context, "\"values\": {");
|
||||
cgltf_write_sizeprop(context, "byteOffset", (int)accessor->sparse.values_byte_offset, 0);
|
||||
CGLTF_WRITE_IDXPROP("bufferView", accessor->sparse.values_buffer_view, context->data->buffer_views);
|
||||
cgltf_write_extras(context, &accessor->sparse.values_extras);
|
||||
cgltf_write_line(context, "}");
|
||||
cgltf_write_extras(context, &accessor->sparse.extras);
|
||||
cgltf_write_line(context, "}");
|
||||
}
|
||||
cgltf_write_extras(context, &accessor->extras);
|
||||
@@ -1159,7 +1084,6 @@ static void cgltf_write_light(cgltf_write_context* context, const cgltf_light* l
|
||||
cgltf_write_floatprop(context, "outerConeAngle", light->spot_outer_cone_angle, 3.14159265358979323846f/4.0f);
|
||||
cgltf_write_line(context, "}");
|
||||
}
|
||||
cgltf_write_extras( context, &light->extras );
|
||||
cgltf_write_line(context, "}");
|
||||
}
|
||||
|
||||
@@ -1173,47 +1097,6 @@ static void cgltf_write_variant(cgltf_write_context* context, const cgltf_materi
|
||||
cgltf_write_line(context, "}");
|
||||
}
|
||||
|
||||
static void cgltf_write_glb(FILE* file, const void* json_buf, const cgltf_size json_size, const void* bin_buf, const cgltf_size bin_size)
|
||||
{
|
||||
char header[GlbHeaderSize];
|
||||
char chunk_header[GlbChunkHeaderSize];
|
||||
char json_pad[3] = { 0x20, 0x20, 0x20 };
|
||||
char bin_pad[3] = { 0, 0, 0 };
|
||||
|
||||
cgltf_size json_padsize = (json_size % 4 != 0) ? 4 - json_size % 4 : 0;
|
||||
cgltf_size bin_padsize = (bin_size % 4 != 0) ? 4 - bin_size % 4 : 0;
|
||||
cgltf_size total_size = GlbHeaderSize + GlbChunkHeaderSize + json_size + json_padsize;
|
||||
if (bin_buf != NULL && bin_size > 0) {
|
||||
total_size += GlbChunkHeaderSize + bin_size + bin_padsize;
|
||||
}
|
||||
|
||||
// Write a GLB header
|
||||
memcpy(header, &GlbMagic, 4);
|
||||
memcpy(header + 4, &GlbVersion, 4);
|
||||
memcpy(header + 8, &total_size, 4);
|
||||
fwrite(header, 1, GlbHeaderSize, file);
|
||||
|
||||
// Write a JSON chunk (header & data)
|
||||
uint32_t json_chunk_size = (uint32_t)(json_size + json_padsize);
|
||||
memcpy(chunk_header, &json_chunk_size, 4);
|
||||
memcpy(chunk_header + 4, &GlbMagicJsonChunk, 4);
|
||||
fwrite(chunk_header, 1, GlbChunkHeaderSize, file);
|
||||
|
||||
fwrite(json_buf, 1, json_size, file);
|
||||
fwrite(json_pad, 1, json_padsize, file);
|
||||
|
||||
if (bin_buf != NULL && bin_size > 0) {
|
||||
// Write a binary chunk (header & data)
|
||||
uint32_t bin_chunk_size = (uint32_t)(bin_size + bin_padsize);
|
||||
memcpy(chunk_header, &bin_chunk_size, 4);
|
||||
memcpy(chunk_header + 4, &GlbMagicBinChunk, 4);
|
||||
fwrite(chunk_header, 1, GlbChunkHeaderSize, file);
|
||||
|
||||
fwrite(bin_buf, 1, bin_size, file);
|
||||
fwrite(bin_pad, 1, bin_padsize, file);
|
||||
}
|
||||
}
|
||||
|
||||
cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, const cgltf_data* data)
|
||||
{
|
||||
cgltf_size expected = cgltf_write(options, NULL, 0, data);
|
||||
@@ -1222,18 +1105,13 @@ cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, co
|
||||
if (expected != actual) {
|
||||
fprintf(stderr, "Error: expected %zu bytes but wrote %zu bytes.\n", expected, actual);
|
||||
}
|
||||
FILE* file = fopen(path, "wb");
|
||||
FILE* file = fopen(path, "wt");
|
||||
if (!file)
|
||||
{
|
||||
return cgltf_result_file_not_found;
|
||||
}
|
||||
// Note that cgltf_write() includes a null terminator, which we omit from the file content.
|
||||
if (options->type == cgltf_file_type_glb) {
|
||||
cgltf_write_glb(file, buffer, actual - 1, data->bin, data->bin_size);
|
||||
} else {
|
||||
// Write a plain JSON file.
|
||||
fwrite(buffer, actual - 1, 1, file);
|
||||
}
|
||||
fwrite(buffer, actual - 1, 1, file);
|
||||
fclose(file);
|
||||
free(buffer);
|
||||
return cgltf_result_success;
|
||||
@@ -1286,15 +1164,9 @@ static void cgltf_write_extensions(cgltf_write_context* context, uint32_t extens
|
||||
if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE) {
|
||||
cgltf_write_stritem(context, "KHR_materials_iridescence");
|
||||
}
|
||||
if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_ANISOTROPY) {
|
||||
cgltf_write_stritem(context, "KHR_materials_anisotropy");
|
||||
}
|
||||
if (extension_flags & CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING) {
|
||||
cgltf_write_stritem(context, "EXT_mesh_gpu_instancing");
|
||||
}
|
||||
if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_DISPERSION) {
|
||||
cgltf_write_stritem(context, "KHR_materials_dispersion");
|
||||
}
|
||||
}
|
||||
|
||||
cgltf_size cgltf_write(const cgltf_options* options, char* buffer, cgltf_size size, const cgltf_data* data)
|
||||
|
||||
-13
@@ -41,19 +41,6 @@ else()
|
||||
endif()
|
||||
install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin )
|
||||
|
||||
set( EXE_NAME test_write_glb )
|
||||
add_executable( ${EXE_NAME} test_write_glb.cpp )
|
||||
set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 )
|
||||
if(MSVC)
|
||||
target_compile_options(${EXE_NAME} PRIVATE /W4 /WX)
|
||||
add_definitions( -D_CRT_SECURE_NO_WARNINGS)
|
||||
else()
|
||||
target_compile_options(${EXE_NAME} PRIVATE -Wall -Wextra -pedantic -Werror)
|
||||
target_compile_options(${EXE_NAME} PUBLIC -fsanitize=address)
|
||||
target_link_options(${EXE_NAME} PUBLIC -fsanitize=address)
|
||||
endif()
|
||||
install( TARGETS ${EXE_NAME} RUNTIME DESTINATION bin )
|
||||
|
||||
set( EXE_NAME test_math )
|
||||
add_executable( ${EXE_NAME} test_math.cpp )
|
||||
set_property( TARGET ${EXE_NAME} PROPERTY CXX_STANDARD 11 )
|
||||
|
||||
Vendored
+1
-2
@@ -52,14 +52,13 @@ if __name__ == "__main__":
|
||||
f = open(".git/info/sparse-checkout", "w+")
|
||||
f.write("2.0/*\n")
|
||||
f.close()
|
||||
os.system("git pull --depth=1 origin main")
|
||||
os.system("git pull --depth=1 origin master")
|
||||
os.chdir("..")
|
||||
collect_files("glTF-Sample-Models/2.0/", ".glb", "cgltf_test")
|
||||
collect_files("glTF-Sample-Models/2.0/", ".gltf", "cgltf_test")
|
||||
collect_files("glTF-Sample-Models/2.0/", ".glb", "test_conversion")
|
||||
collect_files("glTF-Sample-Models/2.0/", ".gltf", "test_conversion")
|
||||
collect_files("glTF-Sample-Models/2.0/", ".gltf", "test_write")
|
||||
collect_files("glTF-Sample-Models/2.0/", ".glb", "test_write_glb")
|
||||
|
||||
result = os.system(get_executable_path("test_math"))
|
||||
if result != 0:
|
||||
|
||||
+5
-7
@@ -26,13 +26,11 @@ int main(int argc, char** argv)
|
||||
if (result == cgltf_result_success)
|
||||
result = cgltf_load_buffers(&options, data, argv[1]);
|
||||
|
||||
// Skip files that use mesh compression since they require extra code to decompress accessor data
|
||||
for (size_t i = 0; i < data->extensions_used_count; ++i)
|
||||
if (strcmp(data->extensions_used[i], "KHR_draco_mesh_compression") == 0 || strcmp(data->extensions_used[i], "EXT_meshopt_compression") == 0)
|
||||
{
|
||||
cgltf_free(data);
|
||||
return 0;
|
||||
}
|
||||
if (strstr(argv[1], "Draco"))
|
||||
{
|
||||
cgltf_free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (result != cgltf_result_success)
|
||||
return result;
|
||||
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
#define CGLTF_IMPLEMENTATION
|
||||
#define CGLTF_WRITE_IMPLEMENTATION
|
||||
#include "../cgltf_write.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
printf("err\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cgltf_options options = {};
|
||||
cgltf_data* data0 = NULL;
|
||||
cgltf_result result = cgltf_parse_file(&options, argv[1], &data0);
|
||||
|
||||
// Silently skip over files that are unreadable since this is a writing test.
|
||||
if (result != cgltf_result_success)
|
||||
{
|
||||
return cgltf_result_success;
|
||||
}
|
||||
|
||||
options.type = cgltf_file_type_glb; // Write back in a GLB format
|
||||
result = cgltf_write_file(&options, "out.glb", data0);
|
||||
if (result != cgltf_result_success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
cgltf_data* data1 = NULL;
|
||||
result = cgltf_parse_file(&options, "out.glb", &data1);
|
||||
if (result != cgltf_result_success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (data0->meshes_count != data1->meshes_count) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Compare binary buffers
|
||||
if (data0->bin_size != data1->bin_size) {
|
||||
return -1;
|
||||
}
|
||||
if (memcmp(data0->bin, data1->bin, data0->bin_size) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cgltf_free(data1);
|
||||
cgltf_free(data0);
|
||||
return cgltf_result_success;
|
||||
}
|
||||
Vendored
+2
-8
@@ -1,12 +1,6 @@
|
||||
# See http://editorconfig.org to read about the EditorConfig format.
|
||||
# - In theory automatically supported by VS2017+ and most common IDE or text editors.
|
||||
# - In practice VS2019-VS2022 stills don't trim trailing whitespaces correctly :(
|
||||
# - Suggest installing this to trim whitespaces:
|
||||
# GitHub https://github.com/madskristensen/TrailingWhitespace
|
||||
# VS2019 https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TrailingWhitespaceVisualizer
|
||||
# VS2022 https://marketplace.visualstudio.com/items?itemName=MadsKristensen.TrailingWhitespace64
|
||||
# (in spite of its name doesn't only visualize but also trims)
|
||||
# - Alternative for older VS2010 to VS2015: https://marketplace.visualstudio.com/items?itemName=EditorConfigTeam.EditorConfig
|
||||
# - Automatically supported by VS2017+ and most common IDE or text editors.
|
||||
# - For older VS2010 to VS2015, install https://marketplace.visualstudio.com/items?itemName=EditorConfigTeam.EditorConfig
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
-1
@@ -1 +0,0 @@
|
||||
custom: ['https://github.com/ocornut/imgui/wiki/Funding']
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user