Compare commits
6 Commits
ImmediateG
...
validation
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56050de5cd | ||
|
|
772136decd | ||
|
|
f664601c51 | ||
|
|
f06b27b7fb | ||
|
|
ef53ce88d4 | ||
|
|
ef18030e1a |
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.google.android.filament.utils
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.MotionEvent
|
||||
import android.view.Surface
|
||||
import android.view.SurfaceView
|
||||
@@ -26,6 +29,7 @@ import com.google.android.filament.android.UiHelper
|
||||
import com.google.android.filament.gltfio.*
|
||||
import kotlinx.coroutines.*
|
||||
import java.nio.Buffer
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
private const val kNearPlane = 0.05f // 5 cm
|
||||
private const val kFarPlane = 1000.0f // 1 km
|
||||
@@ -119,6 +123,8 @@ class ModelViewer(
|
||||
private val target = DoubleArray(3)
|
||||
private val upward = DoubleArray(3)
|
||||
|
||||
private var debugFrameCallback: ((Bitmap) -> Unit)? = null
|
||||
|
||||
init {
|
||||
renderer = engine.createRenderer()
|
||||
scene = engine.createScene()
|
||||
@@ -305,10 +311,39 @@ class ModelViewer(
|
||||
// Render the scene, unless the renderer wants to skip the frame.
|
||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
||||
renderer.render(view)
|
||||
|
||||
debugFrameCallback?.let {
|
||||
val viewport = view.viewport
|
||||
val bitmap = Bitmap.createBitmap(viewport.width, viewport.height,
|
||||
Bitmap.Config.ARGB_8888)
|
||||
val buffer = ByteBuffer.allocateDirect(viewport.width * viewport.height * 4)
|
||||
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
val pixelBufferDescriptor = Texture.PixelBufferDescriptor(buffer,
|
||||
Texture.Format.RGBA, Texture.Type.UBYTE, 1, 0, 0, 0, handler) {
|
||||
buffer.rewind()
|
||||
bitmap.copyPixelsFromBuffer(buffer)
|
||||
it(bitmap)
|
||||
}
|
||||
renderer.readPixels(viewport.left, viewport.bottom, viewport.width,
|
||||
viewport.height, pixelBufferDescriptor)
|
||||
debugFrameCallback = null
|
||||
}
|
||||
|
||||
renderer.endFrame()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets a callback that will be invoked with the next rendered frame as a Bitmap. Note that this
|
||||
* is a one-time callback.
|
||||
*
|
||||
* @param callback callback to be invoked with a rendered frame as [Bitmap]
|
||||
*/
|
||||
fun debugGetNextFrameCallback(callback: (Bitmap) -> Unit) {
|
||||
debugFrameCallback = callback
|
||||
}
|
||||
|
||||
private fun populateScene(asset: FilamentAsset) {
|
||||
val rcm = engine.renderableManager
|
||||
var count = 0
|
||||
|
||||
@@ -316,8 +316,6 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
||||
src/webgpu/WebGPURenderPrimitive.h
|
||||
src/webgpu/WebGPURenderTarget.cpp
|
||||
src/webgpu/WebGPURenderTarget.h
|
||||
src/webgpu/WebGPUStagePool.cpp
|
||||
src/webgpu/WebGPUStagePool.h
|
||||
src/webgpu/WebGPUStrings.h
|
||||
src/webgpu/WebGPUSwapChain.cpp
|
||||
src/webgpu/WebGPUSwapChain.h
|
||||
|
||||
@@ -61,6 +61,16 @@ int NativeWindow::enableFrameTimestamps(ANativeWindow* anw, bool enable) {
|
||||
return pWindow->perform(anw, ENABLE_FRAME_TIMESTAMPS, enable);
|
||||
}
|
||||
|
||||
int NativeWindow::frameTimestampsSupportsPresent(ANativeWindow* anw, bool* outSupportsPresent) {
|
||||
NativeWindow const* pWindow = reinterpret_cast<NativeWindow const*>(anw);
|
||||
int value = 0;
|
||||
bool const success = pWindow->perform(anw, FRAME_TIMESTAMPS_SUPPORTS_PRESENT, &value);
|
||||
if (success) {
|
||||
*outSupportsPresent = bool(value);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
int NativeWindow::getCompositorTiming(ANativeWindow* anw,
|
||||
int64_t* compositeDeadline, int64_t* compositeInterval,
|
||||
int64_t* compositeToPresentLatency) {
|
||||
|
||||
@@ -32,6 +32,7 @@ struct NativeWindow {
|
||||
// is valid query enum value
|
||||
enum {
|
||||
IS_VALID = 17,
|
||||
FRAME_TIMESTAMPS_SUPPORTS_PRESENT = 18,
|
||||
GET_NEXT_FRAME_ID = 24,
|
||||
ENABLE_FRAME_TIMESTAMPS = 25,
|
||||
GET_COMPOSITOR_TIMING = 26,
|
||||
@@ -51,6 +52,7 @@ struct NativeWindow {
|
||||
|
||||
static int getNextFrameId(ANativeWindow* anw, uint64_t* frameId);
|
||||
static int enableFrameTimestamps(ANativeWindow* anw, bool enable);
|
||||
static int frameTimestampsSupportsPresent(ANativeWindow* anw, bool* outSupportsPresent);
|
||||
static int getCompositorTiming(ANativeWindow* anw,
|
||||
int64_t* compositeDeadline, int64_t* compositeInterval,
|
||||
int64_t* compositeToPresentLatency);
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
#include <android/native_window.h>
|
||||
|
||||
#include <utils/compiler.h>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
@@ -36,11 +39,23 @@ bool AndroidSwapChainHelper::setPresentFrameId(
|
||||
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
|
||||
if (status == 0) {
|
||||
std::lock_guard const lock(mLock);
|
||||
auto const pos = mFrameIdToSystemFrameId.find(frameId);
|
||||
if (pos && *pos != sysFrameId) {
|
||||
// we're trying to associate the same frame id to a different frame!
|
||||
return false;
|
||||
// frameIds must be strictly monotonic, if that's not the case (i.e. the new frameId is
|
||||
// less or equal to the last one in the map), we have to clear the map, because the
|
||||
// map's find() assume sorted keys.
|
||||
// This case can happen if two different filament::Renderer are used with the same
|
||||
// ANativeWindow (the Renderer would have different frameIds). This is expected to
|
||||
// be a rare case.
|
||||
if (UTILS_UNLIKELY(!mFrameIdToSystemFrameId.empty() &&
|
||||
frameId <= mFrameIdToSystemFrameId.back().first)) {
|
||||
// this log is expected to happen very rarely
|
||||
DLOG(INFO) << "clearing frame history anw=" << anw
|
||||
<< ", frameId=" << frameId
|
||||
<< ", previous=" << mFrameIdToSystemFrameId.back().first
|
||||
<< ", sysFrameId=" << sysFrameId;
|
||||
// clear the frame history
|
||||
mFrameIdToSystemFrameId.clear();
|
||||
}
|
||||
|
||||
// oldest entry is removed
|
||||
mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
|
||||
return true;
|
||||
|
||||
@@ -146,12 +146,12 @@ void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retri
|
||||
|
||||
bool Platform::hasInsertBlobFunc() const noexcept {
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
return bool(mInsertBlob);
|
||||
return mInsertBlob && bool(*mInsertBlob);
|
||||
}
|
||||
|
||||
bool Platform::hasRetrieveBlobFunc() const noexcept {
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
return bool(mRetrieveBlob);
|
||||
return mRetrieveBlob && bool(*mRetrieveBlob);
|
||||
}
|
||||
|
||||
void Platform::insertBlob(void const* key, size_t keySize, void const* value, size_t valueSize) {
|
||||
@@ -184,7 +184,7 @@ void Platform::setDebugUpdateStatFunc(DebugUpdateStatFunc&& debugUpdateStat) noe
|
||||
|
||||
bool Platform::hasDebugUpdateStatFunc() const noexcept {
|
||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||
return mDebugUpdateStat != nullptr;
|
||||
return mDebugUpdateStat && bool(*mDebugUpdateStat);
|
||||
}
|
||||
|
||||
void Platform::debugUpdateStat(const char* key, uint64_t intValue) {
|
||||
|
||||
@@ -165,7 +165,7 @@ public:
|
||||
size_t size, bool forceGpuBuffer = false);
|
||||
~MetalBuffer();
|
||||
|
||||
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; }
|
||||
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer; }
|
||||
|
||||
MetalBuffer(const MetalBuffer& rhs) = delete;
|
||||
MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
|
||||
@@ -185,14 +185,12 @@ public:
|
||||
* Denotes that this buffer is used for a draw call ensuring that its allocation remains valid
|
||||
* until the end of the current frame.
|
||||
*
|
||||
* @return The MTLBuffer representing the current state of the buffer to bind, or nil if there
|
||||
* is no device allocation.
|
||||
* @return The MTLBuffer representing the current state of the buffer to bind, it never returns
|
||||
* nil.
|
||||
*
|
||||
*/
|
||||
id<MTLBuffer> getGpuBufferForDraw() noexcept;
|
||||
|
||||
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
|
||||
|
||||
void setLabel(const utils::ImmutableCString& label) {
|
||||
#if FILAMENT_METAL_DEBUG_LABELS
|
||||
if (label.empty()) {
|
||||
@@ -235,7 +233,6 @@ private:
|
||||
UploadStrategy mUploadStrategy;
|
||||
TrackedMetalBuffer mBuffer;
|
||||
size_t mBufferSize = 0;
|
||||
void* mCpuBuffer = nullptr;
|
||||
MetalContext& mContext;
|
||||
};
|
||||
|
||||
|
||||
@@ -39,34 +39,30 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
|
||||
mUploadStrategy = UploadStrategy::POOL;
|
||||
}
|
||||
|
||||
// If the buffer is less than 4K in size and is updated frequently, we don't use an explicit
|
||||
// buffer. Instead, we use immediate command encoder methods like setVertexBytes:length:atIndex:.
|
||||
// This won't work for SSBOs, since they are read/write.
|
||||
MTLResourceOptions options = MTLResourceStorageModePrivate;
|
||||
|
||||
/*
|
||||
if (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE &&
|
||||
usage == BufferUsage::DYNAMIC && !forceGpuBuffer) {
|
||||
mBuffer = nil;
|
||||
mCpuBuffer = malloc(size);
|
||||
return;
|
||||
// The buffer will be memory mapped for write operations.
|
||||
if (any(usage & BufferUsage::SHARED_WRITE_BIT)) {
|
||||
#if defined(FILAMENT_IOS) || defined(__arm64__) || defined(__aarch64__)
|
||||
// iOS and Apple Silicon devices use UMA (Unified Memory Architecture), so we use Shared memory.
|
||||
options = MTLResourceStorageModeShared;
|
||||
#else
|
||||
// Intel Macs require Managed memory for CPU/GPU synchronization.
|
||||
options = MTLResourceStorageModeManaged;
|
||||
#endif
|
||||
}
|
||||
*/
|
||||
|
||||
// Otherwise, we allocate a private GPU buffer.
|
||||
{
|
||||
ScopedAllocationTimer timer("generic");
|
||||
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
|
||||
mBuffer = { [context.device newBufferWithLength:size options:options],
|
||||
TrackedMetalBuffer::Type::GENERIC };
|
||||
}
|
||||
|
||||
// mBuffer might fail to be allocated. Clients can check for this by calling
|
||||
// wasAllocationSuccessful().
|
||||
}
|
||||
|
||||
MetalBuffer::~MetalBuffer() {
|
||||
if (mCpuBuffer) {
|
||||
free(mCpuBuffer);
|
||||
}
|
||||
}
|
||||
MetalBuffer::~MetalBuffer() = default;
|
||||
|
||||
void MetalBuffer::copyIntoBuffer(
|
||||
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
|
||||
@@ -83,12 +79,6 @@ void MetalBuffer::copyIntoBuffer(
|
||||
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
|
||||
<< "byteOffset must be a multiple of 4, tag=" << getHandleTag();
|
||||
|
||||
// If we have a cpu buffer, we can directly copy into it.
|
||||
if (mCpuBuffer) {
|
||||
memcpy(static_cast<uint8_t*>(mCpuBuffer) + byteOffset, src, size);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mUploadStrategy) {
|
||||
case UploadStrategy::BUMP_ALLOCATOR:
|
||||
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
|
||||
@@ -106,11 +96,6 @@ void MetalBuffer::copyIntoBufferUnsynchronized(
|
||||
}
|
||||
|
||||
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept {
|
||||
// If there's a CPU buffer, then we return nil here, as the CPU-side buffer will be bound
|
||||
// separately.
|
||||
if (mCpuBuffer) {
|
||||
return nil;
|
||||
}
|
||||
assert_invariant(mBuffer);
|
||||
return mBuffer.get();
|
||||
}
|
||||
@@ -171,41 +156,6 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
|
||||
offsets:metalOffsets.data()
|
||||
withRange:bufferRange];
|
||||
}
|
||||
|
||||
for (size_t b = 0; b < count; b++) {
|
||||
MetalBuffer* const buffer = buffers[b];
|
||||
if (!buffer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const void* cpuBuffer = buffer->getCpuBuffer();
|
||||
if (!cpuBuffer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t bufferIndex = bufferStart + b;
|
||||
const size_t offset = offsets[b];
|
||||
auto* bytes = static_cast<const uint8_t*>(cpuBuffer);
|
||||
|
||||
if (stages & Stage::VERTEX) {
|
||||
[(id<MTLRenderCommandEncoder>) encoder setVertexBytes:(bytes + offset)
|
||||
length:(buffer->getSize() - offset)
|
||||
atIndex:bufferIndex];
|
||||
}
|
||||
if (stages & Stage::FRAGMENT) {
|
||||
[(id<MTLRenderCommandEncoder>) encoder setFragmentBytes:(bytes + offset)
|
||||
length:(buffer->getSize() - offset)
|
||||
atIndex:bufferIndex];
|
||||
}
|
||||
if (stages & Stage::COMPUTE) {
|
||||
// TODO: using setBytes means the data is read-only, which currently isn't enforced.
|
||||
// In practice this won't be an issue since MetalBuffer ensures all SSBOs are realized
|
||||
// through actual id<MTLBuffer> allocations.
|
||||
[(id<MTLComputeCommandEncoder>) encoder setBytes:(bytes + offset)
|
||||
length:(buffer->getSize() - offset)
|
||||
atIndex:bufferIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MetalBuffer::uploadWithPoolBuffer(
|
||||
|
||||
@@ -61,6 +61,8 @@ public:
|
||||
|
||||
MetalContext* getContext() { return mContext; }
|
||||
|
||||
using DriverBase::scheduleDestroy;
|
||||
|
||||
private:
|
||||
|
||||
friend class MetalSwapChain;
|
||||
|
||||
@@ -2275,7 +2275,10 @@ MemoryMappedBufferHandle MetalDriver::mapBufferS() noexcept {
|
||||
void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
|
||||
BufferObjectHandle boh, size_t offset,
|
||||
size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) {
|
||||
construct_handle<MetalMemoryMappedBuffer>(mmbh, boh, offset, size, access);
|
||||
assert_invariant(boh);
|
||||
MetalBufferObject* bo = mHandleAllocator.handle_cast<MetalBufferObject*>(boh);
|
||||
assert_invariant(bo);
|
||||
construct_handle<MetalMemoryMappedBuffer>(mmbh, bo, offset, size, access);
|
||||
mHandleAllocator.associateTagToHandle(mmbh.getId(), std::move(tag));
|
||||
}
|
||||
|
||||
@@ -2283,21 +2286,16 @@ void MetalDriver::unmapBuffer(MemoryMappedBufferHandle mmbh) {
|
||||
if (UTILS_UNLIKELY(!mmbh)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
|
||||
mmb->unmap();
|
||||
destruct_handle<MetalMemoryMappedBuffer>(mmbh);
|
||||
}
|
||||
|
||||
void MetalDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset,
|
||||
BufferDescriptor&& data) {
|
||||
auto mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
|
||||
|
||||
assert_invariant(any(mmb->access & MapBufferAccessFlags::WRITE_BIT));
|
||||
assert_invariant(offset + data.size <= mmb->size);
|
||||
|
||||
// TODO: this isa zero-effort implementation of copyToMemoryMappedBuffer(), where we just
|
||||
// call updateBufferObject(). This could be a fallback implementation for when
|
||||
// shared memory is not available.
|
||||
// On UMA systems, this should just be a memcpy into the memory-mapped buffer.
|
||||
updateBufferObject(mmb->boh, std::move(data), mmb->offset + offset);
|
||||
auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
|
||||
mmb->copy(*this, offset, std::move(data));
|
||||
}
|
||||
|
||||
// explicit instantiation of the Dispatcher
|
||||
|
||||
@@ -569,14 +569,22 @@ struct MetalDescriptorSet : public HwDescriptorSet {
|
||||
|
||||
|
||||
struct MetalMemoryMappedBuffer : public HwMemoryMappedBuffer {
|
||||
MetalMemoryMappedBuffer(BufferObjectHandle boh, size_t const offset,
|
||||
size_t const size, MapBufferAccessFlags const access)
|
||||
: boh(boh), access(access), size(size), offset(offset) {
|
||||
}
|
||||
BufferObjectHandle boh{};
|
||||
MapBufferAccessFlags access{};
|
||||
uint32_t size = 0;
|
||||
uint32_t offset = 0;
|
||||
struct {
|
||||
MetalBufferObject* bo;
|
||||
void* vaddr = nullptr;
|
||||
uint32_t size = 0;
|
||||
uint32_t offset = 0;
|
||||
} mtl;
|
||||
|
||||
MetalMemoryMappedBuffer(MetalBufferObject* bo, size_t offset, size_t size,
|
||||
MapBufferAccessFlags access) noexcept;
|
||||
|
||||
~MetalMemoryMappedBuffer();
|
||||
|
||||
void unmap();
|
||||
|
||||
void copy(MetalDriver& mtld, size_t offset, BufferDescriptor&& data) const;
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
|
||||
@@ -1664,5 +1664,44 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
|
||||
return buffer.get();
|
||||
}
|
||||
|
||||
MetalMemoryMappedBuffer::MetalMemoryMappedBuffer(MetalBufferObject* bo, size_t offset, size_t size,
|
||||
MapBufferAccessFlags access) noexcept : access(access) {
|
||||
MetalBuffer* buffer = bo->getBuffer();
|
||||
assert_invariant(buffer);
|
||||
id<MTLBuffer> mtlBuffer = buffer->getGpuBufferForDraw();
|
||||
|
||||
assert_invariant(offset + size <= bo->byteCount);
|
||||
assert_invariant(mtlBuffer.storageMode != MTLStorageModePrivate);
|
||||
|
||||
mtl.bo = bo;
|
||||
mtl.vaddr = static_cast<char*>(mtlBuffer.contents) + offset;
|
||||
mtl.size = size;
|
||||
mtl.offset = offset;
|
||||
}
|
||||
|
||||
MetalMemoryMappedBuffer::~MetalMemoryMappedBuffer() = default;
|
||||
|
||||
void MetalMemoryMappedBuffer::unmap() {
|
||||
#if !defined(FILAMENT_IOS) && defined(__x86_64__)
|
||||
// Managed memory requires didModifyRange to synchronize changes to the GPU. This is specific to Intel Macs.
|
||||
MetalBuffer* buffer = bo->getBuffer();
|
||||
id<MTLBuffer> mtlBuffer = buffer->getGpuBufferForDraw();
|
||||
if (mtlBuffer && mtlBuffer.storageMode == MTLStorageModeManaged) {
|
||||
[mtlBuffer didModifyRange:NSMakeRange(mtl.offset, mtl.size)];
|
||||
}
|
||||
#endif
|
||||
// Shared memory on UMA systems is coherent; no explicit synchronization is required.
|
||||
}
|
||||
|
||||
void MetalMemoryMappedBuffer::copy(MetalDriver& mtld, size_t offset, BufferDescriptor&& data) const {
|
||||
assert_invariant(any(access & MapBufferAccessFlags::WRITE_BIT));
|
||||
assert_invariant(offset + data.size <= mtl.size);
|
||||
assert_invariant(mtl.vaddr);
|
||||
|
||||
memcpy(static_cast<char*>(mtl.vaddr) + offset, data.buffer, data.size);
|
||||
|
||||
mtld.scheduleDestroy(std::move(data));
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
} // namespace filament
|
||||
|
||||
@@ -106,6 +106,8 @@ struct PlatformEGLAndroid::SwapChainEGLAndroid : public SwapChainEGL {
|
||||
void terminate(PlatformEGLAndroid& platform);
|
||||
bool setPresentFrameId(uint64_t frameId) const noexcept;
|
||||
uint64_t getFrameId(uint64_t frameId) const noexcept;
|
||||
bool compositorTimingSupported = false;
|
||||
bool frameTimestampsSupported = false;
|
||||
private:
|
||||
AndroidSwapChainHelper mImpl{};
|
||||
};
|
||||
@@ -228,9 +230,9 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
||||
"eglGetNativeClientBufferANDROID"));
|
||||
|
||||
if (ext.egl.ANDROID_presentation_time) {
|
||||
eglGetNativeClientBufferANDROID =
|
||||
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress(
|
||||
"eglGetNativeClientBufferANDROID"));
|
||||
eglPresentationTimeANDROID =
|
||||
PFNEGLPRESENTATIONTIMEANDROIDPROC(eglGetProcAddress(
|
||||
"eglPresentationTimeANDROID"));
|
||||
}
|
||||
|
||||
if (ext.egl.ANDROID_get_frame_timestamps) {
|
||||
@@ -289,11 +291,21 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
||||
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
||||
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
||||
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
|
||||
outCompositorTiming->compositeDeadline = CompositorTiming::INVALID;
|
||||
outCompositorTiming->compositeInterval = CompositorTiming::INVALID;
|
||||
outCompositorTiming->compositeToPresentLatency = CompositorTiming::INVALID;
|
||||
|
||||
// From this point on, we always return "success" because some timings were returned.
|
||||
|
||||
if (!static_cast<SwapChainEGLAndroid const *>(swapchain)->compositorTimingSupported) {
|
||||
// if this surface doesn't support it, don't attempt to query the values.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
|
||||
EGLSurface const sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
|
||||
if (sur == EGL_NO_SURFACE) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<EGLnsecsANDROID, 3> values;
|
||||
@@ -304,26 +316,16 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
||||
};
|
||||
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
|
||||
names.size(), names.data(), values.data());
|
||||
if (!success) {
|
||||
return false;
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
// reset current error to EGL_SUCCESS
|
||||
eglGetError();
|
||||
} else {
|
||||
outCompositorTiming->compositeDeadline = values[0];
|
||||
outCompositorTiming->compositeInterval = values[1];
|
||||
outCompositorTiming->compositeToPresentLatency = values[2];
|
||||
}
|
||||
outCompositorTiming->compositeDeadline = values[0];
|
||||
outCompositorTiming->compositeInterval = values[1];
|
||||
outCompositorTiming->compositeToPresentLatency = values[2];
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallback to private APIs
|
||||
auto const anw = static_cast<SwapChainEGL const *>(swapchain)->nativeWindow;
|
||||
int const status = NativeWindow::getCompositorTiming(anw,
|
||||
&outCompositorTiming->compositeDeadline,
|
||||
&outCompositorTiming->compositeInterval,
|
||||
&outCompositorTiming->compositeToPresentLatency);
|
||||
if (status == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return PlatformEGL::queryCompositorTiming(swapchain, outCompositorTiming);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain,
|
||||
@@ -348,6 +350,10 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!static_cast<SwapChainEGLAndroid const *>(swapchain)->frameTimestampsSupported) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
|
||||
EGLSurface const sur = sc->sur;
|
||||
if (sur == EGL_NO_SURFACE) {
|
||||
@@ -368,7 +374,9 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
||||
};
|
||||
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
|
||||
names.size(), names.data(), values.data());
|
||||
if (!success) {
|
||||
if (UTILS_UNLIKELY(!success)) {
|
||||
// reset current error to EGL_SUCCESS
|
||||
eglGetError();
|
||||
return false;
|
||||
}
|
||||
outFrameTimestamps->requestedPresentTime = values[0];
|
||||
@@ -382,28 +390,44 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
||||
outFrameTimestamps->releaseTime = values[8];
|
||||
return true;
|
||||
}
|
||||
|
||||
// fallback to private APIs
|
||||
auto const anw = sc->nativeWindow;
|
||||
int const status = NativeWindow::getFrameTimestamps(anw, hwFrameId,
|
||||
&outFrameTimestamps->requestedPresentTime,
|
||||
&outFrameTimestamps->acquireTime,
|
||||
&outFrameTimestamps->latchTime,
|
||||
&outFrameTimestamps->firstCompositionStartTime,
|
||||
&outFrameTimestamps->lastCompositionStartTime,
|
||||
&outFrameTimestamps->gpuCompositionDoneTime,
|
||||
&outFrameTimestamps->displayPresentTime,
|
||||
&outFrameTimestamps->dequeueReadyTime,
|
||||
&outFrameTimestamps->releaseTime);
|
||||
if (status == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return PlatformEGL::queryFrameTimestamps(swapchain, frameId, outFrameTimestamps);
|
||||
}
|
||||
|
||||
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) {
|
||||
auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, nativeWindow, flags);
|
||||
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
|
||||
EGLDisplay const dpy = getEglDisplay();
|
||||
sc->compositorTimingSupported =
|
||||
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
|
||||
EGL_COMPOSITE_DEADLINE_ANDROID) &&
|
||||
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
|
||||
EGL_COMPOSITE_INTERVAL_ANDROID) &&
|
||||
eglGetCompositorTimingSupportedANDROID(dpy, sc->sur,
|
||||
EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID);
|
||||
sc->frameTimestampsSupported =
|
||||
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
|
||||
EGL_REQUESTED_PRESENT_TIME_ANDROID) &&
|
||||
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
|
||||
EGL_RENDERING_COMPLETE_TIME_ANDROID) &&
|
||||
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
|
||||
EGL_COMPOSITION_LATCH_TIME_ANDROID) &&
|
||||
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
|
||||
EGL_FIRST_COMPOSITION_START_TIME_ANDROID) &&
|
||||
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
|
||||
EGL_LAST_COMPOSITION_START_TIME_ANDROID) &&
|
||||
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
|
||||
EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID) &&
|
||||
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
|
||||
EGL_DISPLAY_PRESENT_TIME_ANDROID) &&
|
||||
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
|
||||
EGL_DEQUEUE_READY_TIME_ANDROID) &&
|
||||
eglGetFrameTimestampSupportedANDROID(dpy, sc->sur,
|
||||
EGL_READS_DONE_TIME_ANDROID);
|
||||
}
|
||||
// This is expected to be a low frequency log, only turned on in debug builds
|
||||
DLOG(INFO) << "anw: " << nativeWindow
|
||||
<< ", compositorTimingSupported=" << sc->compositorTimingSupported
|
||||
<< ", frameTimestampsSupported=" << sc->frameTimestampsSupported;
|
||||
return sc;
|
||||
}
|
||||
|
||||
@@ -725,8 +749,6 @@ PlatformEGLAndroid::SwapChainEGLAndroid::SwapChainEGLAndroid(PlatformEGLAndroid
|
||||
// we ignore the result, it doesn't matter much if it fails
|
||||
eglSurfaceAttrib(platform.getEglDisplay(), sur, EGL_TIMESTAMPS_ANDROID, EGL_TRUE);
|
||||
}
|
||||
} else {
|
||||
NativeWindow::enableFrameTimestamps(EGLNativeWindowType(nativeWindow), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -541,9 +541,15 @@ bool VulkanPlatformAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
||||
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
||||
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
||||
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
|
||||
outCompositorTiming->compositeDeadline = CompositorTiming::INVALID;
|
||||
outCompositorTiming->compositeInterval = CompositorTiming::INVALID;
|
||||
outCompositorTiming->compositeToPresentLatency = CompositorTiming::INVALID;
|
||||
|
||||
// From this point on, we always return "success" because some timings were returned.
|
||||
|
||||
auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain);
|
||||
return vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
|
||||
vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept {
|
||||
|
||||
@@ -361,13 +361,15 @@ bool VulkanPlatformSurfaceSwapChain::queryCompositorTiming(
|
||||
CompositorTiming* outCompositorTiming) const {
|
||||
#ifdef __ANDROID__
|
||||
// fallback to private APIs
|
||||
int const status = NativeWindow::getCompositorTiming(
|
||||
static_cast<ANativeWindow*>(mNativeWindow),
|
||||
&outCompositorTiming->compositeDeadline,
|
||||
&outCompositorTiming->compositeInterval,
|
||||
&outCompositorTiming->compositeToPresentLatency);
|
||||
if (status == 0) {
|
||||
return true;
|
||||
if (UTILS_VERY_LIKELY(mNativeWindow)) {
|
||||
int const status = NativeWindow::getCompositorTiming(
|
||||
static_cast<ANativeWindow*>(mNativeWindow),
|
||||
&outCompositorTiming->compositeDeadline,
|
||||
&outCompositorTiming->compositeInterval,
|
||||
&outCompositorTiming->compositeToPresentLatency);
|
||||
if (status == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include "WebGPUConstants.h"
|
||||
#include "WebGPUQueueManager.h"
|
||||
#include "WebGPUStagePool.h"
|
||||
|
||||
#include "DriverBase.h"
|
||||
#include <backend/BufferDescriptor.h>
|
||||
@@ -30,7 +29,6 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
@@ -67,7 +65,7 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
|
||||
// of 4 by padding with zeros.
|
||||
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
const uint32_t byteOffset, wgpu::Device const& device,
|
||||
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool) {
|
||||
WebGPUQueueManager* const webGPUQueueManager) {
|
||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
|
||||
<< "updateGPUBuffer called with a null buffer";
|
||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
|
||||
@@ -81,54 +79,34 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||
// This may have some performance implications. That should be investigated later.
|
||||
assert_invariant(mBuffer.GetUsage() & wgpu::BufferUsage::CopyDst);
|
||||
|
||||
// // Calculate some alignment related sizes
|
||||
// Calculate some alignment related sizes
|
||||
const size_t remainder = bufferDescriptor.size % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
||||
const size_t mainBulk = bufferDescriptor.size - remainder;
|
||||
const size_t stagingBufferSize =
|
||||
remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
||||
|
||||
Stage stage = webGPUStagePool->acquireBuffer(stagingBufferSize);
|
||||
// create a staging buffer
|
||||
wgpu::BufferDescriptor descriptor{
|
||||
.label = "Filament WebGPU Staging Buffer",
|
||||
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
|
||||
.size = stagingBufferSize,
|
||||
.mappedAtCreation = true };
|
||||
wgpu::Buffer stagingBuffer = device.CreateBuffer(&descriptor);
|
||||
|
||||
std::string mappedRangeIsNull = stage.mappedRange
|
||||
? "no"
|
||||
: "yes";
|
||||
std::cout << "Run Yu: got mapped range on the staging buffer with size "
|
||||
<< stage.buffer.GetSize() << " and it is null? " << mappedRangeIsNull << std::endl;
|
||||
memcpy(stage.mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
|
||||
void* mappedRange = stagingBuffer.GetMappedRange();
|
||||
memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
|
||||
|
||||
stage.buffer.Unmap();
|
||||
// Make sure the padded memory is set to 0 to have deterministic behaviors
|
||||
if (remainder != 0) {
|
||||
uint8_t* paddingStart = static_cast<uint8_t*>(mappedRange) + bufferDescriptor.size;
|
||||
memset(paddingStart, 0, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
|
||||
}
|
||||
|
||||
stagingBuffer.Unmap();
|
||||
|
||||
std::cout << "Run Yu: about to issue copy command with actual staging buffer of size "
|
||||
<< stage.buffer.GetSize() << ", and computed size of " << stagingBufferSize
|
||||
<< ". The mBuffer size is " << mBuffer.GetSize() << std::endl;
|
||||
// Copy the staging buffer contents to the destination buffer.
|
||||
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stage.buffer, 0, mBuffer,
|
||||
byteOffset,
|
||||
remainder == 0 ? bufferDescriptor.size
|
||||
: mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS);
|
||||
webGPUQueueManager->flush();
|
||||
|
||||
struct UserData final {
|
||||
wgpu::Buffer stagingBuffer;
|
||||
WebGPUStagePool* webGPUStagePool;
|
||||
};
|
||||
auto userData = std::make_unique<UserData>(
|
||||
UserData{ .stagingBuffer = stage.buffer, .webGPUStagePool = webGPUStagePool });
|
||||
stage.buffer.MapAsync(wgpu::MapMode::Write, 0, stagingBufferSize,
|
||||
wgpu::CallbackMode::AllowSpontaneous,
|
||||
[data = std::move(userData)](wgpu::MapAsyncStatus status, const char* message) {
|
||||
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
|
||||
std::cout << "Run Yu: successfully mapped a buffer with size "
|
||||
<< data->stagingBuffer.GetSize() << std::endl;
|
||||
void* mappedRange = data->stagingBuffer.GetMappedRange();
|
||||
if (!mappedRange) {
|
||||
std::cout << "Run Yu: MAPPED RANGE IS NULL RIGHT AWAY!!\n";
|
||||
}
|
||||
data->webGPUStagePool->addBufferToPool(data->stagingBuffer, mappedRange);
|
||||
} else {
|
||||
std::cout << "Run Yu: MAPPING UNSUCCESSFUL!!\n";
|
||||
}
|
||||
});
|
||||
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer,
|
||||
byteOffset, stagingBufferSize);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
|
||||
@@ -25,7 +25,6 @@ namespace filament::backend {
|
||||
|
||||
class BufferDescriptor;
|
||||
class WebGPUQueueManager;
|
||||
class WebGPUStagePool;
|
||||
|
||||
/**
|
||||
* A base class for WebGPU buffer objects, providing common functionality for creating and
|
||||
@@ -41,7 +40,7 @@ public:
|
||||
* ensures the calls happen in the expected sequence.
|
||||
*/
|
||||
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Device const& device,
|
||||
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool);
|
||||
WebGPUQueueManager* const webGPUQueueManager);
|
||||
|
||||
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
|
||||
|
||||
|
||||
@@ -107,7 +107,6 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
|
||||
mAdapter{ mPlatform.requestAdapter(nullptr) },
|
||||
mDevice{ mPlatform.requestDevice(mAdapter) },
|
||||
mQueueManager{ mDevice },
|
||||
mStagePool{ mDevice },
|
||||
mPipelineLayoutCache{ mDevice },
|
||||
mPipelineCache{ mDevice },
|
||||
mRenderPassMipmapGenerator{ mDevice, &mQueueManager },
|
||||
@@ -857,7 +856,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
|
||||
// draw calls are made.
|
||||
flush();
|
||||
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
}
|
||||
|
||||
@@ -868,14 +867,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
|
||||
// draw calls are made.
|
||||
flush();
|
||||
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
}
|
||||
|
||||
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
|
||||
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
|
||||
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
||||
scheduleDestroy(std::move(bufferDescriptor));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "webgpu/WebGPUPipelineLayoutCache.h"
|
||||
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
|
||||
#include "webgpu/WebGPUQueueManager.h"
|
||||
#include "webgpu/WebGPUStagePool.h"
|
||||
#include "webgpu/utils/AsyncTaskCounter.h"
|
||||
#include <backend/platforms/WebGPUPlatform.h>
|
||||
|
||||
@@ -82,7 +81,6 @@ private:
|
||||
wgpu::Device mDevice = nullptr;
|
||||
wgpu::Limits mDeviceLimits = {};
|
||||
WebGPUQueueManager mQueueManager;
|
||||
WebGPUStagePool mStagePool;
|
||||
void* mNativeWindow = nullptr;
|
||||
WebGPUSwapChain* mSwapChain = nullptr;
|
||||
uint64_t mNextFakeHandle = 1;
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "WebGPUStagePool.h"
|
||||
|
||||
#include "WebGPUConstants.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
WebGPUStagePool::WebGPUStagePool(wgpu::Device const& device) : mDevice(device) {}
|
||||
|
||||
WebGPUStagePool::~WebGPUStagePool() = default;
|
||||
|
||||
Stage WebGPUStagePool::acquireBuffer(size_t requiredSize) {
|
||||
std::cout << "Run Yu: required size in acquireBuffer: " << requiredSize << std::endl;
|
||||
std::cout << "Run Yu: the pool size is " << mBuffers.size() << std::endl;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
auto iter = mBuffers.lower_bound(requiredSize);
|
||||
if (iter != mBuffers.end()) {
|
||||
const Stage& fromPool = iter->second;
|
||||
std::cout << "Run Yu: found buffer in the pool with size " << fromPool.buffer.GetSize()
|
||||
<< std::endl;
|
||||
if (fromPool.buffer.GetMapState() != wgpu::BufferMapState::Mapped) {
|
||||
std::cout << "Run Yu: buffer from pool is not mapped!!" << std::endl;
|
||||
}
|
||||
|
||||
Stage result{ .buffer = fromPool.buffer, .mappedRange = fromPool.mappedRange };
|
||||
mBuffers.erase(iter);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
wgpu::Buffer newBuffer = createNewBuffer(requiredSize);
|
||||
return { .buffer = newBuffer, .mappedRange = newBuffer.GetMappedRange() };
|
||||
}
|
||||
|
||||
void WebGPUStagePool::addBufferToPool(wgpu::Buffer buffer, void* mappedRange) {
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
std::cout << "Run Yu: adding buffer to the pool with size " << buffer.GetSize() << std::endl;
|
||||
Stage stage {.buffer = buffer, .mappedRange = mappedRange};
|
||||
mBuffers.emplace(buffer.GetSize(), stage);
|
||||
std::cout << "Run Yu: added buffer to the pool with size " << buffer.GetSize() << std::endl;
|
||||
|
||||
bool allMapped = true;
|
||||
for (const auto& pair : mBuffers) {
|
||||
auto state = pair.second.buffer.GetMapState();
|
||||
if (state != wgpu::BufferMapState::Mapped) {
|
||||
allMapped = false;
|
||||
std::cout << "Run Yu: the buffer with size " << pair.second.buffer.GetSize()
|
||||
<< " is not mapped but somehow was added to the pool, its state is "
|
||||
<< static_cast<int>(state) << std::endl;
|
||||
}
|
||||
}
|
||||
if (!allMapped) {
|
||||
std::cout << "Run Yu: found buffers that are not mapped\n";
|
||||
} else {
|
||||
std::cout << "Run Yu: all buffers are mapped\n";
|
||||
}
|
||||
}
|
||||
|
||||
wgpu::Buffer WebGPUStagePool::createNewBuffer(size_t bufferSize) {
|
||||
std::cout << "Run Yu: creating new buffer with size " << bufferSize << std::endl;
|
||||
wgpu::BufferDescriptor descriptor{
|
||||
.label = "Filament WebGPU Staging Buffer",
|
||||
.usage = wgpu::BufferUsage::MapWrite | wgpu::BufferUsage::CopySrc,
|
||||
.size = bufferSize,
|
||||
.mappedAtCreation = true };
|
||||
return mDevice.CreateBuffer(&descriptor);
|
||||
}
|
||||
|
||||
} // namespace filament::backend
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2025 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef TNT_FILAMENT_BACKEND_WEBGPUSTAGEPOOL_H
|
||||
#define TNT_FILAMENT_BACKEND_WEBGPUSTAGEPOOL_H
|
||||
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
namespace filament::backend {
|
||||
|
||||
struct Stage {
|
||||
wgpu::Buffer buffer;
|
||||
void* mappedRange;
|
||||
};
|
||||
|
||||
class WebGPUStagePool {
|
||||
public:
|
||||
WebGPUStagePool(wgpu::Device const& device);
|
||||
~WebGPUStagePool();
|
||||
|
||||
Stage acquireBuffer(size_t requiredSize);
|
||||
void addBufferToPool(wgpu::Buffer buffer, void* mappedRange);
|
||||
private:
|
||||
wgpu::Buffer createNewBuffer(size_t bufferSize);
|
||||
std::multimap<uint32_t, Stage> mBuffers;
|
||||
mutable std::mutex mMutex;
|
||||
|
||||
wgpu::Device mDevice;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TNT_FILAMENT_BACKEND_WEBGPUSTAGEPOOL_H
|
||||
@@ -113,8 +113,10 @@ public:
|
||||
/**
|
||||
* Retrieve a history of frame timing information. The maximum frame history size is
|
||||
* given by getMaxFrameHistorySize().
|
||||
* All or part of the history can be lost when using a different SwapChain in beginFrame().
|
||||
* @param historySize requested history size. The returned vector could be smaller.
|
||||
* @return A vector of FrameInfo.
|
||||
* @see beginFrame()
|
||||
*/
|
||||
utils::FixedCapacityVector<FrameInfo> getFrameInfoHistory(
|
||||
size_t historySize = 1) const noexcept;
|
||||
@@ -326,6 +328,8 @@ public:
|
||||
* or 0 if unknown. This value should be the timestamp of
|
||||
* the last h/w vsync. It is expressed in the
|
||||
* std::chrono::steady_clock time base.
|
||||
* On Android this should be the frame time received from
|
||||
* a Choreographer.
|
||||
* @param swapChain A pointer to the SwapChain instance to use.
|
||||
*
|
||||
* @return
|
||||
@@ -337,6 +341,8 @@ public:
|
||||
*
|
||||
* @note
|
||||
* All calls to render() must happen *after* beginFrame().
|
||||
* It is recommended to use the same swapChain for every call to beginFrame, failing to do
|
||||
* so can result is losing all or part of the FrameInfo history.
|
||||
*
|
||||
* @see
|
||||
* endFrame()
|
||||
|
||||
@@ -56,6 +56,9 @@ public:
|
||||
//! Returns true if the map is full.
|
||||
bool full() const noexcept { return mSize == N; }
|
||||
|
||||
//! Clears the map entirely.
|
||||
void clear() noexcept { mSize = 0; mHead = 0; }
|
||||
|
||||
/**
|
||||
* Inserts a new key-value pair.
|
||||
* The key must be greater than the key of the last inserted element.
|
||||
@@ -65,7 +68,7 @@ public:
|
||||
*/
|
||||
UTILS_NOINLINE void insert(key_type key, mapped_type value) {
|
||||
assert(empty() || key > back().first); // assert monotonic
|
||||
if (full()) {
|
||||
if (UTILS_LIKELY(full())) {
|
||||
// container is full, replace the oldest element
|
||||
mStorage[mHead] = { key, value };
|
||||
mHead = (mHead + 1) % N;
|
||||
|
||||
Reference in New Issue
Block a user