Compare commits

..

2 Commits

23 changed files with 349 additions and 263 deletions

View File

@@ -16,9 +16,6 @@
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
@@ -29,7 +26,6 @@ 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
@@ -123,8 +119,6 @@ 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()
@@ -311,39 +305,10 @@ 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

View File

@@ -316,6 +316,8 @@ 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

View File

@@ -61,16 +61,6 @@ 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) {

View File

@@ -32,7 +32,6 @@ 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,
@@ -52,7 +51,6 @@ 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);

View File

@@ -19,9 +19,6 @@
#include <android/native_window.h>
#include <utils/compiler.h>
#include <utils/Logger.h>
#include <cstddef>
#include <cstdint>
#include <limits>
@@ -39,23 +36,11 @@ bool AndroidSwapChainHelper::setPresentFrameId(
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
if (status == 0) {
std::lock_guard const lock(mLock);
// 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();
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;
}
// oldest entry is removed
mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
return true;

View File

@@ -146,12 +146,12 @@ void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retri
bool Platform::hasInsertBlobFunc() const noexcept {
std::lock_guard<decltype(mMutex)> lock(mMutex);
return mInsertBlob && bool(*mInsertBlob);
return bool(mInsertBlob);
}
bool Platform::hasRetrieveBlobFunc() const noexcept {
std::lock_guard<decltype(mMutex)> lock(mMutex);
return mRetrieveBlob && bool(*mRetrieveBlob);
return 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 && bool(*mDebugUpdateStat);
return mDebugUpdateStat != nullptr;
}
void Platform::debugUpdateStat(const char* key, uint64_t intValue) {

View File

@@ -165,7 +165,7 @@ public:
size_t size, bool forceGpuBuffer = false);
~MetalBuffer();
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer; }
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; }
MetalBuffer(const MetalBuffer& rhs) = delete;
MetalBuffer& operator=(const MetalBuffer& rhs) = delete;
@@ -185,12 +185,14 @@ 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, it never returns
* nil.
* @return The MTLBuffer representing the current state of the buffer to bind, or nil if there
* is no device allocation.
*
*/
id<MTLBuffer> getGpuBufferForDraw() noexcept;
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
void setLabel(const utils::ImmutableCString& label) {
#if FILAMENT_METAL_DEBUG_LABELS
if (label.empty()) {
@@ -233,6 +235,7 @@ private:
UploadStrategy mUploadStrategy;
TrackedMetalBuffer mBuffer;
size_t mBufferSize = 0;
void* mCpuBuffer = nullptr;
MetalContext& mContext;
};

View File

@@ -39,30 +39,34 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
mUploadStrategy = UploadStrategy::POOL;
}
MTLResourceOptions options = MTLResourceStorageModePrivate;
// 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.
// 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
/*
if (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE &&
usage == BufferUsage::DYNAMIC && !forceGpuBuffer) {
mBuffer = nil;
mCpuBuffer = malloc(size);
return;
}
*/
// Otherwise, we allocate a private GPU buffer.
{
ScopedAllocationTimer timer("generic");
mBuffer = { [context.device newBufferWithLength:size options:options],
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
TrackedMetalBuffer::Type::GENERIC };
}
// mBuffer might fail to be allocated. Clients can check for this by calling
// wasAllocationSuccessful().
}
MetalBuffer::~MetalBuffer() = default;
MetalBuffer::~MetalBuffer() {
if (mCpuBuffer) {
free(mCpuBuffer);
}
}
void MetalBuffer::copyIntoBuffer(
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
@@ -79,6 +83,12 @@ 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));
@@ -96,6 +106,11 @@ 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();
}
@@ -156,6 +171,41 @@ 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(

View File

@@ -61,8 +61,6 @@ public:
MetalContext* getContext() { return mContext; }
using DriverBase::scheduleDestroy;
private:
friend class MetalSwapChain;

View File

@@ -2275,10 +2275,7 @@ MemoryMappedBufferHandle MetalDriver::mapBufferS() noexcept {
void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
BufferObjectHandle boh, size_t offset,
size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) {
assert_invariant(boh);
MetalBufferObject* bo = mHandleAllocator.handle_cast<MetalBufferObject*>(boh);
assert_invariant(bo);
construct_handle<MetalMemoryMappedBuffer>(mmbh, bo, offset, size, access);
construct_handle<MetalMemoryMappedBuffer>(mmbh, boh, offset, size, access);
mHandleAllocator.associateTagToHandle(mmbh.getId(), std::move(tag));
}
@@ -2286,16 +2283,21 @@ 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);
mmb->copy(*this, offset, std::move(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);
}
// explicit instantiation of the Dispatcher

View File

@@ -569,22 +569,14 @@ 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{};
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;
uint32_t size = 0;
uint32_t offset = 0;
};
} // namespace backend

View File

@@ -1664,44 +1664,5 @@ 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

View File

@@ -106,8 +106,6 @@ 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{};
};
@@ -230,9 +228,9 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
"eglGetNativeClientBufferANDROID"));
if (ext.egl.ANDROID_presentation_time) {
eglPresentationTimeANDROID =
PFNEGLPRESENTATIONTIMEANDROIDPROC(eglGetProcAddress(
"eglPresentationTimeANDROID"));
eglGetNativeClientBufferANDROID =
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress(
"eglGetNativeClientBufferANDROID"));
}
if (ext.egl.ANDROID_get_frame_timestamps) {
@@ -291,21 +289,11 @@ 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 true;
return false;
}
std::array<EGLnsecsANDROID, 3> values;
@@ -316,16 +304,26 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
};
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
names.size(), names.data(), values.data());
if (UTILS_UNLIKELY(!success)) {
// reset current error to EGL_SUCCESS
eglGetError();
} else {
outCompositorTiming->compositeDeadline = values[0];
outCompositorTiming->compositeInterval = values[1];
outCompositorTiming->compositeToPresentLatency = values[2];
if (!success) {
return false;
}
outCompositorTiming->compositeDeadline = values[0];
outCompositorTiming->compositeInterval = values[1];
outCompositorTiming->compositeToPresentLatency = values[2];
return true;
}
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);
}
bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain,
@@ -350,10 +348,6 @@ 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) {
@@ -374,9 +368,7 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
};
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
names.size(), names.data(), values.data());
if (UTILS_UNLIKELY(!success)) {
// reset current error to EGL_SUCCESS
eglGetError();
if (!success) {
return false;
}
outFrameTimestamps->requestedPresentTime = values[0];
@@ -390,44 +382,28 @@ 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;
}
@@ -749,6 +725,8 @@ 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);
}
}

View File

@@ -541,15 +541,9 @@ 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);
vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
return true;
return vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
}
bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept {

View File

@@ -361,15 +361,13 @@ bool VulkanPlatformSurfaceSwapChain::queryCompositorTiming(
CompositorTiming* outCompositorTiming) const {
#ifdef __ANDROID__
// fallback to private APIs
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;
}
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);

View File

@@ -18,6 +18,7 @@
#include "WebGPUConstants.h"
#include "WebGPUQueueManager.h"
#include "WebGPUStagePool.h"
#include "DriverBase.h"
#include <backend/BufferDescriptor.h>
@@ -29,6 +30,7 @@
#include <cstdint>
#include <cstring>
#include <iostream>
namespace filament::backend {
@@ -65,7 +67,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) {
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool) {
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
<< "updateGPUBuffer called with a null buffer";
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
@@ -79,34 +81,70 @@ 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;
//
// // 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);
MappedStage mappedStage = 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);
void* mappedRange = stagingBuffer.GetMappedRange();
memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
std::string mappedRangeIsNull = mappedStage.mappedRange
? "no"
: "yes";
std::cout << "Run Yu: got mapped range on the staging buffer with size "
<< mappedStage.buffer.GetSize() << " and it is null? " << mappedRangeIsNull << std::endl;
memcpy(mappedStage.mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
// 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);
}
// if (remainder != 0) {
// uint8_t* paddingStart = static_cast<uint8_t*>(mappedRange) + bufferDescriptor.size;
// memset(paddingStart, 0, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - remainder);
// }
// size_t stagingBufferSize = stagingBuffer.GetSize();
// if (stagingBufferSize != bufferDescriptor.size) {
// assert(stagingBufferSize > bufferDescriptor.size);
// assert(stagingBufferSize % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS == 0);
// uint8_t* paddingStart = static_cast<uint8_t*>(mappedRange) + bufferDescriptor.size;
// memset(paddingStart, 0, FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS - (stagingBuffer.GetSize() - bufferDescriptor.size));
// }
stagingBuffer.Unmap();
mappedStage.buffer.Unmap();
std::cout << "Run Yu: about to issue copy command with actual staging buffer of size "
<< mappedStage.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(stagingBuffer, 0, mBuffer,
byteOffset, stagingBufferSize);
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(mappedStage.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 = mappedStage.buffer, .webGPUStagePool = webGPUStagePool });
mappedStage.buffer.MapAsync(
wgpu::MapMode::Write, 0, stagingBufferSize, wgpu::CallbackMode::AllowSpontaneous,
[](wgpu::MapAsyncStatus status, const char* message, UserData* userData) {
if (UTILS_LIKELY(status == wgpu::MapAsyncStatus::Success)) {
std::unique_ptr<UserData> data(static_cast<UserData*>(userData));
userData->webGPUStagePool->addBufferToPool(userData->stagingBuffer);
} else {
std::cout << "Run Yu: MAPPING UNSUCCESSFUL!!\n";
}
},
userData.release());
}
} // namespace filament::backend

View File

@@ -25,6 +25,7 @@ namespace filament::backend {
class BufferDescriptor;
class WebGPUQueueManager;
class WebGPUStagePool;
/**
* A base class for WebGPU buffer objects, providing common functionality for creating and
@@ -40,7 +41,7 @@ public:
* ensures the calls happen in the expected sequence.
*/
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Device const& device,
WebGPUQueueManager* const webGPUQueueManager);
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool);
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }

View File

@@ -107,6 +107,7 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
mAdapter{ mPlatform.requestAdapter(nullptr) },
mDevice{ mPlatform.requestDevice(mAdapter) },
mQueueManager{ mDevice },
mStagePool{ mDevice },
mPipelineLayoutCache{ mDevice },
mPipelineCache{ mDevice },
mRenderPassMipmapGenerator{ mDevice, &mQueueManager },
@@ -856,7 +857,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
// draw calls are made.
flush();
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
}
@@ -867,14 +868,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
// draw calls are made.
flush();
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
}
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
handleCast<WebGPUBufferObject>(bufferObjectHandle)
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
scheduleDestroy(std::move(bufferDescriptor));
}

View File

@@ -25,6 +25,7 @@
#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>
@@ -81,6 +82,7 @@ private:
wgpu::Device mDevice = nullptr;
wgpu::Limits mDeviceLimits = {};
WebGPUQueueManager mQueueManager;
WebGPUStagePool mStagePool;
void* mNativeWindow = nullptr;
WebGPUSwapChain* mSwapChain = nullptr;
uint64_t mNextFakeHandle = 1;

View File

@@ -0,0 +1,88 @@
/*
* 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;
MappedStage 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()) {
std::cout << "Run Yu: found buffer in the pool with size " << iter->second.GetSize()
<< std::endl;
if (iter->second.GetMapState() != wgpu::BufferMapState::Mapped) {
std::cout << "Run Yu: before GetMappedRange the buffer state is not mapped!\n";
}
MappedStage mappedStage = { .buffer = iter->second,
.mappedRange = iter->second.GetMappedRange() };
if (!mappedStage.mappedRange) {
std::cout << "Run Yu: mapped range is null in acquireBuffer!\n";
}
if (mappedStage.buffer.GetMapState() != wgpu::BufferMapState::Mapped) {
std::cout << "Run Yu: after GetMappedRange the buffer state is not mapped!\n";
}
mBuffers.erase(iter);
return mappedStage;
}
}
wgpu::Buffer newBuffer = createNewBuffer(requiredSize);
return { .buffer = newBuffer, .mappedRange = newBuffer.GetMappedRange() };
}
void WebGPUStagePool::addBufferToPool(wgpu::Buffer buffer) {
std::lock_guard<std::mutex> lock(mMutex);
std::cout << "Run Yu: adding buffer to the pool with size " << buffer.GetSize() << std::endl;
mBuffers.insert({buffer.GetSize(), buffer});
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.GetMapState();
if (state != wgpu::BufferMapState::Mapped) {
allMapped = false;
std::cout << "Run Yu: the buffer with size " << pair.second.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";
}
}
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

View File

@@ -0,0 +1,49 @@
/*
* 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 MappedStage {
wgpu::Buffer buffer;
void* mappedRange;
};
class WebGPUStagePool {
public:
WebGPUStagePool(wgpu::Device const& device);
~WebGPUStagePool();
MappedStage acquireBuffer(size_t requiredSize);
void addBufferToPool(wgpu::Buffer buffer);
private:
wgpu::Buffer createNewBuffer(size_t bufferSize);
std::multimap<uint32_t, wgpu::Buffer> mBuffers;
mutable std::mutex mMutex;
wgpu::Device mDevice;
};
}
#endif // TNT_FILAMENT_BACKEND_WEBGPUSTAGEPOOL_H

View File

@@ -113,10 +113,8 @@ 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;
@@ -328,8 +326,6 @@ 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
@@ -341,8 +337,6 @@ 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()

View File

@@ -56,9 +56,6 @@ 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.
@@ -68,7 +65,7 @@ public:
*/
UTILS_NOINLINE void insert(key_type key, mapped_type value) {
assert(empty() || key > back().first); // assert monotonic
if (UTILS_LIKELY(full())) {
if (full()) {
// container is full, replace the oldest element
mStorage[mHead] = { key, value };
mHead = (mHead + 1) % N;