diff --git a/android/filament-android/CMakeLists.txt b/android/filament-android/CMakeLists.txt index 09cb594a78..8c89028b85 100644 --- a/android/filament-android/CMakeLists.txt +++ b/android/filament-android/CMakeLists.txt @@ -56,6 +56,7 @@ set(VERSION_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/libfilament-jni.map") set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -Wl,--version-script=${VERSION_SCRIPT}") add_library(filament-jni SHARED + src/main/cpp/BufferObject.cpp src/main/cpp/Camera.cpp src/main/cpp/Colors.cpp src/main/cpp/ColorGrading.cpp diff --git a/android/filament-android/src/main/cpp/BufferObject.cpp b/android/filament-android/src/main/cpp/BufferObject.cpp new file mode 100644 index 0000000000..e86d81bd5c --- /dev/null +++ b/android/filament-android/src/main/cpp/BufferObject.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 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 + +#include +#include +#include + +#include + +#include + +#include "common/CallbackUtils.h" +#include "common/NioUtils.h" + +using namespace filament; +using namespace backend; + +extern "C" JNIEXPORT jlong JNICALL +Java_com_google_android_filament_BufferObject_nCreateBuilder(JNIEnv *env, jclass type) { + return (jlong) new BufferObject::Builder(); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_BufferObject_nDestroyBuilder(JNIEnv *env, jclass type, + jlong nativeBuilder) { + BufferObject::Builder* builder = (BufferObject::Builder *) nativeBuilder; + delete builder; +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_BufferObject_nBuilderSize(JNIEnv *env, jclass type, + jlong nativeBuilder, jint byteCount) { + BufferObject::Builder* builder = (BufferObject::Builder *) nativeBuilder; + builder->size((uint32_t) byteCount); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_BufferObject_nBuilderBindingType(JNIEnv *env, jclass type, + jlong nativeBuilder, jint bindingType) { + using BindingType = BufferObject::BindingType; + BufferObject::Builder* builder = (BufferObject::Builder *) nativeBuilder; + BindingType types[] = {BindingType::VERTEX}; + builder->bindingType(types[bindingType]); +} + +extern "C" JNIEXPORT jlong JNICALL +Java_com_google_android_filament_BufferObject_nBuilderBuild(JNIEnv *env, jclass type, + jlong nativeBuilder, jlong nativeEngine) { + BufferObject::Builder* builder = (BufferObject::Builder *) nativeBuilder; + Engine *engine = (Engine *) nativeEngine; + return (jlong) builder->build(*engine); +} + +extern "C" JNIEXPORT jint JNICALL +Java_com_google_android_filament_BufferObject_nGetByteCount(JNIEnv *env, jclass type, + jlong nativeBufferObject) { + BufferObject *bufferObject = (BufferObject *) nativeBufferObject; + return (jint) bufferObject->getByteCount(); +} + +extern "C" JNIEXPORT int JNICALL +Java_com_google_android_filament_BufferObject_nSetBuffer(JNIEnv *env, jclass type, + jlong nativeBufferObject, jlong nativeEngine, jobject buffer, int remaining, + jint destOffsetInBytes, jint count, + jobject handler, jobject runnable) { + BufferObject *bufferObject = (BufferObject *) nativeBufferObject; + Engine *engine = (Engine *) nativeEngine; + + AutoBuffer nioBuffer(env, buffer, count); + void* data = nioBuffer.getData(); + size_t sizeInBytes = nioBuffer.getSize(); + if (sizeInBytes > (remaining << nioBuffer.getShift())) { + // BufferOverflowException + return -1; + } + + auto* callback = JniBufferCallback::make(engine, env, handler, runnable, std::move(nioBuffer)); + + BufferDescriptor desc(data, sizeInBytes, &JniBufferCallback::invoke, callback); + + bufferObject->setBuffer(*engine, std::move(desc), (uint32_t) destOffsetInBytes); + + return 0; +} diff --git a/android/filament-android/src/main/cpp/VertexBuffer.cpp b/android/filament-android/src/main/cpp/VertexBuffer.cpp index 60d613ed75..ed9460e17a 100644 --- a/android/filament-android/src/main/cpp/VertexBuffer.cpp +++ b/android/filament-android/src/main/cpp/VertexBuffer.cpp @@ -50,6 +50,13 @@ Java_com_google_android_filament_VertexBuffer_nBuilderVertexCount(JNIEnv *env, j builder->vertexCount((uint32_t) vertexCount); } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_VertexBuffer_nBuilderEnableBufferObjects(JNIEnv *env, jclass type, + jlong nativeBuilder, jboolean enabled) { + VertexBuffer::Builder* builder = (VertexBuffer::Builder *) nativeBuilder; + builder->enableBufferObjects(enabled); +} + extern "C" JNIEXPORT void JNICALL Java_com_google_android_filament_VertexBuffer_nBuilderBufferCount(JNIEnv *env, jclass type, jlong nativeBuilder, jint bufferCount) { @@ -115,6 +122,15 @@ Java_com_google_android_filament_VertexBuffer_nSetBufferAt(JNIEnv *env, jclass t return 0; } +extern "C" JNIEXPORT void JNICALL +Java_com_google_android_filament_VertexBuffer_nSetBufferObjectAt(JNIEnv *env, jclass type, + jlong nativeVertexBuffer, jlong nativeEngine, jint bufferIndex, jlong nativeBufferObject) { + VertexBuffer *vertexBuffer = (VertexBuffer *) nativeVertexBuffer; + Engine *engine = (Engine *) nativeEngine; + BufferObject *bufferObject = (BufferObject *) nativeBufferObject; + vertexBuffer->setBufferObjectAt(*engine, (uint8_t) bufferIndex, bufferObject); +} + extern "C" [[deprecated]] JNIEXPORT void JNICALL Java_com_google_android_filament_VertexBuffer_nPopulateTangentQuaternions(JNIEnv *env, jclass type, jint quatType, jint quatCount, jobject outBuffer, jint outRemaining, diff --git a/android/filament-android/src/main/java/com/google/android/filament/BufferObject.java b/android/filament-android/src/main/java/com/google/android/filament/BufferObject.java new file mode 100644 index 0000000000..7fc63e3652 --- /dev/null +++ b/android/filament-android/src/main/java/com/google/android/filament/BufferObject.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.filament; + +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.Buffer; +import java.nio.BufferOverflowException; + +/** + * A generic GPU buffer containing data. + * + * Usage of this BufferObject is optional. For simple use cases it is not necessary. It is useful + * only when you need to share data between multiple VertexBuffer instances. It also allows you to + * efficiently swap-out the buffers in VertexBuffer. + * + * NOTE: For now this is only used for vertex data, but in the future we may use it for other things + * (e.g. compute). + * + * @see VertexBuffer + */ +public class BufferObject { + private long mNativeObject; + + private BufferObject(long nativeBufferObject) { + mNativeObject = nativeBufferObject; + } + + public static class Builder { + @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) + // Keep to finalize native resources + private final BuilderFinalizer mFinalizer; + private final long mNativeBuilder; + + public enum BindingType { + VERTEX, + } + + public Builder() { + mNativeBuilder = nCreateBuilder(); + mFinalizer = new BuilderFinalizer(mNativeBuilder); + } + + /** + * Size of the buffer in bytes. + * + * @param byteCount Maximum number of bytes the BufferObject can hold. + * @return A reference to this Builder for chaining calls. + */ + @NonNull + public Builder size(@IntRange(from = 1) int byteCount) { + nBuilderSize(mNativeBuilder, byteCount); + return this; + } + + /** + * The binding type for this buffer object. (defaults to VERTEX) + * + * @param BindingType Distinguishes between SSBO, VBO, etc. For now this must be VERTEX. + * @return A reference to this Builder for chaining calls. + */ + @NonNull + public Builder bindingType(@NonNull BindingType bindingType) { + nBuilderBindingType(mNativeBuilder, bindingType.ordinal()); + return this; + } + + /** + * Creates and returns the BufferObject object. After creation, the buffer + * is uninitialized. Use {@link #setBuffer} to initialize the BufferObject. + * + * @param engine reference to the {@link Engine} to associate this BufferObject + * with + * + * @return the newly created BufferObject object + * + * @exception IllegalStateException if the BufferObject could not be created + * + * @see #setBuffer + */ + @NonNull + public BufferObject build(@NonNull Engine engine) { + long nativeBufferObject = nBuilderBuild(mNativeBuilder, engine.getNativeObject()); + if (nativeBufferObject == 0) + throw new IllegalStateException("Couldn't create BufferObject"); + return new BufferObject(nativeBufferObject); + } + + private static class BuilderFinalizer { + private final long mNativeObject; + + BuilderFinalizer(long nativeObject) { + mNativeObject = nativeObject; + } + + @Override + public void finalize() { + try { + super.finalize(); + } catch (Throwable t) { // Ignore + } finally { + nDestroyBuilder(mNativeObject); + } + } + } + } + + /** + * Returns the size of this BufferObject in elements. + * + * @return the number of bytes the BufferObject holds + */ + @IntRange(from = 0) + public int getByteCount() { + return nGetByteCount(getNativeObject()); + } + + /** + * Asynchronously copy-initializes this BufferObject from the data provided. + * + * @param engine reference to the {@link Engine} to associate this + * BufferObject with + * @param buffer a CPU-side {@link Buffer} with the data used to initialize the + * BufferObject. + */ + public void setBuffer(@NonNull Engine engine, @NonNull Buffer buffer) { + setBuffer(engine, buffer, 0, 0, null, null); + } + + /** + * Asynchronously copy-initializes a region of this BufferObject from the data + * provided. + * + * @param engine reference to the {@link Engine} to associate this + * BufferObject with + * @param buffer a CPU-side {@link Buffer} with the data used to initialize the + * BufferObject. + * @param destOffsetInBytes offset in bytes into the BufferObject + * @param count number of bytes to consume, defaults to + * buffer.remaining() + */ + public void setBuffer(@NonNull Engine engine, @NonNull Buffer buffer, + @IntRange(from = 0) int destOffsetInBytes, @IntRange(from = 0) int count) { + setBuffer(engine, buffer, destOffsetInBytes, count, null, null); + } + + /** + * Asynchronously copy-initializes a region of this BufferObject from the data + * provided. + * + * @param engine reference to the {@link Engine} to associate this + * BufferObject with + * @param buffer a CPU-side {@link Buffer} with the data used to initialize the + * BufferObject. + * @param destOffsetInBytes offset in bytes into the BufferObject + * @param count number of bytes to consume, defaults to + * buffer.remaining() + * @param handler an {@link java.util.concurrent.Executor Executor}. On Android this + * can also be a {@link android.os.Handler Handler}. + * @param callback a callback executed by handler when buffer + * is no longer needed. + */ + public void setBuffer(@NonNull Engine engine, @NonNull Buffer buffer, + @IntRange(from = 0) int destOffsetInBytes, @IntRange(from = 0) int count, + @Nullable Object handler, @Nullable Runnable callback) { + int result = nSetBuffer(getNativeObject(), engine.getNativeObject(), buffer, buffer.remaining(), + destOffsetInBytes, count == 0 ? buffer.remaining() : count, handler, callback); + if (result < 0) { + throw new BufferOverflowException(); + } + } + + public long getNativeObject() { + if (mNativeObject == 0) { + throw new IllegalStateException("Calling method on destroyed BufferObject"); + } + return mNativeObject; + } + + void clearNativeObject() { + mNativeObject = 0; + } + + private static native long nCreateBuilder(); + private static native void nDestroyBuilder(long nativeBuilder); + private static native void nBuilderSize(long nativeBuilder, int byteCount); + private static native void nBuilderBindingType(long nativeBuilder, int bindingType); + private static native long nBuilderBuild(long nativeBuilder, long nativeEngine); + + private static native int nGetByteCount(long nativeBufferObject); + private static native int nSetBuffer(long nativeBufferObject, long nativeEngine, + Buffer buffer, int remaining, + int destOffsetInBytes, int count, Object handler, Runnable callback); +} diff --git a/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java b/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java index 6d56e12cba..0ff6f52c2e 100644 --- a/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java +++ b/android/filament-android/src/main/java/com/google/android/filament/VertexBuffer.java @@ -170,6 +170,21 @@ public class VertexBuffer { return this; } + /** + * Allows buffers to be swapped out and shared using BufferObject. + * + * If buffer objects mode is enabled, clients must call setBufferObjectAt rather than + * setBufferAt. This allows sharing of data between VertexBuffer objects, but it may + * slightly increase the memory footprint of Filament's internal bookkeeping. + * + * @param enabled If true, enables buffer object mode. False by default. + */ + @NonNull + public Builder enableBufferObjects(boolean enabled) { + nBuilderEnableBufferObjects(mNativeBuilder, enabled); + return this; + } + /** * Defines how many buffers will be created in this vertex buffer set. These buffers are * later referenced by index from 0 to bufferCount - 1. @@ -397,6 +412,23 @@ public class VertexBuffer { throw new BufferOverflowException(); } } + + /** + * Swaps in the given buffer object. + * + * To use this, you must first call enableBufferObjects() on the Builder. + * + * @param engine Reference to the filament::Engine to associate this VertexBuffer with. + * @param bufferIndex Index of the buffer to initialize. Must be between 0 + * and Builder::bufferCount() - 1. + * @param bufferObject The handle to the GPU data that will be used in this buffer slot. + */ + public void setBufferObjectAt(@NonNull Engine engine, int bufferIndex, + @NonNull BufferObject bufferObject) { + nSetBufferObjectAt(getNativeObject(), engine.getNativeObject(), bufferIndex, + bufferObject.getNativeObject()); + } + /** * Convenience function that consumes normal vectors (and, optionally, tangent vectors) and * produces quaternions that can be passed into a TANGENTS buffer. @@ -443,6 +475,7 @@ public class VertexBuffer { private static native long nCreateBuilder(); private static native void nDestroyBuilder(long nativeBuilder); private static native void nBuilderVertexCount(long nativeBuilder, int vertexCount); + private static native void nBuilderEnableBufferObjects(long nativeBuilder, boolean enabled); private static native void nBuilderBufferCount(long nativeBuilder, int bufferCount); private static native void nBuilderAttribute(long nativeBuilder, int attribute, int bufferIndex, int attributeType, int byteOffset, int byteStride); @@ -455,6 +488,9 @@ public class VertexBuffer { int bufferIndex, Buffer buffer, int remaining, int destOffsetInBytes, int count, Object handler, Runnable callback); + private static native void nSetBufferObjectAt(long nativeVertexBuffer, long nativeEngine, + int bufferIndex, long nativeBufferObject); + private static native void nPopulateTangentQuaternions(int quatType, int quatCount, Buffer outBuffer, int outRemaining, int outStride, Buffer normals, int normalsRemaining, int normalsStride, Buffer tangents, int tangentsRemaining, int tangentsStride); diff --git a/filament/CMakeLists.txt b/filament/CMakeLists.txt index cdc5cbcc05..5da39dcf42 100644 --- a/filament/CMakeLists.txt +++ b/filament/CMakeLists.txt @@ -13,6 +13,7 @@ set(MATERIAL_DIR "${GENERATION_ROOT}/generated/material") set(PUBLIC_HDRS include/filament/Box.h + include/filament/BufferObject.h include/filament/Camera.h include/filament/Color.h include/filament/ColorGrading.h @@ -44,6 +45,7 @@ set(PUBLIC_HDRS set(SRCS src/Box.cpp + src/BufferObject.cpp src/Camera.cpp src/Color.cpp src/ColorGrading.cpp @@ -112,6 +114,7 @@ set(PRIVATE_HDRS src/components/RenderableManager.h src/components/TransformManager.h src/details/Allocators.h + src/details/BufferObject.h src/details/Camera.h src/details/ColorGrading.h src/details/Culler.h diff --git a/filament/backend/include/backend/DriverEnums.h b/filament/backend/include/backend/DriverEnums.h index 1be9eb3d67..791becb8ba 100644 --- a/filament/backend/include/backend/DriverEnums.h +++ b/filament/backend/include/backend/DriverEnums.h @@ -263,6 +263,11 @@ enum class ElementType : uint8_t { HALF4, }; +//! Buffer object binding type +enum class BufferObjectBinding : uint8_t { + VERTEX, +}; + //! Face culling Mode enum class CullingMode : uint8_t { NONE, //!< No culling, front and back faces are visible diff --git a/filament/backend/include/backend/Handle.h b/filament/backend/include/backend/Handle.h index ae46710f10..62572c728c 100644 --- a/filament/backend/include/backend/Handle.h +++ b/filament/backend/include/backend/Handle.h @@ -24,6 +24,7 @@ namespace filament { namespace backend { +struct HwBufferObject; struct HwFence; struct HwIndexBuffer; struct HwProgram; @@ -96,6 +97,7 @@ private: // Types used by the command stream // (we use this renaming because the macro-system doesn't deal well with "<" and ">") +using BufferObjectHandle = Handle; using FenceHandle = Handle; using IndexBufferHandle = Handle; using ProgramHandle = Handle; diff --git a/filament/backend/include/private/backend/DriverAPI.inc b/filament/backend/include/private/backend/DriverAPI.inc index f11e3d9b64..1cb402eee2 100644 --- a/filament/backend/include/private/backend/DriverAPI.inc +++ b/filament/backend/include/private/backend/DriverAPI.inc @@ -168,13 +168,18 @@ DECL_DRIVER_API_R_N(backend::VertexBufferHandle, createVertexBuffer, uint8_t, attributeCount, uint32_t, vertexCount, backend::AttributeArray, attributes, - backend::BufferUsage, usage) + backend::BufferUsage, usage, + bool, bufferObjectsEnabled) DECL_DRIVER_API_R_N(backend::IndexBufferHandle, createIndexBuffer, backend::ElementType, elementType, uint32_t, indexCount, backend::BufferUsage, usage) +DECL_DRIVER_API_R_N(backend::BufferObjectHandle, createBufferObject, + uint32_t, byteCount, + backend::BufferObjectBinding, bindingType) + DECL_DRIVER_API_R_N(backend::TextureHandle, createTexture, backend::SamplerType, target, uint8_t, levels, @@ -261,6 +266,7 @@ DECL_DRIVER_API_R_0(backend::TimerQueryHandle, createTimerQuery) DECL_DRIVER_API_N(destroyVertexBuffer, backend::VertexBufferHandle, vbh) DECL_DRIVER_API_N(destroyIndexBuffer, backend::IndexBufferHandle, ibh) +DECL_DRIVER_API_N(destroyBufferObject, backend::BufferObjectHandle, ibh) DECL_DRIVER_API_N(destroyRenderPrimitive, backend::RenderPrimitiveHandle, rph) DECL_DRIVER_API_N(destroyProgram, backend::ProgramHandle, ph) DECL_DRIVER_API_N(destroySamplerGroup, backend::SamplerGroupHandle, sbh) @@ -311,11 +317,21 @@ DECL_DRIVER_API_N(updateVertexBuffer, backend::BufferDescriptor&&, data, uint32_t, byteOffset) +DECL_DRIVER_API_N(setVertexBufferObject, + backend::VertexBufferHandle, vbh, + size_t, index, + backend::BufferObjectHandle, bufferObject) + DECL_DRIVER_API_N(updateIndexBuffer, backend::IndexBufferHandle, ibh, backend::BufferDescriptor&&, data, uint32_t, byteOffset) +DECL_DRIVER_API_N(updateBufferObject, + backend::BufferObjectHandle, ibh, + backend::BufferDescriptor&&, data, + uint32_t, byteOffset) + DECL_DRIVER_API_N(loadUniformBuffer, backend::UniformBufferHandle, ubh, backend::BufferDescriptor&&, buffer) diff --git a/filament/backend/src/DriverBase.h b/filament/backend/src/DriverBase.h index 5cedddc76e..75017f7a35 100644 --- a/filament/backend/src/DriverBase.h +++ b/filament/backend/src/DriverBase.h @@ -55,18 +55,22 @@ struct HwVertexBuffer : public HwBase { uint32_t vertexCount{}; // 4 uint8_t bufferCount{}; // 1 uint8_t attributeCount{}; // 1 - uint8_t padding[2]{}; // 2 -> total struct is 136 bytes + bool bufferObjectsEnabled{}; // 1 + uint8_t bufferObjectsVersion{}; // 1 -> total struct is 136 bytes HwVertexBuffer() noexcept = default; HwVertexBuffer(uint8_t bufferCount, uint8_t attributeCount, uint32_t elementCount, - AttributeArray const& attributes) noexcept + AttributeArray const& attributes, bool bufferObjectsEnabled) noexcept : attributes(attributes), vertexCount(elementCount), bufferCount(bufferCount), - attributeCount(attributeCount) { + attributeCount(attributeCount), + bufferObjectsEnabled(bufferObjectsEnabled) { } }; +struct HwBufferObject : public HwBase {}; + struct HwIndexBuffer : public HwBase { uint32_t count{}; uint8_t elementSize{}; diff --git a/filament/backend/src/metal/MetalDriver.mm b/filament/backend/src/metal/MetalDriver.mm index b2f565e0be..39f38578b6 100644 --- a/filament/backend/src/metal/MetalDriver.mm +++ b/filament/backend/src/metal/MetalDriver.mm @@ -160,10 +160,10 @@ void MetalDriver::finish(int) { void MetalDriver::createVertexBufferR(Handle vbh, uint8_t bufferCount, uint8_t attributeCount, uint32_t vertexCount, AttributeArray attributes, - BufferUsage usage) { + BufferUsage usage, bool bufferObjectsEnabled) { // TODO: Take BufferUsage into account when creating the buffer. construct_handle(mHandleMap, vbh, *mContext, bufferCount, - attributeCount, vertexCount, attributes); + attributeCount, vertexCount, attributes, bufferObjectsEnabled); } void MetalDriver::createIndexBufferR(Handle ibh, ElementType elementType, @@ -172,6 +172,11 @@ void MetalDriver::createIndexBufferR(Handle ibh, ElementType elem construct_handle(mHandleMap, ibh, *mContext, elementSize, indexCount); } +void MetalDriver::createBufferObjectR(Handle boh, uint32_t byteCount, + BufferObjectBinding bindingType) { + // TODO +} + void MetalDriver::createTextureR(Handle th, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t width, uint32_t height, uint32_t depth, TextureUsage usage) { @@ -321,6 +326,11 @@ Handle MetalDriver::createIndexBufferS() noexcept { return alloc_handle(); } +Handle MetalDriver::createBufferObjectS() noexcept { + // TODO + return {}; +} + Handle MetalDriver::createTextureS() noexcept { return alloc_handle(); } @@ -399,6 +409,12 @@ void MetalDriver::destroyIndexBuffer(Handle ibh) { } } +void MetalDriver::destroyBufferObject(Handle boh) { + if (boh) { + // TODO + } +} + void MetalDriver::destroyRenderPrimitive(Handle rph) { if (rph) { destruct_handle(mHandleMap, rph); @@ -650,6 +666,16 @@ void MetalDriver::updateIndexBuffer(Handle ibh, BufferDescriptor& scheduleDestroy(std::move(data)); } +void MetalDriver::updateBufferObject(Handle boh, BufferDescriptor&& data, + uint32_t byteOffset) { + // TODO +} + +void MetalDriver::setVertexBufferObject(Handle vbh, size_t index, + Handle boh) { + // TODO +} + void MetalDriver::update2DImage(Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, PixelBufferDescriptor&& data) { ASSERT_PRECONDITION(!isInRenderPass(mContext), diff --git a/filament/backend/src/metal/MetalHandles.h b/filament/backend/src/metal/MetalHandles.h index 01dce978a5..7d021b6c80 100644 --- a/filament/backend/src/metal/MetalHandles.h +++ b/filament/backend/src/metal/MetalHandles.h @@ -114,7 +114,7 @@ private: struct MetalVertexBuffer : public HwVertexBuffer { MetalVertexBuffer(MetalContext& context, uint8_t bufferCount, uint8_t attributeCount, - uint32_t vertexCount, AttributeArray const& attributes); + uint32_t vertexCount, AttributeArray const& attributes, bool bufferObjectsEnabled); ~MetalVertexBuffer(); std::vector buffers; diff --git a/filament/backend/src/metal/MetalHandles.mm b/filament/backend/src/metal/MetalHandles.mm index 506105653b..a7186e0c88 100644 --- a/filament/backend/src/metal/MetalHandles.mm +++ b/filament/backend/src/metal/MetalHandles.mm @@ -267,8 +267,9 @@ void MetalSwapChain::scheduleFrameCompletedCallback() { } MetalVertexBuffer::MetalVertexBuffer(MetalContext& context, uint8_t bufferCount, - uint8_t attributeCount, uint32_t vertexCount, AttributeArray const& attributes) - : HwVertexBuffer(bufferCount, attributeCount, vertexCount, attributes) { + uint8_t attributeCount, uint32_t vertexCount, AttributeArray const& attributes, + bool bufferObjectsEnabled) + : HwVertexBuffer(bufferCount, attributeCount, vertexCount, attributes, bufferObjectsEnabled) { buffers.reserve(bufferCount); for (uint8_t bufferIndex = 0; bufferIndex < bufferCount; ++bufferIndex) { diff --git a/filament/backend/src/noop/NoopDriver.cpp b/filament/backend/src/noop/NoopDriver.cpp index 96fa53800a..2b89aa19c3 100644 --- a/filament/backend/src/noop/NoopDriver.cpp +++ b/filament/backend/src/noop/NoopDriver.cpp @@ -85,6 +85,9 @@ void NoopDriver::destroyVertexBuffer(Handle vbh) { void NoopDriver::destroyIndexBuffer(Handle ibh) { } +void NoopDriver::destroyBufferObject(Handle boh) { +} + void NoopDriver::destroyTexture(Handle th) { } @@ -182,6 +185,15 @@ void NoopDriver::updateIndexBuffer(Handle ibh, BufferDescriptor&& scheduleDestroy(std::move(p)); } +void NoopDriver::updateBufferObject(Handle ibh, BufferDescriptor&& p, + uint32_t byteOffset) { + scheduleDestroy(std::move(p)); +} + +void NoopDriver::setVertexBufferObject(Handle vbh, size_t index, + Handle boh) { +} + void NoopDriver::update2DImage(Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, PixelBufferDescriptor&& data) { diff --git a/filament/backend/src/opengl/OpenGLContext.h b/filament/backend/src/opengl/OpenGLContext.h index d72997526d..1b246b9913 100644 --- a/filament/backend/src/opengl/OpenGLContext.h +++ b/filament/backend/src/opengl/OpenGLContext.h @@ -22,6 +22,8 @@ #include #include +#include + #include "GLUtils.h" #include @@ -35,11 +37,23 @@ public: typedef math::details::TVec4 vec4gli; typedef math::details::TVec2 vec2glf; + // TODO: the footprint of this structure can be reduced. We need only 1 bit for index type, 16 + // bits for vertexAttribArray. We can also use fewer bits for vertexBufferVersion, although + // this will require a corollary update in OpenGLDriver::setVertexBufferObject(). struct RenderPrimitive { GLuint vao = 0; GLenum indicesType = GL_UNSIGNED_INT; GLuint elementArray = 0; utils::bitset32 vertexAttribArray; + + // The optional 32-bit handle to a GLVertexBuffer is necessary only if the referenced + // VertexBuffer supports buffer objects. If this is zero, then the VBO handles array is + // immutable. + backend::Handle vertexBufferWithObjects = {}; + + // If this version number does not match vertexBufferWithObjects->bufferObjectsVersion, + // then the VAO needs to be updated. + uint8_t vertexBufferVersion = 0; } gl; OpenGLContext() noexcept; diff --git a/filament/backend/src/opengl/OpenGLDriver.cpp b/filament/backend/src/opengl/OpenGLDriver.cpp index a0209573d0..d7af3e13e5 100644 --- a/filament/backend/src/opengl/OpenGLDriver.cpp +++ b/filament/backend/src/opengl/OpenGLDriver.cpp @@ -412,6 +412,10 @@ Handle OpenGLDriver::createIndexBufferS() noexcept { return initHandle(); } +Handle OpenGLDriver::createBufferObjectS() noexcept { + return initHandle(); +} + Handle OpenGLDriver::createRenderPrimitiveS() noexcept { return initHandle(); } @@ -478,12 +482,13 @@ void OpenGLDriver::createVertexBufferR( uint8_t attributeCount, uint32_t elementCount, AttributeArray attributes, - BufferUsage usage) { + BufferUsage usage, + bool bufferObjectsEnabled) { DEBUG_MARKER() auto& gl = mContext; GLVertexBuffer* vb = construct(vbh, - bufferCount, attributeCount, elementCount, attributes); + bufferCount, attributeCount, elementCount, attributes, bufferObjectsEnabled); GLsizei n = GLsizei(vb->bufferCount); @@ -524,6 +529,24 @@ void OpenGLDriver::createIndexBufferR( CHECK_GL_ERROR(utils::slog.e) } +void OpenGLDriver::createBufferObjectR( + Handle boh, + uint32_t byteCount, + BufferObjectBinding bindingType) { + DEBUG_MARKER() + + auto& gl = mContext; + GLBufferObject* bo = construct(boh); + glGenBuffers(1, &bo->gl.id); + gl.bindVertexArray(nullptr); + + assert_invariant(bindingType == BufferObjectBinding::VERTEX); + + gl.bindBuffer(GL_ARRAY_BUFFER, bo->gl.id); + glBufferData(GL_ARRAY_BUFFER, byteCount, nullptr, GL_STATIC_DRAW); + CHECK_GL_ERROR(utils::slog.e) +} + void OpenGLDriver::createRenderPrimitiveR(Handle rph, int) { DEBUG_MARKER() @@ -757,6 +780,57 @@ void OpenGLDriver::importTextureR(Handle th, intptr_t id, CHECK_GL_ERROR(utils::slog.e) } +void OpenGLDriver::updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb) { + + auto& gl = mContext; + + // NOTE: this is called from draw() and must be as efficient as possible. + + // The VAO for the given render primitive must already be bound. + #ifndef NDEBUG + GLint vaoBinding; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vaoBinding); + assert_invariant(vaoBinding == (GLint) rp->gl.vao); + #endif + + rp->gl.vertexBufferVersion = vb->bufferObjectsVersion; + rp->maxVertexCount = vb->vertexCount; + for (size_t i = 0, n = vb->attributes.size(); i < n; i++) { + const auto& attribute = vb->attributes[i]; + if (attribute.buffer != Attribute::BUFFER_UNUSED) { + uint8_t bi = attribute.buffer; + gl.bindBuffer(GL_ARRAY_BUFFER, vb->gl.buffers[bi]); + if (UTILS_UNLIKELY(attribute.flags & Attribute::FLAG_INTEGER_TARGET)) { + glVertexAttribIPointer(GLuint(i), + getComponentCount(attribute.type), + getComponentType(attribute.type), + attribute.stride, + (void*) uintptr_t(attribute.offset)); + } else { + glVertexAttribPointer(GLuint(i), + getComponentCount(attribute.type), + getComponentType(attribute.type), + getNormalization(attribute.flags & Attribute::FLAG_NORMALIZED), + attribute.stride, + (void*) uintptr_t(attribute.offset)); + } + + gl.enableVertexAttribArray(GLuint(i)); + } else { + + // In some OpenGL implementations, we must supply a properly-typed placeholder for + // every integer input that is declared in the vertex shader. + if (UTILS_UNLIKELY(attribute.flags & Attribute::FLAG_INTEGER_TARGET)) { + glVertexAttribI4ui(GLuint(i), 0, 0, 0, 0); + } else { + glVertexAttrib4f(GLuint(i), 0, 0, 0, 0); + } + + gl.disableVertexAttribArray(GLuint(i)); + } + } +} + void OpenGLDriver::framebufferTexture(backend::TargetBufferInfo const& binfo, GLRenderTarget const* rt, GLenum attachment) noexcept { @@ -1223,6 +1297,17 @@ void OpenGLDriver::destroyIndexBuffer(Handle ibh) { } } +void OpenGLDriver::destroyBufferObject(Handle boh) { + DEBUG_MARKER() + + if (boh) { + auto& gl = mContext; + GLBufferObject const* bo = handle_cast(boh); + gl.deleteBuffers(1, &bo->gl.id, GL_ARRAY_BUFFER); + destruct(boh, bo); + } +} + void OpenGLDriver::destroyRenderPrimitive(Handle rph) { DEBUG_MARKER() @@ -1633,6 +1718,7 @@ void OpenGLDriver::updateVertexBuffer(Handle vbh, auto& gl = mContext; GLVertexBuffer* eb = handle_cast(vbh); + assert_invariant(!eb->bufferObjectsEnabled && "Please use setVertexBufferObject() instead."); gl.bindBuffer(GL_ARRAY_BUFFER, eb->gl.buffers[index]); glBufferSubData(GL_ARRAY_BUFFER, byteOffset, p.size, p.buffer); @@ -1642,6 +1728,29 @@ void OpenGLDriver::updateVertexBuffer(Handle vbh, CHECK_GL_ERROR(utils::slog.e) } +void OpenGLDriver::setVertexBufferObject(Handle vbh, + size_t index, Handle boh) { + DEBUG_MARKER() + + GLVertexBuffer* vb = handle_cast(vbh); + assert_invariant(vb->bufferObjectsEnabled && "Please use updateVertexBuffer() instead."); + + GLBufferObject* bo = handle_cast(boh); + + // If the specified VBO handle is different from what's already in the slot, then update the + // slot and bump the cyclical version number. Dependent VAOs use the version number to detect + // when they should be updated. + if (vb->gl.buffers[index] != bo->gl.id) { + vb->gl.buffers[index] = bo->gl.id; + static constexpr uint32_t kMaxVersion = + std::numeric_limitsbufferObjectsVersion)>::max(); + const uint32_t version = vb->bufferObjectsVersion; + vb->bufferObjectsVersion = (version + 1) % kMaxVersion; + } + + CHECK_GL_ERROR(utils::slog.e) +} + void OpenGLDriver::updateIndexBuffer( Handle ibh, BufferDescriptor&& p, uint32_t byteOffset) { DEBUG_MARKER() @@ -1659,6 +1768,22 @@ void OpenGLDriver::updateIndexBuffer( CHECK_GL_ERROR(utils::slog.e) } +void OpenGLDriver::updateBufferObject( + Handle boh, BufferDescriptor&& bd, uint32_t byteOffset) { + DEBUG_MARKER() + + auto& gl = mContext; + GLBufferObject* bo = handle_cast(boh); + + gl.bindVertexArray(nullptr); + gl.bindBuffer(GL_ARRAY_BUFFER, bo->gl.id); + glBufferSubData(GL_ARRAY_BUFFER, byteOffset, bd.size, bd.buffer); + + scheduleDestroy(std::move(bd)); + + CHECK_GL_ERROR(utils::slog.e) +} + void OpenGLDriver::loadUniformBuffer(Handle ubh, BufferDescriptor&& p) { DEBUG_MARKER() @@ -2389,41 +2514,13 @@ void OpenGLDriver::setRenderPrimitiveBuffer(Handle rph, CHECK_GL_ERROR(utils::slog.e) rp->gl.indicesType = ib->elementSize == 4 ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT; - rp->maxVertexCount = eb->vertexCount; - for (size_t i = 0, n = eb->attributes.size(); i < n; i++) { - const auto& attribute = eb->attributes[i]; - if (attribute.buffer != Attribute::BUFFER_UNUSED) { - uint8_t bi = attribute.buffer; - gl.bindBuffer(GL_ARRAY_BUFFER, eb->gl.buffers[bi]); - if (UTILS_UNLIKELY(attribute.flags & Attribute::FLAG_INTEGER_TARGET)) { - glVertexAttribIPointer(GLuint(i), - getComponentCount(attribute.type), - getComponentType(attribute.type), - attribute.stride, - (void*) uintptr_t(attribute.offset)); - } else { - glVertexAttribPointer(GLuint(i), - getComponentCount(attribute.type), - getComponentType(attribute.type), - getNormalization(attribute.flags & Attribute::FLAG_NORMALIZED), - attribute.stride, - (void*) uintptr_t(attribute.offset)); - } - gl.enableVertexAttribArray(GLuint(i)); - } else { + rp->gl.vertexBufferWithObjects = eb->bufferObjectsEnabled ? vbh : + backend::Handle {}; - // In some OpenGL implementations, we must supply a properly-typed placeholder for - // every integer input that is declared in the vertex shader. - if (UTILS_UNLIKELY(attribute.flags & Attribute::FLAG_INTEGER_TARGET)) { - glVertexAttribI4ui(GLuint(i), 0, 0, 0, 0); - } else { - glVertexAttrib4f(GLuint(i), 0, 0, 0, 0); - } + // update the VBO bindings in the VAO + updateVertexArrayObject(rp, eb); - gl.disableVertexAttribArray(GLuint(i)); - } - } // this records the index buffer into the currently bound VAO gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib->gl.buffer); @@ -3120,9 +3217,19 @@ void OpenGLDriver::draw(PipelineState state, Handle rph) { useProgram(p); - const GLRenderPrimitive* rp = handle_cast(rph); + GLRenderPrimitive* rp = handle_cast(rph); + gl.bindVertexArray(&rp->gl); + // If necessary, mutate the bindings in the VAO. + VertexBufferHandle vbwo = rp->gl.vertexBufferWithObjects; + if (UTILS_UNLIKELY(vbwo)) { + const GLVertexBuffer* vb = handle_cast(vbwo); + if (rp->gl.vertexBufferVersion != vb->bufferObjectsVersion) { + updateVertexArrayObject(rp, vb); + } + } + setRasterState(state.rasterState); gl.polygonOffset(state.polygonOffset.slope, state.polygonOffset.constant); diff --git a/filament/backend/src/opengl/OpenGLDriver.h b/filament/backend/src/opengl/OpenGLDriver.h index 1b306184f7..46e84b8127 100644 --- a/filament/backend/src/opengl/OpenGLDriver.h +++ b/filament/backend/src/opengl/OpenGLDriver.h @@ -69,6 +69,12 @@ public: backend::BufferUsage usage = {}; }; + struct GLBufferObject : public backend::HwBufferObject { + struct { + GLuint id = 0; + } gl; + }; + struct GLVertexBuffer : public backend::HwVertexBuffer { using HwVertexBuffer::HwVertexBuffer; struct { @@ -315,6 +321,8 @@ private: /* Misc... */ + void updateVertexArrayObject(GLRenderPrimitive* rp, GLVertexBuffer const* vb); + void framebufferTexture(backend::TargetBufferInfo const& binfo, GLRenderTarget const* rt, GLenum attachment) noexcept; diff --git a/filament/backend/src/vulkan/VulkanDriver.cpp b/filament/backend/src/vulkan/VulkanDriver.cpp index 75d0a2b014..2662b5250d 100644 --- a/filament/backend/src/vulkan/VulkanDriver.cpp +++ b/filament/backend/src/vulkan/VulkanDriver.cpp @@ -446,9 +446,9 @@ void VulkanDriver::destroyRenderPrimitive(Handle rph) { void VulkanDriver::createVertexBufferR(Handle vbh, uint8_t bufferCount, uint8_t attributeCount, uint32_t elementCount, AttributeArray attributes, - BufferUsage usage) { + BufferUsage usage, bool bufferObjectsEnabled) { auto vertexBuffer = construct_handle(mHandleMap, vbh, mContext, mStagePool, - mDisposer, bufferCount, attributeCount, elementCount, attributes); + mDisposer, bufferCount, attributeCount, elementCount, attributes, bufferObjectsEnabled); mDisposer.createDisposable(vertexBuffer, [this, vbh] () { destruct_handle(mHandleMap, vbh); }); @@ -478,6 +478,22 @@ void VulkanDriver::destroyIndexBuffer(Handle ibh) { } } +void VulkanDriver::createBufferObjectR(Handle boh, + uint32_t byteCount, BufferObjectBinding bindingType) { + // TODO: pass constructor args + auto bufferObject = construct_handle(mHandleMap, boh); + mDisposer.createDisposable(bufferObject, [this, boh] () { + destruct_handle(mHandleMap, boh); + }); +} + +void VulkanDriver::destroyBufferObject(Handle boh) { + if (boh) { + auto bufferObject = handle_cast(mHandleMap, boh); + mDisposer.removeReference(bufferObject); + } +} + void VulkanDriver::createTextureR(Handle th, SamplerType target, uint8_t levels, TextureFormat format, uint8_t samples, uint32_t w, uint32_t h, uint32_t depth, TextureUsage usage) { @@ -622,6 +638,10 @@ Handle VulkanDriver::createIndexBufferS() noexcept { return alloc_handle(); } +Handle VulkanDriver::createBufferObjectS() noexcept { + return alloc_handle(); +} + Handle VulkanDriver::createTextureS() noexcept { return alloc_handle(); } @@ -851,6 +871,11 @@ void VulkanDriver::updateVertexBuffer(Handle vbh, size_t index, scheduleDestroy(std::move(p)); } +void VulkanDriver::setVertexBufferObject(Handle vbh, size_t index, + Handle boh) { + // TODO +} + void VulkanDriver::updateIndexBuffer(Handle ibh, BufferDescriptor&& p, uint32_t byteOffset) { auto& ib = *handle_cast(mHandleMap, ibh); @@ -858,6 +883,13 @@ void VulkanDriver::updateIndexBuffer(Handle ibh, BufferDescriptor scheduleDestroy(std::move(p)); } +void VulkanDriver::updateBufferObject(Handle boh, BufferDescriptor&& bd, + uint32_t byteOffset) { + auto& bo = *handle_cast(mHandleMap, boh); + bo.buffer->loadFromCpu(bd.buffer, byteOffset, bd.size); + scheduleDestroy(std::move(bd)); +} + void VulkanDriver::update2DImage(Handle th, uint32_t level, uint32_t xoffset, uint32_t yoffset, uint32_t width, uint32_t height, PixelBufferDescriptor&& data) { @@ -1765,6 +1797,7 @@ void VulkanDriver::debugCommand(const char* methodName) { "loadUniformBuffer", "updateVertexBuffer", "updateIndexBuffer", + "updateBufferObject", "update2DImage", "updateCubeImage", }; diff --git a/filament/backend/src/vulkan/VulkanHandles.cpp b/filament/backend/src/vulkan/VulkanHandles.cpp index 9851e50248..28d623df5f 100644 --- a/filament/backend/src/vulkan/VulkanHandles.cpp +++ b/filament/backend/src/vulkan/VulkanHandles.cpp @@ -403,8 +403,8 @@ bool VulkanRenderTarget::invalidate() { VulkanVertexBuffer::VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool, VulkanDisposer& disposer, uint8_t bufferCount, uint8_t attributeCount, - uint32_t elementCount, AttributeArray const& attributes) : - HwVertexBuffer(bufferCount, attributeCount, elementCount, attributes) { + uint32_t elementCount, AttributeArray const& attributes, bool boEnabled) : + HwVertexBuffer(bufferCount, attributeCount, elementCount, attributes, boEnabled) { buffers.reserve(bufferCount); for (uint8_t bufferIndex = 0; bufferIndex < bufferCount; ++bufferIndex) { uint32_t size = 0; diff --git a/filament/backend/src/vulkan/VulkanHandles.h b/filament/backend/src/vulkan/VulkanHandles.h index 5de32730fb..d8f1e251ad 100644 --- a/filament/backend/src/vulkan/VulkanHandles.h +++ b/filament/backend/src/vulkan/VulkanHandles.h @@ -83,7 +83,7 @@ struct VulkanSwapChain : public HwSwapChain { struct VulkanVertexBuffer : public HwVertexBuffer { VulkanVertexBuffer(VulkanContext& context, VulkanStagePool& stagePool, VulkanDisposer& disposer, uint8_t bufferCount, uint8_t attributeCount, uint32_t elementCount, - AttributeArray const& attributes); + AttributeArray const& attributes, bool bufferObjectsEnabled); std::vector> buffers; }; @@ -97,6 +97,10 @@ struct VulkanIndexBuffer : public HwIndexBuffer { const std::unique_ptr buffer; }; +struct VulkanBufferObject : public HwBufferObject { + const std::unique_ptr buffer; +}; + struct VulkanUniformBuffer : public HwUniformBuffer { VulkanUniformBuffer(VulkanContext& context, VulkanStagePool& stagePool, VulkanDisposer& disposer, uint32_t numBytes, backend::BufferUsage usage); diff --git a/filament/backend/test/TrianglePrimitive.cpp b/filament/backend/test/TrianglePrimitive.cpp index ad48fad0fa..594f6d5ffd 100644 --- a/filament/backend/test/TrianglePrimitive.cpp +++ b/filament/backend/test/TrianglePrimitive.cpp @@ -48,7 +48,7 @@ TrianglePrimitive::TrianglePrimitive(filament::backend::DriverApi& driverApi, enabledAttributes.set(VertexAttribute::POSITION); mVertexBuffer = mDriverApi.createVertexBuffer(1, 1, mVertexCount, attributes, - BufferUsage::STATIC); + BufferUsage::STATIC, false); BufferDescriptor vertexBufferDesc(gVertices, sizeof(filament::math::float2) * 3, nullptr); mDriverApi.updateVertexBuffer(mVertexBuffer, 0, std::move(vertexBufferDesc), 0); diff --git a/filament/include/filament/BufferObject.h b/filament/include/filament/BufferObject.h new file mode 100644 index 0000000000..1ede31b8cb --- /dev/null +++ b/filament/include/filament/BufferObject.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 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. + */ + +//! \file + +#ifndef TNT_FILAMENT_BUFFEROBJECT_H +#define TNT_FILAMENT_BUFFEROBJECT_H + +#include + +#include + +#include + +#include + +namespace filament { + +class FBufferObject; + +class Engine; + +/** + * A generic GPU buffer containing data. + * + * Usage of this BufferObject is optional. For simple use cases it is not necessary. It is useful + * only when you need to share data between multiple VertexBuffer instances. It also allows you to + * efficiently swap-out the buffers in VertexBuffer. + * + * NOTE: For now this is only used for vertex data, but in the future we may use it for other things + * (e.g. compute). + * + * @see VertexBuffer + */ +class UTILS_PUBLIC BufferObject : public FilamentAPI { + struct BuilderDetails; + +public: + using BufferDescriptor = backend::BufferDescriptor; + using BindingType = backend::BufferObjectBinding; + + class Builder : public BuilderBase { + friend struct BuilderDetails; + public: + Builder() noexcept; + Builder(Builder const& rhs) noexcept; + Builder(Builder&& rhs) noexcept; + ~Builder() noexcept; + Builder& operator=(Builder const& rhs) noexcept; + Builder& operator=(Builder&& rhs) noexcept; + + /** + * Size of the buffer in bytes. + * @param byteCount Maximum number of bytes the BufferObject can hold. + * @return A reference to this Builder for chaining calls. + */ + Builder& size(uint32_t byteCount) noexcept; + + /** + * The binding type for this buffer object. (defaults to VERTEX) + * @param BindingType Distinguishes between SSBO, VBO, etc. For now this must be VERTEX. + * @return A reference to this Builder for chaining calls. + */ + Builder& bindingType(BindingType bindingType) noexcept; + + /** + * Creates the BufferObject and returns a pointer to it. After creation, the buffer + * object is uninitialized. Use BufferObject::setBuffer() to initialize it. + * + * @param engine Reference to the filament::Engine to associate this BufferObject with. + * + * @return pointer to the newly created object or nullptr if exceptions are disabled and + * an error occurred. + * + * @exception utils::PostConditionPanic if a runtime error occurred, such as running out of + * memory or other resources. + * @exception utils::PreConditionPanic if a parameter to a builder function was invalid. + * + * @see IndexBuffer::setBuffer + */ + BufferObject* build(Engine& engine); + private: + friend class FBufferObject; + }; + + /** + * Asynchronously copy-initializes a region of this BufferObject from the data provided. + * + * @param engine Reference to the filament::Engine associated with this BufferObject. + * @param buffer A BufferDescriptor representing the data used to initialize the BufferObject. + * @param byteOffset Offset in bytes into the BufferObject + */ + void setBuffer(Engine& engine, BufferDescriptor&& buffer, uint32_t byteOffset = 0); + + /** + * Returns the size of this BufferObject in elements. + * @return The maximum capacity of the BufferObject. + */ + size_t getByteCount() const noexcept; +}; + +} // namespace filament + +#endif // TNT_FILAMENT_BUFFEROBJECT_H diff --git a/filament/include/filament/Engine.h b/filament/include/filament/Engine.h index df494912cc..301424a96b 100644 --- a/filament/include/filament/Engine.h +++ b/filament/include/filament/Engine.h @@ -28,6 +28,7 @@ class JobSystem; namespace filament { +class BufferObject; class Camera; class ColorGrading; class DebugRegistry; @@ -390,6 +391,7 @@ public: */ Fence* createFence() noexcept; + bool destroy(const BufferObject* p); //!< Destroys a BufferObject object. bool destroy(const VertexBuffer* p); //!< Destroys an VertexBuffer object. bool destroy(const Fence* p); //!< Destroys a Fence object. bool destroy(const IndexBuffer* p); //!< Destroys an IndexBuffer object. diff --git a/filament/include/filament/IndexBuffer.h b/filament/include/filament/IndexBuffer.h index c3f5897a4b..09b479296d 100644 --- a/filament/include/filament/IndexBuffer.h +++ b/filament/include/filament/IndexBuffer.h @@ -84,7 +84,7 @@ public: /** * Creates the IndexBuffer object and returns a pointer to it. After creation, the index - * buffer is uninitialized. Use IndexBuffer::setBuffer() to initialized the IndexBuffer. + * buffer is uninitialized. Use IndexBuffer::setBuffer() to initialize the IndexBuffer. * * @param engine Reference to the filament::Engine to associate this IndexBuffer with. * diff --git a/filament/include/filament/VertexBuffer.h b/filament/include/filament/VertexBuffer.h index 70545b1c48..4cd677dad2 100644 --- a/filament/include/filament/VertexBuffer.h +++ b/filament/include/filament/VertexBuffer.h @@ -31,6 +31,7 @@ namespace filament { class FVertexBuffer; +class BufferObject; class Engine; /** @@ -86,6 +87,17 @@ public: */ Builder& vertexCount(uint32_t vertexCount) noexcept; + /** + * Allows buffers to be swapped out and shared using BufferObject. + * + * If buffer objects mode is enabled, clients must call setBufferObjectAt rather than + * setBufferAt. This allows sharing of data between VertexBuffer objects, but it may + * slightly increase the memory footprint of Filament's internal bookkeeping. + * + * @param enabled If true, enables buffer object mode. False by default. + */ + Builder& enableBufferObjects(bool enabled = true) noexcept; + /** * Sets up an attribute for this vertex buffer set. * @@ -154,6 +166,8 @@ public: /** * Asynchronously copy-initializes the specified buffer from the given buffer data. * + * Do not use this if you called enableBufferObjects() on the Builder. + * * @param engine Reference to the filament::Engine to associate this VertexBuffer with. * @param bufferIndex Index of the buffer to initialize. Must be between 0 * and Builder::bufferCount() - 1. @@ -166,6 +180,18 @@ public: void setBufferAt(Engine& engine, uint8_t bufferIndex, BufferDescriptor&& buffer, uint32_t byteOffset = 0); + /** + * Swaps in the given buffer object. + * + * To use this, you must first call enableBufferObjects() on the Builder. + * + * @param engine Reference to the filament::Engine to associate this VertexBuffer with. + * @param bufferIndex Index of the buffer to initialize. Must be between 0 + * and Builder::bufferCount() - 1. + * @param bufferObject The handle to the GPU data that will be used in this buffer slot. + */ + void setBufferObjectAt(Engine& engine, uint8_t bufferIndex, BufferObject const* bufferObject); + /** * Specifies the quaternion type for the "populateTangentQuaternions" utility. */ diff --git a/filament/src/BufferObject.cpp b/filament/src/BufferObject.cpp new file mode 100644 index 0000000000..7a7503dc8c --- /dev/null +++ b/filament/src/BufferObject.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2021 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 "details/BufferObject.h" + +#include "details/Engine.h" + +#include "FilamentAPI-impl.h" + +namespace filament { + +struct BufferObject::BuilderDetails { + BindingType mBindingType = BindingType::VERTEX; + uint32_t mByteCount = 0; +}; + +using BuilderType = BufferObject; +BuilderType::Builder::Builder() noexcept = default; +BuilderType::Builder::~Builder() noexcept = default; +BuilderType::Builder::Builder(BuilderType::Builder const& rhs) noexcept = default; +BuilderType::Builder::Builder(BuilderType::Builder&& rhs) noexcept = default; +BuilderType::Builder& BuilderType::Builder::operator=(BuilderType::Builder const& rhs) noexcept = default; +BuilderType::Builder& BuilderType::Builder::operator=(BuilderType::Builder&& rhs) noexcept = default; + +BufferObject::Builder& BufferObject::Builder::size(uint32_t byteCount) noexcept { + mImpl->mByteCount = byteCount; + return *this; +} + +BufferObject::Builder& BufferObject::Builder::bindingType(BindingType bindingType) noexcept { + mImpl->mBindingType = bindingType; + return *this; +} + +BufferObject* BufferObject::Builder::build(Engine& engine) { + return upcast(engine).createBufferObject(*this); +} + +// ------------------------------------------------------------------------------------------------ + +FBufferObject::FBufferObject(FEngine& engine, const BufferObject::Builder& builder) + : mByteCount(builder->mByteCount), mBindingType(builder->mBindingType) { + FEngine::DriverApi& driver = engine.getDriverApi(); + mHandle = driver.createBufferObject(builder->mByteCount, builder->mBindingType); +} + +void FBufferObject::terminate(FEngine& engine) { + FEngine::DriverApi& driver = engine.getDriverApi(); + driver.destroyBufferObject(mHandle); +} + +void FBufferObject::setBuffer(FEngine& engine, BufferDescriptor&& buffer, uint32_t byteOffset) { + engine.getDriverApi().updateBufferObject(mHandle, std::move(buffer), byteOffset); +} + +// ------------------------------------------------------------------------------------------------ +// Trampoline calling into private implementation +// ------------------------------------------------------------------------------------------------ + +void BufferObject::setBuffer(Engine& engine, + BufferObject::BufferDescriptor&& buffer, uint32_t byteOffset) { + upcast(this)->setBuffer(upcast(engine), std::move(buffer), byteOffset); +} + +size_t BufferObject::getByteCount() const noexcept { + return upcast(this)->getByteCount(); +} + +} // namespace filament diff --git a/filament/src/Engine.cpp b/filament/src/Engine.cpp index 7b3e633989..0bd5381693 100644 --- a/filament/src/Engine.cpp +++ b/filament/src/Engine.cpp @@ -20,6 +20,7 @@ #include "ResourceAllocator.h" #include "backend/DriverEnums.h" +#include "details/BufferObject.h" #include "details/DFG.h" #include "details/VertexBuffer.h" #include "details/Fence.h" @@ -318,6 +319,7 @@ void FEngine::shutdown() { // this must be done after Skyboxes and before materials destroy(mSkyboxMaterial); + cleanupResourceList(mBufferObjects); cleanupResourceList(mIndexBuffers); cleanupResourceList(mVertexBuffers); cleanupResourceList(mTextures); @@ -546,6 +548,10 @@ inline T* FEngine::create(ResourceList& list, typename T::Builder const& buil return p; } +FBufferObject* FEngine::createBufferObject(const BufferObject::Builder& builder) noexcept { + return create(mBufferObjects, builder); +} + FVertexBuffer* FEngine::createVertexBuffer(const VertexBuffer::Builder& builder) noexcept { return create(mVertexBuffers, builder); } @@ -724,6 +730,10 @@ bool FEngine::terminateAndDestroy(const T* ptr, ResourceList& list) { // ----------------------------------------------------------------------------------------------- +bool FEngine::destroy(const FBufferObject* p) { + return terminateAndDestroy(p, mBufferObjects); +} + bool FEngine::destroy(const FVertexBuffer* p) { return terminateAndDestroy(p, mVertexBuffers); } @@ -925,6 +935,10 @@ SwapChain* Engine::createSwapChain(uint32_t width, uint32_t height, uint64_t fla return upcast(this)->createSwapChain(width, height, flags); } +bool Engine::destroy(const BufferObject* p) { + return upcast(this)->destroy(upcast(p)); +} + bool Engine::destroy(const VertexBuffer* p) { return upcast(this)->destroy(upcast(p)); } diff --git a/filament/src/VertexBuffer.cpp b/filament/src/VertexBuffer.cpp index 80878cb65a..f11af61f86 100644 --- a/filament/src/VertexBuffer.cpp +++ b/filament/src/VertexBuffer.cpp @@ -16,6 +16,7 @@ #include "details/VertexBuffer.h" +#include "details/BufferObject.h" #include "details/Engine.h" #include "FilamentAPI-impl.h" @@ -36,6 +37,7 @@ struct VertexBuffer::BuilderDetails { AttributeBitset mDeclaredAttributes; uint32_t mVertexCount = 0; uint8_t mBufferCount = 0; + bool mBufferObjectsEnabled = false; }; using BuilderType = VertexBuffer; @@ -51,6 +53,11 @@ VertexBuffer::Builder& VertexBuffer::Builder::vertexCount(uint32_t vertexCount) return *this; } +VertexBuffer::Builder& VertexBuffer::Builder::enableBufferObjects(bool enabled) noexcept { + mImpl->mBufferObjectsEnabled = enabled; + return *this; +} + VertexBuffer::Builder& VertexBuffer::Builder::bufferCount(uint8_t bufferCount) noexcept { mImpl->mBufferCount = bufferCount; return *this; @@ -140,7 +147,8 @@ VertexBuffer* VertexBuffer::Builder::build(Engine& engine) { // ------------------------------------------------------------------------------------------------ FVertexBuffer::FVertexBuffer(FEngine& engine, const VertexBuffer::Builder& builder) - : mVertexCount(builder->mVertexCount), mBufferCount(builder->mBufferCount) { + : mVertexCount(builder->mVertexCount), mBufferCount(builder->mBufferCount), + mBufferObjectsEnabled(builder->mBufferObjectsEnabled) { std::copy(std::begin(builder->mAttributes), std::end(builder->mAttributes), mAttributes.begin()); mDeclaredAttributes = builder->mDeclaredAttributes; @@ -174,7 +182,8 @@ FVertexBuffer::FVertexBuffer(FEngine& engine, const VertexBuffer::Builder& build FEngine::DriverApi& driver = engine.getDriverApi(); mHandle = driver.createVertexBuffer( - mBufferCount, attributeCount, mVertexCount, attributeArray, backend::BufferUsage::STATIC); + mBufferCount, attributeCount, mVertexCount, attributeArray, + backend::BufferUsage::STATIC, mBufferObjectsEnabled); } void FVertexBuffer::terminate(FEngine& engine) { @@ -188,12 +197,25 @@ size_t FVertexBuffer::getVertexCount() const noexcept { void FVertexBuffer::setBufferAt(FEngine& engine, uint8_t bufferIndex, backend::BufferDescriptor&& buffer, uint32_t byteOffset) { + ASSERT_PRECONDITION(!mBufferObjectsEnabled, "Please use setBufferObjectAt()"); if (bufferIndex < mBufferCount) { engine.getDriverApi().updateVertexBuffer(mHandle, bufferIndex, std::move(buffer), byteOffset); } else { - ASSERT_PRECONDITION_NON_FATAL(bufferIndex < mBufferCount, - "bufferIndex must be < bufferCount"); + ASSERT_PRECONDITION(bufferIndex < mBufferCount, "bufferIndex must be < bufferCount"); + } +} + +void FVertexBuffer::setBufferObjectAt(FEngine& engine, uint8_t bufferIndex, + FBufferObject const * bufferObject) { + ASSERT_PRECONDITION(mBufferObjectsEnabled, "Please use setBufferAt()"); + ASSERT_PRECONDITION(bufferObject->getBindingType() == BufferObject::BindingType::VERTEX, + "Binding type must be VERTEX."); + if (bufferIndex < mBufferCount) { + auto hwBufferObject = bufferObject->getHwHandle(); + engine.getDriverApi().setVertexBufferObject(mHandle, bufferIndex, hwBufferObject); + } else { + ASSERT_PRECONDITION(bufferIndex < mBufferCount, "bufferIndex must be < bufferCount"); } } @@ -210,6 +232,11 @@ void VertexBuffer::setBufferAt(Engine& engine, uint8_t bufferIndex, upcast(this)->setBufferAt(upcast(engine), bufferIndex, std::move(buffer), byteOffset); } +void VertexBuffer::setBufferObjectAt(Engine& engine, uint8_t bufferIndex, + BufferObject const* bufferObject) { + upcast(this)->setBufferObjectAt(upcast(engine), bufferIndex, upcast(bufferObject)); +} + void VertexBuffer::populateTangentQuaternions(const QuatTangentContext& ctx) { auto* quats = geometry::SurfaceOrientation::Builder() .vertexCount(ctx.quatCount) diff --git a/filament/src/details/BufferObject.h b/filament/src/details/BufferObject.h new file mode 100644 index 0000000000..ef74b52a10 --- /dev/null +++ b/filament/src/details/BufferObject.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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_DETAILS_BUFFEROBJECT_H +#define TNT_FILAMENT_DETAILS_BUFFEROBJECT_H + +#include "upcast.h" + +#include + +#include + +#include + +namespace filament { + +class FEngine; + +class FBufferObject : public BufferObject { +public: + FBufferObject(FEngine& engine, const Builder& builder); + + // frees driver resources, object becomes invalid + void terminate(FEngine& engine); + + backend::Handle getHwHandle() const noexcept { return mHandle; } + + size_t getByteCount() const noexcept { return mByteCount; } + + void setBuffer(FEngine& engine, BufferDescriptor&& buffer, uint32_t byteOffset = 0); + + BindingType getBindingType() const noexcept { return mBindingType; } + +private: + friend class BufferObject; + backend::Handle mHandle; + uint32_t mByteCount; + BindingType mBindingType; +}; + +FILAMENT_UPCAST(BufferObject) + +} // namespace filament + +#endif // TNT_FILAMENT_DETAILS_BUFFEROBJECT_H diff --git a/filament/src/details/Engine.h b/filament/src/details/Engine.h index 568099e290..1260c036d1 100644 --- a/filament/src/details/Engine.h +++ b/filament/src/details/Engine.h @@ -26,6 +26,7 @@ #include "components/RenderableManager.h" #include "details/Allocators.h" +#include "details/BufferObject.h" #include "details/Camera.h" #include "details/DebugRegistry.h" #include "details/Fence.h" @@ -223,6 +224,7 @@ public: template T* create(ResourceList& list, typename T::Builder const& builder) noexcept; + FBufferObject* createBufferObject(const BufferObject::Builder& builder) noexcept; FVertexBuffer* createVertexBuffer(const VertexBuffer::Builder& builder) noexcept; FIndexBuffer* createIndexBuffer(const IndexBuffer::Builder& builder) noexcept; FIndirectLight* createIndirectLight(const IndirectLight::Builder& builder) noexcept; @@ -250,6 +252,7 @@ public: void destroyCameraComponent(utils::Entity entity) noexcept; + bool destroy(const FBufferObject* p); bool destroy(const FVertexBuffer* p); bool destroy(const FFence* p); bool destroy(const FIndexBuffer* p); @@ -340,6 +343,7 @@ private: FCameraManager mCameraManager; ResourceAllocator* mResourceAllocator = nullptr; + ResourceList mBufferObjects{ "BufferObject" }; ResourceList mRenderers{ "Renderer" }; ResourceList mViews{ "View" }; ResourceList mScenes{ "Scene" }; diff --git a/filament/src/details/VertexBuffer.h b/filament/src/details/VertexBuffer.h index 09992e2f4f..07b5c6aedb 100644 --- a/filament/src/details/VertexBuffer.h +++ b/filament/src/details/VertexBuffer.h @@ -32,6 +32,7 @@ namespace filament { +class FBufferObject; class FEngine; class FVertexBuffer : public VertexBuffer { @@ -53,6 +54,9 @@ public: void setBufferAt(FEngine& engine, uint8_t bufferIndex, backend::BufferDescriptor&& buffer, uint32_t byteOffset = 0); + void setBufferObjectAt(FEngine& engine, uint8_t bufferIndex, + FBufferObject const * bufferObject); + private: friend class VertexBuffer; @@ -65,6 +69,7 @@ private: AttributeBitset mDeclaredAttributes; uint32_t mVertexCount = 0; uint8_t mBufferCount = 0; + bool mBufferObjectsEnabled = false; }; FILAMENT_UPCAST(VertexBuffer) diff --git a/java/filament/CMakeLists.txt b/java/filament/CMakeLists.txt index b4cfed5c78..e39f25508b 100644 --- a/java/filament/CMakeLists.txt +++ b/java/filament/CMakeLists.txt @@ -40,6 +40,7 @@ set(COMMON_DIR ${ANDROID_DIR}/common) include_directories(${JNI_INCLUDE_DIRS} ${ANDROID_DIR}) set(JNI_SOURCE_FILES + ${FILAMENT_DIR}/src/main/cpp/BufferObject.cpp ${FILAMENT_DIR}/src/main/cpp/Camera.cpp ${FILAMENT_DIR}/src/main/cpp/Colors.cpp ${FILAMENT_DIR}/src/main/cpp/ColorGrading.cpp @@ -116,6 +117,7 @@ get_filename_component(FILAMENT_JAVA_DIR ${FILAMENT_JAVA_DIR} ABSOLUTE) set(JAVA_SOURCE_FILES ${FILAMENT_JAVA_DIR}/com/google/android/filament/Asserts.java ${FILAMENT_JAVA_DIR}/com/google/android/filament/Box.java + ${FILAMENT_JAVA_DIR}/com/google/android/filament/BufferObject.java ${FILAMENT_JAVA_DIR}/com/google/android/filament/Camera.java ${FILAMENT_JAVA_DIR}/com/google/android/filament/Colors.java ${FILAMENT_JAVA_DIR}/com/google/android/filament/ColorGrading.java diff --git a/web/filament-js/extensions.js b/web/filament-js/extensions.js index ef05f7e89e..32d6938812 100644 --- a/web/filament-js/extensions.js +++ b/web/filament-js/extensions.js @@ -306,6 +306,18 @@ Filament.loadClassExtensions = function() { this._setVignetteOptions(options); }; + /// BufferObject ::core class:: + + /// setBuffer ::method:: + /// engine ::argument:: [Engine] + /// buffer ::argument:: asset string, or Uint8Array, or [Buffer] + /// byteOffset ::argument:: non-negative integer + Filament.BufferObject.prototype.setBuffer = function(engine, buffer, byteOffset = 0) { + buffer = getBufferDescriptor(buffer); + this._setBuffer(engine, buffer, byteOffset); + buffer.delete(); + }; + /// VertexBuffer ::core class:: /// setBufferAt ::method:: diff --git a/web/filament-js/filament.d.ts b/web/filament-js/filament.d.ts index 8642bd6ad8..a3231463ff 100644 --- a/web/filament-js/filament.d.ts +++ b/web/filament-js/filament.d.ts @@ -225,6 +225,7 @@ export class VertexBuffer$Builder { public bufferCount(count: number): VertexBuffer$Builder; public attribute(attrib: VertexAttribute, bufindex: number, atype: VertexBuffer$AttributeType, offset: number, stride: number): VertexBuffer$Builder; + public enableBufferObjects(enabled: boolean): VertexBuffer$Builder; public normalized(attrib: VertexAttribute): VertexBuffer$Builder; public normalizedIf(attrib: VertexAttribute, normalized: boolean): VertexBuffer$Builder; public build(engine: Engine): VertexBuffer; @@ -236,6 +237,12 @@ export class IndexBuffer$Builder { public build(engine: Engine): IndexBuffer; } +export class BufferObject$Builder { + public size(byteCount: number): BufferObject$Builder; + public bindingType(type: BufferObject$BindingType): BufferObject$Builder; + public build(engine: Engine): BufferObject; +} + export class RenderableManager$Builder { public geometry(slot: number, ptype: RenderableManager$PrimitiveType, vb: VertexBuffer, ib: IndexBuffer): RenderableManager$Builder; @@ -364,6 +371,12 @@ export class VertexBuffer { public static Builder(): VertexBuffer$Builder; public setBufferAt(engine: Engine, bufindex: number, f32array: BufferReference, byteOffset?: number): void; + public setBufferObjectAt(engine: Engine, bufindex: number, bo: BufferObject): void; +} + +export class BufferObject { + public static Builder(): BufferObject$Builder; + public setBuffer(engine: Engine, data: BufferReference, byteOffset?: number): void; } export class IndexBuffer { @@ -719,6 +732,10 @@ export enum IndexBuffer$IndexType { UINT, } +export enum BufferObject$BindingType { + VERTEX_BINDING, +} + export enum LightManager$Type { SUN, DIRECTIONAL, @@ -955,6 +972,10 @@ export enum VertexAttribute { MORPH_TANGENTS_3 = CUSTOM7, } +export enum BufferObject$BindingType { + VERTEX, +} + export enum VertexBuffer$AttributeType { BYTE, BYTE2, diff --git a/web/filament-js/jsbindings.cpp b/web/filament-js/jsbindings.cpp index 5ea32c448d..33a7c27cc9 100644 --- a/web/filament-js/jsbindings.cpp +++ b/web/filament-js/jsbindings.cpp @@ -34,6 +34,7 @@ #include +#include #include #include #include @@ -117,6 +118,7 @@ namespace emscripten { namespace internal { BIND(Animator) BIND(AssetLoader) + BIND(BufferObject) BIND(Camera) BIND(ColorGrading) BIND(Engine) @@ -204,6 +206,7 @@ using SkyBuilder = Skybox::Builder; using SurfaceBuilder = SurfaceOrientation::Builder; using TexBuilder = Texture::Builder; using VertexBuilder = VertexBuffer::Builder; +using BufferBuilder = BufferObject::Builder; // We avoid directly exposing backend::BufferDescriptor because embind does not support move // semantics and void* doesn't make sense to JavaScript anyway. This little wrapper class is exposed @@ -1162,6 +1165,25 @@ class_("LightManager") class_("LightManager$Instance"); /// delete ::method:: Frees an instance obtained via `getInstance` +class_("BufferObject$Builder") + .function("_build", EMBIND_LAMBDA(BufferObject*, (BufferBuilder* builder, Engine* engine), { + return builder->build(*engine); + }), allow_raw_pointers()) + .BUILDER_FUNCTION("bindingType", BufferBuilder, (BufferBuilder* builder, + BufferObject::BindingType bt), { + return &builder->bindingType(bt); }) + .BUILDER_FUNCTION("size", BufferBuilder, (BufferBuilder* builder, int byteCount), { + return &builder->size(byteCount); }); + +/// BufferObject ::core class:: Represents a single GPU buffer. +class_("BufferObject") + .class_function("Builder", (BufferBuilder (*)()) [] { return BufferBuilder(); }) + .function("getByteCount", &BufferObject::getByteCount) + .function("_setBuffer", EMBIND_LAMBDA(void, (BufferObject* self, + Engine* engine, BufferDescriptor bd, uint32_t byteOffset), { + self->setBuffer(*engine, std::move(*bd.bd), byteOffset); + }), allow_raw_pointers()); + class_("VertexBuffer$Builder") .function("_build", EMBIND_LAMBDA(VertexBuffer*, (VertexBuilder* builder, Engine* engine), { return builder->build(*engine); @@ -1175,6 +1197,8 @@ class_("VertexBuffer$Builder") return &builder->attribute(attr, bufferIndex, attrType, byteOffset, byteStride); }) .BUILDER_FUNCTION("vertexCount", VertexBuilder, (VertexBuilder* builder, int count), { return &builder->vertexCount(count); }) + .BUILDER_FUNCTION("enableBufferObjects", VertexBuilder, (VertexBuilder* builder, bool enable), { + return &builder->enableBufferObjects(enable); }) .BUILDER_FUNCTION("normalized", VertexBuilder, (VertexBuilder* builder, VertexAttribute attrib), { return &builder->normalized(attrib); }) @@ -1187,6 +1211,7 @@ class_("VertexBuffer$Builder") /// VertexBuffer ::core class:: Bundle of buffers and associated vertex attributes. class_("VertexBuffer") .class_function("Builder", (VertexBuilder (*)()) [] { return VertexBuilder(); }) + .function("setBufferObjectAt", &VertexBuffer::setBufferObjectAt, allow_raw_pointers()) .function("_setBufferAt", EMBIND_LAMBDA(void, (VertexBuffer* self, Engine* engine, uint8_t bufferIndex, BufferDescriptor vbd, uint32_t byteOffset), { self->setBufferAt(*engine, bufferIndex, std::move(*vbd.bd), byteOffset); diff --git a/web/filament-js/jsenums.cpp b/web/filament-js/jsenums.cpp index 9e63c23213..226b60b42c 100644 --- a/web/filament-js/jsenums.cpp +++ b/web/filament-js/jsenums.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include #include #include @@ -69,6 +70,9 @@ enum_("VertexAttribute") .value("MORPH_TANGENTS_2", MORPH_TANGENTS_2) .value("MORPH_TANGENTS_3", MORPH_TANGENTS_3); +enum_("BufferObject$BindingType") + .value("VERTEX_BINDING", BufferObject::BindingType::VERTEX); + enum_("VertexBuffer$AttributeType") .value("BYTE", VertexBuffer::AttributeType::BYTE) .value("BYTE2", VertexBuffer::AttributeType::BYTE2)