Преглед на файлове

Improved compute system error handling (#1851)

* Added asserts when mapping/unmapping a buffer 2x
* Install Vulkan SDK for sonar check
* Added nodiscard to Result
* Add test to check failing shader creation
* Unit test for failing shader compilation
Jorrit Rouwe преди 4 седмици
родител
ревизия
c200feb120
променени са 37 файла, в които са добавени 530 реда и са изтрити 256 реда
  1. 2 0
      .github/workflows/sonar-cloud.yml
  2. 13 5
      Jolt/Compute/ComputeBuffer.h
  3. 3 0
      Jolt/Compute/ComputeQueue.h
  4. 3 0
      Jolt/Compute/ComputeShader.h
  5. 15 13
      Jolt/Compute/ComputeSystem.h
  6. 23 5
      Jolt/Compute/DX12/ComputeBufferDX12.cpp
  7. 5 4
      Jolt/Compute/DX12/ComputeBufferDX12.h
  8. 10 10
      Jolt/Compute/DX12/ComputeQueueDX12.cpp
  9. 1 1
      Jolt/Compute/DX12/ComputeQueueDX12.h
  10. 83 41
      Jolt/Compute/DX12/ComputeSystemDX12.cpp
  11. 3 3
      Jolt/Compute/DX12/ComputeSystemDX12.h
  12. 11 9
      Jolt/Compute/DX12/ComputeSystemDX12Impl.cpp
  13. 1 1
      Jolt/Compute/DX12/ComputeSystemDX12Impl.h
  14. 13 0
      Jolt/Compute/DX12/IncludeDX12.h
  15. 5 3
      Jolt/Compute/MTL/ComputeBufferMTL.h
  16. 16 8
      Jolt/Compute/MTL/ComputeBufferMTL.mm
  17. 3 3
      Jolt/Compute/MTL/ComputeSystemMTL.h
  18. 34 18
      Jolt/Compute/MTL/ComputeSystemMTL.mm
  19. 11 6
      Jolt/Compute/MTL/ComputeSystemMTLImpl.mm
  20. 19 9
      Jolt/Compute/VK/ComputeBufferVK.cpp
  21. 4 3
      Jolt/Compute/VK/ComputeBufferVK.h
  22. 9 9
      Jolt/Compute/VK/ComputeQueueVK.cpp
  23. 2 2
      Jolt/Compute/VK/ComputeQueueVK.h
  24. 6 6
      Jolt/Compute/VK/ComputeShaderVK.cpp
  25. 1 1
      Jolt/Compute/VK/ComputeShaderVK.h
  26. 40 17
      Jolt/Compute/VK/ComputeSystemVK.cpp
  27. 5 5
      Jolt/Compute/VK/ComputeSystemVK.h
  28. 40 23
      Jolt/Compute/VK/ComputeSystemVKImpl.cpp
  29. 1 1
      Jolt/Compute/VK/ComputeSystemVKImpl.h
  30. 10 11
      Jolt/Compute/VK/ComputeSystemVKWithAllocator.cpp
  31. 2 2
      Jolt/Compute/VK/ComputeSystemVKWithAllocator.h
  32. 14 0
      Jolt/Compute/VK/IncludeVK.h
  33. 1 1
      Jolt/Core/Result.h
  34. 5 2
      Samples/SamplesApp.cpp
  35. 20 6
      TestFramework/Renderer/DX12/RendererDX12.cpp
  36. 17 5
      TestFramework/Renderer/VK/RendererVK.cpp
  37. 79 23
      UnitTests/Compute/ComputeTests.cpp

+ 2 - 0
.github/workflows/sonar-cloud.yml

@@ -36,6 +36,8 @@ jobs:
       - uses: actions/checkout@v6
         with:
           fetch-depth: 0  # Shallow clones should be disabled for a better relevancy of analysis
+      - name: Install Vulkan
+        run: ${{github.workspace}}/Build/ubuntu24_install_vulkan_sdk.sh
       - name: Install build-wrapper
         uses: SonarSource/sonarqube-scan-action/install-build-wrapper@v7
       - name: Configure CMake

+ 13 - 5
Jolt/Compute/ComputeBuffer.h

@@ -6,9 +6,13 @@
 
 #include <Jolt/Core/Reference.h>
 #include <Jolt/Core/NonCopyable.h>
+#include <Jolt/Core/Result.h>
 
 JPH_NAMESPACE_BEGIN
 
+class ComputeBuffer;
+using ComputeBufferResult = Result<Ref<ComputeBuffer>>;
+
 /// Buffer that can be read from / written to by a compute shader
 class JPH_EXPORT ComputeBuffer : public RefTarget<ComputeBuffer>, public NonCopyable
 {
@@ -27,7 +31,7 @@ public:
 
 	/// Constructor / Destructor
 								ComputeBuffer(EType inType, uint64 inSize, uint inStride) : mType(inType), mSize(inSize), mStride(inStride) { }
-	virtual						~ComputeBuffer() = default;
+	virtual						~ComputeBuffer()								{ JPH_ASSERT(!mIsMapped); }
 
 	/// Properties
 	EType						GetType() const									{ return mType; }
@@ -42,20 +46,24 @@ public:
 	};
 
 	/// Map / unmap buffer (get pointer to data).
-	void *						Map(EMode inMode)								{ return MapInternal(inMode); }
-	template <typename T> T *	Map(EMode inMode)								{ JPH_ASSERT(sizeof(T) == mStride); return reinterpret_cast<T *>(MapInternal(inMode)); }
-	virtual void				Unmap() = 0;
+	void *						Map(EMode inMode)								{ JPH_ASSERT(!mIsMapped); JPH_IF_ENABLE_ASSERTS(mIsMapped = true;) return MapInternal(inMode); }
+	template <typename T> T *	Map(EMode inMode)								{ JPH_ASSERT(!mIsMapped); JPH_IF_ENABLE_ASSERTS(mIsMapped = true;) JPH_ASSERT(sizeof(T) == mStride); return reinterpret_cast<T *>(MapInternal(inMode)); }
+	void						Unmap()											{ JPH_ASSERT(mIsMapped); JPH_IF_ENABLE_ASSERTS(mIsMapped = false;) UnmapInternal(); }
 
 	/// Create a readback buffer of the same size and stride that can be used to read the data stored in this buffer on CPU.
 	/// Note that this could also be implemented as 'return this' in case the underlying implementation allows locking GPU data on CPU directly.
-	virtual Ref<ComputeBuffer>	CreateReadBackBuffer() const = 0;
+	virtual ComputeBufferResult	CreateReadBackBuffer() const = 0;
 
 protected:
 	EType						mType;
 	uint64						mSize;
 	uint						mStride;
+#ifdef JPH_ENABLE_ASSERTS
+	bool						mIsMapped = false;
+#endif // JPH_ENABLE_ASSERTS
 
 	virtual void *				MapInternal(EMode inMode) = 0;
+	virtual void				UnmapInternal() = 0;
 };
 
 JPH_NAMESPACE_END

+ 3 - 0
Jolt/Compute/ComputeQueue.h

@@ -6,6 +6,7 @@
 
 #include <Jolt/Core/Reference.h>
 #include <Jolt/Core/NonCopyable.h>
+#include <Jolt/Core/Result.h>
 
 JPH_NAMESPACE_BEGIN
 
@@ -77,4 +78,6 @@ public:
 	}
 };
 
+using ComputeQueueResult = Result<Ref<ComputeQueue>>;
+
 JPH_NAMESPACE_END

+ 3 - 0
Jolt/Compute/ComputeShader.h

@@ -6,6 +6,7 @@
 
 #include <Jolt/Core/Reference.h>
 #include <Jolt/Core/NonCopyable.h>
+#include <Jolt/Core/Result.h>
 
 JPH_NAMESPACE_BEGIN
 
@@ -35,4 +36,6 @@ private:
 	uint32					mGroupSizeZ;
 };
 
+using ComputeShaderResult = Result<Ref<ComputeShader>>;
+
 JPH_NAMESPACE_END

+ 15 - 13
Jolt/Compute/ComputeSystem.h

@@ -17,52 +17,54 @@ public:
 	JPH_OVERRIDE_NEW_DELETE
 
 	/// Destructor
-	virtual							~ComputeSystem() = default;
+	virtual								~ComputeSystem() = default;
 
 	/// Compile a compute shader
-	virtual Ref<ComputeShader>		CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY = 1, uint32 inGroupSizeZ = 1) = 0;
+	virtual ComputeShaderResult			CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY = 1, uint32 inGroupSizeZ = 1) = 0;
 
 	/// Create a buffer for use with a compute shader
-	virtual Ref<ComputeBuffer>		CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData = nullptr) = 0;
+	virtual ComputeBufferResult			CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData = nullptr) = 0;
 
 	/// Create a queue for executing compute shaders
-	virtual Ref<ComputeQueue>		CreateComputeQueue() = 0;
+	virtual ComputeQueueResult			CreateComputeQueue() = 0;
 
 	/// Callback used when loading shaders
-	using ShaderLoader = std::function<bool(const char *inName, Array<uint8> &outData)>;
-	ShaderLoader					mShaderLoader = [](const char *, Array<uint8> &) { JPH_ASSERT(false, "Override this function"); return false; };
+	using ShaderLoader = std::function<bool(const char *inName, Array<uint8> &outData, String &outError)>;
+	ShaderLoader						mShaderLoader = [](const char *, Array<uint8> &, String &outError) { JPH_ASSERT(false, "Override this function"); outError = "Not implemented"; return false; };
 };
 
+using ComputeSystemResult = Result<Ref<ComputeSystem>>;
+
 #ifdef JPH_USE_VK
 /// Factory function to create a compute system using Vulkan
-extern JPH_EXPORT ComputeSystem *	CreateComputeSystemVK();
+extern JPH_EXPORT ComputeSystemResult	CreateComputeSystemVK();
 #endif
 
 #ifdef JPH_USE_DX12
 
 /// Factory function to create a compute system using DirectX 12
-extern JPH_EXPORT ComputeSystem *	CreateComputeSystemDX12();
+extern JPH_EXPORT ComputeSystemResult	CreateComputeSystemDX12();
 
 /// Factory function to create the default compute system for this platform
-inline ComputeSystem *				CreateComputeSystem()		{ return CreateComputeSystemDX12(); }
+inline ComputeSystemResult 				CreateComputeSystem()		{ return CreateComputeSystemDX12(); }
 
 #elif defined(JPH_USE_MTL)
 
 /// Factory function to create a compute system using Metal
-extern JPH_EXPORT ComputeSystem *	CreateComputeSystemMTL();
+extern JPH_EXPORT ComputeSystemResult	CreateComputeSystemMTL();
 
 /// Factory function to create the default compute system for this platform
-inline ComputeSystem *				CreateComputeSystem()		{ return CreateComputeSystemMTL(); }
+inline ComputeSystemResult 				CreateComputeSystem()		{ return CreateComputeSystemMTL(); }
 
 #elif defined(JPH_USE_VK)
 
 /// Factory function to create the default compute system for this platform
-inline ComputeSystem *				CreateComputeSystem()		{ return CreateComputeSystemVK(); }
+inline ComputeSystemResult 				CreateComputeSystem()		{ return CreateComputeSystemVK(); }
 
 #else
 
 /// Fallback implementation when no compute system is available
-inline ComputeSystem *				CreateComputeSystem()		{ return nullptr; }
+inline ComputeSystemResult 				CreateComputeSystem()		{ return nullptr; }
 
 #endif
 

+ 23 - 5
Jolt/Compute/DX12/ComputeBufferDX12.cpp

@@ -11,32 +11,44 @@
 
 JPH_NAMESPACE_BEGIN
 
-ComputeBufferDX12::ComputeBufferDX12(ComputeSystemDX12 *inComputeSystem, EType inType, uint64 inSize, uint inStride, const void *inData) :
+ComputeBufferDX12::ComputeBufferDX12(ComputeSystemDX12 *inComputeSystem, EType inType, uint64 inSize, uint inStride) :
 	ComputeBuffer(inType, inSize, inStride),
 	mComputeSystem(inComputeSystem)
 {
-	uint64 buffer_size = inSize * inStride;
+}
+
+bool ComputeBufferDX12::Initialize(const void *inData)
+{
+	uint64 buffer_size = mSize * mStride;
 
-	switch (inType)
+	switch (mType)
 	{
 	case EType::UploadBuffer:
 		mBufferCPU = mComputeSystem->CreateD3DResource(D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE, buffer_size);
 		mBufferGPU = mComputeSystem->CreateD3DResource(D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_FLAG_NONE, buffer_size);
+		if (mBufferCPU == nullptr || mBufferGPU == nullptr)
+			return false;
 		break;
 
 	case EType::ConstantBuffer:
 		mBufferCPU = mComputeSystem->CreateD3DResource(D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE, buffer_size);
+		if (mBufferCPU == nullptr)
+			return false;
 		break;
 
 	case EType::ReadbackBuffer:
 		JPH_ASSERT(inData == nullptr, "Can't upload data to a readback buffer");
 		mBufferCPU = mComputeSystem->CreateD3DResource(D3D12_HEAP_TYPE_READBACK, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_FLAG_NONE, buffer_size);
+		if (mBufferCPU == nullptr)
+			return false;
 		break;
 
 	case EType::Buffer:
 		JPH_ASSERT(inData != nullptr);
 		mBufferCPU = mComputeSystem->CreateD3DResource(D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE, buffer_size);
 		mBufferGPU = mComputeSystem->CreateD3DResource(D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_FLAG_NONE, buffer_size);
+		if (mBufferCPU == nullptr || mBufferGPU == nullptr)
+			return false;
 		mNeedsSync = true;
 		break;
 
@@ -44,9 +56,13 @@ ComputeBufferDX12::ComputeBufferDX12(ComputeSystemDX12 *inComputeSystem, EType i
 		if (inData != nullptr)
 		{
 			mBufferCPU = mComputeSystem->CreateD3DResource(D3D12_HEAP_TYPE_UPLOAD, D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_FLAG_NONE, buffer_size);
+			if (mBufferCPU == nullptr)
+				return false;
 			mNeedsSync = true;
 		}
 		mBufferGPU = mComputeSystem->CreateD3DResource(D3D12_HEAP_TYPE_DEFAULT, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS, buffer_size);
+		if (mBufferGPU == nullptr)
+			return false;
 		break;
 	}
 
@@ -59,6 +75,8 @@ ComputeBufferDX12::ComputeBufferDX12(ComputeSystemDX12 *inComputeSystem, EType i
 		memcpy(data, inData, size_t(buffer_size));
 		mBufferCPU->Unmap(0, nullptr);
 	}
+
+	return true;
 }
 
 bool ComputeBufferDX12::Barrier(ID3D12GraphicsCommandList *inCommandList, D3D12_RESOURCE_STATES inTo) const
@@ -134,12 +152,12 @@ void *ComputeBufferDX12::MapInternal(EMode inMode)
 	return mapped_resource;
 }
 
-void ComputeBufferDX12::Unmap()
+void ComputeBufferDX12::UnmapInternal()
 {
 	mBufferCPU->Unmap(0, nullptr);
 }
 
-Ref<ComputeBuffer> ComputeBufferDX12::CreateReadBackBuffer() const
+ComputeBufferResult ComputeBufferDX12::CreateReadBackBuffer() const
 {
 	return mComputeSystem->CreateComputeBuffer(EType::ReadbackBuffer, mSize, mStride);
 }

+ 5 - 4
Jolt/Compute/DX12/ComputeBufferDX12.h

@@ -21,7 +21,9 @@ public:
 	JPH_OVERRIDE_NEW_DELETE
 
 	/// Constructor
-									ComputeBufferDX12(ComputeSystemDX12 *inComputeSystem, EType inType, uint64 inSize, uint inStride, const void *inData);
+									ComputeBufferDX12(ComputeSystemDX12 *inComputeSystem, EType inType, uint64 inSize, uint inStride);
+
+	bool							Initialize(const void *inData);
 
 	ID3D12Resource *				GetResourceCPU() const									{ return mBufferCPU.Get(); }
 	ID3D12Resource *				GetResourceGPU() const									{ return mBufferGPU.Get(); }
@@ -31,12 +33,11 @@ public:
 	void							RWBarrier(ID3D12GraphicsCommandList *inCommandList);
 	bool							SyncCPUToGPU(ID3D12GraphicsCommandList *inCommandList) const;
 
-	virtual void					Unmap() override;
-
-	Ref<ComputeBuffer>				CreateReadBackBuffer() const override;
+	ComputeBufferResult				CreateReadBackBuffer() const override;
 
 private:
 	virtual void *					MapInternal(EMode inMode) override;
+	virtual void					UnmapInternal() override;
 
 	ComputeSystemDX12 *				mComputeSystem;
 	mutable ComPtr<ID3D12Resource>	mBufferCPU;

+ 10 - 10
Jolt/Compute/DX12/ComputeQueueDX12.cpp

@@ -20,28 +20,28 @@ ComputeQueueDX12::~ComputeQueueDX12()
 		CloseHandle(mFenceEvent);
 }
 
-bool ComputeQueueDX12::Initialize(ID3D12Device *inDevice, D3D12_COMMAND_LIST_TYPE inType)
+bool ComputeQueueDX12::Initialize(ID3D12Device *inDevice, D3D12_COMMAND_LIST_TYPE inType, ComputeQueueResult &outResult)
 {
 	D3D12_COMMAND_QUEUE_DESC queue_desc = {};
 	queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
 	queue_desc.Type = inType;
 	queue_desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_HIGH;
-	if (HRFailed(inDevice->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&mCommandQueue))))
+	if (HRFailed(inDevice->CreateCommandQueue(&queue_desc, IID_PPV_ARGS(&mCommandQueue)), outResult))
 		return false;
 
-	if (HRFailed(inDevice->CreateCommandAllocator(inType, IID_PPV_ARGS(&mCommandAllocator))))
+	if (HRFailed(inDevice->CreateCommandAllocator(inType, IID_PPV_ARGS(&mCommandAllocator)), outResult))
 		return false;
 
 	// Create the command list
-	if (HRFailed(inDevice->CreateCommandList(0, inType, mCommandAllocator.Get(), nullptr, IID_PPV_ARGS(&mCommandList))))
+	if (HRFailed(inDevice->CreateCommandList(0, inType, mCommandAllocator.Get(), nullptr, IID_PPV_ARGS(&mCommandList)), outResult))
 		return false;
 
 	// Command lists are created in the recording state, but there is nothing to record yet. The main loop expects it to be closed, so close it now
-	if (HRFailed(mCommandList->Close()))
+	if (HRFailed(mCommandList->Close(), outResult))
 		return false;
 
 	// Create synchronization object
-	if (HRFailed(inDevice->CreateFence(mFenceValue, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence))))
+	if (HRFailed(inDevice->CreateFence(mFenceValue, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)), outResult))
 		return false;
 
 	// Increment fence value so we don't skip waiting the first time a command list is executed
@@ -49,7 +49,7 @@ bool ComputeQueueDX12::Initialize(ID3D12Device *inDevice, D3D12_COMMAND_LIST_TYP
 
 	// Create an event handle to use for frame synchronization
 	mFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
-	if (HRFailed(HRESULT_FROM_WIN32(GetLastError())))
+	if (HRFailed(HRESULT_FROM_WIN32(GetLastError()), outResult))
 		return false;
 
 	return true;
@@ -87,11 +87,11 @@ void ComputeQueueDX12::SetShader(const ComputeShader *inShader)
 void ComputeQueueDX12::SyncCPUToGPU(const ComputeBufferDX12 *inBuffer)
 {
 	// Ensure that any CPU writes are visible to the GPU
-	if (inBuffer->SyncCPUToGPU(mCommandList.Get()))
+	if (inBuffer->SyncCPUToGPU(mCommandList.Get())
+		&& (inBuffer->GetType() == ComputeBuffer::EType::Buffer || inBuffer->GetType() == ComputeBuffer::EType::RWBuffer))
 	{
 		// After the first upload, the CPU buffer is no longer needed for Buffer and RWBuffer types
-		if (inBuffer->GetType() == ComputeBuffer::EType::Buffer || inBuffer->GetType() == ComputeBuffer::EType::RWBuffer)
-			mDelayedFreedBuffers.emplace_back(inBuffer->ReleaseResourceCPU());
+		mDelayedFreedBuffers.emplace_back(inBuffer->ReleaseResourceCPU());
 	}
 }
 

+ 1 - 1
Jolt/Compute/DX12/ComputeQueueDX12.h

@@ -24,7 +24,7 @@ public:
 	virtual								~ComputeQueueDX12() override;
 
 	/// Initialize the queue
-	bool								Initialize(ID3D12Device *inDevice, D3D12_COMMAND_LIST_TYPE inType);
+	bool								Initialize(ID3D12Device *inDevice, D3D12_COMMAND_LIST_TYPE inType, ComputeQueueResult &outResult);
 
 	/// Start the command list (requires waiting until the previous one is finished)
 	ID3D12GraphicsCommandList *			Start();

+ 83 - 41
Jolt/Compute/DX12/ComputeSystemDX12.cpp

@@ -66,13 +66,19 @@ ComPtr<ID3D12Resource> ComputeSystemDX12::CreateD3DResource(D3D12_HEAP_TYPE inHe
 	return resource;
 }
 
-Ref<ComputeShader> ComputeSystemDX12::CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ)
+ComputeShaderResult ComputeSystemDX12::CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ)
 {
+	ComputeShaderResult result;
+
 	// Read shader source file
 	Array<uint8> data;
+	String error;
 	String file_name = String(inName) + ".hlsl";
-	if (!mShaderLoader(file_name.c_str(), data))
-		return nullptr;
+	if (!mShaderLoader(file_name.c_str(), data, error))
+	{
+		result.SetError(error);
+		return result;
+	}
 
 #ifndef JPH_USE_DXC // Use FXC, the old shader compiler?
 
@@ -100,7 +106,8 @@ Ref<ComputeShader> ComputeSystemDX12::CreateComputeShader(const char *inName, ui
 		{
 			// Read the header file
 			Array<uint8> file_data;
-			if (!mShaderLoader(inFileName, file_data))
+			String error;
+			if (!mShaderLoader(inFileName, file_data, error))
 				return E_FAIL;
 			if (file_data.empty())
 			{
@@ -146,14 +153,19 @@ Ref<ComputeShader> ComputeSystemDX12::CreateComputeShader(const char *inName, ui
 				error_blob.GetAddressOf())))
 	{
 		if (error_blob)
-			Trace("Shader compile error: %s", (const char *)error_blob->GetBufferPointer());
-		return nullptr;
+			result.SetError((const char *)error_blob->GetBufferPointer());
+		else
+			result.SetError("Shader compile error");
+		return result;
 	}
 
 	// Get shader description
 	ComPtr<ID3D12ShaderReflection> reflector;
 	if (FAILED(D3DReflect(shader_blob->GetBufferPointer(), shader_blob->GetBufferSize(), IID_PPV_ARGS(&reflector))))
-		return nullptr;
+	{
+		result.SetError("Failed to reflect shader");
+		return result;
+	}
 
 #else
 
@@ -195,7 +207,8 @@ Ref<ComputeShader> ComputeSystemDX12::CreateComputeShader(const char *inName, ui
 
 			// Load the header
 			Array<uint8> file_data;
-			if (!mShaderLoader(file_name, file_data))
+			String error;
+			if (!mShaderLoader(file_name, file_data, error))
 				return E_FAIL;
 
 			// Create a blob from the loaded data
@@ -215,8 +228,8 @@ Ref<ComputeShader> ComputeSystemDX12::CreateComputeShader(const char *inName, ui
 	DxcIncludeHandler include_handler(utils.Get(), mShaderLoader);
 
 	ComPtr<IDxcBlobEncoding> source;
-	if (HRFailed(utils->CreateBlob(data.data(), (uint)data.size(), CP_UTF8, source.GetAddressOf())))
-		return nullptr;
+	if (HRFailed(utils->CreateBlob(data.data(), (uint)data.size(), CP_UTF8, source.GetAddressOf()), result))
+		return result;
 
 	ComPtr<IDxcCompiler3> compiler;
 	DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(compiler.GetAddressOf()));
@@ -237,30 +250,33 @@ Ref<ComputeShader> ComputeSystemDX12::CreateComputeShader(const char *inName, ui
 	source_buffer.Ptr = source->GetBufferPointer();
 	source_buffer.Size = source->GetBufferSize();
 	source_buffer.Encoding = 0;
-	ComPtr<IDxcResult> result;
-	if (FAILED(compiler->Compile(&source_buffer, arguments.data(), (uint32)arguments.size(), &include_handler, IID_PPV_ARGS(result.GetAddressOf()))))
-		return nullptr;
+	ComPtr<IDxcResult> compile_result;
+	if (FAILED(compiler->Compile(&source_buffer, arguments.data(), (uint32)arguments.size(), &include_handler, IID_PPV_ARGS(compile_result.GetAddressOf()))))
+	{
+		result.SetError("Failed to compile shader");
+		return result;
+	}
 
 	// Check for compilation errors
 	ComPtr<IDxcBlobUtf8> errors;
-	result->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(errors.GetAddressOf()), nullptr);
+	compile_result->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(errors.GetAddressOf()), nullptr);
 	if (errors != nullptr && errors->GetStringLength() > 0)
 	{
-		Trace((char *)errors->GetBufferPointer());
-		return nullptr;
+		result.SetError((const char *)errors->GetBufferPointer());
+		return result;
 	}
 
 	// Get the compiled shader code
 	ComPtr<ID3DBlob> shader_blob;
-	if (HRFailed(result->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(shader_blob.GetAddressOf()), nullptr)))
-		return nullptr;
+	if (HRFailed(compile_result->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(shader_blob.GetAddressOf()), nullptr), result))
+		return result;
 
 	if (mDebug == EDebug::DebugSymbols)
 	{
 		// Get shader hash and create PDB file name
 		ComPtr<IDxcBlob> hash;
-		if (HRFailed(result->GetOutput(DXC_OUT_SHADER_HASH, IID_PPV_ARGS(hash.GetAddressOf()), nullptr)))
-			return nullptr;
+		if (HRFailed(compile_result->GetOutput(DXC_OUT_SHADER_HASH, IID_PPV_ARGS(hash.GetAddressOf()), nullptr), result))
+			return result;
 		DxcShaderHash *hash_buf = (DxcShaderHash *)hash->GetBufferPointer();
 		String hash_str;
 		for (BYTE b : hash_buf->HashDigest)
@@ -269,8 +285,8 @@ Ref<ComputeShader> ComputeSystemDX12::CreateComputeShader(const char *inName, ui
 
 		// Get PDB file from the compiler
 		ComPtr<IDxcBlob> pdb;
-		if (HRFailed(result->GetOutput(DXC_OUT_PDB, IID_PPV_ARGS(pdb.GetAddressOf()), nullptr)))
-			return nullptr;
+		if (HRFailed(compile_result->GetOutput(DXC_OUT_PDB, IID_PPV_ARGS(pdb.GetAddressOf()), nullptr), result))
+			return result;
 
 		// Write PDB file to the temp folder
 		char temp_path[MAX_PATH];
@@ -281,27 +297,27 @@ Ref<ComputeShader> ComputeSystemDX12::CreateComputeShader(const char *inName, ui
 
 	// Get reflection data
 	ComPtr<IDxcBlob> reflection_data;
-	if (HRFailed(result->GetOutput(DXC_OUT_REFLECTION, IID_PPV_ARGS(reflection_data.GetAddressOf()), nullptr)))
-		return nullptr;
+	if (HRFailed(compile_result->GetOutput(DXC_OUT_REFLECTION, IID_PPV_ARGS(reflection_data.GetAddressOf()), nullptr), result))
+		return result;
 	DxcBuffer reflection_buffer;
 	reflection_buffer.Ptr = reflection_data->GetBufferPointer();
 	reflection_buffer.Size = reflection_data->GetBufferSize();
 	reflection_buffer.Encoding = 0;
 	ComPtr<ID3D12ShaderReflection> reflector;
-	if (HRFailed(utils->CreateReflection(&reflection_buffer, IID_PPV_ARGS(reflector.GetAddressOf()))))
-		return nullptr;
+	if (HRFailed(utils->CreateReflection(&reflection_buffer, IID_PPV_ARGS(reflector.GetAddressOf())), result))
+		return result;
 
 #endif // JPH_USE_DXC
 
 	// Get the shader description
 	D3D12_SHADER_DESC shader_desc;
-	if (HRFailed(reflector->GetDesc(&shader_desc)))
-		return nullptr;
+	if (HRFailed(reflector->GetDesc(&shader_desc), result))
+		return result;
 
 	// Verify that the group sizes match the shader's thread group size
 	UINT thread_group_size_x, thread_group_size_y, thread_group_size_z;
-	if (HRFailed(reflector->GetThreadGroupSize(&thread_group_size_x, &thread_group_size_y, &thread_group_size_z)))
-		return nullptr;
+	if (HRFailed(reflector->GetThreadGroupSize(&thread_group_size_x, &thread_group_size_y, &thread_group_size_z), result))
+		return result;
 	JPH_ASSERT(inGroupSizeX == thread_group_size_x, "Group size X mismatch");
 	JPH_ASSERT(inGroupSizeY == thread_group_size_y, "Group size Y mismatch");
 	JPH_ASSERT(inGroupSizeZ == thread_group_size_z, "Group size Z mismatch");
@@ -370,12 +386,20 @@ Ref<ComputeShader> ComputeSystemDX12::CreateComputeShader(const char *inName, ui
 	if (FAILED(D3D12SerializeVersionedRootSignature(&root_sig_desc, &serialized_sig, &root_sig_error_blob)))
 	{
 		if (root_sig_error_blob)
-			Trace("Failed to create root signature: %s", (const char *)root_sig_error_blob->GetBufferPointer());
-		return nullptr;
+		{
+			error = StringFormat("Failed to create root signature: %s", (const char *)root_sig_error_blob->GetBufferPointer());
+			result.SetError(error);
+		}
+		else
+			result.SetError("Failed to create root signature");
+		return result;
 	}
 	ComPtr<ID3D12RootSignature> root_sig;
 	if (FAILED(mDevice->CreateRootSignature(0, serialized_sig->GetBufferPointer(), serialized_sig->GetBufferSize(), IID_PPV_ARGS(&root_sig))))
-		return nullptr;
+	{
+		result.SetError("Failed to create root signature");
+		return result;
+	}
 
 	// Create a pipeline state object from the root signature and the shader
 	ComPtr<ID3D12PipelineState> pipeline_state;
@@ -383,7 +407,10 @@ Ref<ComputeShader> ComputeSystemDX12::CreateComputeShader(const char *inName, ui
 	compute_state_desc.pRootSignature = root_sig.Get();
 	compute_state_desc.CS = { shader_blob->GetBufferPointer(), shader_blob->GetBufferSize() };
 	if (FAILED(mDevice->CreateComputePipelineState(&compute_state_desc, IID_PPV_ARGS(&pipeline_state))))
-		return nullptr;
+	{
+		result.SetError("Failed to create compute pipeline state");
+		return result;
+	}
 
 	// Set name on DX12 objects for easier debugging
 	wchar_t w_name[1024];
@@ -391,20 +418,35 @@ Ref<ComputeShader> ComputeSystemDX12::CreateComputeShader(const char *inName, ui
 	mbstowcs_s(&converted_chars, w_name, 1024, inName, _TRUNCATE);
 	pipeline_state->SetName(w_name);
 
-	return new ComputeShaderDX12(shader_blob, root_sig, pipeline_state, std::move(binding_names), std::move(name_to_index), inGroupSizeX, inGroupSizeY, inGroupSizeZ);
+	result.Set(new ComputeShaderDX12(shader_blob, root_sig, pipeline_state, std::move(binding_names), std::move(name_to_index), inGroupSizeX, inGroupSizeY, inGroupSizeZ));
+	return result;
 }
 
-Ref<ComputeBuffer> ComputeSystemDX12::CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData)
+ComputeBufferResult ComputeSystemDX12::CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData)
 {
-	return new ComputeBufferDX12(this, inType, inSize, inStride, inData);
+	ComputeBufferResult result;
+
+	Ref<ComputeBufferDX12> buffer = new ComputeBufferDX12(this, inType, inSize, inStride);
+	if (!buffer->Initialize(inData))
+	{
+		result.SetError("Failed to create compute buffer");
+		return result;
+	}
+
+	result.Set(buffer.GetPtr());
+	return result;
 }
 
-Ref<ComputeQueue> ComputeSystemDX12::CreateComputeQueue()
+ComputeQueueResult ComputeSystemDX12::CreateComputeQueue()
 {
+	ComputeQueueResult result;
+
 	Ref<ComputeQueueDX12> queue = new ComputeQueueDX12();
-	if (!queue->Initialize(mDevice.Get(), D3D12_COMMAND_LIST_TYPE_COMPUTE))
-		return nullptr;
-	return queue.GetPtr();
+	if (!queue->Initialize(mDevice.Get(), D3D12_COMMAND_LIST_TYPE_COMPUTE, result))
+		return result;
+
+	result.Set(queue.GetPtr());
+	return result;
 }
 
 JPH_NAMESPACE_END

+ 3 - 3
Jolt/Compute/DX12/ComputeSystemDX12.h

@@ -32,9 +32,9 @@ public:
 	void							Shutdown();
 
 	// See: ComputeSystem
-	virtual Ref<ComputeShader>		CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ) override;
-	virtual Ref<ComputeBuffer>		CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData = nullptr) override;
-	virtual Ref<ComputeQueue>		CreateComputeQueue() override;
+	virtual ComputeShaderResult  	CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ) override;
+	virtual ComputeBufferResult  	CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData = nullptr) override;
+	virtual ComputeQueueResult  	CreateComputeQueue() override;
 
 	/// Access to the DX12 device
 	ID3D12Device *					GetDevice() const								{ return mDevice.Get(); }

+ 11 - 9
Jolt/Compute/DX12/ComputeSystemDX12Impl.cpp

@@ -27,7 +27,7 @@ ComputeSystemDX12Impl::~ComputeSystemDX12Impl()
 #endif
 }
 
-bool ComputeSystemDX12Impl::Initialize()
+bool ComputeSystemDX12Impl::Initialize(ComputeSystemResult &outResult)
 {
 #if defined(JPH_DEBUG)
 	// Enable the D3D12 debug layer
@@ -37,7 +37,7 @@ bool ComputeSystemDX12Impl::Initialize()
 #endif
 
 	// Create DXGI factory
-	if (HRFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mDXGIFactory))))
+	if (HRFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mDXGIFactory)), outResult))
 		return false;
 
 	// Find adapter
@@ -101,7 +101,7 @@ bool ComputeSystemDX12Impl::Initialize()
 	}
 
 	// Check if we managed to obtain a device
-	if (HRFailed(result))
+	if (HRFailed(result, outResult))
 		return false;
 
 	// Initialize the compute interface
@@ -132,14 +132,16 @@ bool ComputeSystemDX12Impl::Initialize()
 	return true;
 }
 
-ComputeSystem *CreateComputeSystemDX12()
+ComputeSystemResult CreateComputeSystemDX12()
 {
-	ComputeSystemDX12Impl *compute = new ComputeSystemDX12Impl();
-	if (compute->Initialize())
-		return compute;
+	ComputeSystemResult result;
 
-	delete compute;
-	return nullptr;
+	Ref<ComputeSystemDX12Impl> compute = new ComputeSystemDX12Impl();
+	if (!compute->Initialize(result))
+		return result;
+
+	result.Set(compute.GetPtr());
+	return result;
 }
 
 JPH_NAMESPACE_END

+ 1 - 1
Jolt/Compute/DX12/ComputeSystemDX12Impl.h

@@ -20,7 +20,7 @@ public:
 	virtual 						~ComputeSystemDX12Impl() override;
 
 	/// Initialize the compute system
-	bool							Initialize();
+	bool							Initialize(ComputeSystemResult &outResult);
 
 	IDXGIFactory4 *					GetDXGIFactory() const						{ return mDXGIFactory.Get(); }
 

+ 13 - 0
Jolt/Compute/DX12/IncludeDX12.h

@@ -7,6 +7,7 @@
 #ifdef JPH_USE_DX12
 
 #include <Jolt/Core/IncludeWindows.h>
+#include <Jolt/Core/StringTools.h>
 
 JPH_SUPPRESS_WARNINGS_STD_BEGIN
 JPH_MSVC_SUPPRESS_WARNING(4265) // 'X': class has virtual functions, but its non-trivial destructor is not virtual; instances of this class may not be destructed correctly
@@ -25,6 +26,18 @@ JPH_NAMESPACE_BEGIN
 
 using Microsoft::WRL::ComPtr;
 
+template <class Result>
+inline bool HRFailed(HRESULT inHR, Result &outResult)
+{
+	if (SUCCEEDED(inHR))
+		return false;
+
+	String error = StringFormat("Call failed with error code: %08X", inHR);
+	outResult.SetError(error);
+	JPH_ASSERT(false);
+	return true;
+}
+
 inline bool HRFailed(HRESULT inHR)
 {
 	if (SUCCEEDED(inHR))

+ 5 - 3
Jolt/Compute/MTL/ComputeBufferMTL.h

@@ -17,18 +17,20 @@ public:
 	JPH_OVERRIDE_NEW_DELETE
 
 	/// Constructor
-									ComputeBufferMTL(ComputeSystemMTL *inComputeSystem, EType inType, uint64 inSize, uint inStride, const void *inData);
+									ComputeBufferMTL(ComputeSystemMTL *inComputeSystem, EType inType, uint64 inSize, uint inStride);
 	virtual							~ComputeBufferMTL() override;
 
-	virtual void					Unmap() override;
+	bool							Initialize(const void *inData);
 
-	virtual Ref<ComputeBuffer>		CreateReadBackBuffer() const override;
+	virtual ComputeBufferResult		CreateReadBackBuffer() const override;
 
 	id<MTLBuffer>					GetBuffer() const							{ return mBuffer; }
 
 private:
 	virtual void *					MapInternal(EMode inMode) override;
+	virtual void					UnmapInternal() override;
 
+	ComputeSystemMTL *				mComputeSystem;
 	id<MTLBuffer>					mBuffer;
 };
 

+ 16 - 8
Jolt/Compute/MTL/ComputeBufferMTL.mm

@@ -10,14 +10,20 @@
 
 JPH_NAMESPACE_BEGIN
 
-ComputeBufferMTL::ComputeBufferMTL(ComputeSystemMTL *inComputeSystem, EType inType, uint64 inSize, uint inStride, const void *inData) :
-	ComputeBuffer(inType, inSize, inStride)
+ComputeBufferMTL::ComputeBufferMTL(ComputeSystemMTL *inComputeSystem, EType inType, uint64 inSize, uint inStride) :
+	ComputeBuffer(inType, inSize, inStride),
+	mComputeSystem(inComputeSystem)
 {
-	NSUInteger size = NSUInteger(inSize) * inStride;
+}
+
+bool ComputeBufferMTL::Initialize(const void *inData)
+{
+	NSUInteger size = NSUInteger(mSize) * mStride;
 	if (inData != nullptr)
-		mBuffer = [inComputeSystem->GetDevice() newBufferWithBytes: inData length: size options: MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeShared | MTLResourceHazardTrackingModeTracked];
+		mBuffer = [mComputeSystem->GetDevice() newBufferWithBytes: inData length: size options: MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeShared | MTLResourceHazardTrackingModeTracked];
 	else
-		mBuffer = [inComputeSystem->GetDevice() newBufferWithLength: size options: MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeShared | MTLResourceHazardTrackingModeTracked];
+		mBuffer = [mComputeSystem->GetDevice() newBufferWithLength: size options: MTLResourceCPUCacheModeDefaultCache | MTLResourceStorageModeShared | MTLResourceHazardTrackingModeTracked];
+	return mBuffer != nil;
 }
 
 ComputeBufferMTL::~ComputeBufferMTL()
@@ -30,13 +36,15 @@ void *ComputeBufferMTL::MapInternal(EMode inMode)
 	return mBuffer.contents;
 }
 
-void ComputeBufferMTL::Unmap()
+void ComputeBufferMTL::UnmapInternal()
 {
 }
 
-Ref<ComputeBuffer> ComputeBufferMTL::CreateReadBackBuffer() const
+ComputeBufferResult ComputeBufferMTL::CreateReadBackBuffer() const
 {
-	return const_cast<ComputeBufferMTL *>(this);
+	ComputeBufferResult result;
+	result.Set(const_cast<ComputeBufferMTL *>(this));
+	return result;
 }
 
 JPH_NAMESPACE_END

+ 3 - 3
Jolt/Compute/MTL/ComputeSystemMTL.h

@@ -23,9 +23,9 @@ public:
 	void							Shutdown();
 
 	// See: ComputeSystem
-	virtual Ref<ComputeShader>		CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ) override;
-	virtual Ref<ComputeBuffer>		CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData = nullptr) override;
-	virtual Ref<ComputeQueue>		CreateComputeQueue() override;
+	virtual ComputeShaderResult		CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ) override;
+	virtual ComputeBufferResult		CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData = nullptr) override;
+	virtual ComputeQueueResult		CreateComputeQueue() override;
 
 	/// Get the metal device
 	id<MTLDevice>					GetDevice() const						{ return mDevice; }

+ 34 - 18
Jolt/Compute/MTL/ComputeSystemMTL.mm

@@ -26,29 +26,32 @@ void ComputeSystemMTL::Shutdown()
 	[mDevice release];
 }
 
-Ref<ComputeShader> ComputeSystemMTL::CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ)
+ComputeShaderResult ComputeSystemMTL::CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ)
 {
+	ComputeShaderResult result;
+
 	if (mShaderLibrary == nil)
 	{
 		// Load the shader library containing all shaders
 		Array<uint8> *data = new Array<uint8>();
-		if (!mShaderLoader("Jolt.metallib", *data))
+		String error;
+		if (!mShaderLoader("Jolt.metallib", *data, error))
 		{
-			JPH_ASSERT(false, "Failed to load shader library");
+			result.SetError(error);
 			delete data;
-			return nullptr;
+			return result;
 		}
 
 		// Convert to dispatch data
 		dispatch_data_t data_dispatch = dispatch_data_create(data->data(), data->size(), nullptr, ^{ delete data; });
 
 		// Create the library
-		NSError *error = nullptr;
-		mShaderLibrary = [mDevice newLibraryWithData: data_dispatch error: &error];
-		if (error != nil)
+		NSError *ns_error = nullptr;
+		mShaderLibrary = [mDevice newLibraryWithData: data_dispatch error: &ns_error];
+		if (ns_error != nil)
 		{
-			JPH_ASSERT(false, "Failed to load shader library");
-			return nullptr;
+			result.SetError("Failed to laod shader library");
+			return result;
 		}
 	}
 
@@ -56,8 +59,8 @@ Ref<ComputeShader> ComputeSystemMTL::CreateComputeShader(const char *inName, uin
 	id<MTLFunction> function = [mShaderLibrary newFunctionWithName: [NSString stringWithCString: inName encoding: NSUTF8StringEncoding]];
 	if (function == nil)
 	{
-		Trace("Failed to create compute shader: %s", inName);
-		return nullptr;
+		result.SetError("Failed to instantiate compute shader");
+		return result;
 	}
 
 	// Create the pipeline
@@ -66,22 +69,35 @@ Ref<ComputeShader> ComputeSystemMTL::CreateComputeShader(const char *inName, uin
 	id<MTLComputePipelineState> pipeline_state = [mDevice newComputePipelineStateWithFunction: function options: MTLPipelineOptionBindingInfo | MTLPipelineOptionBufferTypeInfo reflection: &reflection error: &error];
 	if (error != nil || pipeline_state == nil)
 	{
-		JPH_ASSERT(false, "Failed to create compute pipeline");
+		result.SetError("Failed to create compute pipeline");
 		[function release];
-		return nullptr;
+		return result;
 	}
 
-	return new ComputeShaderMTL(pipeline_state, reflection, inGroupSizeX, inGroupSizeY, inGroupSizeZ);
+	result.Set(new ComputeShaderMTL(pipeline_state, reflection, inGroupSizeX, inGroupSizeY, inGroupSizeZ));
+	return result;
 }
 
-Ref<ComputeBuffer> ComputeSystemMTL::CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData)
+ComputeBufferResult ComputeSystemMTL::CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData)
 {
-	return new ComputeBufferMTL(this, inType, inSize, inStride, inData);
+	ComputeBufferResult result;
+
+	Ref<ComputeBufferMTL> buffer = new ComputeBufferMTL(this, inType, inSize, inStride);
+	if (!buffer->Initialize(inData))
+	{
+		result.SetError("Failed to create compute buffer");
+		return result;
+	}
+
+	result.Set(buffer.GetPtr());
+	return result;
 }
 
-Ref<ComputeQueue> ComputeSystemMTL::CreateComputeQueue()
+ComputeQueueResult ComputeSystemMTL::CreateComputeQueue()
 {
-	return new ComputeQueueMTL(mDevice);
+	ComputeQueueResult result;
+	result.Set(new ComputeQueueMTL(mDevice));
+	return result;
 }
 
 JPH_NAMESPACE_END

+ 11 - 6
Jolt/Compute/MTL/ComputeSystemMTLImpl.mm

@@ -24,14 +24,19 @@ bool ComputeSystemMTLImpl::Initialize()
 	return ComputeSystemMTL::Initialize(device);
 }
 
-ComputeSystem *CreateComputeSystemMTL()
+ComputeSystemResult CreateComputeSystemMTL()
 {
-	ComputeSystemMTLImpl *compute = new ComputeSystemMTLImpl;
-	if (compute->Initialize())
-		return compute;
+	ComputeSystemResult result;
 
-	delete compute;
-	return nullptr;
+	Ref<ComputeSystemMTLImpl> compute = new ComputeSystemMTLImpl;
+	if (!compute->Initialize())
+	{
+		result.SetError("Failed to initialize compute system");
+		return result;
+	}
+
+	result.Set(compute.GetPtr());
+	return result;
 }
 
 JPH_NAMESPACE_END

+ 19 - 9
Jolt/Compute/VK/ComputeBufferVK.cpp

@@ -11,13 +11,17 @@
 
 JPH_NAMESPACE_BEGIN
 
-ComputeBufferVK::ComputeBufferVK(ComputeSystemVK *inComputeSystem, EType inType, uint64 inSize, uint inStride, const void *inData) :
+ComputeBufferVK::ComputeBufferVK(ComputeSystemVK *inComputeSystem, EType inType, uint64 inSize, uint inStride) :
 	ComputeBuffer(inType, inSize, inStride),
 	mComputeSystem(inComputeSystem)
 {
-	VkDeviceSize buffer_size = VkDeviceSize(inSize * inStride);
+}
+
+bool ComputeBufferVK::Initialize(const void *inData)
+{
+	VkDeviceSize buffer_size = VkDeviceSize(mSize * mStride);
 
-	switch (inType)
+	switch (mType)
 	{
 	case EType::Buffer:
 		JPH_ASSERT(inData != nullptr);
@@ -25,8 +29,10 @@ ComputeBufferVK::ComputeBufferVK(ComputeSystemVK *inComputeSystem, EType inType,
 
 	case EType::UploadBuffer:
 	case EType::RWBuffer:
-		mComputeSystem->CreateBuffer(buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, mBufferCPU);
-		mComputeSystem->CreateBuffer(buffer_size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mBufferGPU);
+		if (!mComputeSystem->CreateBuffer(buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, mBufferCPU))
+			return false;
+		if (!mComputeSystem->CreateBuffer(buffer_size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mBufferGPU))
+			return false;
 		if (inData != nullptr)
 		{
 			void *data = mComputeSystem->MapBuffer(mBufferCPU);
@@ -37,7 +43,8 @@ ComputeBufferVK::ComputeBufferVK(ComputeSystemVK *inComputeSystem, EType inType,
 		break;
 
 	case EType::ConstantBuffer:
-		mComputeSystem->CreateBuffer(buffer_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, mBufferCPU);
+		if (!mComputeSystem->CreateBuffer(buffer_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, mBufferCPU))
+			return false;
 		if (inData != nullptr)
 		{
 			void* data = mComputeSystem->MapBuffer(mBufferCPU);
@@ -48,9 +55,12 @@ ComputeBufferVK::ComputeBufferVK(ComputeSystemVK *inComputeSystem, EType inType,
 
 	case EType::ReadbackBuffer:
 		JPH_ASSERT(inData == nullptr, "Can't upload data to a readback buffer");
-		mComputeSystem->CreateBuffer(buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, mBufferCPU);
+		if (!mComputeSystem->CreateBuffer(buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, mBufferCPU))
+			return false;
 		break;
 	}
+
+	return true;
 }
 
 ComputeBufferVK::~ComputeBufferVK()
@@ -115,12 +125,12 @@ void *ComputeBufferVK::MapInternal(EMode inMode)
 	return mComputeSystem->MapBuffer(mBufferCPU);
 }
 
-void ComputeBufferVK::Unmap()
+void ComputeBufferVK::UnmapInternal()
 {
 	mComputeSystem->UnmapBuffer(mBufferCPU);
 }
 
-Ref<ComputeBuffer> ComputeBufferVK::CreateReadBackBuffer() const
+ComputeBufferResult ComputeBufferVK::CreateReadBackBuffer() const
 {
 	return mComputeSystem->CreateComputeBuffer(ComputeBuffer::EType::ReadbackBuffer, mSize, mStride);
 }

+ 4 - 3
Jolt/Compute/VK/ComputeBufferVK.h

@@ -21,12 +21,12 @@ public:
 	JPH_OVERRIDE_NEW_DELETE
 
 	/// Constructor
-									ComputeBufferVK(ComputeSystemVK *inComputeSystem, EType inType, uint64 inSize, uint inStride, const void *inData);
+									ComputeBufferVK(ComputeSystemVK *inComputeSystem, EType inType, uint64 inSize, uint inStride);
 	virtual							~ComputeBufferVK() override;
 
-	virtual void					Unmap() override;
+	bool							Initialize(const void *inData);
 
-	virtual Ref<ComputeBuffer>		CreateReadBackBuffer() const override;
+	virtual ComputeBufferResult		CreateReadBackBuffer() const override;
 
 	VkBuffer						GetBufferCPU() const									{ return mBufferCPU.mBuffer; }
 	VkBuffer						GetBufferGPU() const									{ return mBufferGPU.mBuffer; }
@@ -37,6 +37,7 @@ public:
 
 private:
 	virtual void *					MapInternal(EMode inMode) override;
+	virtual void					UnmapInternal() override;
 
 	ComputeSystemVK *				mComputeSystem;
 	mutable BufferVK				mBufferCPU;

+ 9 - 9
Jolt/Compute/VK/ComputeQueueVK.cpp

@@ -31,7 +31,7 @@ ComputeQueueVK::~ComputeQueueVK()
 		vkDestroyFence(device, mFence, nullptr);
 }
 
-bool ComputeQueueVK::Initialize(uint32 inComputeQueueIndex)
+bool ComputeQueueVK::Initialize(uint32 inComputeQueueIndex, ComputeQueueResult &outResult)
 {
 	// Get the queue
 	VkDevice device = mComputeSystem->GetDevice();
@@ -42,7 +42,7 @@ bool ComputeQueueVK::Initialize(uint32 inComputeQueueIndex)
 	pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
 	pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
 	pool_info.queueFamilyIndex = inComputeQueueIndex;
-	if (VKFailed(vkCreateCommandPool(device, &pool_info, nullptr, &mCommandPool)))
+	if (VKFailed(vkCreateCommandPool(device, &pool_info, nullptr, &mCommandPool), outResult))
 		return false;
 
 	// Create descriptor pool
@@ -55,7 +55,7 @@ bool ComputeQueueVK::Initialize(uint32 inComputeQueueIndex)
 	descriptor_info.poolSizeCount = std::size(descriptor_pool_sizes);
 	descriptor_info.pPoolSizes = descriptor_pool_sizes;
 	descriptor_info.maxSets = 256;
-	if (VKFailed(vkCreateDescriptorPool(device, &descriptor_info, nullptr, &mDescriptorPool)))
+	if (VKFailed(vkCreateDescriptorPool(device, &descriptor_info, nullptr, &mDescriptorPool), outResult))
 		return false;
 
 	// Create a command buffer
@@ -64,13 +64,13 @@ bool ComputeQueueVK::Initialize(uint32 inComputeQueueIndex)
 	alloc_info.commandPool = mCommandPool;
 	alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
 	alloc_info.commandBufferCount = 1;
-	if (VKFailed(vkAllocateCommandBuffers(device, &alloc_info, &mCommandBuffer)))
+	if (VKFailed(vkAllocateCommandBuffers(device, &alloc_info, &mCommandBuffer), outResult))
 		return false;
 
 	// Create a fence
 	VkFenceCreateInfo fence_info = {};
 	fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
-	if (VKFailed(vkCreateFence(device, &fence_info, nullptr, &mFence)))
+	if (VKFailed(vkCreateFence(device, &fence_info, nullptr, &mFence), outResult))
 		return false;
 
 	return true;
@@ -118,11 +118,11 @@ void ComputeQueueVK::SetConstantBuffer(const char *inName, const ComputeBuffer *
 void ComputeQueueVK::SyncCPUToGPU(const ComputeBufferVK *inBuffer)
 {
 	// Ensure that any CPU writes are visible to the GPU
-	if (inBuffer->SyncCPUToGPU(mCommandBuffer))
+	if (inBuffer->SyncCPUToGPU(mCommandBuffer)
+		&& (inBuffer->GetType() == ComputeBuffer::EType::Buffer || inBuffer->GetType()  == ComputeBuffer::EType::RWBuffer))
 	{
 		// After the first upload, the CPU buffer is no longer needed for Buffer and RWBuffer types
-		if (inBuffer->GetType() == ComputeBuffer::EType::Buffer || inBuffer->GetType()  == ComputeBuffer::EType::RWBuffer)
-			mDelayedFreedBuffers.push_back(inBuffer->ReleaseBufferCPU());
+		mDelayedFreedBuffers.push_back(inBuffer->ReleaseBufferCPU());
 	}
 }
 
@@ -176,7 +176,7 @@ void ComputeQueueVK::ScheduleReadback(ComputeBuffer *inDst, const ComputeBuffer
 		return;
 
 	const ComputeBufferVK *src_vk = static_cast<const ComputeBufferVK *>(inSrc);
-	ComputeBufferVK *dst_vk = static_cast<ComputeBufferVK *>(inDst);
+	const ComputeBufferVK *dst_vk = static_cast<ComputeBufferVK *>(inDst);
 
 	// Barrier to start reading from GPU buffer and writing to CPU buffer
 	src_vk->Barrier(mCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT, false);

+ 2 - 2
Jolt/Compute/VK/ComputeQueueVK.h

@@ -25,11 +25,11 @@ public:
 	JPH_OVERRIDE_NEW_DELETE
 
 	/// Constructor / Destructor
-										ComputeQueueVK(ComputeSystemVK *inComputeSystem) : mComputeSystem(inComputeSystem) { }
+	explicit							ComputeQueueVK(ComputeSystemVK *inComputeSystem) : mComputeSystem(inComputeSystem) { }
 	virtual								~ComputeQueueVK() override;
 
 	/// Initialize the queue
-	bool								Initialize(uint32 inComputeQueueIndex);
+	bool								Initialize(uint32 inComputeQueueIndex, ComputeQueueResult &outResult);
 
 	// See: ComputeQueue
 	virtual void						SetShader(const ComputeShader *inShader) override;

+ 6 - 6
Jolt/Compute/VK/ComputeShaderVK.cpp

@@ -25,7 +25,7 @@ ComputeShaderVK::~ComputeShaderVK()
 		vkDestroyPipeline(mDevice, mPipeline, nullptr);
 }
 
-bool ComputeShaderVK::Initialize(const Array<uint8> &inSPVCode, VkBuffer inDummyBuffer)
+bool ComputeShaderVK::Initialize(const Array<uint8> &inSPVCode, VkBuffer inDummyBuffer, ComputeShaderResult &outResult)
 {
 	const uint32 *spv_words = reinterpret_cast<const uint32 *>(inSPVCode.data());
 	size_t spv_word_count = inSPVCode.size() / sizeof(uint32);
@@ -120,7 +120,7 @@ bool ComputeShaderVK::Initialize(const Array<uint8> &inSPVCode, VkBuffer inDummy
 
 	// Build name to binding map
 	UnorderedMap<String, std::pair<uint32, VkDescriptorType>> name_to_binding;
-	for (const UnorderedMap<uint32, uint32>::value_type& entry : id_to_binding)
+	for (const UnorderedMap<uint32, uint32>::value_type &entry : id_to_binding)
 	{
 		uint32 target_id = entry.first;
 		uint32 binding = entry.second;
@@ -186,7 +186,7 @@ bool ComputeShaderVK::Initialize(const Array<uint8> &inSPVCode, VkBuffer inDummy
 		layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
 		layout_info.bindingCount = (uint32)mLayoutBindings.size();
 		layout_info.pBindings = mLayoutBindings.data();
-		if (VKFailed(vkCreateDescriptorSetLayout(mDevice, &layout_info, nullptr, &mDescriptorSetLayout)))
+		if (VKFailed(vkCreateDescriptorSetLayout(mDevice, &layout_info, nullptr, &mDescriptorSetLayout), outResult))
 			return false;
 	}
 
@@ -195,7 +195,7 @@ bool ComputeShaderVK::Initialize(const Array<uint8> &inSPVCode, VkBuffer inDummy
 	pl_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
 	pl_info.setLayoutCount = mDescriptorSetLayout != VK_NULL_HANDLE ? 1 : 0;
 	pl_info.pSetLayouts = mDescriptorSetLayout != VK_NULL_HANDLE ? &mDescriptorSetLayout : nullptr;
-	if (VKFailed(vkCreatePipelineLayout(mDevice, &pl_info, nullptr, &mPipelineLayout)))
+	if (VKFailed(vkCreatePipelineLayout(mDevice, &pl_info, nullptr, &mPipelineLayout), outResult))
 		return false;
 
 	// Create shader module
@@ -203,7 +203,7 @@ bool ComputeShaderVK::Initialize(const Array<uint8> &inSPVCode, VkBuffer inDummy
 	create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
 	create_info.codeSize = inSPVCode.size();
 	create_info.pCode = spv_words;
-	if (VKFailed(vkCreateShaderModule(mDevice, &create_info, nullptr, &mShaderModule)))
+	if (VKFailed(vkCreateShaderModule(mDevice, &create_info, nullptr, &mShaderModule), outResult))
 		return false;
 
 	// Create compute pipeline
@@ -214,7 +214,7 @@ bool ComputeShaderVK::Initialize(const Array<uint8> &inSPVCode, VkBuffer inDummy
 	pipe_info.stage.module = mShaderModule;
 	pipe_info.stage.pName = "main";
 	pipe_info.layout = mPipelineLayout;
-	if (VKFailed(vkCreateComputePipelines(mDevice, VK_NULL_HANDLE, 1, &pipe_info, nullptr, &mPipeline)))
+	if (VKFailed(vkCreateComputePipelines(mDevice, VK_NULL_HANDLE, 1, &pipe_info, nullptr, &mPipeline), outResult))
 		return false;
 
 	return true;

+ 1 - 1
Jolt/Compute/VK/ComputeShaderVK.h

@@ -24,7 +24,7 @@ public:
 	virtual								~ComputeShaderVK() override;
 
 	/// Initialize from SPIR-V code
-	bool								Initialize(const Array<uint8> &inSPVCode, VkBuffer inDummyBuffer);
+	bool								Initialize(const Array<uint8> &inSPVCode, VkBuffer inDummyBuffer, ComputeShaderResult &outResult);
 
 	/// Get index of parameter in buffer infos
 	uint32								NameToBufferInfoIndex(const char *inName) const;

+ 40 - 17
Jolt/Compute/VK/ComputeSystemVK.cpp

@@ -13,7 +13,7 @@
 
 JPH_NAMESPACE_BEGIN
 
-bool ComputeSystemVK::Initialize(VkPhysicalDevice inPhysicalDevice, VkDevice inDevice, uint32 inComputeQueueIndex)
+bool ComputeSystemVK::Initialize(VkPhysicalDevice inPhysicalDevice, VkDevice inDevice, uint32 inComputeQueueIndex, ComputeSystemResult &outResult)
 {
 	mPhysicalDevice = inPhysicalDevice;
 	mDevice = inDevice;
@@ -23,10 +23,17 @@ bool ComputeSystemVK::Initialize(VkPhysicalDevice inPhysicalDevice, VkDevice inD
 	mVkSetDebugUtilsObjectNameEXT = reinterpret_cast<PFN_vkSetDebugUtilsObjectNameEXT>(reinterpret_cast<void *>(vkGetDeviceProcAddr(mDevice, "vkSetDebugUtilsObjectNameEXT")));
 
 	if (!InitializeMemory())
+	{
+		outResult.SetError("Failed to initialize memory subsystem");
 		return false;
+	}
 
 	// Create the dummy buffer. This is used to bind to shaders for which we have no buffer. We can't rely on VK_EXT_robustness2 being available to set nullDescriptor = VK_TRUE (it is unavailable on macOS).
-	CreateBuffer(1024, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mDummyBuffer);
+	if (!CreateBuffer(1024, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, mDummyBuffer))
+	{
+		outResult.SetError("Failed to create dummy buffer");
+		return false;
+	}
 
 	return true;
 }
@@ -42,21 +49,24 @@ void ComputeSystemVK::Shutdown()
 	ShutdownMemory();
 }
 
-Ref<ComputeShader> ComputeSystemVK::CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ)
+ComputeShaderResult ComputeSystemVK::CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ)
 {
+	ComputeShaderResult result;
+
 	// Read shader source file
 	Array<uint8> data;
 	String file_name = String(inName) + ".spv";
-	if (!mShaderLoader(file_name.c_str(), data))
-		return nullptr;
-
-	Ref<ComputeShaderVK> shader = new ComputeShaderVK(mDevice, inGroupSizeX, inGroupSizeY, inGroupSizeZ);
-	if (!shader->Initialize(data, mDummyBuffer.mBuffer))
+	String error;
+	if (!mShaderLoader(file_name.c_str(), data, error))
 	{
-		Trace("Failed to create compute shader: %s", file_name.c_str());
-		return nullptr;
+		result.SetError(error);
+		return result;
 	}
 
+	Ref<ComputeShaderVK> shader = new ComputeShaderVK(mDevice, inGroupSizeX, inGroupSizeY, inGroupSizeZ);
+	if (!shader->Initialize(data, mDummyBuffer.mBuffer, result))
+		return result;
+
 	// Name the pipeline so we can easily find it in a profile
 	if (mVkSetDebugUtilsObjectNameEXT != nullptr)
 	{
@@ -69,20 +79,33 @@ Ref<ComputeShader> ComputeSystemVK::CreateComputeShader(const char *inName, uint
 		mVkSetDebugUtilsObjectNameEXT(mDevice, &info);
 	}
 
-	return shader.GetPtr();
+	result.Set(shader.GetPtr());
+	return result;
 }
 
-Ref<ComputeBuffer> ComputeSystemVK::CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData)
+ComputeBufferResult ComputeSystemVK::CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData)
 {
-	return new ComputeBufferVK(this, inType, inSize, inStride, inData);
+	ComputeBufferResult result;
+
+	Ref<ComputeBufferVK> buffer = new ComputeBufferVK(this, inType, inSize, inStride);
+	if (!buffer->Initialize(inData))
+	{
+		result.SetError("Failed to create compute buffer");
+		return result;
+	}
+
+	result.Set(buffer.GetPtr());
+	return result;
 }
 
-Ref<ComputeQueue> ComputeSystemVK::CreateComputeQueue()
+ComputeQueueResult ComputeSystemVK::CreateComputeQueue()
 {
+	ComputeQueueResult result;
 	Ref<ComputeQueueVK> q = new ComputeQueueVK(this);
-	if (!q->Initialize(mComputeQueueIndex))
-		return nullptr;
-	return q.GetPtr();
+	if (!q->Initialize(mComputeQueueIndex, result))
+		return result;
+	result.Set(q.GetPtr());
+	return result;
 }
 
 JPH_NAMESPACE_END

+ 5 - 5
Jolt/Compute/VK/ComputeSystemVK.h

@@ -20,19 +20,19 @@ public:
 	JPH_OVERRIDE_NEW_DELETE
 
 	// Initialize / shutdown the compute system
-	bool							Initialize(VkPhysicalDevice inPhysicalDevice, VkDevice inDevice, uint32 inComputeQueueIndex);
+	bool							Initialize(VkPhysicalDevice inPhysicalDevice, VkDevice inDevice, uint32 inComputeQueueIndex, ComputeSystemResult &outResult);
 	void							Shutdown();
 
 	// See: ComputeSystem
-	virtual Ref<ComputeShader>		CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ) override;
-	virtual Ref<ComputeBuffer>		CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData = nullptr) override;
-	virtual Ref<ComputeQueue>		CreateComputeQueue() override;
+	virtual ComputeShaderResult		CreateComputeShader(const char *inName, uint32 inGroupSizeX, uint32 inGroupSizeY, uint32 inGroupSizeZ) override;
+	virtual ComputeBufferResult		CreateComputeBuffer(ComputeBuffer::EType inType, uint64 inSize, uint inStride, const void *inData = nullptr) override;
+	virtual ComputeQueueResult		CreateComputeQueue() override;
 
 	/// Access to the Vulkan device
 	VkDevice						GetDevice() const												{ return mDevice; }
 
 	/// Allow the application to override buffer creation and memory mapping in case it uses its own allocator
-	virtual void					CreateBuffer(VkDeviceSize inSize, VkBufferUsageFlags inUsage, VkMemoryPropertyFlags inProperties, BufferVK &outBuffer) = 0;
+	virtual bool					CreateBuffer(VkDeviceSize inSize, VkBufferUsageFlags inUsage, VkMemoryPropertyFlags inProperties, BufferVK &outBuffer) = 0;
 	virtual void					FreeBuffer(BufferVK &ioBuffer) = 0;
 	virtual void *					MapBuffer(BufferVK &ioBuffer) = 0;
 	virtual void					UnmapBuffer(BufferVK &ioBuffer) = 0;

+ 40 - 23
Jolt/Compute/VK/ComputeSystemVKImpl.cpp

@@ -40,7 +40,7 @@ ComputeSystemVKImpl::~ComputeSystemVKImpl()
 		vkDestroyInstance(mInstance, nullptr);
 }
 
-bool ComputeSystemVKImpl::Initialize()
+bool ComputeSystemVKImpl::Initialize(ComputeSystemResult &outResult)
 {
 	// Required instance extensions
 	Array<const char *> required_instance_extensions;
@@ -62,11 +62,11 @@ bool ComputeSystemVKImpl::Initialize()
 
 	// Query supported instance extensions
 	uint32 instance_extension_count = 0;
-	if (VKFailed(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr)))
+	if (VKFailed(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr), outResult))
 		return false;
 	Array<VkExtensionProperties> instance_extensions;
 	instance_extensions.resize(instance_extension_count);
-	if (VKFailed(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, instance_extensions.data())))
+	if (VKFailed(vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, instance_extensions.data()), outResult))
 		return false;
 
 	// Query supported validation layers
@@ -115,14 +115,14 @@ bool ComputeSystemVKImpl::Initialize()
 
 	instance_create_info.enabledExtensionCount = (uint32)required_instance_extensions.size();
 	instance_create_info.ppEnabledExtensionNames = required_instance_extensions.data();
-	if (VKFailed(vkCreateInstance(&instance_create_info, nullptr, &mInstance)))
+	if (VKFailed(vkCreateInstance(&instance_create_info, nullptr, &mInstance), outResult))
 		return false;
 
 #ifdef JPH_DEBUG
 	// Finalize debug messenger callback
 	PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)(std::uintptr_t)vkGetInstanceProcAddr(mInstance, "vkCreateDebugUtilsMessengerEXT");
 	if (vkCreateDebugUtilsMessengerEXT != nullptr)
-		if (VKFailed(vkCreateDebugUtilsMessengerEXT(mInstance, &messenger_create_info, nullptr, &mDebugMessenger)))
+		if (VKFailed(vkCreateDebugUtilsMessengerEXT(mInstance, &messenger_create_info, nullptr, &mDebugMessenger), outResult))
 			return false;
 #endif
 
@@ -131,11 +131,11 @@ bool ComputeSystemVKImpl::Initialize()
 
 	// Select device
 	uint32 device_count = 0;
-	if (VKFailed(vkEnumeratePhysicalDevices(mInstance, &device_count, nullptr)))
+	if (VKFailed(vkEnumeratePhysicalDevices(mInstance, &device_count, nullptr), outResult))
 		return false;
 	Array<VkPhysicalDevice> devices;
 	devices.resize(device_count);
-	if (VKFailed(vkEnumeratePhysicalDevices(mInstance, &device_count, devices.data())))
+	if (VKFailed(vkEnumeratePhysicalDevices(mInstance, &device_count, devices.data()), outResult))
 		return false;
 	struct Device
 	{
@@ -229,7 +229,10 @@ bool ComputeSystemVKImpl::Initialize()
 		available_devices.push_back({ device, properties.deviceName, selected_format, graphics_queue, present_queue, compute_queue, score });
 	}
 	if (available_devices.empty())
+	{
+		outResult.SetError("No suitable Vulkan device found");
 		return false;
+	}
 
 	// Sort the devices by score
 	QuickSort(available_devices.begin(), available_devices.end(), [](const Device &inLHS, const Device &inRHS) {
@@ -240,20 +243,32 @@ bool ComputeSystemVKImpl::Initialize()
 	// Create device
 	float queue_priority = 1.0f;
 	VkDeviceQueueCreateInfo queue_create_info[3] = {};
-	for (size_t i = 0; i < std::size(queue_create_info); ++i)
+	for (VkDeviceQueueCreateInfo &q : queue_create_info)
 	{
-		queue_create_info[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
-		queue_create_info[i].queueCount = 1;
-		queue_create_info[i].pQueuePriorities = &queue_priority;
+		q.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+		q.queueCount = 1;
+		q.pQueuePriorities = &queue_priority;
 	}
 	uint32 num_queues = 0;
 	queue_create_info[num_queues++].queueFamilyIndex = selected_device.mGraphicsQueueIndex;
+	bool found = false;
 	for (uint32 i = 0; i < num_queues; ++i)
-		if (queue_create_info[i].queueFamilyIndex != selected_device.mPresentQueueIndex)
-			queue_create_info[num_queues++].queueFamilyIndex = selected_device.mPresentQueueIndex;
+		if (queue_create_info[i].queueFamilyIndex == selected_device.mPresentQueueIndex)
+		{
+			found = true;
+			break;
+		}
+	if (!found)
+		queue_create_info[num_queues++].queueFamilyIndex = selected_device.mPresentQueueIndex;
+	found = false;
 	for (uint32 i = 0; i < num_queues; ++i)
-		if (queue_create_info[i].queueFamilyIndex != selected_device.mComputeQueueIndex)
-			queue_create_info[num_queues++].queueFamilyIndex = selected_device.mComputeQueueIndex;
+		if (queue_create_info[i].queueFamilyIndex == selected_device.mComputeQueueIndex)
+		{
+			found = true;
+			break;
+		}
+	if (!found)
+		queue_create_info[num_queues++].queueFamilyIndex = selected_device.mComputeQueueIndex;
 
 	VkPhysicalDeviceScalarBlockLayoutFeatures enable_scalar_block = {};
 	enable_scalar_block.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES;
@@ -277,7 +292,7 @@ bool ComputeSystemVKImpl::Initialize()
 	device_create_info.pEnabledFeatures = nullptr;
 
 	VkDevice device = VK_NULL_HANDLE;
-	if (VKFailed(vkCreateDevice(selected_device.mPhysicalDevice, &device_create_info, nullptr, &device)))
+	if (VKFailed(vkCreateDevice(selected_device.mPhysicalDevice, &device_create_info, nullptr, &device), outResult))
 		return false;
 
 	// Get the queues
@@ -290,17 +305,19 @@ bool ComputeSystemVKImpl::Initialize()
 	mSelectedFormat = selected_device.mFormat;
 
 	// Initialize the compute system
-	return ComputeSystemVK::Initialize(selected_device.mPhysicalDevice, device, selected_device.mComputeQueueIndex);
+	return ComputeSystemVK::Initialize(selected_device.mPhysicalDevice, device, selected_device.mComputeQueueIndex, outResult);
 }
 
-ComputeSystem *CreateComputeSystemVK()
+ComputeSystemResult CreateComputeSystemVK()
 {
-	ComputeSystemVKImpl *compute = new ComputeSystemVKImpl;
-	if (compute->Initialize())
-		return compute;
+	ComputeSystemResult result;
+
+	Ref<ComputeSystemVKImpl> compute = new ComputeSystemVKImpl;
+	if (!compute->Initialize(result))
+		return result;
 
-	delete compute;
-	return nullptr;
+	result.Set(compute.GetPtr());
+	return result;
 }
 
 JPH_NAMESPACE_END

+ 1 - 1
Jolt/Compute/VK/ComputeSystemVKImpl.h

@@ -20,7 +20,7 @@ public:
 	virtual							~ComputeSystemVKImpl() override;
 
 	/// Initialize the compute system
-	bool							Initialize();
+	bool							Initialize(ComputeSystemResult &outResult);
 
 protected:
 	/// Override to perform actions once the instance has been created

+ 10 - 11
Jolt/Compute/VK/ComputeSystemVKWithAllocator.cpp

@@ -24,14 +24,14 @@ bool ComputeSystemVKWithAllocator::InitializeMemory()
 void ComputeSystemVKWithAllocator::ShutdownMemory()
 {
 	// Free all memory
-	for (MemoryCache::value_type &mc : mMemoryCache)
-		for (Memory &m : mc.second)
+	for (const MemoryCache::value_type &mc : mMemoryCache)
+		for (const Memory &m : mc.second)
 			if (m.mOffset == 0)
 				FreeMemory(*m.mMemory);
 	mMemoryCache.clear();
 }
 
-uint32 ComputeSystemVKWithAllocator::FindMemoryType(uint32 inTypeFilter, VkMemoryPropertyFlags inProperties)
+uint32 ComputeSystemVKWithAllocator::FindMemoryType(uint32 inTypeFilter, VkMemoryPropertyFlags inProperties) const
 {
 	for (uint32 i = 0; i < mMemoryProperties.memoryTypeCount; i++)
 		if ((inTypeFilter & (1 << i))
@@ -62,7 +62,7 @@ void ComputeSystemVKWithAllocator::FreeMemory(MemoryVK &ioMemory)
 	ioMemory.mMemory = VK_NULL_HANDLE;
 }
 
-void ComputeSystemVKWithAllocator::CreateBuffer(VkDeviceSize inSize, VkBufferUsageFlags inUsage, VkMemoryPropertyFlags inProperties, BufferVK &outBuffer)
+bool ComputeSystemVKWithAllocator::CreateBuffer(VkDeviceSize inSize, VkBufferUsageFlags inUsage, VkMemoryPropertyFlags inProperties, BufferVK &outBuffer)
 {
 	// Create a new buffer
 	outBuffer.mSize = inSize;
@@ -75,7 +75,7 @@ void ComputeSystemVKWithAllocator::CreateBuffer(VkDeviceSize inSize, VkBufferUsa
 	if (VKFailed(vkCreateBuffer(mDevice, &create_info, nullptr, &outBuffer.mBuffer)))
 	{
 		outBuffer.mBuffer = VK_NULL_HANDLE;
-		return;
+		return false;
 	}
 
 	VkMemoryRequirements mem_requirements;
@@ -118,6 +118,7 @@ void ComputeSystemVKWithAllocator::CreateBuffer(VkDeviceSize inSize, VkBufferUsa
 
 	// Bind the memory to the buffer
 	vkBindBufferMemory(mDevice, outBuffer.mBuffer, outBuffer.mMemory->mMemory, outBuffer.mOffset);
+	return true;
 }
 
 void ComputeSystemVKWithAllocator::FreeBuffer(BufferVK &ioBuffer)
@@ -141,13 +142,11 @@ void ComputeSystemVKWithAllocator::FreeBuffer(BufferVK &ioBuffer)
 
 void *ComputeSystemVKWithAllocator::MapBuffer(BufferVK& ioBuffer)
 {
-	if (++ioBuffer.mMemory->mMappedCount == 1)
+	if (++ioBuffer.mMemory->mMappedCount == 1
+		&& VKFailed(vkMapMemory(mDevice, ioBuffer.mMemory->mMemory, 0, VK_WHOLE_SIZE, 0, &ioBuffer.mMemory->mMappedPtr)))
 	{
-		if (VKFailed(vkMapMemory(mDevice, ioBuffer.mMemory->mMemory, 0, VK_WHOLE_SIZE, 0, &ioBuffer.mMemory->mMappedPtr)))
-		{
-			ioBuffer.mMemory->mMappedCount = 0;
-			return nullptr;
-		}
+		ioBuffer.mMemory->mMappedCount = 0;
+		return nullptr;
 	}
 
 	return static_cast<uint8 *>(ioBuffer.mMemory->mMappedPtr) + ioBuffer.mOffset;

+ 2 - 2
Jolt/Compute/VK/ComputeSystemVKWithAllocator.h

@@ -19,7 +19,7 @@ public:
 	JPH_OVERRIDE_NEW_DELETE
 
 	/// Allow the application to override buffer creation and memory mapping in case it uses its own allocator
-	virtual void					CreateBuffer(VkDeviceSize inSize, VkBufferUsageFlags inUsage, VkMemoryPropertyFlags inProperties, BufferVK &outBuffer) override;
+	virtual bool					CreateBuffer(VkDeviceSize inSize, VkBufferUsageFlags inUsage, VkMemoryPropertyFlags inProperties, BufferVK &outBuffer) override;
 	virtual void					FreeBuffer(BufferVK &ioBuffer) override;
 	virtual void *					MapBuffer(BufferVK &ioBuffer) override;
 	virtual void					UnmapBuffer(BufferVK &ioBuffer) override;
@@ -28,7 +28,7 @@ protected:
 	virtual bool					InitializeMemory() override;
 	virtual void					ShutdownMemory() override;
 
-	uint32							FindMemoryType(uint32 inTypeFilter, VkMemoryPropertyFlags inProperties);
+	uint32							FindMemoryType(uint32 inTypeFilter, VkMemoryPropertyFlags inProperties) const;
 	void							AllocateMemory(VkDeviceSize inSize, uint32 inMemoryTypeBits, VkMemoryPropertyFlags inProperties, MemoryVK &ioMemory);
 	void							FreeMemory(MemoryVK &ioMemory);
 

+ 14 - 0
Jolt/Compute/VK/IncludeVK.h

@@ -4,6 +4,8 @@
 
 #pragma once
 
+#include <Jolt/Core/StringTools.h>
+
 #ifdef JPH_USE_VK
 
 JPH_SUPPRESS_WARNINGS_STD_BEGIN
@@ -25,6 +27,18 @@ inline bool VKFailed(VkResult inResult)
 	return true;
 }
 
+template <class Result>
+inline bool VKFailed(VkResult inResult, Result &outResult)
+{
+	if (inResult == VK_SUCCESS)
+		return false;
+
+	String error = StringFormat("Vulkan call failed with error code: %d", (int)inResult);
+	outResult.SetError(error);
+	JPH_ASSERT(false);
+	return true;
+}
+
 JPH_NAMESPACE_END
 
 #endif // JPH_USE_VK

+ 1 - 1
Jolt/Core/Result.h

@@ -8,7 +8,7 @@ JPH_NAMESPACE_BEGIN
 
 /// Helper class that either contains a valid result or an error
 template <class Type>
-class Result
+class [[nodiscard]] Result
 {
 public:
 	/// Default constructor

+ 5 - 2
Samples/SamplesApp.cpp

@@ -472,7 +472,7 @@ SamplesApp::SamplesApp(const String &inCommandLine) :
 	mJobSystemValidating = new JobSystemSingleThreaded(cMaxPhysicsJobs);
 
 	// Set shader loader
-	mRenderer->GetComputeSystem().mShaderLoader = [](const char *inName, Array<uint8> &outData) {
+	mRenderer->GetComputeSystem().mShaderLoader = [](const char *inName, Array<uint8> &outData, String &outError) {
 	#ifdef JPH_PLATFORM_MACOS
 		// In macOS the shaders are copied to the bundle
 		String base_path = "Jolt/Shaders/";
@@ -485,7 +485,10 @@ SamplesApp::SamplesApp(const String &inCommandLine) :
 	};
 
 	// Create compute queue
-	mComputeQueue = mRenderer->GetComputeSystem().CreateComputeQueue();
+	ComputeQueueResult queue_result = mRenderer->GetComputeSystem().CreateComputeQueue();
+	if (queue_result.HasError())
+		FatalError(queue_result.GetError().c_str());
+	mComputeQueue = queue_result.Get();
 
 	{
 		// Disable allocation checking

+ 20 - 6
TestFramework/Renderer/DX12/RendererDX12.cpp

@@ -125,8 +125,9 @@ void RendererDX12::Initialize(ApplicationWindow *inWindow)
 {
 	Renderer::Initialize(inWindow);
 
-	if (!ComputeSystemDX12Impl::Initialize())
-		FatalError("Failed to initialize DirectX");
+	ComputeSystemResult result;
+	if (!ComputeSystemDX12Impl::Initialize(result))
+		FatalError(result.GetError().c_str());
 
 	// Disable full screen transitions
 	IDXGIFactory4 *factory = GetDXGIFactory();
@@ -252,14 +253,27 @@ void RendererDX12::Initialize(ApplicationWindow *inWindow)
 		FatalErrorIfFailed(HRESULT_FROM_WIN32(GetLastError()));
 
 	// Initialize the queue used to upload resources to the GPU
-	mUploadQueue.Initialize(device, D3D12_COMMAND_LIST_TYPE_DIRECT);
+	ComputeQueueResult queue_result;
+	if (!mUploadQueue.Initialize(device, D3D12_COMMAND_LIST_TYPE_DIRECT, queue_result))
+		FatalError(queue_result.GetError().c_str());
 
 	// Create constant buffer. One per frame to avoid overwriting the constant buffer while the GPU is still using it.
 	for (uint n = 0; n < cFrameCount; ++n)
 	{
-		mVertexShaderConstantBufferProjection[n] = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(VertexShaderConstantBuffer));
-		mVertexShaderConstantBufferOrtho[n] = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(VertexShaderConstantBuffer));
-		mPixelShaderConstantBuffer[n] = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(PixelShaderConstantBuffer));
+		ComputeBufferResult buffer_result = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(VertexShaderConstantBuffer));
+		if (buffer_result.HasError())
+			FatalError(buffer_result.GetError().c_str());
+		mVertexShaderConstantBufferProjection[n] = buffer_result.Get();
+
+		buffer_result = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(VertexShaderConstantBuffer));
+		if (buffer_result.HasError())
+			FatalError(buffer_result.GetError().c_str());
+		mVertexShaderConstantBufferOrtho[n] = buffer_result.Get();
+
+		buffer_result = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(PixelShaderConstantBuffer));
+		if (buffer_result.HasError())
+			FatalError(buffer_result.GetError().c_str());
+		mPixelShaderConstantBuffer[n] = buffer_result.Get();
 	}
 
 	// Create depth only texture (no color buffer, as seen from light)

+ 17 - 5
TestFramework/Renderer/VK/RendererVK.cpp

@@ -118,8 +118,9 @@ void RendererVK::Initialize(ApplicationWindow *inWindow)
 	// Flip the sign of the projection matrix
 	mPerspectiveYSign = -1.0f;
 
-	if (!ComputeSystemVKImpl::Initialize())
-		FatalError("Unable to initialize Vulkan");
+	ComputeSystemResult result;
+	if (!ComputeSystemVKImpl::Initialize(result))
+		FatalError(result.GetError().c_str());
 
 	// Check if fill mode non solid is supported
 	VkPhysicalDeviceFeatures2 supported_features2 = {};
@@ -151,9 +152,20 @@ void RendererVK::Initialize(ApplicationWindow *inWindow)
 	// Create constant buffer. One per frame to avoid overwriting the constant buffer while the GPU is still using it.
 	for (uint n = 0; n < cFrameCount; ++n)
 	{
-		mVertexShaderConstantBufferProjection[n] = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(VertexShaderConstantBuffer));
-		mVertexShaderConstantBufferOrtho[n] = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(VertexShaderConstantBuffer));
-		mPixelShaderConstantBuffer[n] = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(PixelShaderConstantBuffer));
+		ComputeBufferResult buffer_result = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(VertexShaderConstantBuffer));
+		if (buffer_result.HasError())
+			FatalError(buffer_result.GetError().c_str());
+		mVertexShaderConstantBufferProjection[n] = buffer_result.Get();
+
+		buffer_result = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(VertexShaderConstantBuffer));
+		if (buffer_result.HasError())
+			FatalError(buffer_result.GetError().c_str());
+		mVertexShaderConstantBufferOrtho[n] = buffer_result.Get();
+
+		buffer_result = CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(PixelShaderConstantBuffer));
+		if (buffer_result.HasError())
+			FatalError(buffer_result.GetError().c_str());
+		mPixelShaderConstantBuffer[n] = buffer_result.Get();
 	}
 
 	// Create descriptor set layout for the uniform buffers

+ 79 - 23
UnitTests/Compute/ComputeTests.cpp

@@ -24,9 +24,19 @@ JPH_SUPPRESS_WARNINGS_STD_END
 
 TEST_SUITE("ComputeTests")
 {
+	static const char *cInvalidShaderName = "InvalidShader";
+	static const char *cInvalidShaderCode = "invalid_shader_code";
+
 	static void RunTests(ComputeSystem *inComputeSystem)
 	{
-		inComputeSystem->mShaderLoader = [](const char *inName, Array<uint8> &outData) {
+		inComputeSystem->mShaderLoader = [](const char *inName, Array<uint8> &outData, String &outError) {
+			// Special case to test what happens when an invalid file is returned
+			if (strstr(inName, cInvalidShaderName) != nullptr)
+			{
+				outData.assign(cInvalidShaderCode, cInvalidShaderCode + strlen(cInvalidShaderCode));
+				return true;
+			}
+
 		#if defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS)
 			// In macOS the shaders are copied to the bundle
 			CFBundleRef bundle = CFBundleGetMainBundle();
@@ -72,17 +82,31 @@ TEST_SUITE("ComputeTests")
 			// Open file
 			std::ifstream input((base_path + inName).c_str(), std::ios::in | std::ios::binary);
 			if (!input.is_open())
+			{
+				outError = String("Could not open shader file: ") + base_path + inName;
+				#if defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS)
+					outError += "\nThis can fail on macOS when dxc or spirv-cross could not be found so the shaders could not be compiled.";
+				#endif
 				return false;
+			}
 
 			// Read contents of file
 			input.seekg(0, ios_base::end);
 			ifstream::pos_type length = input.tellg();
 			input.seekg(0, ios_base::beg);
 			outData.resize(size_t(length));
+			if (length == 0)
+				return true;
 			input.read((char *)&outData[0], length);
 			return true;
 		};
 
+		// Test failing shader creation
+		{
+			ComputeShaderResult shader_result = inComputeSystem->CreateComputeShader("NonExistingShader", 64);
+			CHECK(shader_result.HasError());
+		}
+
 		constexpr uint32 cNumElements = 1234; // Not a multiple of cTestComputeGroupSize
 		constexpr uint32 cNumIterations = 10;
 		constexpr JPH_float3 cFloat3Value = JPH_float3(0, 0, 0);
@@ -93,11 +117,17 @@ TEST_SUITE("ComputeTests")
 		// Can't change context buffer while commands are queued, so create multiple constant buffers
 		Ref<ComputeBuffer> context[cNumIterations];
 		for (uint32 iter = 0; iter < cNumIterations; ++iter)
-			context[iter] = inComputeSystem->CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(TestComputeContext));
+		{
+			ComputeBufferResult buffer_result = inComputeSystem->CreateComputeBuffer(ComputeBuffer::EType::ConstantBuffer, 1, sizeof(TestComputeContext));
+			CHECK(!buffer_result.HasError());
+			context[iter] = buffer_result.Get();
+		}
 		CHECK(context != nullptr);
 
 		// Create an upload buffer
-		Ref<ComputeBuffer> upload_buffer = inComputeSystem->CreateComputeBuffer(ComputeBuffer::EType::UploadBuffer, 1, sizeof(uint32));
+		ComputeBufferResult upload_buffer_result = inComputeSystem->CreateComputeBuffer(ComputeBuffer::EType::UploadBuffer, 1, sizeof(uint32));
+		CHECK(!upload_buffer_result.HasError());
+		Ref<ComputeBuffer> upload_buffer = upload_buffer_result.Get();
 		CHECK(upload_buffer != nullptr);
 		uint32 *upload_data = upload_buffer->Map<uint32>(ComputeBuffer::EMode::Write);
 		upload_data[0] = cUploadValue;
@@ -108,28 +138,38 @@ TEST_SUITE("ComputeTests")
 		Array<uint32> optional_data(cNumElements);
 		for (uint32 &d : optional_data)
 			d = rnd();
-		Ref<ComputeBuffer> optional_buffer = inComputeSystem->CreateComputeBuffer(ComputeBuffer::EType::Buffer, cNumElements, sizeof(uint32), optional_data.data());
+		ComputeBufferResult optional_buffer_result = inComputeSystem->CreateComputeBuffer(ComputeBuffer::EType::Buffer, cNumElements, sizeof(uint32), optional_data.data());
+		CHECK(!optional_buffer_result.HasError());
+		Ref<ComputeBuffer> optional_buffer = optional_buffer_result.Get();
 		CHECK(optional_buffer != nullptr);
 
 		// Create a read-write buffer
-		Ref<ComputeBuffer> buffer = inComputeSystem->CreateComputeBuffer(ComputeBuffer::EType::RWBuffer, cNumElements, sizeof(uint32));
+		ComputeBufferResult buffer_result = inComputeSystem->CreateComputeBuffer(ComputeBuffer::EType::RWBuffer, cNumElements, sizeof(uint32));
+		CHECK(!buffer_result.HasError());
+		Ref<ComputeBuffer> buffer = buffer_result.Get();
 		CHECK(buffer != nullptr);
 
 		// Create a read back buffer
-		Ref<ComputeBuffer> readback_buffer = buffer->CreateReadBackBuffer();
+		ComputeBufferResult readback_buffer_result = buffer->CreateReadBackBuffer();
+		CHECK(!readback_buffer_result.HasError());
+		Ref<ComputeBuffer> readback_buffer = readback_buffer_result.Get();
 		CHECK(readback_buffer != nullptr);
 
 		// Create the shader
-		Ref<ComputeShader> shader = inComputeSystem->CreateComputeShader("TestCompute", cTestComputeGroupSize);
-		CHECK(shader != nullptr);
-		if (shader == nullptr)
+		ComputeShaderResult shader_result = inComputeSystem->CreateComputeShader("TestCompute", cTestComputeGroupSize);
+		if (shader_result.HasError())
 		{
-			Trace("Shader could not be loaded. This can fail on macOS when dxc or spirv-cross could not be found so the shaders could not be compiled.");
+			Trace("Shader could not be created: %s", shader_result.GetError().c_str());
 			return;
 		}
+		Ref<ComputeShader> shader = shader_result.Get();
+		CHECK(shader != nullptr);
 
 		// Create the queue
-		Ref<ComputeQueue> queue = inComputeSystem->CreateComputeQueue();
+		ComputeQueueResult queue_result = inComputeSystem->CreateComputeQueue();
+		CHECK(!queue_result.HasError());
+		Ref<ComputeQueue> queue = queue_result.Get();
+		CHECK(queue != nullptr);
 
 		// Schedule work
 		for (uint32 iter = 0; iter < cNumIterations; ++iter)
@@ -187,30 +227,46 @@ TEST_SUITE("ComputeTests")
 #ifdef JPH_USE_DX12
 	TEST_CASE("TestComputeDX12")
 	{
-		Ref<ComputeSystem> compute_system = CreateComputeSystemDX12();
-		CHECK(compute_system != nullptr);
-		if (compute_system != nullptr)
-			RunTests(compute_system);
+		ComputeSystemResult compute_system = CreateComputeSystemDX12();
+		CHECK(!compute_system.HasError());
+		if (!compute_system.HasError())
+		{
+			CHECK(compute_system.Get() != nullptr);
+			RunTests(compute_system.Get());
+
+			// Test failing shader compilation
+			{
+				ComputeShaderResult shader_result = compute_system.Get()->CreateComputeShader(cInvalidShaderName, 64);
+				CHECK(shader_result.HasError());
+				CHECK(strstr(shader_result.GetError().c_str(), cInvalidShaderCode) != nullptr); // Assume that the error message contains the invalid code
+			}
+		}
 	}
 #endif // JPH_USE_DX12
 
 #ifdef JPH_USE_MTL
 	TEST_CASE("TestComputeMTL")
 	{
-		Ref<ComputeSystem> compute_system = CreateComputeSystemMTL();
-		CHECK(compute_system != nullptr);
-		if (compute_system != nullptr)
-			RunTests(compute_system);
+		ComputeSystemResult compute_system = CreateComputeSystemMTL();
+		CHECK(!compute_system.HasError());
+		if (!compute_system.HasError())
+		{
+			CHECK(compute_system.Get() != nullptr);
+			RunTests(compute_system.Get());
+		}
 	}
 #endif // JPH_USE_MTL
 
 #ifdef JPH_USE_VK
 	TEST_CASE("TestComputeVK")
 	{
-		Ref<ComputeSystem> compute_system = CreateComputeSystemVK();
-		CHECK(compute_system != nullptr);
-		if (compute_system != nullptr)
-			RunTests(compute_system);
+		ComputeSystemResult compute_system = CreateComputeSystemVK();
+		CHECK(!compute_system.HasError());
+		if (!compute_system.HasError())
+		{
+			CHECK(compute_system.Get() != nullptr);
+			RunTests(compute_system.Get());
+		}
 	}
 #endif // JPH_USE_VK
 }