Compare commits
2 Commits
validation
...
ImmediateG
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44ef0af4a4 | ||
|
|
c9b5bfc4a4 |
@@ -16,9 +16,6 @@
|
|||||||
|
|
||||||
package com.google.android.filament.utils
|
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.MotionEvent
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
@@ -29,7 +26,6 @@ import com.google.android.filament.android.UiHelper
|
|||||||
import com.google.android.filament.gltfio.*
|
import com.google.android.filament.gltfio.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.nio.Buffer
|
import java.nio.Buffer
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
private const val kNearPlane = 0.05f // 5 cm
|
private const val kNearPlane = 0.05f // 5 cm
|
||||||
private const val kFarPlane = 1000.0f // 1 km
|
private const val kFarPlane = 1000.0f // 1 km
|
||||||
@@ -123,8 +119,6 @@ class ModelViewer(
|
|||||||
private val target = DoubleArray(3)
|
private val target = DoubleArray(3)
|
||||||
private val upward = DoubleArray(3)
|
private val upward = DoubleArray(3)
|
||||||
|
|
||||||
private var debugFrameCallback: ((Bitmap) -> Unit)? = null
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
renderer = engine.createRenderer()
|
renderer = engine.createRenderer()
|
||||||
scene = engine.createScene()
|
scene = engine.createScene()
|
||||||
@@ -311,39 +305,10 @@ class ModelViewer(
|
|||||||
// Render the scene, unless the renderer wants to skip the frame.
|
// Render the scene, unless the renderer wants to skip the frame.
|
||||||
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
if (renderer.beginFrame(swapChain!!, frameTimeNanos)) {
|
||||||
renderer.render(view)
|
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()
|
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) {
|
private fun populateScene(asset: FilamentAsset) {
|
||||||
val rcm = engine.renderableManager
|
val rcm = engine.renderableManager
|
||||||
var count = 0
|
var count = 0
|
||||||
|
|||||||
@@ -316,6 +316,8 @@ if (FILAMENT_SUPPORTS_WEBGPU)
|
|||||||
src/webgpu/WebGPURenderPrimitive.h
|
src/webgpu/WebGPURenderPrimitive.h
|
||||||
src/webgpu/WebGPURenderTarget.cpp
|
src/webgpu/WebGPURenderTarget.cpp
|
||||||
src/webgpu/WebGPURenderTarget.h
|
src/webgpu/WebGPURenderTarget.h
|
||||||
|
src/webgpu/WebGPUStagePool.cpp
|
||||||
|
src/webgpu/WebGPUStagePool.h
|
||||||
src/webgpu/WebGPUStrings.h
|
src/webgpu/WebGPUStrings.h
|
||||||
src/webgpu/WebGPUSwapChain.cpp
|
src/webgpu/WebGPUSwapChain.cpp
|
||||||
src/webgpu/WebGPUSwapChain.h
|
src/webgpu/WebGPUSwapChain.h
|
||||||
|
|||||||
@@ -61,16 +61,6 @@ int NativeWindow::enableFrameTimestamps(ANativeWindow* anw, bool enable) {
|
|||||||
return pWindow->perform(anw, ENABLE_FRAME_TIMESTAMPS, 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,
|
int NativeWindow::getCompositorTiming(ANativeWindow* anw,
|
||||||
int64_t* compositeDeadline, int64_t* compositeInterval,
|
int64_t* compositeDeadline, int64_t* compositeInterval,
|
||||||
int64_t* compositeToPresentLatency) {
|
int64_t* compositeToPresentLatency) {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ struct NativeWindow {
|
|||||||
// is valid query enum value
|
// is valid query enum value
|
||||||
enum {
|
enum {
|
||||||
IS_VALID = 17,
|
IS_VALID = 17,
|
||||||
FRAME_TIMESTAMPS_SUPPORTS_PRESENT = 18,
|
|
||||||
GET_NEXT_FRAME_ID = 24,
|
GET_NEXT_FRAME_ID = 24,
|
||||||
ENABLE_FRAME_TIMESTAMPS = 25,
|
ENABLE_FRAME_TIMESTAMPS = 25,
|
||||||
GET_COMPOSITOR_TIMING = 26,
|
GET_COMPOSITOR_TIMING = 26,
|
||||||
@@ -52,7 +51,6 @@ struct NativeWindow {
|
|||||||
|
|
||||||
static int getNextFrameId(ANativeWindow* anw, uint64_t* frameId);
|
static int getNextFrameId(ANativeWindow* anw, uint64_t* frameId);
|
||||||
static int enableFrameTimestamps(ANativeWindow* anw, bool enable);
|
static int enableFrameTimestamps(ANativeWindow* anw, bool enable);
|
||||||
static int frameTimestampsSupportsPresent(ANativeWindow* anw, bool* outSupportsPresent);
|
|
||||||
static int getCompositorTiming(ANativeWindow* anw,
|
static int getCompositorTiming(ANativeWindow* anw,
|
||||||
int64_t* compositeDeadline, int64_t* compositeInterval,
|
int64_t* compositeDeadline, int64_t* compositeInterval,
|
||||||
int64_t* compositeToPresentLatency);
|
int64_t* compositeToPresentLatency);
|
||||||
|
|||||||
@@ -19,9 +19,6 @@
|
|||||||
|
|
||||||
#include <android/native_window.h>
|
#include <android/native_window.h>
|
||||||
|
|
||||||
#include <utils/compiler.h>
|
|
||||||
#include <utils/Logger.h>
|
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -39,23 +36,11 @@ bool AndroidSwapChainHelper::setPresentFrameId(
|
|||||||
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
|
int const status = NativeWindow::getNextFrameId(anw, &sysFrameId);
|
||||||
if (status == 0) {
|
if (status == 0) {
|
||||||
std::lock_guard const lock(mLock);
|
std::lock_guard const lock(mLock);
|
||||||
// frameIds must be strictly monotonic, if that's not the case (i.e. the new frameId is
|
auto const pos = mFrameIdToSystemFrameId.find(frameId);
|
||||||
// less or equal to the last one in the map), we have to clear the map, because the
|
if (pos && *pos != sysFrameId) {
|
||||||
// map's find() assume sorted keys.
|
// we're trying to associate the same frame id to a different frame!
|
||||||
// This case can happen if two different filament::Renderer are used with the same
|
return false;
|
||||||
// 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
|
// oldest entry is removed
|
||||||
mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
|
mFrameIdToSystemFrameId.insert(frameId, sysFrameId);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -146,12 +146,12 @@ void Platform::setBlobFunc(InsertBlobFunc&& insertBlob, RetrieveBlobFunc&& retri
|
|||||||
|
|
||||||
bool Platform::hasInsertBlobFunc() const noexcept {
|
bool Platform::hasInsertBlobFunc() const noexcept {
|
||||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||||
return mInsertBlob && bool(*mInsertBlob);
|
return bool(mInsertBlob);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Platform::hasRetrieveBlobFunc() const noexcept {
|
bool Platform::hasRetrieveBlobFunc() const noexcept {
|
||||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
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) {
|
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 {
|
bool Platform::hasDebugUpdateStatFunc() const noexcept {
|
||||||
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
std::lock_guard<decltype(mMutex)> lock(mMutex);
|
||||||
return mDebugUpdateStat && bool(*mDebugUpdateStat);
|
return mDebugUpdateStat != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Platform::debugUpdateStat(const char* key, uint64_t intValue) {
|
void Platform::debugUpdateStat(const char* key, uint64_t intValue) {
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ public:
|
|||||||
size_t size, bool forceGpuBuffer = false);
|
size_t size, bool forceGpuBuffer = false);
|
||||||
~MetalBuffer();
|
~MetalBuffer();
|
||||||
|
|
||||||
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer; }
|
[[nodiscard]] bool wasAllocationSuccessful() const noexcept { return mBuffer || mCpuBuffer; }
|
||||||
|
|
||||||
MetalBuffer(const MetalBuffer& rhs) = delete;
|
MetalBuffer(const MetalBuffer& rhs) = delete;
|
||||||
MetalBuffer& operator=(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
|
* Denotes that this buffer is used for a draw call ensuring that its allocation remains valid
|
||||||
* until the end of the current frame.
|
* until the end of the current frame.
|
||||||
*
|
*
|
||||||
* @return The MTLBuffer representing the current state of the buffer to bind, it never returns
|
* @return The MTLBuffer representing the current state of the buffer to bind, or nil if there
|
||||||
* nil.
|
* is no device allocation.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
id<MTLBuffer> getGpuBufferForDraw() noexcept;
|
id<MTLBuffer> getGpuBufferForDraw() noexcept;
|
||||||
|
|
||||||
|
void* getCpuBuffer() const noexcept { return mCpuBuffer; }
|
||||||
|
|
||||||
void setLabel(const utils::ImmutableCString& label) {
|
void setLabel(const utils::ImmutableCString& label) {
|
||||||
#if FILAMENT_METAL_DEBUG_LABELS
|
#if FILAMENT_METAL_DEBUG_LABELS
|
||||||
if (label.empty()) {
|
if (label.empty()) {
|
||||||
@@ -233,6 +235,7 @@ private:
|
|||||||
UploadStrategy mUploadStrategy;
|
UploadStrategy mUploadStrategy;
|
||||||
TrackedMetalBuffer mBuffer;
|
TrackedMetalBuffer mBuffer;
|
||||||
size_t mBufferSize = 0;
|
size_t mBufferSize = 0;
|
||||||
|
void* mCpuBuffer = nullptr;
|
||||||
MetalContext& mContext;
|
MetalContext& mContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -39,30 +39,34 @@ MetalBuffer::MetalBuffer(MetalContext& context, BufferObjectBinding bindingType,
|
|||||||
mUploadStrategy = UploadStrategy::POOL;
|
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 (size <= 4 * 1024 && bindingType != BufferObjectBinding::SHADER_STORAGE &&
|
||||||
#if defined(FILAMENT_IOS) || defined(__arm64__) || defined(__aarch64__)
|
usage == BufferUsage::DYNAMIC && !forceGpuBuffer) {
|
||||||
// iOS and Apple Silicon devices use UMA (Unified Memory Architecture), so we use Shared memory.
|
mBuffer = nil;
|
||||||
options = MTLResourceStorageModeShared;
|
mCpuBuffer = malloc(size);
|
||||||
#else
|
return;
|
||||||
// Intel Macs require Managed memory for CPU/GPU synchronization.
|
|
||||||
options = MTLResourceStorageModeManaged;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Otherwise, we allocate a private GPU buffer.
|
||||||
{
|
{
|
||||||
ScopedAllocationTimer timer("generic");
|
ScopedAllocationTimer timer("generic");
|
||||||
mBuffer = { [context.device newBufferWithLength:size options:options],
|
mBuffer = { [context.device newBufferWithLength:size options:MTLResourceStorageModePrivate],
|
||||||
TrackedMetalBuffer::Type::GENERIC };
|
TrackedMetalBuffer::Type::GENERIC };
|
||||||
}
|
}
|
||||||
|
|
||||||
// mBuffer might fail to be allocated. Clients can check for this by calling
|
// mBuffer might fail to be allocated. Clients can check for this by calling
|
||||||
// wasAllocationSuccessful().
|
// wasAllocationSuccessful().
|
||||||
}
|
}
|
||||||
|
|
||||||
MetalBuffer::~MetalBuffer() = default;
|
MetalBuffer::~MetalBuffer() {
|
||||||
|
if (mCpuBuffer) {
|
||||||
|
free(mCpuBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MetalBuffer::copyIntoBuffer(
|
void MetalBuffer::copyIntoBuffer(
|
||||||
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
|
void* src, size_t size, size_t byteOffset, TagResolver&& getHandleTag) {
|
||||||
@@ -79,6 +83,12 @@ void MetalBuffer::copyIntoBuffer(
|
|||||||
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
|
FILAMENT_CHECK_PRECONDITION(!(byteOffset & 0x3))
|
||||||
<< "byteOffset must be a multiple of 4, tag=" << getHandleTag();
|
<< "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) {
|
switch (mUploadStrategy) {
|
||||||
case UploadStrategy::BUMP_ALLOCATOR:
|
case UploadStrategy::BUMP_ALLOCATOR:
|
||||||
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
|
uploadWithBumpAllocator(src, size, byteOffset, std::move(getHandleTag));
|
||||||
@@ -96,6 +106,11 @@ void MetalBuffer::copyIntoBufferUnsynchronized(
|
|||||||
}
|
}
|
||||||
|
|
||||||
id<MTLBuffer> MetalBuffer::getGpuBufferForDraw() noexcept {
|
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);
|
assert_invariant(mBuffer);
|
||||||
return mBuffer.get();
|
return mBuffer.get();
|
||||||
}
|
}
|
||||||
@@ -156,6 +171,41 @@ void MetalBuffer::bindBuffers(id<MTLCommandBuffer> cmdBuffer, id<MTLCommandEncod
|
|||||||
offsets:metalOffsets.data()
|
offsets:metalOffsets.data()
|
||||||
withRange:bufferRange];
|
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(
|
void MetalBuffer::uploadWithPoolBuffer(
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ public:
|
|||||||
|
|
||||||
MetalContext* getContext() { return mContext; }
|
MetalContext* getContext() { return mContext; }
|
||||||
|
|
||||||
using DriverBase::scheduleDestroy;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
friend class MetalSwapChain;
|
friend class MetalSwapChain;
|
||||||
|
|||||||
@@ -2275,10 +2275,7 @@ MemoryMappedBufferHandle MetalDriver::mapBufferS() noexcept {
|
|||||||
void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
|
void MetalDriver::mapBufferR(MemoryMappedBufferHandle mmbh,
|
||||||
BufferObjectHandle boh, size_t offset,
|
BufferObjectHandle boh, size_t offset,
|
||||||
size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) {
|
size_t size, MapBufferAccessFlags access, utils::ImmutableCString&& tag) {
|
||||||
assert_invariant(boh);
|
construct_handle<MetalMemoryMappedBuffer>(mmbh, boh, offset, size, access);
|
||||||
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));
|
mHandleAllocator.associateTagToHandle(mmbh.getId(), std::move(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2286,16 +2283,21 @@ void MetalDriver::unmapBuffer(MemoryMappedBufferHandle mmbh) {
|
|||||||
if (UTILS_UNLIKELY(!mmbh)) {
|
if (UTILS_UNLIKELY(!mmbh)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
|
|
||||||
mmb->unmap();
|
|
||||||
destruct_handle<MetalMemoryMappedBuffer>(mmbh);
|
destruct_handle<MetalMemoryMappedBuffer>(mmbh);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MetalDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset,
|
void MetalDriver::copyToMemoryMappedBuffer(MemoryMappedBufferHandle mmbh, size_t offset,
|
||||||
BufferDescriptor&& data) {
|
BufferDescriptor&& data) {
|
||||||
auto* mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
|
auto mmb = handle_cast<MetalMemoryMappedBuffer>(mmbh);
|
||||||
mmb->copy(*this, offset, std::move(data));
|
|
||||||
|
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
|
// explicit instantiation of the Dispatcher
|
||||||
|
|||||||
@@ -569,22 +569,14 @@ struct MetalDescriptorSet : public HwDescriptorSet {
|
|||||||
|
|
||||||
|
|
||||||
struct MetalMemoryMappedBuffer : public HwMemoryMappedBuffer {
|
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{};
|
MapBufferAccessFlags access{};
|
||||||
struct {
|
uint32_t size = 0;
|
||||||
MetalBufferObject* bo;
|
uint32_t offset = 0;
|
||||||
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
|
} // namespace backend
|
||||||
|
|||||||
@@ -1664,44 +1664,5 @@ id<MTLBuffer> MetalDescriptorSet::finalizeAndGetBuffer(MetalDriver* driver, Shad
|
|||||||
return buffer.get();
|
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 backend
|
||||||
} // namespace filament
|
} // namespace filament
|
||||||
|
|||||||
@@ -106,8 +106,6 @@ struct PlatformEGLAndroid::SwapChainEGLAndroid : public SwapChainEGL {
|
|||||||
void terminate(PlatformEGLAndroid& platform);
|
void terminate(PlatformEGLAndroid& platform);
|
||||||
bool setPresentFrameId(uint64_t frameId) const noexcept;
|
bool setPresentFrameId(uint64_t frameId) const noexcept;
|
||||||
uint64_t getFrameId(uint64_t frameId) const noexcept;
|
uint64_t getFrameId(uint64_t frameId) const noexcept;
|
||||||
bool compositorTimingSupported = false;
|
|
||||||
bool frameTimestampsSupported = false;
|
|
||||||
private:
|
private:
|
||||||
AndroidSwapChainHelper mImpl{};
|
AndroidSwapChainHelper mImpl{};
|
||||||
};
|
};
|
||||||
@@ -230,9 +228,9 @@ Driver* PlatformEGLAndroid::createDriver(void* sharedContext,
|
|||||||
"eglGetNativeClientBufferANDROID"));
|
"eglGetNativeClientBufferANDROID"));
|
||||||
|
|
||||||
if (ext.egl.ANDROID_presentation_time) {
|
if (ext.egl.ANDROID_presentation_time) {
|
||||||
eglPresentationTimeANDROID =
|
eglGetNativeClientBufferANDROID =
|
||||||
PFNEGLPRESENTATIONTIMEANDROIDPROC(eglGetProcAddress(
|
PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC(eglGetProcAddress(
|
||||||
"eglPresentationTimeANDROID"));
|
"eglGetNativeClientBufferANDROID"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ext.egl.ANDROID_get_frame_timestamps) {
|
if (ext.egl.ANDROID_get_frame_timestamps) {
|
||||||
@@ -291,21 +289,11 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
|||||||
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
||||||
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
||||||
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
|
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)) {
|
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
|
||||||
EGLSurface const sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
|
EGLSurface const sur = static_cast<SwapChainEGL const *>(swapchain)->sur;
|
||||||
if (sur == EGL_NO_SURFACE) {
|
if (sur == EGL_NO_SURFACE) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<EGLnsecsANDROID, 3> values;
|
std::array<EGLnsecsANDROID, 3> values;
|
||||||
@@ -316,16 +304,26 @@ bool PlatformEGLAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
|||||||
};
|
};
|
||||||
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
|
EGLBoolean const success = eglGetCompositorTimingANDROID(getEglDisplay(), sur,
|
||||||
names.size(), names.data(), values.data());
|
names.size(), names.data(), values.data());
|
||||||
if (UTILS_UNLIKELY(!success)) {
|
if (!success) {
|
||||||
// reset current error to EGL_SUCCESS
|
return false;
|
||||||
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;
|
||||||
}
|
}
|
||||||
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,
|
bool PlatformEGLAndroid::setPresentFrameId(SwapChain const* swapchain,
|
||||||
@@ -350,10 +348,6 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!static_cast<SwapChainEGLAndroid const *>(swapchain)->frameTimestampsSupported) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
|
if (UTILS_LIKELY(ext.egl.ANDROID_get_frame_timestamps)) {
|
||||||
EGLSurface const sur = sc->sur;
|
EGLSurface const sur = sc->sur;
|
||||||
if (sur == EGL_NO_SURFACE) {
|
if (sur == EGL_NO_SURFACE) {
|
||||||
@@ -374,9 +368,7 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
|||||||
};
|
};
|
||||||
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
|
EGLBoolean const success = eglGetFrameTimestampsANDROID(getEglDisplay(), sur, hwFrameId,
|
||||||
names.size(), names.data(), values.data());
|
names.size(), names.data(), values.data());
|
||||||
if (UTILS_UNLIKELY(!success)) {
|
if (!success) {
|
||||||
// reset current error to EGL_SUCCESS
|
|
||||||
eglGetError();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
outFrameTimestamps->requestedPresentTime = values[0];
|
outFrameTimestamps->requestedPresentTime = values[0];
|
||||||
@@ -390,44 +382,28 @@ bool PlatformEGLAndroid::queryFrameTimestamps(SwapChain const* swapchain, uint64
|
|||||||
outFrameTimestamps->releaseTime = values[8];
|
outFrameTimestamps->releaseTime = values[8];
|
||||||
return true;
|
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);
|
return PlatformEGL::queryFrameTimestamps(swapchain, frameId, outFrameTimestamps);
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) {
|
Platform::SwapChain* PlatformEGLAndroid::createSwapChain(void* nativeWindow, uint64_t const flags) {
|
||||||
auto* const sc = new(std::nothrow) SwapChainEGLAndroid(*this, nativeWindow, 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;
|
return sc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -749,6 +725,8 @@ PlatformEGLAndroid::SwapChainEGLAndroid::SwapChainEGLAndroid(PlatformEGLAndroid
|
|||||||
// we ignore the result, it doesn't matter much if it fails
|
// we ignore the result, it doesn't matter much if it fails
|
||||||
eglSurfaceAttrib(platform.getEglDisplay(), sur, EGL_TIMESTAMPS_ANDROID, EGL_TRUE);
|
eglSurfaceAttrib(platform.getEglDisplay(), sur, EGL_TIMESTAMPS_ANDROID, EGL_TRUE);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
NativeWindow::enableFrameTimestamps(EGLNativeWindowType(nativeWindow), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -541,15 +541,9 @@ bool VulkanPlatformAndroid::queryCompositorTiming(SwapChain const* swapchain,
|
|||||||
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
outCompositorTiming->frameTime = preferredTimeline.frameTime;
|
||||||
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
outCompositorTiming->expectedPresentTime = preferredTimeline.expectedPresentTime;
|
||||||
outCompositorTiming->frameTimelineDeadline = preferredTimeline.frameTimelineDeadline;
|
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);
|
auto vulkanSwapchain = static_cast<VulkanPlatformSwapChainBase const *>(swapchain);
|
||||||
vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
|
return vulkanSwapchain->queryCompositorTiming(outCompositorTiming);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept {
|
bool VulkanPlatformAndroid::setPresentFrameId(SwapChain const* swapchain, uint64_t frameId) noexcept {
|
||||||
|
|||||||
@@ -361,15 +361,13 @@ bool VulkanPlatformSurfaceSwapChain::queryCompositorTiming(
|
|||||||
CompositorTiming* outCompositorTiming) const {
|
CompositorTiming* outCompositorTiming) const {
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
// fallback to private APIs
|
// fallback to private APIs
|
||||||
if (UTILS_VERY_LIKELY(mNativeWindow)) {
|
int const status = NativeWindow::getCompositorTiming(
|
||||||
int const status = NativeWindow::getCompositorTiming(
|
static_cast<ANativeWindow*>(mNativeWindow),
|
||||||
static_cast<ANativeWindow*>(mNativeWindow),
|
&outCompositorTiming->compositeDeadline,
|
||||||
&outCompositorTiming->compositeDeadline,
|
&outCompositorTiming->compositeInterval,
|
||||||
&outCompositorTiming->compositeInterval,
|
&outCompositorTiming->compositeToPresentLatency);
|
||||||
&outCompositorTiming->compositeToPresentLatency);
|
if (status == 0) {
|
||||||
if (status == 0) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming);
|
return VulkanPlatformSwapChainBase::queryCompositorTiming(outCompositorTiming);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#include "WebGPUConstants.h"
|
#include "WebGPUConstants.h"
|
||||||
#include "WebGPUQueueManager.h"
|
#include "WebGPUQueueManager.h"
|
||||||
|
#include "WebGPUStagePool.h"
|
||||||
|
|
||||||
#include "DriverBase.h"
|
#include "DriverBase.h"
|
||||||
#include <backend/BufferDescriptor.h>
|
#include <backend/BufferDescriptor.h>
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
namespace filament::backend {
|
namespace filament::backend {
|
||||||
|
|
||||||
@@ -65,7 +67,7 @@ WebGPUBufferBase::WebGPUBufferBase(wgpu::Device const& device, const wgpu::Buffe
|
|||||||
// of 4 by padding with zeros.
|
// of 4 by padding with zeros.
|
||||||
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
||||||
const uint32_t byteOffset, wgpu::Device const& device,
|
const uint32_t byteOffset, wgpu::Device const& device,
|
||||||
WebGPUQueueManager* const webGPUQueueManager) {
|
WebGPUQueueManager* const webGPUQueueManager, WebGPUStagePool* const webGPUStagePool) {
|
||||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
|
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.buffer)
|
||||||
<< "updateGPUBuffer called with a null buffer";
|
<< "updateGPUBuffer called with a null buffer";
|
||||||
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
|
FILAMENT_CHECK_PRECONDITION(bufferDescriptor.size + byteOffset <= mBuffer.GetSize())
|
||||||
@@ -79,34 +81,54 @@ void WebGPUBufferBase::updateGPUBuffer(BufferDescriptor const& bufferDescriptor,
|
|||||||
// This may have some performance implications. That should be investigated later.
|
// This may have some performance implications. That should be investigated later.
|
||||||
assert_invariant(mBuffer.GetUsage() & wgpu::BufferUsage::CopyDst);
|
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 remainder = bufferDescriptor.size % FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
||||||
const size_t mainBulk = bufferDescriptor.size - remainder;
|
const size_t mainBulk = bufferDescriptor.size - remainder;
|
||||||
const size_t stagingBufferSize =
|
const size_t stagingBufferSize =
|
||||||
remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
remainder == 0 ? bufferDescriptor.size : mainBulk + FILAMENT_WEBGPU_BUFFER_SIZE_MODULUS;
|
||||||
|
|
||||||
// create a staging buffer
|
Stage stage = webGPUStagePool->acquireBuffer(stagingBufferSize);
|
||||||
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();
|
std::string mappedRangeIsNull = stage.mappedRange
|
||||||
memcpy(mappedRange, bufferDescriptor.buffer, bufferDescriptor.size);
|
? "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);
|
||||||
|
|
||||||
// Make sure the padded memory is set to 0 to have deterministic behaviors
|
stage.buffer.Unmap();
|
||||||
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.
|
// Copy the staging buffer contents to the destination buffer.
|
||||||
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stagingBuffer, 0, mBuffer,
|
webGPUQueueManager->getCommandEncoder().CopyBufferToBuffer(stage.buffer, 0, mBuffer,
|
||||||
byteOffset, stagingBufferSize);
|
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";
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace filament::backend
|
} // namespace filament::backend
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ namespace filament::backend {
|
|||||||
|
|
||||||
class BufferDescriptor;
|
class BufferDescriptor;
|
||||||
class WebGPUQueueManager;
|
class WebGPUQueueManager;
|
||||||
|
class WebGPUStagePool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A base class for WebGPU buffer objects, providing common functionality for creating and
|
* 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.
|
* ensures the calls happen in the expected sequence.
|
||||||
*/
|
*/
|
||||||
void updateGPUBuffer(BufferDescriptor const&, uint32_t byteOffset, wgpu::Device const& device,
|
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; }
|
[[nodiscard]] wgpu::Buffer const& getBuffer() const { return mBuffer; }
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ WebGPUDriver::WebGPUDriver(WebGPUPlatform& platform,
|
|||||||
mAdapter{ mPlatform.requestAdapter(nullptr) },
|
mAdapter{ mPlatform.requestAdapter(nullptr) },
|
||||||
mDevice{ mPlatform.requestDevice(mAdapter) },
|
mDevice{ mPlatform.requestDevice(mAdapter) },
|
||||||
mQueueManager{ mDevice },
|
mQueueManager{ mDevice },
|
||||||
|
mStagePool{ mDevice },
|
||||||
mPipelineLayoutCache{ mDevice },
|
mPipelineLayoutCache{ mDevice },
|
||||||
mPipelineCache{ mDevice },
|
mPipelineCache{ mDevice },
|
||||||
mRenderPassMipmapGenerator{ mDevice, &mQueueManager },
|
mRenderPassMipmapGenerator{ mDevice, &mQueueManager },
|
||||||
@@ -856,7 +857,7 @@ void WebGPUDriver::updateIndexBuffer(Handle<HwIndexBuffer> indexBufferHandle,
|
|||||||
// draw calls are made.
|
// draw calls are made.
|
||||||
flush();
|
flush();
|
||||||
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
|
handleCast<WebGPUIndexBuffer>(indexBufferHandle)
|
||||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
||||||
scheduleDestroy(std::move(bufferDescriptor));
|
scheduleDestroy(std::move(bufferDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -867,14 +868,14 @@ void WebGPUDriver::updateBufferObject(Handle<HwBufferObject> bufferObjectHandle,
|
|||||||
// draw calls are made.
|
// draw calls are made.
|
||||||
flush();
|
flush();
|
||||||
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
||||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
||||||
scheduleDestroy(std::move(bufferDescriptor));
|
scheduleDestroy(std::move(bufferDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
|
void WebGPUDriver::updateBufferObjectUnsynchronized(Handle<HwBufferObject> bufferObjectHandle,
|
||||||
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
|
BufferDescriptor&& bufferDescriptor, const uint32_t byteOffset) {
|
||||||
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
handleCast<WebGPUBufferObject>(bufferObjectHandle)
|
||||||
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager);
|
->updateGPUBuffer(bufferDescriptor, byteOffset, mDevice, &mQueueManager, &mStagePool);
|
||||||
scheduleDestroy(std::move(bufferDescriptor));
|
scheduleDestroy(std::move(bufferDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "webgpu/WebGPUPipelineLayoutCache.h"
|
#include "webgpu/WebGPUPipelineLayoutCache.h"
|
||||||
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
|
#include "webgpu/WebGPURenderPassMipmapGenerator.h"
|
||||||
#include "webgpu/WebGPUQueueManager.h"
|
#include "webgpu/WebGPUQueueManager.h"
|
||||||
|
#include "webgpu/WebGPUStagePool.h"
|
||||||
#include "webgpu/utils/AsyncTaskCounter.h"
|
#include "webgpu/utils/AsyncTaskCounter.h"
|
||||||
#include <backend/platforms/WebGPUPlatform.h>
|
#include <backend/platforms/WebGPUPlatform.h>
|
||||||
|
|
||||||
@@ -81,6 +82,7 @@ private:
|
|||||||
wgpu::Device mDevice = nullptr;
|
wgpu::Device mDevice = nullptr;
|
||||||
wgpu::Limits mDeviceLimits = {};
|
wgpu::Limits mDeviceLimits = {};
|
||||||
WebGPUQueueManager mQueueManager;
|
WebGPUQueueManager mQueueManager;
|
||||||
|
WebGPUStagePool mStagePool;
|
||||||
void* mNativeWindow = nullptr;
|
void* mNativeWindow = nullptr;
|
||||||
WebGPUSwapChain* mSwapChain = nullptr;
|
WebGPUSwapChain* mSwapChain = nullptr;
|
||||||
uint64_t mNextFakeHandle = 1;
|
uint64_t mNextFakeHandle = 1;
|
||||||
|
|||||||
86
filament/backend/src/webgpu/WebGPUStagePool.cpp
Normal file
86
filament/backend/src/webgpu/WebGPUStagePool.cpp
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
49
filament/backend/src/webgpu/WebGPUStagePool.h
Normal file
49
filament/backend/src/webgpu/WebGPUStagePool.h
Normal 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 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,10 +113,8 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Retrieve a history of frame timing information. The maximum frame history size is
|
* Retrieve a history of frame timing information. The maximum frame history size is
|
||||||
* given by getMaxFrameHistorySize().
|
* 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.
|
* @param historySize requested history size. The returned vector could be smaller.
|
||||||
* @return A vector of FrameInfo.
|
* @return A vector of FrameInfo.
|
||||||
* @see beginFrame()
|
|
||||||
*/
|
*/
|
||||||
utils::FixedCapacityVector<FrameInfo> getFrameInfoHistory(
|
utils::FixedCapacityVector<FrameInfo> getFrameInfoHistory(
|
||||||
size_t historySize = 1) const noexcept;
|
size_t historySize = 1) const noexcept;
|
||||||
@@ -328,8 +326,6 @@ public:
|
|||||||
* or 0 if unknown. This value should be the timestamp of
|
* or 0 if unknown. This value should be the timestamp of
|
||||||
* the last h/w vsync. It is expressed in the
|
* the last h/w vsync. It is expressed in the
|
||||||
* std::chrono::steady_clock time base.
|
* 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.
|
* @param swapChain A pointer to the SwapChain instance to use.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
@@ -341,8 +337,6 @@ public:
|
|||||||
*
|
*
|
||||||
* @note
|
* @note
|
||||||
* All calls to render() must happen *after* beginFrame().
|
* 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
|
* @see
|
||||||
* endFrame()
|
* endFrame()
|
||||||
|
|||||||
@@ -56,9 +56,6 @@ public:
|
|||||||
//! Returns true if the map is full.
|
//! Returns true if the map is full.
|
||||||
bool full() const noexcept { return mSize == N; }
|
bool full() const noexcept { return mSize == N; }
|
||||||
|
|
||||||
//! Clears the map entirely.
|
|
||||||
void clear() noexcept { mSize = 0; mHead = 0; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a new key-value pair.
|
* Inserts a new key-value pair.
|
||||||
* The key must be greater than the key of the last inserted element.
|
* 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) {
|
UTILS_NOINLINE void insert(key_type key, mapped_type value) {
|
||||||
assert(empty() || key > back().first); // assert monotonic
|
assert(empty() || key > back().first); // assert monotonic
|
||||||
if (UTILS_LIKELY(full())) {
|
if (full()) {
|
||||||
// container is full, replace the oldest element
|
// container is full, replace the oldest element
|
||||||
mStorage[mHead] = { key, value };
|
mStorage[mHead] = { key, value };
|
||||||
mHead = (mHead + 1) % N;
|
mHead = (mHead + 1) % N;
|
||||||
|
|||||||
Reference in New Issue
Block a user