From 5afd23fab12ad419a7f530645093ea41e8324f7b Mon Sep 17 00:00:00 2001 From: Matthias Frei Date: Tue, 3 Feb 2026 13:26:04 +0100 Subject: [PATCH 1/2] Add OwnedBytes to transfer owned buffers through djinni OwnedBytes transfers ownership over buffer to callee. When received in C++, convert back to a unique_ptr; otherwise, e.g. in swift, destroy with OwnedBytesDestructer::free. --- .github/workflows/test.yml | 7 +- CMakePresets.json | 55 ++++++++++++++ .../shared/graphics/common/OwnedBytes.kt | 14 ++++ .../graphics/common/OwnedBytesDestructor.kt | 31 ++++++++ .../jni/graphics/common/NativeOwnedBytes.cpp | 32 +++++++++ .../jni/graphics/common/NativeOwnedBytes.h | 34 +++++++++ .../common/NativeOwnedBytesDestructor.cpp | 28 ++++++++ .../common/NativeOwnedBytesDestructor.h | 32 +++++++++ bridging/ios/MCOwnedBytes+Private.h | 26 +++++++ bridging/ios/MCOwnedBytes+Private.mm | 25 +++++++ bridging/ios/MCOwnedBytes.h | 27 +++++++ bridging/ios/MCOwnedBytes.mm | 37 ++++++++++ bridging/ios/MCOwnedBytesDestructor+Private.h | 33 +++++++++ .../ios/MCOwnedBytesDestructor+Private.mm | 60 ++++++++++++++++ bridging/ios/MCOwnedBytesDestructor.h | 12 ++++ bridging/ts/graphics/common/common.ts | 17 +++++ bridging/wasm/NativeOwnedBytes.cpp | 21 ++++++ bridging/wasm/NativeOwnedBytes.h | 21 ++++++ bridging/wasm/NativeOwnedBytesDestructor.cpp | 32 +++++++++ bridging/wasm/NativeOwnedBytesDestructor.h | 30 ++++++++ djinni/graphics/common/common.djinni | 12 ++++ djinni/yaml/owned_bytes.yaml | 39 ++++++++++ djinni/yaml/owned_bytes_destructor.yaml | 39 ++++++++++ shared/public/OwnedBytes.h | 25 +++++++ shared/public/OwnedBytesDestructor.h | 13 ++++ shared/public/OwnedBytesHelper.h | 40 +++++++++++ shared/public/ReleasableAllocator.h | 71 +++++++++++++++++++ shared/src/utils/OwnedBytesDestructor.cpp | 6 ++ shared/test/CMakeLists.txt | 9 +-- shared/test/TestOwnedBytes.cpp | 65 +++++++++++++++++ shared/test/TestReleasableAllocator.cpp | 58 +++++++++++++++ 31 files changed, 944 insertions(+), 7 deletions(-) create mode 100644 CMakePresets.json create mode 100644 bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/common/OwnedBytes.kt create mode 100644 bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/common/OwnedBytesDestructor.kt create mode 100644 bridging/android/jni/graphics/common/NativeOwnedBytes.cpp create mode 100644 bridging/android/jni/graphics/common/NativeOwnedBytes.h create mode 100644 bridging/android/jni/graphics/common/NativeOwnedBytesDestructor.cpp create mode 100644 bridging/android/jni/graphics/common/NativeOwnedBytesDestructor.h create mode 100644 bridging/ios/MCOwnedBytes+Private.h create mode 100644 bridging/ios/MCOwnedBytes+Private.mm create mode 100644 bridging/ios/MCOwnedBytes.h create mode 100644 bridging/ios/MCOwnedBytes.mm create mode 100644 bridging/ios/MCOwnedBytesDestructor+Private.h create mode 100644 bridging/ios/MCOwnedBytesDestructor+Private.mm create mode 100644 bridging/ios/MCOwnedBytesDestructor.h create mode 100644 bridging/wasm/NativeOwnedBytes.cpp create mode 100644 bridging/wasm/NativeOwnedBytes.h create mode 100644 bridging/wasm/NativeOwnedBytesDestructor.cpp create mode 100644 bridging/wasm/NativeOwnedBytesDestructor.h create mode 100644 djinni/yaml/owned_bytes.yaml create mode 100644 djinni/yaml/owned_bytes_destructor.yaml create mode 100644 shared/public/OwnedBytes.h create mode 100644 shared/public/OwnedBytesDestructor.h create mode 100644 shared/public/OwnedBytesHelper.h create mode 100644 shared/public/ReleasableAllocator.h create mode 100644 shared/src/utils/OwnedBytesDestructor.cpp create mode 100644 shared/test/TestOwnedBytes.cpp create mode 100644 shared/test/TestReleasableAllocator.cpp diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b6a448923..47454711b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,15 +24,16 @@ jobs: - name: Build run: | - cmake -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug -DBUILD_JVM=OFF -DBUILD_STANDALONE=OFF -B build-debug - cmake --build build-debug -- tests + cmake -DCMAKE_CXX_COMPILER_LAUNCHER=ccache --preset sanitize -DBUILD_JVM=OFF -DBUILD_STANDALONE=OFF + cmake --build --preset sanitize -- tests - name: Test run: | mkdir test-results # Run tests. # We dont care about benchmark results, but still run the benchmark # tests as quickly as possible to catch when something breaks. - build-debug/shared/test/tests \ + UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \ + build-sanitize/shared/test/tests \ --benchmark-warmup-time 0 \ --benchmark-samples 1 \ --benchmark-no-analysis \ diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 000000000..511933f8c --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,55 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 23, + "patch": 0 + }, + "configurePresets": [ + { + "name": "default", + "displayName": "Base build preset", + "generator": "Ninja", + "cacheVariables": { + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_EXPORT_COMPILE_COMMANDS": "on" + } + }, + { + "name": "debug", + "inherits": "default", + "displayName": "Debug build", + "binaryDir": "${sourceDir}/build-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "release", + "inherits": "default", + "displayName": "Release build", + "binaryDir": "${sourceDir}/build-release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "sanitize", + "inherits": "default", + "displayName": "ASan/UBSan build", + "binaryDir": "${sourceDir}/build-sanitize", + "cacheVariables": { + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_LINKER": "clang++", + "CMAKE_CXX_FLAGS": "-fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer" + } + } + ], + "buildPresets": [ + { + "name": "sanitize", + "displayName": "Default Build Preset", + "configurePreset": "sanitize" + } + ] +} diff --git a/bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/common/OwnedBytes.kt b/bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/common/OwnedBytes.kt new file mode 100644 index 000000000..da382cb98 --- /dev/null +++ b/bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/common/OwnedBytes.kt @@ -0,0 +1,14 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +package io.openmobilemaps.mapscore.shared.graphics.common + +/** + * Pass ownership over byte buffer to callee. + * The new owner must ensure to call owned_bytes_destructor.free() or equivalent eventually. + */ +data class OwnedBytes( + val address: Long, + val elementCount: Int, + val bytesPerElement: Int, +) \ No newline at end of file diff --git a/bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/common/OwnedBytesDestructor.kt b/bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/common/OwnedBytesDestructor.kt new file mode 100644 index 000000000..4d4d79cb2 --- /dev/null +++ b/bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/common/OwnedBytesDestructor.kt @@ -0,0 +1,31 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +package io.openmobilemaps.mapscore.shared.graphics.common + +import com.snapchat.djinni.NativeObjectManager +import java.util.concurrent.atomic.AtomicBoolean + +abstract class OwnedBytesDestructor { + + companion object { + @JvmStatic + external fun free(bytes: OwnedBytes): Unit + } + + public class CppProxy : OwnedBytesDestructor { + private val nativeRef: Long + private val destroyed: AtomicBoolean = AtomicBoolean(false) + + private constructor(nativeRef: Long) { + if (nativeRef == 0L) error("nativeRef is zero") + this.nativeRef = nativeRef + NativeObjectManager.register(this, nativeRef) + } + + companion object { + @JvmStatic + external fun nativeDestroy(nativeRef: Long) + } + } +} diff --git a/bridging/android/jni/graphics/common/NativeOwnedBytes.cpp b/bridging/android/jni/graphics/common/NativeOwnedBytes.cpp new file mode 100644 index 000000000..94aa2cb85 --- /dev/null +++ b/bridging/android/jni/graphics/common/NativeOwnedBytes.cpp @@ -0,0 +1,32 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#include "NativeOwnedBytes.h" // my header +#include "Marshal.hpp" + +namespace djinni_generated { + +NativeOwnedBytes::NativeOwnedBytes() = default; + +NativeOwnedBytes::~NativeOwnedBytes() = default; + +auto NativeOwnedBytes::fromCpp(JNIEnv* jniEnv, const CppType& c) -> ::djinni::LocalRef { + const auto& data = ::djinni::JniClass::get(); + auto r = ::djinni::LocalRef{jniEnv->NewObject(data.clazz.get(), data.jconstructor, + ::djinni::get(::djinni::I64::fromCpp(jniEnv, c.address)), + ::djinni::get(::djinni::I32::fromCpp(jniEnv, c.elementCount)), + ::djinni::get(::djinni::I32::fromCpp(jniEnv, c.bytesPerElement)))}; + ::djinni::jniExceptionCheck(jniEnv); + return r; +} + +auto NativeOwnedBytes::toCpp(JNIEnv* jniEnv, JniType j) -> CppType { + ::djinni::JniLocalScope jscope(jniEnv, 4); + assert(j != nullptr); + const auto& data = ::djinni::JniClass::get(); + return {::djinni::I64::toCpp(jniEnv, jniEnv->GetLongField(j, data.field_address)), + ::djinni::I32::toCpp(jniEnv, jniEnv->GetIntField(j, data.field_elementCount)), + ::djinni::I32::toCpp(jniEnv, jniEnv->GetIntField(j, data.field_bytesPerElement))}; +} + +} // namespace djinni_generated diff --git a/bridging/android/jni/graphics/common/NativeOwnedBytes.h b/bridging/android/jni/graphics/common/NativeOwnedBytes.h new file mode 100644 index 000000000..3119d1b3f --- /dev/null +++ b/bridging/android/jni/graphics/common/NativeOwnedBytes.h @@ -0,0 +1,34 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#pragma once + +#include "OwnedBytes.h" +#include "djinni_support.hpp" + +namespace djinni_generated { + +class NativeOwnedBytes final { +public: + using CppType = ::OwnedBytes; + using JniType = jobject; + + using Boxed = NativeOwnedBytes; + + ~NativeOwnedBytes(); + + static CppType toCpp(JNIEnv* jniEnv, JniType j); + static ::djinni::LocalRef fromCpp(JNIEnv* jniEnv, const CppType& c); + +private: + NativeOwnedBytes(); + friend ::djinni::JniClass; + + const ::djinni::GlobalRef clazz { ::djinni::jniFindClass("io/openmobilemaps/mapscore/shared/graphics/common/OwnedBytes") }; + const jmethodID jconstructor { ::djinni::jniGetMethodID(clazz.get(), "", "(JII)V") }; + const jfieldID field_address { ::djinni::jniGetFieldID(clazz.get(), "address", "J") }; + const jfieldID field_elementCount { ::djinni::jniGetFieldID(clazz.get(), "elementCount", "I") }; + const jfieldID field_bytesPerElement { ::djinni::jniGetFieldID(clazz.get(), "bytesPerElement", "I") }; +}; + +} // namespace djinni_generated diff --git a/bridging/android/jni/graphics/common/NativeOwnedBytesDestructor.cpp b/bridging/android/jni/graphics/common/NativeOwnedBytesDestructor.cpp new file mode 100644 index 000000000..ac06a2e14 --- /dev/null +++ b/bridging/android/jni/graphics/common/NativeOwnedBytesDestructor.cpp @@ -0,0 +1,28 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#include "NativeOwnedBytesDestructor.h" // my header +#include "NativeOwnedBytes.h" + +namespace djinni_generated { + +NativeOwnedBytesDestructor::NativeOwnedBytesDestructor() : ::djinni::JniInterface<::OwnedBytesDestructor, NativeOwnedBytesDestructor>("io/openmobilemaps/mapscore/shared/graphics/common/OwnedBytesDestructor$CppProxy") {} + +NativeOwnedBytesDestructor::~NativeOwnedBytesDestructor() = default; + + +CJNIEXPORT void JNICALL Java_io_openmobilemaps_mapscore_shared_graphics_common_OwnedBytesDestructor_00024CppProxy_nativeDestroy(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef) +{ + try { + delete reinterpret_cast<::djinni::CppProxyHandle<::OwnedBytesDestructor>*>(nativeRef); + } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, ) +} + +CJNIEXPORT void JNICALL Java_io_openmobilemaps_mapscore_shared_graphics_common_OwnedBytesDestructor_free(JNIEnv* jniEnv, jobject /*this*/, jobject j_bytes) +{ + try { + ::OwnedBytesDestructor::free(::djinni_generated::NativeOwnedBytes::toCpp(jniEnv, j_bytes)); + } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, ) +} + +} // namespace djinni_generated diff --git a/bridging/android/jni/graphics/common/NativeOwnedBytesDestructor.h b/bridging/android/jni/graphics/common/NativeOwnedBytesDestructor.h new file mode 100644 index 000000000..4aab6bb90 --- /dev/null +++ b/bridging/android/jni/graphics/common/NativeOwnedBytesDestructor.h @@ -0,0 +1,32 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#pragma once + +#include "OwnedBytesDestructor.h" +#include "djinni_support.hpp" + +namespace djinni_generated { + +class NativeOwnedBytesDestructor final : ::djinni::JniInterface<::OwnedBytesDestructor, NativeOwnedBytesDestructor> { +public: + using CppType = std::shared_ptr<::OwnedBytesDestructor>; + using CppOptType = std::shared_ptr<::OwnedBytesDestructor>; + using JniType = jobject; + + using Boxed = NativeOwnedBytesDestructor; + + ~NativeOwnedBytesDestructor(); + + static CppType toCpp(JNIEnv* jniEnv, JniType j) { return ::djinni::JniClass::get()._fromJava(jniEnv, j); } + static ::djinni::LocalRef fromCppOpt(JNIEnv* jniEnv, const CppOptType& c) { return {jniEnv, ::djinni::JniClass::get()._toJava(jniEnv, c)}; } + static ::djinni::LocalRef fromCpp(JNIEnv* jniEnv, const CppType& c) { return fromCppOpt(jniEnv, c); } + +private: + NativeOwnedBytesDestructor(); + friend ::djinni::JniClass; + friend ::djinni::JniInterface<::OwnedBytesDestructor, NativeOwnedBytesDestructor>; + +}; + +} // namespace djinni_generated diff --git a/bridging/ios/MCOwnedBytes+Private.h b/bridging/ios/MCOwnedBytes+Private.h new file mode 100644 index 000000000..2e7fedb82 --- /dev/null +++ b/bridging/ios/MCOwnedBytes+Private.h @@ -0,0 +1,26 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni +#ifdef __cplusplus + +#import "MCOwnedBytes.h" +#include "OwnedBytes.h" + +static_assert(__has_feature(objc_arc), "Djinni requires ARC to be enabled for this file"); + +@class MCOwnedBytes; + +namespace djinni_generated { + +struct OwnedBytes +{ + using CppType = ::OwnedBytes; + using ObjcType = MCOwnedBytes*; + + using Boxed = OwnedBytes; + + static CppType toCpp(ObjcType objc); + static ObjcType fromCpp(const CppType& cpp); +}; + +} // namespace djinni_generated +#endif diff --git a/bridging/ios/MCOwnedBytes+Private.mm b/bridging/ios/MCOwnedBytes+Private.mm new file mode 100644 index 000000000..b9a8d708a --- /dev/null +++ b/bridging/ios/MCOwnedBytes+Private.mm @@ -0,0 +1,25 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#import "MCOwnedBytes+Private.h" +#import "DJIMarshal+Private.h" +#include + +namespace djinni_generated { + +auto OwnedBytes::toCpp(ObjcType obj) -> CppType +{ + assert(obj); + return {::djinni::I64::toCpp(obj.address), + ::djinni::I32::toCpp(obj.elementCount), + ::djinni::I32::toCpp(obj.bytesPerElement)}; +} + +auto OwnedBytes::fromCpp(const CppType& cpp) -> ObjcType +{ + return [[MCOwnedBytes alloc] initWithAddress:(::djinni::I64::fromCpp(cpp.address)) + elementCount:(::djinni::I32::fromCpp(cpp.elementCount)) + bytesPerElement:(::djinni::I32::fromCpp(cpp.bytesPerElement))]; +} + +} // namespace djinni_generated diff --git a/bridging/ios/MCOwnedBytes.h b/bridging/ios/MCOwnedBytes.h new file mode 100644 index 000000000..f88aaef1d --- /dev/null +++ b/bridging/ios/MCOwnedBytes.h @@ -0,0 +1,27 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#import + +/** + * Pass ownership over byte buffer to callee. + * The new owner must ensure to call owned_bytes_destructor.free() or equivalent eventually. + */ +NS_SWIFT_SENDABLE +@interface MCOwnedBytes : NSObject +- (nonnull instancetype)init NS_UNAVAILABLE; ++ (nonnull instancetype)new NS_UNAVAILABLE; +- (nonnull instancetype)initWithAddress:(int64_t)address + elementCount:(int32_t)elementCount + bytesPerElement:(int32_t)bytesPerElement NS_DESIGNATED_INITIALIZER; ++ (nonnull instancetype)ownedBytesWithAddress:(int64_t)address + elementCount:(int32_t)elementCount + bytesPerElement:(int32_t)bytesPerElement; + +@property (nonatomic, readonly) int64_t address; + +@property (nonatomic, readonly) int32_t elementCount; + +@property (nonatomic, readonly) int32_t bytesPerElement; + +@end diff --git a/bridging/ios/MCOwnedBytes.mm b/bridging/ios/MCOwnedBytes.mm new file mode 100644 index 000000000..fce7a95d1 --- /dev/null +++ b/bridging/ios/MCOwnedBytes.mm @@ -0,0 +1,37 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#import "MCOwnedBytes.h" + + +@implementation MCOwnedBytes + +- (nonnull instancetype)initWithAddress:(int64_t)address + elementCount:(int32_t)elementCount + bytesPerElement:(int32_t)bytesPerElement +{ + if (self = [super init]) { + _address = address; + _elementCount = elementCount; + _bytesPerElement = bytesPerElement; + } + return self; +} + ++ (nonnull instancetype)ownedBytesWithAddress:(int64_t)address + elementCount:(int32_t)elementCount + bytesPerElement:(int32_t)bytesPerElement +{ + return [[self alloc] initWithAddress:address + elementCount:elementCount + bytesPerElement:bytesPerElement]; +} + +#ifndef DJINNI_DISABLE_DESCRIPTION_METHODS +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@ %p address:%@ elementCount:%@ bytesPerElement:%@>", self.class, (void *)self, @(self.address), @(self.elementCount), @(self.bytesPerElement)]; +} + +#endif +@end diff --git a/bridging/ios/MCOwnedBytesDestructor+Private.h b/bridging/ios/MCOwnedBytesDestructor+Private.h new file mode 100644 index 000000000..f7fedfacb --- /dev/null +++ b/bridging/ios/MCOwnedBytesDestructor+Private.h @@ -0,0 +1,33 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni +#ifdef __cplusplus + +#include "OwnedBytesDestructor.h" +#include + +static_assert(__has_feature(objc_arc), "Djinni requires ARC to be enabled for this file"); + +@class MCOwnedBytesDestructor; + +namespace djinni_generated { + +class OwnedBytesDestructor +{ +public: + using CppType = std::shared_ptr<::OwnedBytesDestructor>; + using CppOptType = std::shared_ptr<::OwnedBytesDestructor>; + using ObjcType = MCOwnedBytesDestructor*; + + using Boxed = OwnedBytesDestructor; + + static CppType toCpp(ObjcType objc); + static ObjcType fromCppOpt(const CppOptType& cpp); + static ObjcType fromCpp(const CppType& cpp) { return fromCppOpt(cpp); } + +private: + class ObjcProxy; +}; + +} // namespace djinni_generated + +#endif diff --git a/bridging/ios/MCOwnedBytesDestructor+Private.mm b/bridging/ios/MCOwnedBytesDestructor+Private.mm new file mode 100644 index 000000000..2ee8bbc3a --- /dev/null +++ b/bridging/ios/MCOwnedBytesDestructor+Private.mm @@ -0,0 +1,60 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#import "MCOwnedBytesDestructor+Private.h" +#import "MCOwnedBytesDestructor.h" +#import "DJICppWrapperCache+Private.h" +#import "DJIError.h" +#import "DJIMarshal+Private.h" +#import "MCOwnedBytes+Private.h" +#include +#include +#include + +static_assert(__has_feature(objc_arc), "Djinni requires ARC to be enabled for this file"); + +@interface MCOwnedBytesDestructor () + +- (id)initWithCpp:(const std::shared_ptr<::OwnedBytesDestructor>&)cppRef; + +@end + +@implementation MCOwnedBytesDestructor { + ::djinni::CppProxyCache::Handle> _cppRefHandle; +} + +- (id)initWithCpp:(const std::shared_ptr<::OwnedBytesDestructor>&)cppRef +{ + if (self = [super init]) { + _cppRefHandle.assign(cppRef); + } + return self; +} + ++ (void)free:(nonnull MCOwnedBytes *)bytes { + try { + ::OwnedBytesDestructor::free(::djinni_generated::OwnedBytes::toCpp(bytes)); + } DJINNI_TRANSLATE_EXCEPTIONS() +} + +namespace djinni_generated { + +auto OwnedBytesDestructor::toCpp(ObjcType objc) -> CppType +{ + if (!objc) { + return nullptr; + } + return objc->_cppRefHandle.get(); +} + +auto OwnedBytesDestructor::fromCppOpt(const CppOptType& cpp) -> ObjcType +{ + if (!cpp) { + return nil; + } + return ::djinni::get_cpp_proxy(cpp); +} + +} // namespace djinni_generated + +@end diff --git a/bridging/ios/MCOwnedBytesDestructor.h b/bridging/ios/MCOwnedBytesDestructor.h new file mode 100644 index 000000000..a905972a8 --- /dev/null +++ b/bridging/ios/MCOwnedBytesDestructor.h @@ -0,0 +1,12 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#import "MCOwnedBytes.h" +#import + + +@interface MCOwnedBytesDestructor : NSObject + ++ (void)free:(nonnull MCOwnedBytes *)bytes; + +@end diff --git a/bridging/ts/graphics/common/common.ts b/bridging/ts/graphics/common/common.ts index d79766cdd..1c56a467d 100644 --- a/bridging/ts/graphics/common/common.ts +++ b/bridging/ts/graphics/common/common.ts @@ -101,5 +101,22 @@ export interface /*record*/ SharedBytes { bytesPerElement: number; } +/** + * Pass ownership over byte buffer to callee. + * The new owner must ensure to call owned_bytes_destructor.free() or equivalent eventually. + */ +export interface /*record*/ OwnedBytes { + address: bigint; + elementCount: number; + bytesPerElement: number; +} + +export interface OwnedBytesDestructor { +} +export interface OwnedBytesDestructor_statics { + free(bytes: OwnedBytes): void; +} + export interface Common_statics { + OwnedBytesDestructor: OwnedBytesDestructor_statics; } diff --git a/bridging/wasm/NativeOwnedBytes.cpp b/bridging/wasm/NativeOwnedBytes.cpp new file mode 100644 index 000000000..13c625323 --- /dev/null +++ b/bridging/wasm/NativeOwnedBytes.cpp @@ -0,0 +1,21 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#include "NativeOwnedBytes.h" // my header + +namespace djinni_generated { + +auto NativeOwnedBytes::toCpp(const JsType& j) -> CppType { + return {::djinni::I64::Boxed::toCpp(j["address"]), + ::djinni::I32::Boxed::toCpp(j["elementCount"]), + ::djinni::I32::Boxed::toCpp(j["bytesPerElement"])}; +} +auto NativeOwnedBytes::fromCpp(const CppType& c) -> JsType { + em::val js = em::val::object(); + js.set("address", ::djinni::I64::Boxed::fromCpp(c.address)); + js.set("elementCount", ::djinni::I32::Boxed::fromCpp(c.elementCount)); + js.set("bytesPerElement", ::djinni::I32::Boxed::fromCpp(c.bytesPerElement)); + return js; +} + +} // namespace djinni_generated diff --git a/bridging/wasm/NativeOwnedBytes.h b/bridging/wasm/NativeOwnedBytes.h new file mode 100644 index 000000000..143e0748f --- /dev/null +++ b/bridging/wasm/NativeOwnedBytes.h @@ -0,0 +1,21 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#pragma once + +#include "OwnedBytes.h" +#include "djinni_wasm.hpp" + +namespace djinni_generated { + +struct NativeOwnedBytes +{ + using CppType = ::OwnedBytes; + using JsType = em::val; + using Boxed = NativeOwnedBytes; + + static CppType toCpp(const JsType& j); + static JsType fromCpp(const CppType& c); +}; + +} // namespace djinni_generated diff --git a/bridging/wasm/NativeOwnedBytesDestructor.cpp b/bridging/wasm/NativeOwnedBytesDestructor.cpp new file mode 100644 index 000000000..ac5224143 --- /dev/null +++ b/bridging/wasm/NativeOwnedBytesDestructor.cpp @@ -0,0 +1,32 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#include "NativeOwnedBytesDestructor.h" // my header +#include "NativeOwnedBytes.h" + +namespace djinni_generated { + +em::val NativeOwnedBytesDestructor::cppProxyMethods() { + static const em::val methods = em::val::array(std::vector { + }); + return methods; +} + +void NativeOwnedBytesDestructor::free(const em::val& w_bytes) { + try { + ::OwnedBytesDestructor::free(::djinni_generated::NativeOwnedBytes::toCpp(w_bytes)); + } + catch(const std::exception& e) { + return ::djinni::ExceptionHandlingTraits::handleNativeException(e); + } +} + +EMSCRIPTEN_BINDINGS(_owned_bytes_destructor) { + em::class_<::OwnedBytesDestructor>("OwnedBytesDestructor") + .smart_ptr>("OwnedBytesDestructor") + .function("nativeDestroy", &NativeOwnedBytesDestructor::nativeDestroy) + .class_function("free", NativeOwnedBytesDestructor::free) + ; +} + +} // namespace djinni_generated diff --git a/bridging/wasm/NativeOwnedBytesDestructor.h b/bridging/wasm/NativeOwnedBytesDestructor.h new file mode 100644 index 000000000..3ebeab4df --- /dev/null +++ b/bridging/wasm/NativeOwnedBytesDestructor.h @@ -0,0 +1,30 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#pragma once + +#include "OwnedBytesDestructor.h" +#include "djinni_wasm.hpp" + +namespace djinni_generated { + +struct NativeOwnedBytesDestructor : ::djinni::JsInterface<::OwnedBytesDestructor, NativeOwnedBytesDestructor> { + using CppType = std::shared_ptr<::OwnedBytesDestructor>; + using CppOptType = std::shared_ptr<::OwnedBytesDestructor>; + using JsType = em::val; + using Boxed = NativeOwnedBytesDestructor; + + static CppType toCpp(JsType j) { return _fromJs(j); } + static JsType fromCppOpt(const CppOptType& c) { return {_toJs(c)}; } + static JsType fromCpp(const CppType& c) { + ::djinni::checkForNull(c.get(), "NativeOwnedBytesDestructor::fromCpp"); + return fromCppOpt(c); + } + + static em::val cppProxyMethods(); + + static void free(const em::val& w_bytes); + +}; + +} // namespace djinni_generated diff --git a/djinni/graphics/common/common.djinni b/djinni/graphics/common/common.djinni index ce2a72218..c1cf5ba7b 100644 --- a/djinni/graphics/common/common.djinni +++ b/djinni/graphics/common/common.djinni @@ -96,3 +96,15 @@ shared_bytes = record { element_count: i32; bytes_per_element: i32; } + +# Pass ownership over byte buffer to callee. +# The new owner must ensure to call owned_bytes_destructor.free() or equivalent eventually. +owned_bytes = record { + address: i64; + element_count: i32; + bytes_per_element: i32; +} + +owned_bytes_destructor = interface +c { + static free(bytes: owned_bytes); +} diff --git a/djinni/yaml/owned_bytes.yaml b/djinni/yaml/owned_bytes.yaml new file mode 100644 index 000000000..ccfb3cd22 --- /dev/null +++ b/djinni/yaml/owned_bytes.yaml @@ -0,0 +1,39 @@ +# AUTOGENERATED FILE - DO NOT MODIFY! +# This file was generated by Djinni from common.djinni +name: owned_bytes +typedef: 'record' +params: [] +prefix: "" +cpp: + typename: '::OwnedBytes' + header: '"OwnedBytes.h"' + byValue: false +objc: + typename: 'MCOwnedBytes' + pointer: true + hash: '%s.hash' + boxed: 'MCOwnedBytes' + header: '"MCOwnedBytes.h"' +objcpp: + translator: '::djinni_generated::OwnedBytes' + header: '"MCOwnedBytes+Private.h"' +java: + reference: true + typename: 'io.openmobilemaps.mapscore.shared.graphics.common.OwnedBytes' + writeToParcel: '%s.writeToParcel(out, flags)' + generic: true + readFromParcel: 'new %s(in)' + hash: '%s.hashCode()' + boxed: 'io.openmobilemaps.mapscore.shared.graphics.common.OwnedBytes' +jni: + translator: '::djinni_generated::NativeOwnedBytes' + header: '"NativeOwnedBytes.h"' + typename: jobject + typeSignature: 'Lio/openmobilemaps/mapscore/shared/graphics/common/OwnedBytes;' +wasm: + translator: '::djinni_generated::NativeOwnedBytes' + header: '"NativeOwnedBytes.h"' + typename: em::val +ts: + typename: OwnedBytes + module: '@djinni/maps-core/graphics/common/common' diff --git a/djinni/yaml/owned_bytes_destructor.yaml b/djinni/yaml/owned_bytes_destructor.yaml new file mode 100644 index 000000000..8a59b5757 --- /dev/null +++ b/djinni/yaml/owned_bytes_destructor.yaml @@ -0,0 +1,39 @@ +# AUTOGENERATED FILE - DO NOT MODIFY! +# This file was generated by Djinni from common.djinni +name: owned_bytes_destructor +typedef: 'interface +c' +params: [] +prefix: "" +cpp: + typename: '::OwnedBytesDestructor' + header: '"OwnedBytesDestructor.h"' + byValue: false +objc: + typename: 'MCOwnedBytesDestructor' + pointer: true + hash: '%s.hash' + boxed: 'MCOwnedBytesDestructor' + header: '"MCOwnedBytesDestructor.h"' +objcpp: + translator: '::djinni_generated::OwnedBytesDestructor' + header: '"MCOwnedBytesDestructor+Private.h"' +java: + reference: true + typename: 'io.openmobilemaps.mapscore.shared.graphics.common.OwnedBytesDestructor' + writeToParcel: '%s.writeToParcel(out, flags)' + generic: true + readFromParcel: 'new %s(in)' + hash: '%s.hashCode()' + boxed: 'io.openmobilemaps.mapscore.shared.graphics.common.OwnedBytesDestructor' +jni: + translator: '::djinni_generated::NativeOwnedBytesDestructor' + header: '"NativeOwnedBytesDestructor.h"' + typename: jobject + typeSignature: 'Lio/openmobilemaps/mapscore/shared/graphics/common/OwnedBytesDestructor;' +wasm: + translator: '::djinni_generated::NativeOwnedBytesDestructor' + header: '"NativeOwnedBytesDestructor.h"' + typename: em::val +ts: + typename: OwnedBytesDestructor + module: '@djinni/maps-core/graphics/common/common' diff --git a/shared/public/OwnedBytes.h b/shared/public/OwnedBytes.h new file mode 100644 index 000000000..86b5830af --- /dev/null +++ b/shared/public/OwnedBytes.h @@ -0,0 +1,25 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#pragma once + +#include +#include + +/** + * Pass ownership over byte buffer to callee. + * The new owner must ensure to call owned_bytes_destructor.free() or equivalent eventually. + */ +struct OwnedBytes final { + int64_t address; + int32_t elementCount; + int32_t bytesPerElement; + + OwnedBytes(int64_t address_, + int32_t elementCount_, + int32_t bytesPerElement_) + : address(std::move(address_)) + , elementCount(std::move(elementCount_)) + , bytesPerElement(std::move(bytesPerElement_)) + {} +}; diff --git a/shared/public/OwnedBytesDestructor.h b/shared/public/OwnedBytesDestructor.h new file mode 100644 index 000000000..2270daa27 --- /dev/null +++ b/shared/public/OwnedBytesDestructor.h @@ -0,0 +1,13 @@ +// AUTOGENERATED FILE - DO NOT MODIFY! +// This file was generated by Djinni from common.djinni + +#pragma once + +struct OwnedBytes; + +class OwnedBytesDestructor { +public: + virtual ~OwnedBytesDestructor() = default; + + static void free(const OwnedBytes & bytes); +}; diff --git a/shared/public/OwnedBytesHelper.h b/shared/public/OwnedBytesHelper.h new file mode 100644 index 000000000..aaadc7333 --- /dev/null +++ b/shared/public/OwnedBytesHelper.h @@ -0,0 +1,40 @@ +#pragma once + +#include "OwnedBytes.h" +#include "ReleasableAllocator.h" +#include +#include +#include +#include + +class OwnedBytesHelper { +public: + template + static std::pair, size_t> toUniquePtr(const OwnedBytes & bytes) { + assert(bytes.bytesPerElement == sizeof(T)); + if(bytes.bytesPerElement != sizeof(T)) { + std::free((void*)bytes.address); + return std::make_pair(nullptr, 0); + } + return std::make_pair(std::unique_ptr(reinterpret_cast(bytes.address)), bytes.elementCount); + } + + template + [[nodiscard]] + static OwnedBytes fromVector(std::vector> &&vec) { + size_t size = vec.size(); + auto ptr = ReleasableAllocator::release(std::move(vec)); + return fromUniquePtr(std::move(ptr), size); + } + + template + [[nodiscard]] + static OwnedBytes fromUniquePtr(std::unique_ptr ptr, size_t size) { + const int32_t elementCount = static_cast(size); + const int32_t bytesPerElement = sizeof(T); + return OwnedBytes( + reinterpret_cast(ptr.release()), + elementCount, + bytesPerElement); + } +}; diff --git a/shared/public/ReleasableAllocator.h b/shared/public/ReleasableAllocator.h new file mode 100644 index 000000000..e082b725b --- /dev/null +++ b/shared/public/ReleasableAllocator.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include + +/** + * ReleasableAllocator is a pseudo-allocator for std::vector, allowing to move + * the owned data out of a std::vector instance. + * + * This implementation only works for trivial types. + * + * Usage Example: + * + * std::vector> exampleVec; + * // ... use vector as usual, e.g. exampleVec.push_back(...); + * std::unique_ptr exampleData = ReleasableAllocator::release(std::move(exampleVec)); + * + * */ +template +class ReleasableAllocator +{ +public: + using value_type = T; + + ReleasableAllocator() + : released(nullptr) + {} + +private: + ReleasableAllocator(T *released) + : released(released) + {} + +public: + + [[nodiscard]] T* allocate(std::size_t n) + { + return std::allocator().allocate(n); + } + + void deallocate(T* p, std::size_t n) noexcept + { + assert(released == nullptr || p == released); + if(p == released) { + return; + } + std::allocator().deallocate(p, n); + } + + static std::unique_ptr release(std::vector>&& v) noexcept + { + // Note: there is no write-acccess to the allocator instance. + // Instead, we create a new vector instance with a modified allocator instance. + ReleasableAllocator alloc(v.data()); + std::vector> vv(std::move(v), alloc); + assert(v.data() == nullptr); + assert(vv.get_allocator().released == alloc.released); + assert(vv.get_allocator().released == vv.data()); + return std::unique_ptr{vv.data()}; + } + +private: + // Data that should be ignored in a #deallocate call during #release. + T* released; +}; + +template +bool operator==(const ReleasableAllocator& a, const ReleasableAllocator& b) { + return true; +} diff --git a/shared/src/utils/OwnedBytesDestructor.cpp b/shared/src/utils/OwnedBytesDestructor.cpp new file mode 100644 index 000000000..11bb8939f --- /dev/null +++ b/shared/src/utils/OwnedBytesDestructor.cpp @@ -0,0 +1,6 @@ +#include "OwnedBytesDestructor.h" +#include "OwnedBytes.h" + +void OwnedBytesDestructor::free(const OwnedBytes & bytes) { + ::operator delete(reinterpret_cast(bytes.address)); +} diff --git a/shared/test/CMakeLists.txt b/shared/test/CMakeLists.txt index 0b2665639..441e48477 100644 --- a/shared/test/CMakeLists.txt +++ b/shared/test/CMakeLists.txt @@ -10,14 +10,15 @@ FetchContent_MakeAvailable(Catch2) add_executable(tests "TestActor.cpp" "TestGeoJsonParser.cpp" - "TestSymbolAnimationCoordinatorMap.cpp" - "TestTileSource.cpp" "TestGeometryHandler.cpp" + "TestInternedString.cpp" + "TestOwnedBytes.cpp" + "TestReleasableAllocator.cpp" "TestStyleParser.cpp" + "TestSymbolAnimationCoordinatorMap.cpp" + "TestTileSource.cpp" "TestValueEvaluate.cpp" "TestVectorSet.cpp" - "TestStyleParser.cpp" - "TestInternedString.cpp" "helper/TestData.cpp" "helper/TestLocalDataProvider.h" ) diff --git a/shared/test/TestOwnedBytes.cpp b/shared/test/TestOwnedBytes.cpp new file mode 100644 index 000000000..1c91d1cae --- /dev/null +++ b/shared/test/TestOwnedBytes.cpp @@ -0,0 +1,65 @@ +#include "OwnedBytesHelper.h" +#include "OwnedBytesDestructor.h" + +#include +#include + +TEST_CASE("OwnedBytes to unique ptr") { + + OwnedBytes ownedBytes(0, 0, 0); + { + std::vector> vec(3); + vec[0] = 9.0; + vec[1] = 13.0; + vec[2] = 19.0; + float* vecData = vec.data(); + ownedBytes = OwnedBytesHelper::fromVector(std::move(vec)); + CHECK(vec.data() == nullptr); + REQUIRE(ownedBytes.address == reinterpret_cast(vecData)); + } + + { + auto [data, size] = OwnedBytesHelper::toUniquePtr(ownedBytes); + REQUIRE(size == 3); + CHECK(data.get()[0] == 9.0); + CHECK(data.get()[1] == 13.0); + CHECK(data.get()[2] == 19.0); + } +} + +TEST_CASE("OwnedBytes to OwnedBytesDestructor") { + struct FizzBuzz { + // intentionally stupid order for alignment + bool fizz; + float num; + bool buzz; + }; + + OwnedBytes ownedBytes(0, 0, 0); + { + std::vector> vec; + for(int i = 0; i < 99; ++i) { + vec.push_back(FizzBuzz{ + .fizz = (i % 3) == 0, + .num = (float) i, + .buzz = (i % 5) == 0 + }); + } + FizzBuzz* vecData = vec.data(); + ownedBytes = OwnedBytesHelper::fromVector(std::move(vec)); + CHECK(vec.data() == nullptr); + REQUIRE(ownedBytes.address == reinterpret_cast(vecData)); + } + + + { + const FizzBuzz* data = reinterpret_cast(ownedBytes.address); + REQUIRE(ownedBytes.elementCount == 99); + for(int i = 0; i < 99; ++i) { + CHECK(data[i].fizz == ((i % 3) == 0)); + CHECK(data[i].num == (float)i); + CHECK(data[i].buzz == ((i % 5) == 0)); + } + } + OwnedBytesDestructor::free(ownedBytes); +} diff --git a/shared/test/TestReleasableAllocator.cpp b/shared/test/TestReleasableAllocator.cpp new file mode 100644 index 000000000..3fd7bb564 --- /dev/null +++ b/shared/test/TestReleasableAllocator.cpp @@ -0,0 +1,58 @@ +#include "ReleasableAllocator.h" + +#include + +TEST_CASE("TestReleasableAllocator") { + size_t size; + std::unique_ptr releasedData; + + // Create vec in scope to check that data survives after it is dropped + { + std::vector> vec; + vec.push_back(0.0); + vec.reserve(8); + vec.push_back(1.0); + + auto *dataPtr = vec.data(); + + size = vec.size(); + releasedData = ReleasableAllocator::release(std::move(vec)); + CHECK(vec.data() == nullptr); // sanity check for std::vector + REQUIRE(releasedData.get() == dataPtr); + } + + REQUIRE(size == 2); + CHECK(releasedData.get()[0] == 0.0); + CHECK(releasedData.get()[1] == 1.0); +} + +TEST_CASE("TestReleasableAllocator") { + struct TestAggregate { + float x; + bool even; + }; + + size_t size; + std::unique_ptr releasedData; + + // Create vec in scope to check that releasedData survives after it is dropped + { + std::vector> vec; + for(int i = 0; i < 13; i++) { + vec.push_back({ i * 7.0f, (i%2 == 0)}); + } + + auto* dataPtr = vec.data(); + + size = vec.size(); + releasedData = ReleasableAllocator::release(std::move(vec)); + CHECK(vec.data() == nullptr); // sanity check for std::vector + REQUIRE(releasedData.get() == dataPtr); + } + + REQUIRE(size == 13); + for(int i = 0; i < 13; i++) { + CHECK(releasedData.get()[i].x == i*7.0f); + CHECK(releasedData.get()[i].even == (i%2 == 0)); + } +} From a847021f33ca9010a4e5ed10d7545f225a088da0 Mon Sep 17 00:00:00 2001 From: Matthias Frei Date: Fri, 6 Feb 2026 21:43:16 +0100 Subject: [PATCH 2/2] Use owned buffer for line data, avoid one copy for GL --- .../graphics/objects/LineGroup2dOpenGl.cpp | 21 +++++--------- .../cpp/graphics/objects/LineGroup2dOpenGl.h | 8 +++-- .../graphics/objects/LineGroup2dInterface.kt | 6 ++-- .../objects/NativeLineGroup2dInterface.cpp | 14 ++++----- .../objects/NativeLineGroup2dInterface.h | 4 +-- .../ios/MCLineGroup2dInterface+Private.mm | 16 +++++----- bridging/ios/MCLineGroup2dInterface.h | 6 ++-- .../ts/graphics/objects/graphicsobjects.ts | 4 +-- bridging/wasm/NativeLineGroup2dInterface.cpp | 6 ++-- .../graphics/objects/graphicsobjects.djinni | 3 +- ios/graphics/Helpers/MTLDevice+Helpers.swift | 29 +++++++++++++++++++ ios/graphics/Model/Line/LineGroup2d.swift | 6 ++-- shared/public/LineGeometryBuilder.h | 19 +++++++----- shared/public/LineGroup2dInterface.h | 4 +-- 14 files changed, 88 insertions(+), 58 deletions(-) diff --git a/android/src/main/cpp/graphics/objects/LineGroup2dOpenGl.cpp b/android/src/main/cpp/graphics/objects/LineGroup2dOpenGl.cpp index 0eb79caf3..e97a68a69 100644 --- a/android/src/main/cpp/graphics/objects/LineGroup2dOpenGl.cpp +++ b/android/src/main/cpp/graphics/objects/LineGroup2dOpenGl.cpp @@ -9,6 +9,7 @@ */ #include "LineGroup2dOpenGl.h" +#include "OwnedBytesHelper.h" #include #include #include @@ -21,23 +22,17 @@ std::shared_ptr LineGroup2dOpenGl::asGraphicsObject() { bool LineGroup2dOpenGl::isReady() { return ready; } -void LineGroup2dOpenGl::setLines(const ::SharedBytes & lines, const ::SharedBytes & indices, const Vec3D &origin, bool is3d) { +void LineGroup2dOpenGl::setLines(const ::OwnedBytes & lines, const ::OwnedBytes & indices, const Vec3D &origin, bool is3d) { std::lock_guard lock(dataMutex); ready = false; dataReady = false; - lineIndices.resize(indices.elementCount); - lineAttributes.resize(lines.elementCount); + std::tie(lineAttributes, lineAttributesSize) = OwnedBytesHelper::toUniquePtr(lines); + std::tie(lineIndices, lineIndicesSize) = OwnedBytesHelper::toUniquePtr(indices); + lineOrigin = origin; this->is3d = is3d; - if (indices.elementCount > 0) { - std::memcpy(lineIndices.data(), (void *) indices.address, indices.elementCount * indices.bytesPerElement); - } - if (lines.elementCount > 0) { - std::memcpy(lineAttributes.data(), (void *) lines.address, lines.elementCount * lines.bytesPerElement); - } - dataReady = true; } @@ -73,7 +68,7 @@ void LineGroup2dOpenGl::setup(const std::shared_ptr<::RenderingContextInterface> glGenBuffers(1, &vertexAttribBuffer); } glBindBuffer(GL_ARRAY_BUFFER, vertexAttribBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * lineAttributes.size(), &lineAttributes[0], GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * lineAttributesSize, lineAttributes.get(), GL_STATIC_DRAW); size_t floatSize = sizeof(GLfloat); size_t dimensionality = is3d ? 3 : 2; @@ -110,7 +105,7 @@ void LineGroup2dOpenGl::setup(const std::shared_ptr<::RenderingContextInterface> glGenBuffers(1, &indexBuffer); } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * lineIndices.size(), &lineIndices[0], GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * lineIndicesSize, lineIndices.get(), GL_STATIC_DRAW); glBindVertexArray(0); @@ -188,7 +183,7 @@ void LineGroup2dOpenGl::render(const std::shared_ptr<::RenderingContextInterface shaderProgram->preRender(openGlContext, isScreenSpaceCoords); // Draw the triangle - glDrawElements(GL_TRIANGLES, lineIndices.size(), GL_UNSIGNED_INT, nullptr); + glDrawElements(GL_TRIANGLES, lineIndicesSize, GL_UNSIGNED_INT, nullptr); glBindVertexArray(0); diff --git a/android/src/main/cpp/graphics/objects/LineGroup2dOpenGl.h b/android/src/main/cpp/graphics/objects/LineGroup2dOpenGl.h index 6ae6f1c2f..bcc1f96e3 100644 --- a/android/src/main/cpp/graphics/objects/LineGroup2dOpenGl.h +++ b/android/src/main/cpp/graphics/objects/LineGroup2dOpenGl.h @@ -30,7 +30,7 @@ class LineGroup2dOpenGl : public GraphicsObjectInterface, // LineGroup2dInterface - virtual void setLines(const ::SharedBytes & lines, const ::SharedBytes & indices, const Vec3D &origin, bool is3d) override; + virtual void setLines(const ::OwnedBytes & lines, const ::OwnedBytes & indices, const Vec3D &origin, bool is3d) override; virtual std::shared_ptr asGraphicsObject() override; @@ -72,9 +72,11 @@ class LineGroup2dOpenGl : public GraphicsObjectInterface, GLuint vao; GLuint vertexAttribBuffer = -1; - std::vector lineAttributes; + size_t lineAttributesSize; + std::unique_ptr lineAttributes; GLuint indexBuffer = -1; - std::vector lineIndices; + size_t lineIndicesSize; + std::unique_ptr lineIndices; bool glDataBuffersGenerated = false; Vec3D lineOrigin = Vec3D(0.0, 0.0, 0.0); diff --git a/bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/objects/LineGroup2dInterface.kt b/bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/objects/LineGroup2dInterface.kt index 46d7a0041..ed7079cd0 100644 --- a/bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/objects/LineGroup2dInterface.kt +++ b/bridging/android/java/io/openmobilemaps/mapscore/shared/graphics/objects/LineGroup2dInterface.kt @@ -8,7 +8,7 @@ import java.util.concurrent.atomic.AtomicBoolean abstract class LineGroup2dInterface { - abstract fun setLines(lines: io.openmobilemaps.mapscore.shared.graphics.common.SharedBytes, indices: io.openmobilemaps.mapscore.shared.graphics.common.SharedBytes, origin: io.openmobilemaps.mapscore.shared.graphics.common.Vec3D, is3d: Boolean) + abstract fun setLines(lines: io.openmobilemaps.mapscore.shared.graphics.common.OwnedBytes, indices: io.openmobilemaps.mapscore.shared.graphics.common.OwnedBytes, origin: io.openmobilemaps.mapscore.shared.graphics.common.Vec3D, is3d: Boolean) abstract fun asGraphicsObject(): GraphicsObjectInterface @@ -27,11 +27,11 @@ abstract class LineGroup2dInterface { external fun nativeDestroy(nativeRef: Long) } - override fun setLines(lines: io.openmobilemaps.mapscore.shared.graphics.common.SharedBytes, indices: io.openmobilemaps.mapscore.shared.graphics.common.SharedBytes, origin: io.openmobilemaps.mapscore.shared.graphics.common.Vec3D, is3d: Boolean) { + override fun setLines(lines: io.openmobilemaps.mapscore.shared.graphics.common.OwnedBytes, indices: io.openmobilemaps.mapscore.shared.graphics.common.OwnedBytes, origin: io.openmobilemaps.mapscore.shared.graphics.common.Vec3D, is3d: Boolean) { assert(!this.destroyed.get()) { error("trying to use a destroyed object") } native_setLines(this.nativeRef, lines, indices, origin, is3d) } - private external fun native_setLines(_nativeRef: Long, lines: io.openmobilemaps.mapscore.shared.graphics.common.SharedBytes, indices: io.openmobilemaps.mapscore.shared.graphics.common.SharedBytes, origin: io.openmobilemaps.mapscore.shared.graphics.common.Vec3D, is3d: Boolean) + private external fun native_setLines(_nativeRef: Long, lines: io.openmobilemaps.mapscore.shared.graphics.common.OwnedBytes, indices: io.openmobilemaps.mapscore.shared.graphics.common.OwnedBytes, origin: io.openmobilemaps.mapscore.shared.graphics.common.Vec3D, is3d: Boolean) override fun asGraphicsObject(): GraphicsObjectInterface { assert(!this.destroyed.get()) { error("trying to use a destroyed object") } diff --git a/bridging/android/jni/graphics/objects/NativeLineGroup2dInterface.cpp b/bridging/android/jni/graphics/objects/NativeLineGroup2dInterface.cpp index d8fe672d3..7c42e31dd 100644 --- a/bridging/android/jni/graphics/objects/NativeLineGroup2dInterface.cpp +++ b/bridging/android/jni/graphics/objects/NativeLineGroup2dInterface.cpp @@ -4,7 +4,7 @@ #include "NativeLineGroup2dInterface.h" // my header #include "Marshal.hpp" #include "NativeGraphicsObjectInterface.h" -#include "NativeSharedBytes.h" +#include "NativeOwnedBytes.h" #include "NativeVec3D.h" namespace djinni_generated { @@ -17,13 +17,13 @@ NativeLineGroup2dInterface::JavaProxy::JavaProxy(JniType j) : Handle(::djinni::j NativeLineGroup2dInterface::JavaProxy::~JavaProxy() = default; -void NativeLineGroup2dInterface::JavaProxy::setLines(const ::SharedBytes & c_lines, const ::SharedBytes & c_indices, const ::Vec3D & c_origin, bool c_is3d) { +void NativeLineGroup2dInterface::JavaProxy::setLines(const ::OwnedBytes & c_lines, const ::OwnedBytes & c_indices, const ::Vec3D & c_origin, bool c_is3d) { auto jniEnv = ::djinni::jniGetThreadEnv(); ::djinni::JniLocalScope jscope(jniEnv, 10); const auto& data = ::djinni::JniClass<::djinni_generated::NativeLineGroup2dInterface>::get(); jniEnv->CallVoidMethod(Handle::get().get(), data.method_setLines, - ::djinni::get(::djinni_generated::NativeSharedBytes::fromCpp(jniEnv, c_lines)), - ::djinni::get(::djinni_generated::NativeSharedBytes::fromCpp(jniEnv, c_indices)), + ::djinni::get(::djinni_generated::NativeOwnedBytes::fromCpp(jniEnv, c_lines)), + ::djinni::get(::djinni_generated::NativeOwnedBytes::fromCpp(jniEnv, c_indices)), ::djinni::get(::djinni_generated::NativeVec3D::fromCpp(jniEnv, c_origin)), ::djinni::get(::djinni::Bool::fromCpp(jniEnv, c_is3d))); ::djinni::jniExceptionCheck(jniEnv); @@ -44,12 +44,12 @@ CJNIEXPORT void JNICALL Java_io_openmobilemaps_mapscore_shared_graphics_objects_ } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, ) } -CJNIEXPORT void JNICALL Java_io_openmobilemaps_mapscore_shared_graphics_objects_LineGroup2dInterface_00024CppProxy_native_1setLines(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef, ::djinni_generated::NativeSharedBytes::JniType j_lines, ::djinni_generated::NativeSharedBytes::JniType j_indices, ::djinni_generated::NativeVec3D::JniType j_origin, jboolean j_is3d) +CJNIEXPORT void JNICALL Java_io_openmobilemaps_mapscore_shared_graphics_objects_LineGroup2dInterface_00024CppProxy_native_1setLines(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef, ::djinni_generated::NativeOwnedBytes::JniType j_lines, ::djinni_generated::NativeOwnedBytes::JniType j_indices, ::djinni_generated::NativeVec3D::JniType j_origin, jboolean j_is3d) { try { const auto& ref = ::djinni::objectFromHandleAddress<::LineGroup2dInterface>(nativeRef); - ref->setLines(::djinni_generated::NativeSharedBytes::toCpp(jniEnv, j_lines), - ::djinni_generated::NativeSharedBytes::toCpp(jniEnv, j_indices), + ref->setLines(::djinni_generated::NativeOwnedBytes::toCpp(jniEnv, j_lines), + ::djinni_generated::NativeOwnedBytes::toCpp(jniEnv, j_indices), ::djinni_generated::NativeVec3D::toCpp(jniEnv, j_origin), ::djinni::Bool::toCpp(jniEnv, j_is3d)); } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, ) diff --git a/bridging/android/jni/graphics/objects/NativeLineGroup2dInterface.h b/bridging/android/jni/graphics/objects/NativeLineGroup2dInterface.h index 819a52b2f..8a63b43bb 100644 --- a/bridging/android/jni/graphics/objects/NativeLineGroup2dInterface.h +++ b/bridging/android/jni/graphics/objects/NativeLineGroup2dInterface.h @@ -33,7 +33,7 @@ class NativeLineGroup2dInterface final : ::djinni::JniInterface<::LineGroup2dInt JavaProxy(JniType j); ~JavaProxy(); - void setLines(const ::SharedBytes & lines, const ::SharedBytes & indices, const ::Vec3D & origin, bool is3d) override; + void setLines(const ::OwnedBytes & lines, const ::OwnedBytes & indices, const ::Vec3D & origin, bool is3d) override; /*not-null*/ std::shared_ptr<::GraphicsObjectInterface> asGraphicsObject() override; private: @@ -41,7 +41,7 @@ class NativeLineGroup2dInterface final : ::djinni::JniInterface<::LineGroup2dInt }; const ::djinni::GlobalRef clazz { ::djinni::jniFindClass("io/openmobilemaps/mapscore/shared/graphics/objects/LineGroup2dInterface") }; - const jmethodID method_setLines { ::djinni::jniGetMethodID(clazz.get(), "setLines", "(Lio/openmobilemaps/mapscore/shared/graphics/common/SharedBytes;Lio/openmobilemaps/mapscore/shared/graphics/common/SharedBytes;Lio/openmobilemaps/mapscore/shared/graphics/common/Vec3D;Z)V") }; + const jmethodID method_setLines { ::djinni::jniGetMethodID(clazz.get(), "setLines", "(Lio/openmobilemaps/mapscore/shared/graphics/common/OwnedBytes;Lio/openmobilemaps/mapscore/shared/graphics/common/OwnedBytes;Lio/openmobilemaps/mapscore/shared/graphics/common/Vec3D;Z)V") }; const jmethodID method_asGraphicsObject { ::djinni::jniGetMethodID(clazz.get(), "asGraphicsObject", "()Lio/openmobilemaps/mapscore/shared/graphics/objects/GraphicsObjectInterface;") }; }; diff --git a/bridging/ios/MCLineGroup2dInterface+Private.mm b/bridging/ios/MCLineGroup2dInterface+Private.mm index f273a6ac6..6880c31e8 100644 --- a/bridging/ios/MCLineGroup2dInterface+Private.mm +++ b/bridging/ios/MCLineGroup2dInterface+Private.mm @@ -8,7 +8,7 @@ #import "DJIMarshal+Private.h" #import "DJIObjcWrapperCache+Private.h" #import "MCGraphicsObjectInterface+Private.h" -#import "MCSharedBytes+Private.h" +#import "MCOwnedBytes+Private.h" #import "MCVec3D+Private.h" #include #include @@ -34,13 +34,13 @@ - (id)initWithCpp:(const std::shared_ptr<::LineGroup2dInterface>&)cppRef return self; } -- (void)setLines:(nonnull MCSharedBytes *)lines - indices:(nonnull MCSharedBytes *)indices +- (void)setLines:(nonnull MCOwnedBytes *)lines + indices:(nonnull MCOwnedBytes *)indices origin:(nonnull MCVec3D *)origin is3d:(BOOL)is3d { try { - _cppRefHandle.get()->setLines(::djinni_generated::SharedBytes::toCpp(lines), - ::djinni_generated::SharedBytes::toCpp(indices), + _cppRefHandle.get()->setLines(::djinni_generated::OwnedBytes::toCpp(lines), + ::djinni_generated::OwnedBytes::toCpp(indices), ::djinni_generated::Vec3D::toCpp(origin), ::djinni::Bool::toCpp(is3d)); } DJINNI_TRANSLATE_EXCEPTIONS() @@ -62,11 +62,11 @@ - (void)setLines:(nonnull MCSharedBytes *)lines friend class ::djinni_generated::LineGroup2dInterface; public: using ObjcProxyBase::ObjcProxyBase; - void setLines(const ::SharedBytes & c_lines, const ::SharedBytes & c_indices, const ::Vec3D & c_origin, bool c_is3d) override + void setLines(const ::OwnedBytes & c_lines, const ::OwnedBytes & c_indices, const ::Vec3D & c_origin, bool c_is3d) override { @autoreleasepool { - [djinni_private_get_proxied_objc_object() setLines:(::djinni_generated::SharedBytes::fromCpp(c_lines)) - indices:(::djinni_generated::SharedBytes::fromCpp(c_indices)) + [djinni_private_get_proxied_objc_object() setLines:(::djinni_generated::OwnedBytes::fromCpp(c_lines)) + indices:(::djinni_generated::OwnedBytes::fromCpp(c_indices)) origin:(::djinni_generated::Vec3D::fromCpp(c_origin)) is3d:(::djinni::Bool::fromCpp(c_is3d))]; } diff --git a/bridging/ios/MCLineGroup2dInterface.h b/bridging/ios/MCLineGroup2dInterface.h index 4db34e8f1..4220c6a6f 100644 --- a/bridging/ios/MCLineGroup2dInterface.h +++ b/bridging/ios/MCLineGroup2dInterface.h @@ -1,7 +1,7 @@ // AUTOGENERATED FILE - DO NOT MODIFY! // This file was generated by Djinni from graphicsobjects.djinni -#import "MCSharedBytes.h" +#import "MCOwnedBytes.h" #import "MCVec3D.h" #import @protocol MCGraphicsObjectInterface; @@ -9,8 +9,8 @@ @protocol MCLineGroup2dInterface -- (void)setLines:(nonnull MCSharedBytes *)lines - indices:(nonnull MCSharedBytes *)indices +- (void)setLines:(nonnull MCOwnedBytes *)lines + indices:(nonnull MCOwnedBytes *)indices origin:(nonnull MCVec3D *)origin is3d:(BOOL)is3d; diff --git a/bridging/ts/graphics/objects/graphicsobjects.ts b/bridging/ts/graphics/objects/graphicsobjects.ts index b2876b9c8..02b42ee1a 100644 --- a/bridging/ts/graphics/objects/graphicsobjects.ts +++ b/bridging/ts/graphics/objects/graphicsobjects.ts @@ -1,7 +1,7 @@ // AUTOGENERATED FILE - DO NOT MODIFY! // This file was generated by Djinni from graphicsobjects.djinni -import type { Quad2dD, Quad3dD, RectD, SharedBytes, Vec2D, Vec2F, Vec3D } from "@djinni/maps-core/graphics/common/common" +import type { OwnedBytes, Quad2dD, Quad3dD, RectD, SharedBytes, Vec2D, Vec2F, Vec3D } from "@djinni/maps-core/graphics/common/common" import type { RenderPassConfig, RenderingContextInterface } from "@djinni/maps-core/graphics/core" import type { FontData } from "@djinni/maps-core/map/loader/loader" import type { ShaderProgramInterface } from "@djinni/maps-core/graphics/shader/shader" @@ -115,7 +115,7 @@ export interface /*record*/ RenderLineDescription { } export interface LineGroup2dInterface { - setLines(lines: SharedBytes, indices: SharedBytes, origin: Vec3D, is3d: boolean): void; + setLines(lines: OwnedBytes, indices: OwnedBytes, origin: Vec3D, is3d: boolean): void; asGraphicsObject(): GraphicsObjectInterface; } diff --git a/bridging/wasm/NativeLineGroup2dInterface.cpp b/bridging/wasm/NativeLineGroup2dInterface.cpp index 3d84e371c..934c2c90c 100644 --- a/bridging/wasm/NativeLineGroup2dInterface.cpp +++ b/bridging/wasm/NativeLineGroup2dInterface.cpp @@ -3,7 +3,7 @@ #include "NativeLineGroup2dInterface.h" // my header #include "NativeGraphicsObjectInterface.h" -#include "NativeSharedBytes.h" +#include "NativeOwnedBytes.h" #include "NativeVec3D.h" namespace djinni_generated { @@ -18,8 +18,8 @@ em::val NativeLineGroup2dInterface::cppProxyMethods() { void NativeLineGroup2dInterface::setLines(const CppType& self, const em::val& w_lines,const em::val& w_indices,const em::val& w_origin,bool w_is3d) { try { - self->setLines(::djinni_generated::NativeSharedBytes::toCpp(w_lines), - ::djinni_generated::NativeSharedBytes::toCpp(w_indices), + self->setLines(::djinni_generated::NativeOwnedBytes::toCpp(w_lines), + ::djinni_generated::NativeOwnedBytes::toCpp(w_indices), ::djinni_generated::NativeVec3D::toCpp(w_origin), ::djinni::Bool::toCpp(w_is3d)); } diff --git a/djinni/graphics/objects/graphicsobjects.djinni b/djinni/graphics/objects/graphicsobjects.djinni index 7614da77b..a5823b506 100644 --- a/djinni/graphics/objects/graphicsobjects.djinni +++ b/djinni/graphics/objects/graphicsobjects.djinni @@ -7,6 +7,7 @@ @extern "../../yaml/vec_3_d.yaml" @extern "../../yaml/vec_2_d.yaml" @extern "../../yaml/vec_2_f.yaml" +@extern "../../yaml/owned_bytes.yaml" @extern "../../yaml/shared_bytes.yaml" @extern "../../yaml/font_data.yaml" @@ -128,7 +129,7 @@ render_line_description = record { } line_group_2d_interface = interface +c +j +o { - set_lines(lines: shared_bytes, indices: shared_bytes, origin: vec_3_d, is_3d: bool); + set_lines(lines: owned_bytes, indices: owned_bytes, origin: vec_3_d, is_3d: bool); as_graphics_object(): graphics_object_interface; } diff --git a/ios/graphics/Helpers/MTLDevice+Helpers.swift b/ios/graphics/Helpers/MTLDevice+Helpers.swift index 79972eb51..94f46319d 100644 --- a/ios/graphics/Helpers/MTLDevice+Helpers.swift +++ b/ios/graphics/Helpers/MTLDevice+Helpers.swift @@ -20,6 +20,35 @@ extension MTLDevice { return self.makeBuffer(bytes: pointer, length: Int(sharedBytes.elementCount * sharedBytes.bytesPerElement), options: []) } + + func makeBuffer(from ownedBytes: MCOwnedBytes) -> MTLBuffer? { + defer { + MCOwnedBytesDestructor.free(ownedBytes) + } + + guard let pointer = UnsafeRawPointer(bitPattern: Int(ownedBytes.address)), + ownedBytes.elementCount > 0 + else { return nil } + + return self.makeBuffer(bytes: pointer, length: Int(ownedBytes.elementCount * ownedBytes.bytesPerElement), options: []) + /* + // This copy-free creation of the buffer object might be nice, but + // requires to use mmap/munmap directly instead of malloc/free. + // We would also need to be more careful; overallocations during + // creation of the data (e.g in LineGeometryBuilder) would no longer be + // temporary, but hurt for the entire lifetime of this buffer. + // This is all feasible, but unclear if its worth the additional code. + guard let pointer = UnsafeMutableRawPointer(bitPattern: Int(ownedBytes.address)), + ownedBytes.elementCount > 0 + else { return nil } + return self.makeBuffer( + bytesNoCopy: pointer, + length: Int(ownedBytes.elementCount * ownedBytes.bytesPerElement), options: [], + deallocator: { (UnsafeMutableRawPointer, Int) -> Void in + MCOwnedBytesDestructor.free(ownedBytes); + }) + */ + } } extension MTLBuffer { diff --git a/ios/graphics/Model/Line/LineGroup2d.swift b/ios/graphics/Model/Line/LineGroup2d.swift index 1803de8a4..9a092d5ed 100644 --- a/ios/graphics/Model/Line/LineGroup2d.swift +++ b/ios/graphics/Model/Line/LineGroup2d.swift @@ -127,7 +127,7 @@ final class LineGroup2d: BaseGraphicsObject, @unchecked Sendable { extension LineGroup2d: MCLineGroup2dInterface { - func setLines(_ lines: MCSharedBytes, indices: MCSharedBytes, origin: MCVec3D, is3d: Bool) { + func setLines(_ lines: MCOwnedBytes, indices: MCOwnedBytes, origin: MCVec3D, is3d: Bool) { guard lines.elementCount != 0 else { lock.withCritical { lineVerticesBuffer = nil @@ -146,8 +146,8 @@ extension LineGroup2d: MCLineGroup2dInterface { } else { fatalError() } - self.lineVerticesBuffer.copyOrCreate(from: lines, device: device) - self.lineIndicesBuffer.copyOrCreate(from: indices, device: device) + self.lineVerticesBuffer = device.makeBuffer(from: lines) + self.lineIndicesBuffer = device.makeBuffer(from: indices) if self.lineVerticesBuffer != nil, self.lineIndicesBuffer != nil { self.lineVerticesBuffer?.label = "LineGroup2d.verticesBuffer" self.lineIndicesBuffer?.label = "LineGroup2d.indicesBuffer" diff --git a/shared/public/LineGeometryBuilder.h b/shared/public/LineGeometryBuilder.h index d68da448f..01f2f27d1 100644 --- a/shared/public/LineGeometryBuilder.h +++ b/shared/public/LineGeometryBuilder.h @@ -9,6 +9,8 @@ #include "LineGroup2dInterface.h" #include "LineCapType.h" #include "LineJoinType.h" +#include "ReleasableAllocator.h" +#include "OwnedBytesHelper.h" class LineGeometryBuilder { public: @@ -21,8 +23,8 @@ class LineGeometryBuilder { capType = LineCapType::ROUND; // Force round cap for dot optimization } - std::vector lineAttributes; - std::vector lineIndices; + std::vector> lineAttributes; + std::vector> lineIndices; reserveEstimatedNumVertices(lines, defaultJoinType, capType, is3d, lineAttributes, lineIndices); uint32_t vertexCount = 0; @@ -113,7 +115,7 @@ class LineGeometryBuilder { cosHalfAngle = extrude.x * normal.x + extrude.y * normal.y; turnDirection = (lastNormal.x * normal.y - lastNormal.y * normal.x); // 2D cross product } - + extrudeScale = cosHalfAngle != 0 ? std::abs(1.0 / cosHalfAngle) : 1.0; if (extrudeScale > 2.0) { @@ -295,15 +297,15 @@ class LineGeometryBuilder { } } - auto attributes = SharedBytes((int64_t)lineAttributes.data(), (int32_t)lineAttributes.size(), (int32_t)sizeof(float)); - auto indices = SharedBytes((int64_t)lineIndices.data(), (int32_t)lineIndices.size(), (int32_t)sizeof(uint32_t)); - line->setLines(attributes, indices, origin, is3d); + line->setLines(OwnedBytesHelper::fromVector(std::move(lineAttributes)), OwnedBytesHelper::fromVector(std::move(lineIndices)), origin, is3d); } static void pushLineVertex(const Vec3D &p, const Vec3D &extrude, const float extrudeScale, const float side, const float prefixTotalLineLength, const float prefixCorrection, const int lineStyleIndex, const bool addTriangle, const bool reverse, uint32_t &vertexCount, int32_t &prePreIndex, int32_t &preIndex, - std::vector &lineAttributes, std::vector &lineIndices, bool is3d) { + std::vector> &lineAttributes, + std::vector> &lineIndices, + bool is3d) { lineAttributes.push_back(p.x); lineAttributes.push_back(p.y); @@ -366,7 +368,8 @@ class LineGeometryBuilder { static void reserveEstimatedNumVertices(const std::vector, int>> &lines, LineJoinType joinType, LineCapType capType, bool is3d, - std::vector &lineAttributes, std::vector &lineIndices) + std::vector> &lineAttributes, + std::vector> &lineIndices) { const int64_t capVertices = capType == LineCapType::ROUND ? roundCapVertexCount : 0; const int64_t capTriangles = capType == LineCapType::ROUND ? roundCapVertexCount : 0; diff --git a/shared/public/LineGroup2dInterface.h b/shared/public/LineGroup2dInterface.h index 65bea6707..d37ab90b8 100644 --- a/shared/public/LineGroup2dInterface.h +++ b/shared/public/LineGroup2dInterface.h @@ -3,7 +3,7 @@ #pragma once -#include "SharedBytes.h" +#include "OwnedBytes.h" #include "Vec3D.h" #include @@ -13,7 +13,7 @@ class LineGroup2dInterface { public: virtual ~LineGroup2dInterface() = default; - virtual void setLines(const ::SharedBytes & lines, const ::SharedBytes & indices, const ::Vec3D & origin, bool is3d) = 0; + virtual void setLines(const ::OwnedBytes & lines, const ::OwnedBytes & indices, const ::Vec3D & origin, bool is3d) = 0; virtual /*not-null*/ std::shared_ptr asGraphicsObject() = 0; };