diff options
| author | Egor Yusov <egor.yusov@gmail.com> | 2019-12-05 04:19:44 +0000 |
|---|---|---|
| committer | Egor Yusov <egor.yusov@gmail.com> | 2019-12-05 04:19:44 +0000 |
| commit | 30a1be62b64b07d3e48eb35e95294859c42ae41f (patch) | |
| tree | 8550cd0a698469a388075ffaa814d43ab865b538 /UnitTests | |
| parent | appveyor: fixed run test instruction (diff) | |
| download | DiligentCore-30a1be62b64b07d3e48eb35e95294859c42ae41f.tar.gz DiligentCore-30a1be62b64b07d3e48eb35e95294859c42ae41f.zip | |
Added Allocator test, ring buffer test and RefCntAutoPtr test
Diffstat (limited to 'UnitTests')
| -rw-r--r-- | UnitTests/CMakeLists.txt | 8 | ||||
| -rw-r--r-- | UnitTests/src/Common/AllocatorTest.cpp | 82 | ||||
| -rw-r--r-- | UnitTests/src/Common/MathLibTest.cpp | 81 | ||||
| -rw-r--r-- | UnitTests/src/Common/RefCntAutoPtrTest.cpp | 909 | ||||
| -rw-r--r-- | UnitTests/src/GraphicsAccessories/RingBufferTest.cpp | 291 |
5 files changed, 1328 insertions, 43 deletions
diff --git a/UnitTests/CMakeLists.txt b/UnitTests/CMakeLists.txt index 6793bd63..ed203451 100644 --- a/UnitTests/CMakeLists.txt +++ b/UnitTests/CMakeLists.txt @@ -5,8 +5,11 @@ project(DiligentCoreTest) file(GLOB COMMON_SOURCE src/Common/*) file(GLOB COMMON_INCLUDE include/Common/*) -set(SOURCE ${COMMON_SOURCE}) -set(INCLUDE ${COMMON_INCLUDE}) +file(GLOB GRAPHICS_ACCESSORIES_SOURCE src/GraphicsAccessories/*) +file(GLOB GRAPHICS_ACCESSORIES_INCLUDE include/GraphicsAccessories/*) + +set(SOURCE ${COMMON_SOURCE} ${GRAPHICS_ACCESSORIES_SOURCE}) +set(INCLUDE ${COMMON_INCLUDE} ${GRAPHICS_ACCESSORIES_INCLUDE}) add_executable(DiligentCoreTest ${SOURCE} ${INCLUDE}) set_common_target_properties(DiligentCoreTest) @@ -16,6 +19,7 @@ PRIVATE gtest_main Diligent-BuildSettings Diligent-TargetPlatform + Diligent-GraphicsAccessories Diligent-Common ) diff --git a/UnitTests/src/Common/AllocatorTest.cpp b/UnitTests/src/Common/AllocatorTest.cpp new file mode 100644 index 00000000..8ab37914 --- /dev/null +++ b/UnitTests/src/Common/AllocatorTest.cpp @@ -0,0 +1,82 @@ +/* Copyright 2019 Diligent Graphics LLC + * + * 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 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +// EngineSandbox.cpp : Defines the entry point for the application. +// + +#include "DefaultRawMemoryAllocator.h" +#include "FixedBlockMemoryAllocator.h" + +#include "gtest/gtest.h" + +using namespace Diligent; + +namespace +{ + +TEST(Common_FixedBlockMemoryAllocator, AllocDealloc) +{ + constexpr Uint32 AllocSize = 32; + constexpr Uint32 NumAllocationsPerPage = 16; + + FixedBlockMemoryAllocator TestAllocator(DefaultRawMemoryAllocator::GetAllocator(), AllocSize, NumAllocationsPerPage); + + void* Allocations[NumAllocationsPerPage][2] = {}; + for (int p = 0; p < 2; ++p) + { + for (int a = 1; a < NumAllocationsPerPage; ++a) + { + for (int i = 0; i < a; ++i) + Allocations[i][p] = TestAllocator.Allocate(AllocSize, "Fixed block allocator test", __FILE__, __LINE__); + + for (int i = a - 1; i >= 0; --i) + TestAllocator.Free(Allocations[i][p]); + + for (int i = 0; i < a; ++i) + { + auto* NewAlloc = TestAllocator.Allocate(AllocSize, "Fixed block allocator test", __FILE__, __LINE__); + EXPECT_EQ(Allocations[i][p], NewAlloc); + } + + for (int i = a - 1; i >= 0; --i) + TestAllocator.Free(Allocations[i][p]); + } + for (int i = 0; i < NumAllocationsPerPage; ++i) + Allocations[i][p] = TestAllocator.Allocate(AllocSize, "Fixed block allocator test", __FILE__, __LINE__); + } + + for (int p = 0; p < 2; ++p) + for (int i = 0; i < NumAllocationsPerPage; ++i) + TestAllocator.Free(Allocations[i][p]); + + for (int p = 0; p < 2; ++p) + for (int i = 0; i < NumAllocationsPerPage; ++i) + Allocations[i][p] = TestAllocator.Allocate(AllocSize, "Fixed block allocator test", __FILE__, __LINE__); + + for (int p = 0; p < 2; ++p) + for (int s = 0; s < 5; ++s) + for (int i = s; i < NumAllocationsPerPage; i += 5) + TestAllocator.Free(Allocations[i][p]); +} + +} // namespace diff --git a/UnitTests/src/Common/MathLibTest.cpp b/UnitTests/src/Common/MathLibTest.cpp index 9a80d30a..fcef9345 100644 --- a/UnitTests/src/Common/MathLibTest.cpp +++ b/UnitTests/src/Common/MathLibTest.cpp @@ -35,7 +35,7 @@ namespace { // Constructors -TEST(MathLibTest, VectorConstructors) +TEST(Common_BasicMath, VectorConstructors) { { float2 f2{1, 2}; @@ -75,7 +75,7 @@ TEST(MathLibTest, VectorConstructors) } // a - b -TEST(MathLibTest, OpeartorMinus) +TEST(Common_BasicMath, OpeartorMinus) { { auto v = float2{5, 3} - float2{1, 2}; @@ -100,7 +100,7 @@ TEST(MathLibTest, OpeartorMinus) } // a -= b -TEST(MathLibTest, OpeartorMinusEqual) +TEST(Common_BasicMath, OpeartorMinusEqual) { { auto v = float2{5, 3}; @@ -128,7 +128,7 @@ TEST(MathLibTest, OpeartorMinusEqual) } // -a -TEST(MathLibTest, UnaryMinus) +TEST(Common_BasicMath, UnaryMinus) { { auto v = -float2{1, 2}; @@ -153,7 +153,7 @@ TEST(MathLibTest, UnaryMinus) } // a + b -TEST(MathLibTest, OperatorPlus) +TEST(Common_BasicMath, OperatorPlus) { // a + b { @@ -179,7 +179,7 @@ TEST(MathLibTest, OperatorPlus) } // a += b -TEST(MathLibTest, OpeartorPlusEqual) +TEST(Common_BasicMath, OpeartorPlusEqual) { { auto v = float2{5, 3}; @@ -207,7 +207,7 @@ TEST(MathLibTest, OpeartorPlusEqual) } // a * b -TEST(MathLibTest, VectorVectorMultiply) +TEST(Common_BasicMath, VectorVectorMultiply) { { auto v = float2{5, 3} * float2{1, 2}; @@ -232,7 +232,7 @@ TEST(MathLibTest, VectorVectorMultiply) } // a *= b -TEST(MathLibTest, VectorVectorMultiplyEqual) +TEST(Common_BasicMath, VectorVectorMultiplyEqual) { { auto v = float2{5, 3}; @@ -261,7 +261,7 @@ TEST(MathLibTest, VectorVectorMultiplyEqual) // a * s -TEST(MathLibTest, VectorScalarMultiply) +TEST(Common_BasicMath, VectorScalarMultiply) { { auto v = float2{5, 3} * 2; @@ -286,7 +286,7 @@ TEST(MathLibTest, VectorScalarMultiply) } // a *= s -TEST(MathLibTest, VectorScalarMultiplyEqual) +TEST(Common_BasicMath, VectorScalarMultiplyEqual) { { auto v = float2{5, 3}; @@ -314,7 +314,7 @@ TEST(MathLibTest, VectorScalarMultiplyEqual) } // s * a -TEST(MathLibTest, ScalarVectorMultiply) +TEST(Common_BasicMath, ScalarVectorMultiply) { { auto v = 2.f * float2{5, 3}; @@ -339,7 +339,7 @@ TEST(MathLibTest, ScalarVectorMultiply) } // a / s -TEST(MathLibTest, VectorScalarDivision) +TEST(Common_BasicMath, VectorScalarDivision) { { auto v = float2{10, 6} / 2; @@ -364,7 +364,7 @@ TEST(MathLibTest, VectorScalarDivision) } // a / b -TEST(MathLibTest, VectorVectorDivision) +TEST(Common_BasicMath, VectorVectorDivision) { { auto v = float2{6, 4} / float2{1, 2}; @@ -389,7 +389,7 @@ TEST(MathLibTest, VectorVectorDivision) } // a /= b -TEST(MathLibTest, VectorVectorDivideEqual) +TEST(Common_BasicMath, VectorVectorDivideEqual) { { auto v = float2{6, 4}; @@ -418,7 +418,7 @@ TEST(MathLibTest, VectorVectorDivideEqual) // a /= s -TEST(MathLibTest, VectorScalarDivideEqual) +TEST(Common_BasicMath, VectorScalarDivideEqual) { { auto v = float2{6, 4}; @@ -447,7 +447,7 @@ TEST(MathLibTest, VectorScalarDivideEqual) // max -TEST(MathLibTest, StdMax) +TEST(Common_BasicMath, StdMax) { { auto v = std::max(float2{6, 4}, float2{1, 40}); @@ -472,7 +472,7 @@ TEST(MathLibTest, StdMax) } // min -TEST(MathLibTest, StdMin) +TEST(Common_BasicMath, StdMin) { { auto v = std::min(float2{6, 4}, float2{1, 40}); @@ -497,7 +497,7 @@ TEST(MathLibTest, StdMin) } // a == b -TEST(MathLibTest, ComparisonOperators) +TEST(Common_BasicMath, ComparisonOperators) { { EXPECT_TRUE(float2(1, 2) == float2(1, 2)); @@ -574,7 +574,7 @@ TEST(MathLibTest, ComparisonOperators) } // Functions -TEST(MathLibTest, Abs) +TEST(Common_BasicMath, Abs) { { // clang-format off @@ -625,7 +625,7 @@ TEST(MathLibTest, Abs) } -TEST(MathLibTest, MatrixConstructors) +TEST(Common_BasicMath, MatrixConstructors) { // Matrix 2x2 { @@ -716,7 +716,7 @@ TEST(MathLibTest, MatrixConstructors) } } -TEST(MathLibTest, MatrixInverse) +TEST(Common_BasicMath, MatrixInverse) { { // clang-format off @@ -760,7 +760,7 @@ TEST(MathLibTest, MatrixInverse) } -TEST(MathLibTest, Hash) +TEST(Common_BasicMath, Hash) { { EXPECT_NE(std::hash<float2>{}(float2{1, 2}), 0); @@ -798,7 +798,7 @@ TEST(MathLibTest, Hash) } } -TEST(MathLibTest, OrthoProjection) +TEST(Common_BasicMath, OrthoProjection) { { float4x4 OrthoProj = float4x4::Ortho(2.f, 4.f, -4.f, 12.f, false); @@ -837,20 +837,7 @@ TEST(MathLibTest, OrthoProjection) } } -TEST(MathLibTest, Planes) -{ - Plane3D plane = {}; - EXPECT_NE(std::hash<Plane3D>{}(plane), 0); - - ViewFrustum frustum = {}; - EXPECT_NE(std::hash<ViewFrustum>{}(frustum), 0); - - ViewFrustumExt frustm_ext = {}; - EXPECT_NE(std::hash<ViewFrustumExt>{}(frustm_ext), 0); -} - - -TEST(MathLibTest, MakeObject) +TEST(Common_BasicMath, MakeObject) { double data[] = {1, 2, 3, 4, 5, 6, 7, 8, @@ -865,7 +852,7 @@ TEST(MathLibTest, MakeObject) EXPECT_EQ(float2x2::MakeMatrix(data), float2x2(1, 2, 3, 4)); } -TEST(MathLibTest, MatrixMultiply) +TEST(Common_BasicMath, MatrixMultiply) { { float2x2 m1(1, 2, 3, 4); @@ -903,7 +890,7 @@ TEST(MathLibTest, MatrixMultiply) } } -TEST(MathLibTest, VectorRecast) +TEST(Common_BasicMath, VectorRecast) { { EXPECT_EQ(float2(1, 2).Recast<int>(), Vector2<int>(1, 2)); @@ -912,7 +899,7 @@ TEST(MathLibTest, VectorRecast) } } -TEST(MathLibTest, StdFloorCeil) +TEST(Common_BasicMath, StdFloorCeil) { { EXPECT_EQ(std::floor(float2(0.1f, 1.2f)), float2(0, 1)); @@ -924,7 +911,19 @@ TEST(MathLibTest, StdFloorCeil) } } -TEST(MathLibTest, HermiteSpline) +TEST(Common_AdvancedMath, Planes) +{ + Plane3D plane = {}; + EXPECT_NE(std::hash<Plane3D>{}(plane), 0); + + ViewFrustum frustum = {}; + EXPECT_NE(std::hash<ViewFrustum>{}(frustum), 0); + + ViewFrustumExt frustm_ext = {}; + EXPECT_NE(std::hash<ViewFrustumExt>{}(frustm_ext), 0); +} + +TEST(Common_AdvancedMath, HermiteSpline) { EXPECT_NE(HermiteSpline(float3(1, 2, 3), float3(4, 5, 6), float3(7, 8, 9), float3(10, 11, 12), 0.1f), float3(0, 0, 0)); EXPECT_NE(HermiteSpline(double3(1, 2, 3), double3(4, 5, 6), double3(7, 8, 9), double3(10, 11, 12), 0.1), double3(0, 0, 0)); diff --git a/UnitTests/src/Common/RefCntAutoPtrTest.cpp b/UnitTests/src/Common/RefCntAutoPtrTest.cpp new file mode 100644 index 00000000..46800e69 --- /dev/null +++ b/UnitTests/src/Common/RefCntAutoPtrTest.cpp @@ -0,0 +1,909 @@ +/* Copyright 2019 Diligent Graphics LLC + * + * 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 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +// EngineSandbox.cpp : Defines the entry point for the application. +// + +#include <thread> +#include <atomic> +#include <mutex> +#include <condition_variable> +#include <algorithm> + +#include "DefaultRawMemoryAllocator.h" +#include "RefCntAutoPtr.h" +#include "RefCountedObjectImpl.h" +#include "ThreadSignal.h" + +#include "gtest/gtest.h" + +using namespace Diligent; + +template <typename Type> +Type* MakeNewObj() +{ + return MakeNewRCObj<Type>{}(); +} + +namespace +{ + +class Object : public Diligent::RefCountedObject<Diligent::IObject> +{ +public: + static void Create(Object** ppObj) + { + *ppObj = MakeNewObj<Object>(); + (*ppObj)->AddRef(); + } + + virtual void QueryInterface(const Diligent::INTERFACE_ID& IID, Diligent::IObject** ppInterface) + { + *ppInterface = nullptr; + if (IID == Diligent::IID_Unknown) + { + *ppInterface = this; + (*ppInterface)->AddRef(); + } + } + + Object(Diligent::IReferenceCounters* pRefCounters) : + RefCountedObject<Diligent::IObject>{pRefCounters}, + m_Value(0) + { + } + + ~Object() {} + std::atomic_int m_Value; +}; + + +class DerivedObject : public Object +{ +public: + DerivedObject(Diligent::IReferenceCounters* pRefCounters) : + Object{pRefCounters}, + m_Value2{1} + {} + int m_Value2; +}; + +using SmartPtr = Diligent::RefCntAutoPtr<Object>; +using WeakPtr = Diligent::RefCntWeakPtr<Object>; + +TEST(Common_RefCntAutoPtr, Constructors) +{ + { + SmartPtr SP0; + SmartPtr SP1(nullptr); + auto* pRawPtr = MakeNewObj<Object>(); + SmartPtr SP2(pRawPtr); + SmartPtr SP2_1(pRawPtr); + EXPECT_EQ(SP2, SP2_1); + + SmartPtr SP3(SP0); + SmartPtr SP4(SP2); + SmartPtr SP5(std::move(SP3)); + EXPECT_TRUE(!SP3); + SmartPtr SP6(std::move(SP4)); + EXPECT_TRUE(!SP4); + + RefCntAutoPtr<DerivedObject> DerivedSP(MakeNewObj<DerivedObject>()); + + SmartPtr SP7(DerivedSP); + SmartPtr SP8(std::move(DerivedSP)); + EXPECT_EQ(SP7, SP8); + EXPECT_TRUE(!DerivedSP); + } +} + +TEST(Common_RefCntAutoPtr, AttachDetach) +{ + { + auto* pRawPtr = MakeNewObj<Object>(); + + SmartPtr SP0; + SP0.Attach(nullptr); + EXPECT_TRUE(!SP0); + SP0.Attach(pRawPtr); + EXPECT_TRUE(SP0); + pRawPtr->AddRef(); + } + + { + auto* pRawPtr = MakeNewObj<Object>(); + + SmartPtr SP0; + pRawPtr->AddRef(); + SP0.Attach(pRawPtr); + EXPECT_TRUE(SP0); + SP0.Attach(nullptr); + EXPECT_TRUE(!SP0); + } + + { + auto* pRawPtr = MakeNewObj<Object>(); + + SmartPtr SP0(MakeNewObj<Object>()); + SP0.Attach(pRawPtr); + EXPECT_TRUE(SP0); + pRawPtr->AddRef(); + } + + { + SmartPtr SP0(MakeNewObj<Object>()); + EXPECT_TRUE(SP0); + + auto* pRawPtr = MakeNewObj<Object>(); + pRawPtr->AddRef(); + SP0.Attach(pRawPtr); + auto* pRawPtr2 = SP0.Detach(); + pRawPtr2->Release(); + + auto* pRawPtr3 = SmartPtr().Detach(); + EXPECT_TRUE(pRawPtr3 == nullptr); + auto* pRawPtr4 = SmartPtr(MakeNewObj<Object>()).Detach(); + EXPECT_TRUE(pRawPtr4 != nullptr); + pRawPtr4->Release(); + } +} + +TEST(Common_RefCntAutoPtr, OperatorEqual) +{ + { + SmartPtr SP0; + auto pRawPtr1 = MakeNewObj<Object>(); + SmartPtr SP1(pRawPtr1); + SmartPtr SP2(pRawPtr1); + SP0 = SP0; + SP0 = std::move(SP0); + SP0 = nullptr; + EXPECT_EQ(SP0, nullptr); + + SP1 = pRawPtr1; + SP1 = SP1; + SP1 = std::move(SP1); + EXPECT_EQ(SP1, pRawPtr1); + + SP1 = SP2; + SP1 = std::move(SP2); + EXPECT_EQ(SP1, pRawPtr1); + + auto pRawPtr2 = MakeNewObj<Object>(); + SmartPtr SP3(pRawPtr2); + + SP0 = pRawPtr2; + SmartPtr SP4; + SP4 = SP3; + SmartPtr SP5; + SP5 = std::move(SP4); + EXPECT_TRUE(!SP4); + + SP1 = pRawPtr2; + SP1 = nullptr; + SP1 = std::move(SP5); + EXPECT_TRUE(!SP5); + + RefCntAutoPtr<DerivedObject> DerivedSP(MakeNewObj<DerivedObject>()); + SP1 = DerivedSP; + SP2 = std::move(DerivedSP); + EXPECT_TRUE(!DerivedSP); + } +} + +TEST(Common_RefCntAutoPtr, LogicalOperators) +{ + { + auto pRawPtr1 = MakeNewObj<Object>(); + auto pRawPtr2 = MakeNewObj<Object>(); + SmartPtr SP0, SP1(pRawPtr1), SP2(pRawPtr1), SP3(pRawPtr2); + EXPECT_TRUE(!SP0); + bool b1 = SP0.operator bool(); + EXPECT_TRUE(!b1); + + + EXPECT_TRUE(!(!SP1)); + EXPECT_TRUE(SP1); + EXPECT_TRUE(SP0 != SP1); + EXPECT_TRUE(SP0 == SP0); + EXPECT_TRUE(SP1 == SP1); + EXPECT_TRUE(SP1 == SP2); + EXPECT_TRUE(SP1 != SP3); + EXPECT_TRUE(SP0 < SP3); + EXPECT_TRUE((SP1 < SP3) == (pRawPtr1 < pRawPtr2)); + } +} + +TEST(Common_RefCntAutoPtr, OperatorAmpersand) +{ + { + SmartPtr SP0, SP1(MakeNewObj<Object>()), SP2, SP3, SP4(MakeNewObj<Object>()); + auto* pRawPtr = MakeNewObj<Object>(); + pRawPtr->AddRef(); + + *static_cast<Object**>(&SP0) = pRawPtr; + SP0.Detach(); + *&SP2 = pRawPtr; + SP2.Detach(); + + Object::Create(&SP3); + + Object::Create(&SP1); + *static_cast<Object**>(&SP4) = pRawPtr; + + { + SmartPtr SP5(MakeNewObj<Object>()); + auto pDblPtr = &SP5; + *pDblPtr = MakeNewObj<Object>(); + (*pDblPtr)->AddRef(); + auto pDblPtr2 = &SP5; + Object::Create(pDblPtr2); + } + + SmartPtr SP6(MakeNewObj<Object>()); + // This will not work: + // Object **pDblPtr3 = &SP6; + // *pDblPtr3 = new Object; + } +} + +TEST(Common_RefCntWeakPtr, Constructors) +{ + { + SmartPtr SP0, SP1(MakeNewObj<Object>()); + WeakPtr WP0; + WeakPtr WP1(WP0); + WeakPtr WP2(SP0); + WeakPtr WP3(SP1); + WeakPtr WP4(WP3); + WeakPtr WP5(std::move(WP0)); + WeakPtr WP6(std::move(WP4)); + + auto* pRawPtr = MakeNewObj<Object>(); + pRawPtr->AddRef(); + WeakPtr WP7(pRawPtr); + pRawPtr->Release(); + } +} + + +TEST(Common_RefCntWeakPtr, OperatorEqual) +{ + { + auto* pRawPtr = MakeNewObj<Object>(); + SmartPtr SP0, SP1(pRawPtr); + WeakPtr WP0, WP1(SP1), WP2(SP1); + WP0 = WP0; + WP0 = std::move(WP0); + WP1 = WP1; + WP1 = std::move(WP1); + WP1 = WP2; + WP1 = std::move(WP2); + WP1 = pRawPtr; + WP0 = pRawPtr; + WP0.Release(); + WP0 = WP2; + + WP1 = WP0; + WP0 = SP1; + WP2 = std::move(WP1); + } +} + +TEST(Common_RefCntWeakPtr, Lock) +{ + { + SmartPtr SP0, SP1(MakeNewObj<Object>()); + WeakPtr WP0, WP1(SP0), WP2(SP1), WP3(SP1); + assert(WP0 == WP1); + assert(WP0 != WP2); + assert(WP2 == WP3); + SP1.Release(); + assert(WP2 == WP3); + } + + // Test Lock() + { + SmartPtr SP0, SP1(MakeNewObj<Object>()); + WeakPtr WP0, WP1(SP0), WP2(SP1); + WeakPtr WP3(WP2); + auto L1 = WP0.Lock(); + assert(!L1); + L1 = WP1.Lock(); + assert(!L1); + L1 = WP2.Lock(); + assert(L1); + L1 = WP3.Lock(); + assert(L1); + auto pRawPtr = SP1.Detach(); + L1.Release(); + + L1 = WP3.Lock(); + assert(L1); + L1.Release(); + + pRawPtr->Release(); + + L1 = WP3.Lock(); + assert(!L1); + } +} + +TEST(Common_RefCntAutoPtr, Misc) +{ + { + class OwnerTest : public RefCountedObject<IObject> + { + public: + OwnerTest(IReferenceCounters* pRefCounters) : + RefCountedObject<IObject>(pRefCounters), + Obj(NEW_RC_OBJ(DefaultRawMemoryAllocator::GetAllocator(), "Test object", Object, this)()) + { + } + + virtual void QueryInterface(const INTERFACE_ID& IID, IObject** ppInterface) {} + ~OwnerTest() + { + Obj->~Object(); + DefaultRawMemoryAllocator::GetAllocator().Free(Obj); + } + + private: + Object* Obj; + }; + + OwnerTest* pOwnerObject = MakeNewObj<OwnerTest>(); + pOwnerObject->AddRef(); + pOwnerObject->Release(); + } + + { + class SelfRefTest : public RefCountedObject<IObject> + { + public: + SelfRefTest(IReferenceCounters* pRefCounters) : + RefCountedObject<IObject>(pRefCounters), + wpSelf(this) + {} + + virtual void QueryInterface(const INTERFACE_ID& IID, IObject** ppInterface) {} + + private: + RefCntWeakPtr<SelfRefTest> wpSelf; + }; + + SelfRefTest* pSelfRefTest = MakeNewObj<SelfRefTest>(); + pSelfRefTest->AddRef(); + pSelfRefTest->Release(); + } + + { + class ExceptionTest1 : public RefCountedObject<IObject> + { + public: + ExceptionTest1(IReferenceCounters* pRefCounters) : + RefCountedObject<IObject>(pRefCounters), + wpSelf(this) + { + throw std::runtime_error("test exception"); + } + + virtual void QueryInterface(const INTERFACE_ID& IID, IObject** ppInterface) {} + + private: + RefCntWeakPtr<ExceptionTest1> wpSelf; + }; + + try + { + auto* pExceptionTest = MakeNewObj<ExceptionTest1>(); + (void)pExceptionTest; + } + catch (std::runtime_error&) + { + } + } + + { + class ExceptionTest2 : public RefCountedObject<IObject> + { + public: + ExceptionTest2(IReferenceCounters* pRefCounters) : + RefCountedObject<IObject>(pRefCounters), + wpSelf(this) + { + throw std::runtime_error("test exception"); + } + + virtual void QueryInterface(const INTERFACE_ID& IID, IObject** ppInterface) {} + + private: + RefCntWeakPtr<ExceptionTest2> wpSelf; + }; + + try + { + auto* pExceptionTest = NEW_RC_OBJ(DefaultRawMemoryAllocator::GetAllocator(), "Test object", ExceptionTest2)(); + (void)pExceptionTest; + } + catch (std::runtime_error&) + { + } + } + + { + class ExceptionTest3 : public RefCountedObject<IObject> + { + public: + ExceptionTest3(IReferenceCounters* pRefCounters) : + RefCountedObject<IObject>(pRefCounters), + m_Member(*this) + { + } + + class Subclass + { + public: + Subclass(ExceptionTest3& parent) : + wpSelf(&parent) + { + throw std::runtime_error("test exception"); + } + + private: + RefCntWeakPtr<ExceptionTest3> wpSelf; + }; + virtual void QueryInterface(const INTERFACE_ID& IID, IObject** ppInterface) {} + + private: + Subclass m_Member; + }; + + try + { + auto* pExceptionTest = NEW_RC_OBJ(DefaultRawMemoryAllocator::GetAllocator(), "Test object", ExceptionTest3)(); + (void)pExceptionTest; + } + catch (std::runtime_error&) + { + } + } + + { + class OwnerObject : public RefCountedObject<IObject> + { + public: + OwnerObject(IReferenceCounters* pRefCounters) : + RefCountedObject<IObject>(pRefCounters) + {} + + void CreateMember() + { + try + { + m_pMember = NEW_RC_OBJ(DefaultRawMemoryAllocator::GetAllocator(), "Test object", ExceptionTest4, this)(*this); + } + catch (...) + { + } + } + virtual void QueryInterface(const INTERFACE_ID& IID, IObject** ppInterface) {} + + class ExceptionTest4 : public RefCountedObject<IObject> + { + public: + ExceptionTest4(IReferenceCounters* pRefCounters, OwnerObject& owner) : + RefCountedObject<IObject>(pRefCounters), + m_Member(owner, *this) + { + } + + class Subclass + { + public: + Subclass(OwnerObject& owner, ExceptionTest4& parent) : + wpParent(&parent), + wpOwner(&owner) + { + throw std::runtime_error("test exception"); + } + + private: + RefCntWeakPtr<ExceptionTest4> wpParent; + RefCntWeakPtr<OwnerObject> wpOwner; + }; + virtual void QueryInterface(const INTERFACE_ID& IID, IObject** ppInterface) {} + + private: + Subclass m_Member; + }; + + RefCntAutoPtr<ExceptionTest4> m_pMember; + }; + + RefCntAutoPtr<OwnerObject> pOwner(NEW_RC_OBJ(DefaultRawMemoryAllocator::GetAllocator(), "Test object", OwnerObject)()); + pOwner->CreateMember(); + } + + + { + class OwnerObject : public RefCountedObject<IObject> + { + public: + OwnerObject(IReferenceCounters* pRefCounters) : + RefCountedObject<IObject>(pRefCounters) + { + m_pMember = NEW_RC_OBJ(DefaultRawMemoryAllocator::GetAllocator(), "Test object", ExceptionTest4, this)(*this); + } + + virtual void QueryInterface(const INTERFACE_ID& IID, IObject** ppInterface) {} + + class ExceptionTest4 : public RefCountedObject<IObject> + { + public: + ExceptionTest4(IReferenceCounters* pRefCounters, OwnerObject& owner) : + RefCountedObject<IObject>(pRefCounters), + m_Member(owner, *this) + { + } + + class Subclass + { + public: + Subclass(OwnerObject& owner, ExceptionTest4& parent) : + wpParent(&parent), + wpOwner(&owner) + { + throw std::runtime_error("test exception"); + } + + private: + RefCntWeakPtr<ExceptionTest4> wpParent; + RefCntWeakPtr<OwnerObject> wpOwner; + }; + virtual void QueryInterface(const INTERFACE_ID& IID, IObject** ppInterface) {} + + private: + Subclass m_Member; + }; + + RefCntAutoPtr<ExceptionTest4> m_pMember; + }; + + try + { + RefCntAutoPtr<OwnerObject> pOwner(NEW_RC_OBJ(DefaultRawMemoryAllocator::GetAllocator(), "Test object", OwnerObject)()); + } + catch (...) + { + } + } + + { + class TestObject : public RefCountedObject<IObject> + { + public: + TestObject(IReferenceCounters* pRefCounters) : + RefCountedObject<IObject>(pRefCounters) + { + } + + virtual void QueryInterface(const INTERFACE_ID& IID, IObject** ppInterface) override final {} + + inline virtual Atomics::Long Release() override final + { + return RefCountedObject<IObject>::Release( + [&]() // + { + ppWeakPtr->Release(); + } // + ); + } + RefCntWeakPtr<TestObject>* ppWeakPtr = nullptr; + }; + + RefCntAutoPtr<TestObject> pObj(NEW_RC_OBJ(DefaultRawMemoryAllocator::GetAllocator(), "Test object", TestObject)()); + RefCntWeakPtr<TestObject> pWeakPtr(pObj); + + pObj->ppWeakPtr = &pWeakPtr; + pObj.Release(); + } +} + +class RefCntAutoPtrThreadingTest +{ +public: + ~RefCntAutoPtrThreadingTest(); + + void StartConcurrencyTest(); + void RunConcurrencyTest(); + + static void WorkerThreadFunc(RefCntAutoPtrThreadingTest* This, size_t ThreadNum); + + void StartWorkerThreadsAndWait(int SignalIdx); + void WaitSiblingWorkerThreads(int SignalIdx); + + std::vector<std::thread> m_Threads; + + Object* m_pSharedObject = nullptr; + int m_NumTestsPerformed = 0; +#ifdef _DEBUG + static const int NumThreadInterations = 10000; +#else + static const int NumThreadInterations = 50000; +#endif + ThreadingTools::Signal m_WorkerThreadSignal[2]; + ThreadingTools::Signal m_MainThreadSignal; + + std::mutex m_NumThreadsCompletedMtx; + std::atomic_int m_NumThreadsCompleted[2]; + std::atomic_int m_NumThreadsReady; +}; + + +RefCntAutoPtrThreadingTest::~RefCntAutoPtrThreadingTest() +{ + m_WorkerThreadSignal[0].Trigger(true, -1); + + for (auto& t : m_Threads) + t.join(); + + LOG_INFO_MESSAGE("Performed ", m_NumTestsPerformed, " concurrency ", (m_NumTestsPerformed > 1 ? "tests" : "test"), + " with ", NumThreadInterations, " iterations on ", m_Threads.size(), " threads"); +} + +void RefCntAutoPtrThreadingTest::WaitSiblingWorkerThreads(int SignalIdx) +{ + auto NumThreads = static_cast<int>(m_Threads.size()); + if (++m_NumThreadsCompleted[SignalIdx] == NumThreads) + { + ASSERT_FALSE(m_WorkerThreadSignal[1 - SignalIdx].IsTriggered()); + m_MainThreadSignal.Trigger(); + } + else + { + while (m_NumThreadsCompleted[SignalIdx] < NumThreads) + std::this_thread::yield(); + } +} + +void RefCntAutoPtrThreadingTest::StartWorkerThreadsAndWait(int SignalIdx) +{ + m_NumThreadsCompleted[SignalIdx] = 0; + m_WorkerThreadSignal[SignalIdx].Trigger(true); + + m_MainThreadSignal.Wait(true, 1); +} + +void RefCntAutoPtrThreadingTest::WorkerThreadFunc(RefCntAutoPtrThreadingTest* This, size_t ThreadNum) +{ + const int NumThreads = static_cast<int>(This->m_Threads.size()); + while (true) + { + for (int i = 0; i < NumThreadInterations; ++i) + { + // Wait until main() sends data + auto SignaledValue = This->m_WorkerThreadSignal[0].Wait(true, NumThreads); + if (SignaledValue < 0) + { + return; + } + + { + auto* pObject = This->m_pSharedObject; + for (int j = 0; j < 100; ++j) + { + //LOG_INFO_MESSAGE("t",std::this_thread::get_id(), ": AddRef" ); + pObject->m_Value++; + pObject->AddRef(); + } + This->WaitSiblingWorkerThreads(0); + + This->m_WorkerThreadSignal[1].Wait(true, NumThreads); + for (int j = 0; j < 100; ++j) + { + //LOG_INFO_MESSAGE("t",std::this_thread::get_id(), ": Release" ); + pObject->m_Value--; + pObject->Release(); + } + This->WaitSiblingWorkerThreads(1); + } + + { + This->m_WorkerThreadSignal[0].Wait(true, NumThreads); + auto* pObject = This->m_pSharedObject; + auto* pRefCounters = pObject->GetReferenceCounters(); + if (ThreadNum % 3 == 0) + { + pObject->m_Value++; + pObject->AddRef(); + } + else + pRefCounters->AddWeakRef(); + This->WaitSiblingWorkerThreads(0); + + This->m_WorkerThreadSignal[1].Wait(true, NumThreads); + if (ThreadNum % 3 == 0) + { + pObject->m_Value--; + pObject->Release(); + } + else + pRefCounters->ReleaseWeakRef(); + This->WaitSiblingWorkerThreads(1); + } + + { + // Test interferences of ReleaseStrongRef() and GetObject() + + // Goal: catch scenario when GetObject() runs between + // AtomicDecrement() and acquiring the lock in ReleaseStrongRef(): + + // m_lNumStrongReferences == 1 + // + + // Scenario I + // + // Thread 1 | Thread 2 | Thread 3 + // | | + // | | + // | | + // | 1. Acquire the lock | + // | 2. Increment m_lNumStrongReferences | + // 1. Decrement m_lNumStrongReferences | 3. Read StrongRefCnt > 1 | + // 2. Test RefCount!=0 | 4. Return the reference to object | + // 3. DO NOT destroy the object | | + // 4. Wait for the lock | | + + + // Scenario I + // + // Thread 1 | Thread 2 | Thread 3 + // | | + // | | + // 1. Decrement m_lNumStrongReferences | | + // | 1. Acquire the lock | + // 2. Test RefCount==0 | 2. Increment m_lNumStrongReferences | + // 3. Start destroying the object | 3. Read StrongRefCnt == 1 | + // 4. Wait for the lock | 4. DO NOT create the object | + // | 5. Decrement m_lNumStrongReferences | + // | | 1. Acquire the lock + // | | 2. Increment m_lNumStrongReferences + // | | 3. Read StrongRefCnt == 1 + // | | 4. DO NOT create the object + // | | 5. Decrement m_lNumStrongReferences + // 5. Acquire the lock | + // 6. DESTROY the object | + + This->m_WorkerThreadSignal[0].Wait(true, NumThreads); + auto* pObject = This->m_pSharedObject; + + RefCntWeakPtr<Object> weakPtr(pObject); + RefCntAutoPtr<Object> strongPtr, strongPtr2; + if (ThreadNum < 2) + { + strongPtr = pObject; + strongPtr->m_Value++; + } + else + weakPtr = WeakPtr(pObject); + This->WaitSiblingWorkerThreads(0); + + This->m_WorkerThreadSignal[1].Wait(true, NumThreads); + if (ThreadNum == 0) + { + strongPtr->m_Value--; + strongPtr.Release(); + } + else + { + strongPtr2 = weakPtr.Lock(); + if (strongPtr2) + strongPtr2->m_Value++; + weakPtr.Release(); + } + This->WaitSiblingWorkerThreads(1); + } + + + { + This->m_WorkerThreadSignal[0].Wait(true, NumThreads); + auto* pObject = This->m_pSharedObject; + + RefCntWeakPtr<Object> weakPtr; + RefCntAutoPtr<Object> strongPtr; + if (ThreadNum % 4 == 0) + { + strongPtr = pObject; + strongPtr->m_Value++; + } + else + weakPtr = WeakPtr(pObject); + This->WaitSiblingWorkerThreads(0); + + This->m_WorkerThreadSignal[1].Wait(true, NumThreads); + if (ThreadNum % 4 == 0) + { + strongPtr->m_Value--; + strongPtr.Release(); + } + else + { + auto Ptr = weakPtr.Lock(); + if (Ptr) + Ptr->m_Value++; + Ptr.Release(); + } + This->WaitSiblingWorkerThreads(1); + } + } + } +} + +void RefCntAutoPtrThreadingTest::StartConcurrencyTest() +{ + auto numCores = std::thread::hardware_concurrency(); + m_Threads.resize(std::max(numCores, 2u)); + for (auto& t : m_Threads) + t = std::thread(WorkerThreadFunc, this, &t - m_Threads.data()); +} + +void RefCntAutoPtrThreadingTest::RunConcurrencyTest() +{ + for (int i = 0; i < NumThreadInterations; ++i) + { + m_pSharedObject = MakeNewObj<Object>(); + + StartWorkerThreadsAndWait(0); + + StartWorkerThreadsAndWait(1); + + m_pSharedObject = MakeNewObj<Object>(); + + StartWorkerThreadsAndWait(0); + + StartWorkerThreadsAndWait(1); + + m_pSharedObject = MakeNewObj<Object>(); + + StartWorkerThreadsAndWait(0); + + StartWorkerThreadsAndWait(1); + + m_pSharedObject = MakeNewObj<Object>(); + + StartWorkerThreadsAndWait(0); + + StartWorkerThreadsAndWait(1); + } + ++m_NumTestsPerformed; +} + +TEST(Common_RefCntAutoPtr, Threading) +{ + RefCntAutoPtrThreadingTest ThreadingTest; + ThreadingTest.StartConcurrencyTest(); + ThreadingTest.RunConcurrencyTest(); +} + +} // namespace diff --git a/UnitTests/src/GraphicsAccessories/RingBufferTest.cpp b/UnitTests/src/GraphicsAccessories/RingBufferTest.cpp new file mode 100644 index 00000000..f89a0441 --- /dev/null +++ b/UnitTests/src/GraphicsAccessories/RingBufferTest.cpp @@ -0,0 +1,291 @@ +/* Copyright 2019 Diligent Graphics LLC + * + * 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 + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +// EngineSandbox.cpp : Defines the entry point for the application. +// + +#include "RingBuffer.h" +#include "DefaultRawMemoryAllocator.h" + +#include "gtest/gtest.h" + +using namespace Diligent; + +namespace +{ + +TEST(GraphicsAccessories_RingBuffer, AllocDealloc) +{ + auto& Allocator = DefaultRawMemoryAllocator::GetAllocator(); + { + RingBuffer RB(1023, Allocator); + + auto Offset = RB.Allocate(120, 16); + // + // O h + // | | | + // 0 128 + EXPECT_EQ(Offset, 0); + + + Offset = RB.Allocate(10, 1); + // + // t O h + // | | | | + // 0 128 138 + EXPECT_EQ(Offset, 128); + + Offset = RB.Allocate(10, 32); + // + // t O h + // | | | | + // 0 128 138 160 192 + EXPECT_EQ(Offset, 160); + + Offset = RB.Allocate(17, 1); + // + // t O h + // | | | | + // 0 128 138 160 192 209 + EXPECT_EQ(Offset, 192); + + Offset = RB.Allocate(65, 64); + // + // t O h + // | | | | + // 0 128 138 160 192 209 256 384 + EXPECT_EQ(Offset, 256); + + RB.FinishCurrentFrame(1); + // + // t h1 + // | | | + // 0 384 + + + Offset = RB.Allocate(100, 256); + // + // t h1 O h + // | | | | | + // 0 384 512 768 + EXPECT_EQ(Offset, 512); + + + Offset = RB.Allocate(127, 1); + // + // t h1 O h + // | | | | | + // 0 384 512 768 895 = 1023-128 + EXPECT_EQ(Offset, 768); + + Offset = RB.Allocate(128, 2); + EXPECT_EQ(Offset, RingBuffer::InvalidOffset); + + Offset = RB.Allocate(128, 1); + // + // t h1 O h + // | | | | + // 0 384 512 768 895 1023 + EXPECT_EQ(Offset, 895); + + RB.FinishCurrentFrame(2); + // + // t h1 h2,h + // | | | + // 0 384 1023 + EXPECT_TRUE(RB.IsFull()); + + Offset = RB.Allocate(1, 1); + EXPECT_EQ(Offset, RingBuffer::InvalidOffset); + + RingBuffer RB1(std::move(RB)); + EXPECT_TRUE(RB.IsEmpty()); + RB1.ReleaseCompletedFrames(1); + // + // t h2 + // | | | + // 0 384 1023 + + RB1.ReleaseCompletedFrames(2); + // + // h,t + // | | + // 0 1023 + EXPECT_TRUE(RB1.IsEmpty()); + EXPECT_EQ(RB1.GetUsedSize(), 0); + + Offset = RB1.Allocate(256, 1); + // + // O h t + // | | | + // 0 256 1023 + EXPECT_EQ(Offset, 0); + + + Offset = RB1.Allocate(256, 16); + RB1.ReleaseCompletedFrames(0); + // + // O h t + // | | | | + // 0 256 512 1023 + EXPECT_EQ(Offset, 256); + RB1.FinishCurrentFrame(2); + RB1.FinishCurrentFrame(3); // ignored + // + // h2 t + // | | | + // 0 512 1023 + RB1.ReleaseCompletedFrames(2); + // + // h,t + // | | + // 0 1023 + + RB1.ReleaseCompletedFrames(3); + + EXPECT_EQ(RB1.GetUsedSize(), 0); + EXPECT_TRUE(RB1.IsEmpty()); + + Offset = RB1.Allocate(512, 1); + // + // O h + // | | | + // 0 512 1023 + EXPECT_EQ(Offset, 0); + + RB1.FinishCurrentFrame(4); + RB1.FinishCurrentFrame(5); + // + // t h4 + // | | | + // 0 512 1023 + + Offset = RB1.Allocate(129, 1); + // + // t h4,O h + // | | | | + // 0 512 641 1023 + EXPECT_EQ(Offset, 512); + + RB1.ReleaseCompletedFrames(4); + // + // t h + // | | | | + // 0 512 641 1023 + + + Offset = RB1.Allocate(128, 128); + // + // t O h + // | | | | | + // 0 512 641 768 896 1023 + EXPECT_EQ(Offset, 768); + + Offset = RB1.Allocate(513, 64); + EXPECT_EQ(Offset, RingBuffer::InvalidOffset); + + Offset = RB1.Allocate(513, 1); + EXPECT_EQ(Offset, RingBuffer::InvalidOffset); + + Offset = RB1.Allocate(255, 1); + // + // O h t + // | | | | + // 0 255 512 1023 + EXPECT_EQ(Offset, 0); + + RB1.FinishCurrentFrame(6); + // + // O h,h6 t + // | | | | + // 0 255 512 1023 + + + Offset = RB1.Allocate(256, 2); + // + // h6 O t,h + // | | | | | + // 0 255 256 512 1023 + EXPECT_EQ(Offset, 256); + + EXPECT_TRUE(RB1.IsFull()); + Offset = RB1.Allocate(1, 1); + EXPECT_EQ(Offset, RingBuffer::InvalidOffset); + RB1.ReleaseCompletedFrames(6); + // + // t h + // | | | | + // 0 255 512 1023 + + Offset = RB1.Allocate(511, 1); + // + // t O h + // | | | | + // 0 255 512 1023 + EXPECT_EQ(Offset, 512); + + Offset = RB1.Allocate(191, 1); + // + // O h t + // | | | | + // 0 191 255 1023 + EXPECT_EQ(Offset, 0); + + Offset = RB1.Allocate(64, 2); + EXPECT_EQ(Offset, RingBuffer::InvalidOffset); + + Offset = RB1.Allocate(64, 1); + // + // O t,h + // | | | | + // 0 191 255 1023 + EXPECT_EQ(Offset, 191); + + Offset = RB1.Allocate(1, 1); + EXPECT_EQ(Offset, RingBuffer::InvalidOffset); + + RB1.FinishCurrentFrame(7); + RB1.ReleaseCompletedFrames(7); + } + + { + RingBuffer RB(1024, Allocator); + + auto offset = RB.Allocate(512, 1); + RB.FinishCurrentFrame(0); + RB.FinishCurrentFrame(1); + RB.ReleaseCompletedFrames(1); + RB.FinishCurrentFrame(2); + RB.FinishCurrentFrame(3); + offset = RB.Allocate(512, 1); + RB.FinishCurrentFrame(4); + RB.ReleaseCompletedFrames(2); + RB.ReleaseCompletedFrames(3); + RB.ReleaseCompletedFrames(4); + offset = RB.Allocate(512, 1); + RB.FinishCurrentFrame(5); + RB.ReleaseCompletedFrames(5); + } +} + +} // namespace |
