Initial commit of all the NXT integration.
More like squashed history, contributors were:
- Kai Ninomiya
- Corentin Wallez
diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt
new file mode 100644
index 0000000..cfb2a55
--- /dev/null
+++ b/src/backend/CMakeLists.txt
@@ -0,0 +1,133 @@
+# Copyright 2017 The NXT Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(COMMON_DIR ${CMAKE_CURRENT_SOURCE_DIR}/common)
+set(METAL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/metal)
+set(OPENGL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/opengl)
+set(TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests)
+
+list(APPEND BACKEND_SOURCES
+ ${COMMON_DIR}/BindGroup.cpp
+ ${COMMON_DIR}/BindGroup.h
+ ${COMMON_DIR}/BindGroupLayout.cpp
+ ${COMMON_DIR}/BindGroupLayout.h
+ ${COMMON_DIR}/BitSetIterator.h
+ ${COMMON_DIR}/Buffer.cpp
+ ${COMMON_DIR}/Buffer.h
+ ${COMMON_DIR}/CommandAllocator.cpp
+ ${COMMON_DIR}/CommandAllocator.h
+ ${COMMON_DIR}/CommandBuffer.cpp
+ ${COMMON_DIR}/CommandBuffer.h
+ ${COMMON_DIR}/Device.cpp
+ ${COMMON_DIR}/Device.h
+ ${COMMON_DIR}/Forward.h
+ ${COMMON_DIR}/InputState.cpp
+ ${COMMON_DIR}/InputState.h
+ ${COMMON_DIR}/Math.cpp
+ ${COMMON_DIR}/Math.h
+ ${COMMON_DIR}/PerStage.cpp
+ ${COMMON_DIR}/PerStage.h
+ ${COMMON_DIR}/Pipeline.cpp
+ ${COMMON_DIR}/Pipeline.h
+ ${COMMON_DIR}/PipelineLayout.cpp
+ ${COMMON_DIR}/PipelineLayout.h
+ ${COMMON_DIR}/Queue.cpp
+ ${COMMON_DIR}/Queue.h
+ ${COMMON_DIR}/RefCounted.cpp
+ ${COMMON_DIR}/RefCounted.h
+ ${COMMON_DIR}/Sampler.cpp
+ ${COMMON_DIR}/Sampler.h
+ ${COMMON_DIR}/ShaderModule.cpp
+ ${COMMON_DIR}/ShaderModule.h
+ ${COMMON_DIR}/Texture.cpp
+ ${COMMON_DIR}/Texture.h
+ ${COMMON_DIR}/ToBackend.h
+)
+
+# OpenGL Backend
+
+Generate(
+ LIB_NAME opengl_autogen
+ LIB_TYPE STATIC
+ PRINT_NAME "OpenGL backend autogenerated files"
+ COMMAND_LINE_ARGS
+ ${GENERATOR_COMMON_ARGS}
+ -T opengl
+)
+target_link_libraries(opengl_autogen glfw glad nxtcpp)
+target_include_directories(opengl_autogen PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+target_include_directories(opengl_autogen PUBLIC ${GENERATED_DIR})
+SetCXX14(opengl_autogen)
+SetPIC(opengl_autogen)
+
+list(APPEND BACKEND_SOURCES
+ ${OPENGL_DIR}/CommandBufferGL.cpp
+ ${OPENGL_DIR}/CommandBufferGL.h
+ ${OPENGL_DIR}/OpenGLBackend.cpp
+ ${OPENGL_DIR}/OpenGLBackend.h
+ ${OPENGL_DIR}/PipelineGL.cpp
+ ${OPENGL_DIR}/PipelineGL.h
+ ${OPENGL_DIR}/PipelineLayoutGL.cpp
+ ${OPENGL_DIR}/PipelineLayoutGL.h
+ ${OPENGL_DIR}/SamplerGL.cpp
+ ${OPENGL_DIR}/SamplerGL.h
+ ${OPENGL_DIR}/ShaderModuleGL.cpp
+ ${OPENGL_DIR}/ShaderModuleGL.h
+ ${OPENGL_DIR}/TextureGL.cpp
+ ${OPENGL_DIR}/TextureGL.h
+)
+
+# Metal Backend
+
+if (APPLE)
+ Generate(
+ LIB_NAME metal_autogen
+ LIB_TYPE STATIC
+ PRINT_NAME "Metal backend autogenerated files"
+ COMMAND_LINE_ARGS
+ ${GENERATOR_COMMON_ARGS}
+ -T metal
+ )
+ target_link_libraries(metal_autogen glfw glad nxtcpp "-framework QuartzCore" "-framework Metal")
+ target_include_directories(metal_autogen PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+ target_include_directories(metal_autogen PUBLIC ${GENERATED_DIR})
+ SetCXX14(metal_autogen)
+ SetPIC(metal_autogen)
+
+ list(APPEND BACKEND_SOURCES
+ ${METAL_DIR}/MetalBackend.mm
+ ${METAL_DIR}/MetalBackend.h
+ )
+endif()
+
+add_library(nxt_backend SHARED ${BACKEND_SOURCES})
+target_link_libraries(nxt_backend opengl_autogen glfw glad spirv-cross)
+if (APPLE)
+ target_link_libraries(nxt_backend metal_autogen)
+endif()
+target_include_directories(nxt_backend PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+SetCXX14(nxt_backend)
+
+add_executable(backend_unittests
+ ${TESTS_DIR}/BitSetIteratorTests.cpp
+ ${TESTS_DIR}/CommandAllocatorTests.cpp
+ ${TESTS_DIR}/MathTests.cpp
+ ${TESTS_DIR}/PerStageTests.cpp
+ ${TESTS_DIR}/RefCountedTests.cpp
+ ${TESTS_DIR}/ToBackendTests.cpp
+ ${TESTS_DIR}/UnittestsMain.cpp
+)
+target_link_libraries(backend_unittests nxt_backend gtest)
+target_include_directories(backend_unittests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+SetCXX14(backend_unittests)
diff --git a/src/backend/common/BindGroup.cpp b/src/backend/common/BindGroup.cpp
new file mode 100644
index 0000000..744325a
--- /dev/null
+++ b/src/backend/common/BindGroup.cpp
@@ -0,0 +1,213 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "BindGroup.h"
+
+#include "BindGroupLayout.h"
+#include "Buffer.h"
+#include "Device.h"
+#include "Texture.h"
+
+namespace backend {
+
+ // BindGroup
+
+ BindGroupBase::BindGroupBase(BindGroupBuilder* builder)
+ : layout(std::move(builder->layout)), usage(builder->usage), bindings(std::move(builder->bindings)) {
+ }
+
+ const BindGroupLayoutBase* BindGroupBase::GetLayout() const {
+ return layout.Get();
+ }
+
+ nxt::BindGroupUsage BindGroupBase::GetUsage() const {
+ return usage;
+ }
+
+ BufferViewBase* BindGroupBase::GetBindingAsBufferView(size_t binding) {
+ ASSERT(binding < kMaxBindingsPerGroup);
+ ASSERT(layout->GetBindingInfo().mask[binding]);
+ ASSERT(layout->GetBindingInfo().types[binding] == nxt::BindingType::UniformBuffer ||
+ layout->GetBindingInfo().types[binding] == nxt::BindingType::StorageBuffer);
+ return reinterpret_cast<BufferViewBase*>(bindings[binding].Get());
+ }
+
+ SamplerBase* BindGroupBase::GetBindingAsSampler(size_t binding) {
+ ASSERT(binding < kMaxBindingsPerGroup);
+ ASSERT(layout->GetBindingInfo().mask[binding]);
+ ASSERT(layout->GetBindingInfo().types[binding] == nxt::BindingType::Sampler);
+ return reinterpret_cast<SamplerBase*>(bindings[binding].Get());
+ }
+
+ TextureViewBase* BindGroupBase::GetBindingAsTextureView(size_t binding) {
+ ASSERT(binding < kMaxBindingsPerGroup);
+ ASSERT(layout->GetBindingInfo().mask[binding]);
+ ASSERT(layout->GetBindingInfo().types[binding] == nxt::BindingType::SampledTexture);
+ return reinterpret_cast<TextureViewBase*>(bindings[binding].Get());
+ }
+
+ // BindGroupBuilder
+
+ enum BindGroupSetProperties {
+ BINDGROUP_PROPERTY_USAGE = 0x1,
+ BINDGROUP_PROPERTY_LAYOUT = 0x2,
+ };
+
+ BindGroupBuilder::BindGroupBuilder(DeviceBase* device)
+ : device(device) {
+ }
+
+ bool BindGroupBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ BindGroupBase* BindGroupBuilder::GetResult() {
+ constexpr int allProperties = BINDGROUP_PROPERTY_USAGE | BINDGROUP_PROPERTY_LAYOUT;
+ if ((propertiesSet & allProperties) != allProperties) {
+ device->HandleError("Bindgroup missing properties");
+ return nullptr;
+ }
+
+ if (setMask != layout->GetBindingInfo().mask) {
+ device->HandleError("Bindgroup missing bindings");
+ return nullptr;
+ }
+
+ consumed = true;
+ return device->CreateBindGroup(this);
+ }
+
+ void BindGroupBuilder::SetLayout(BindGroupLayoutBase* layout) {
+ if ((propertiesSet & BINDGROUP_PROPERTY_LAYOUT) != 0) {
+ device->HandleError("Bindgroup layout property set multiple times");
+ return;
+ }
+
+ this->layout = layout;
+ propertiesSet |= BINDGROUP_PROPERTY_LAYOUT;
+ }
+
+ void BindGroupBuilder::SetUsage(nxt::BindGroupUsage usage) {
+ if ((propertiesSet & BINDGROUP_PROPERTY_USAGE) != 0) {
+ device->HandleError("Bindgroup usage property set multiple times");
+ return;
+ }
+
+ this->usage = usage;
+ propertiesSet |= BINDGROUP_PROPERTY_USAGE;
+ }
+
+ void BindGroupBuilder::SetBufferViews(uint32_t start, uint32_t count, BufferViewBase* const * bufferViews) {
+ if (!SetBindingsValidationBase(start, count)) {
+ return;
+ }
+
+ const auto& layoutInfo = layout->GetBindingInfo();
+ for (size_t i = start, j = 0; i < start + count; ++i, ++j) {
+ nxt::BufferUsageBit requiredBit;
+ switch (layoutInfo.types[i]) {
+ case nxt::BindingType::UniformBuffer:
+ requiredBit = nxt::BufferUsageBit::Uniform;
+ break;
+
+ case nxt::BindingType::StorageBuffer:
+ requiredBit = nxt::BufferUsageBit::Storage;
+ break;
+
+ case nxt::BindingType::Sampler:
+ case nxt::BindingType::SampledTexture:
+ device->HandleError("Setting buffer for a wrong binding type");
+ return;
+ }
+
+ if (!(bufferViews[j]->GetBuffer()->GetAllowedUsage() & requiredBit)) {
+ device->HandleError("Buffer needs to allow the correct usage bit");
+ return;
+ }
+ }
+
+ SetBindingsBase(start, count, reinterpret_cast<RefCounted* const *>(bufferViews));
+ }
+
+ void BindGroupBuilder::SetSamplers(uint32_t start, uint32_t count, SamplerBase* const * samplers) {
+ if (!SetBindingsValidationBase(start, count)) {
+ return;
+ }
+
+ const auto& layoutInfo = layout->GetBindingInfo();
+ for (size_t i = start, j = 0; i < start + count; ++i, ++j) {
+ if (layoutInfo.types[i] != nxt::BindingType::Sampler) {
+ device->HandleError("Setting binding for a wrong layout binding type");
+ return;
+ }
+ }
+
+ SetBindingsBase(start, count, reinterpret_cast<RefCounted* const *>(samplers));
+ }
+
+ void BindGroupBuilder::SetTextureViews(uint32_t start, uint32_t count, TextureViewBase* const * textureViews) {
+ if (!SetBindingsValidationBase(start, count)) {
+ return;
+ }
+
+ const auto& layoutInfo = layout->GetBindingInfo();
+ for (size_t i = start, j = 0; i < start + count; ++i, ++j) {
+ if (layoutInfo.types[i] != nxt::BindingType::SampledTexture) {
+ device->HandleError("Setting binding for a wrong layout binding type");
+ return;
+ }
+
+ if (!(textureViews[j]->GetTexture()->GetAllowedUsage() & nxt::TextureUsageBit::Sampled)) {
+ device->HandleError("Texture needs to allow the sampled usage bit");
+ return;
+ }
+ }
+
+ SetBindingsBase(start, count, reinterpret_cast<RefCounted* const *>(textureViews));
+ }
+
+ void BindGroupBuilder::SetBindingsBase(uint32_t start, uint32_t count, RefCounted* const * objects) {
+ for (size_t i = start, j = 0; i < start + count; ++i, ++j) {
+ setMask.set(i);
+ bindings[i] = objects[j];
+ }
+ }
+
+ bool BindGroupBuilder::SetBindingsValidationBase(uint32_t start, uint32_t count) {
+ if (start + count > kMaxBindingsPerGroup) {
+ device->HandleError("Setting bindings type over maximum number of bindings");
+ return false;
+ }
+
+ if ((propertiesSet & BINDGROUP_PROPERTY_LAYOUT) == 0) {
+ device->HandleError("Bindgroup layout must be set before views");
+ return false;
+ }
+
+ const auto& layoutInfo = layout->GetBindingInfo();
+ for (size_t i = start, j = 0; i < start + count; ++i, ++j) {
+ if (setMask[i]) {
+ device->HandleError("Setting already set binding");
+ return false;
+ }
+
+ if (!layoutInfo.mask[i]) {
+ device->HandleError("Setting binding that isn't present in the layout");
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/backend/common/BindGroup.h b/src/backend/common/BindGroup.h
new file mode 100644
index 0000000..4e6f900
--- /dev/null
+++ b/src/backend/common/BindGroup.h
@@ -0,0 +1,95 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_BINDGROUP_H_
+#define BACKEND_COMMON_BINDGROUP_H_
+
+#include "Forward.h"
+#include "RefCounted.h"
+
+#include "nxt/nxtcpp.h"
+
+#include <array>
+#include <bitset>
+#include <type_traits>
+
+namespace backend {
+
+ class BindGroupBase : public RefCounted {
+ public:
+ BindGroupBase(BindGroupBuilder* builder);
+
+ const BindGroupLayoutBase* GetLayout() const;
+ nxt::BindGroupUsage GetUsage() const;
+ BufferViewBase* GetBindingAsBufferView(size_t binding);
+ SamplerBase* GetBindingAsSampler(size_t binding);
+ TextureViewBase* GetBindingAsTextureView(size_t binding);
+
+ private:
+ Ref<BindGroupLayoutBase> layout;
+ nxt::BindGroupUsage usage;
+ std::array<Ref<RefCounted>, kMaxBindingsPerGroup> bindings;
+ };
+
+ class BindGroupBuilder : public RefCounted {
+ public:
+ BindGroupBuilder(DeviceBase* device);
+
+ bool WasConsumed() const;
+
+ // NXT API
+ BindGroupBase* GetResult();
+ void SetLayout(BindGroupLayoutBase* layout);
+ void SetUsage(nxt::BindGroupUsage usage);
+
+ template<typename T>
+ void SetBufferViews(uint32_t start, uint32_t count, T* const* bufferViews) {
+ static_assert(std::is_base_of<BufferViewBase, T>::value, "");
+ SetBufferViews(start, count, reinterpret_cast<BufferViewBase* const*>(bufferViews));
+ }
+ void SetBufferViews(uint32_t start, uint32_t count, BufferViewBase* const * bufferViews);
+
+ template<typename T>
+ void SetSamplers(uint32_t start, uint32_t count, T* const* samplers) {
+ static_assert(std::is_base_of<SamplerBase, T>::value, "");
+ SetSamplers(start, count, reinterpret_cast<SamplerBase* const*>(samplers));
+ }
+ void SetSamplers(uint32_t start, uint32_t count, SamplerBase* const * samplers);
+
+ template<typename T>
+ void SetTextureViews(uint32_t start, uint32_t count, T* const* textureViews) {
+ static_assert(std::is_base_of<TextureViewBase, T>::value, "");
+ SetTextureViews(start, count, reinterpret_cast<TextureViewBase* const*>(textureViews));
+ }
+ void SetTextureViews(uint32_t start, uint32_t count, TextureViewBase* const * textureViews);
+
+ private:
+ friend class BindGroupBase;
+
+ void SetBindingsBase(uint32_t start, uint32_t count, RefCounted* const * objects);
+ bool SetBindingsValidationBase(uint32_t start, uint32_t count);
+
+ DeviceBase* device;
+ std::bitset<kMaxBindingsPerGroup> setMask;
+ int propertiesSet = 0;
+ bool consumed = false;
+
+ Ref<BindGroupLayoutBase> layout;
+ nxt::BindGroupUsage usage;
+ std::array<Ref<RefCounted>, kMaxBindingsPerGroup> bindings;
+ };
+
+}
+
+#endif // BACKEND_COMMON_BINDGROUP_H_
diff --git a/src/backend/common/BindGroupLayout.cpp b/src/backend/common/BindGroupLayout.cpp
new file mode 100644
index 0000000..27c6d92
--- /dev/null
+++ b/src/backend/common/BindGroupLayout.cpp
@@ -0,0 +1,144 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "BindGroupLayout.h"
+
+#include "Device.h"
+
+#include <functional>
+
+namespace backend {
+
+ namespace {
+
+ // Workaround for Chrome's stdlib having a broken std::hash for enums and bitsets
+ template<typename T>
+ typename std::enable_if<std::is_enum<T>::value, size_t>::type Hash(T value) {
+ using Integral = typename nxt::UnderlyingType<T>::type;
+ return std::hash<Integral>()(static_cast<Integral>(value));
+ }
+
+ template<size_t N>
+ size_t Hash(const std::bitset<N>& value) {
+ static_assert(N <= sizeof(unsigned long long) * 8, "");
+ return std::hash<unsigned long long>()(value.to_ullong());
+ }
+
+
+ // TODO(cwallez@chromium.org): see if we can use boost's hash combined or some equivalent
+ // this currently assumes that size_t is 64 bits
+ void CombineHashes(size_t* h1, size_t h2) {
+ *h1 ^= (h2 << 7) + (h2 >> (64 - 7)) + 0x304975;
+ }
+
+ size_t HashBindingInfo(const BindGroupLayoutBase::LayoutBindingInfo& info) {
+ size_t hash = Hash(info.mask);
+
+ for (size_t binding = 0; binding < kMaxBindingsPerGroup; ++binding) {
+ if (info.mask[binding]) {
+ CombineHashes(&hash, Hash(info.visibilities[binding]));
+ CombineHashes(&hash, Hash(info.types[binding]));
+ }
+ }
+
+ return hash;
+ }
+
+ bool operator== (const BindGroupLayoutBase::LayoutBindingInfo& a, const BindGroupLayoutBase::LayoutBindingInfo& b) {
+ if (a.mask != b.mask) {
+ return false;
+ }
+
+ for (size_t binding = 0; binding < kMaxBindingsPerGroup; ++binding) {
+ if (a.mask[binding]) {
+ if (a.visibilities[binding] != b.visibilities[binding]) {
+ return false;
+ }
+ if (a.types[binding] != b.types[binding]) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+ }
+
+ // BindGroupLayoutBase
+
+ BindGroupLayoutBase::BindGroupLayoutBase(BindGroupLayoutBuilder* builder, bool blueprint)
+ : device(builder->device), bindingInfo(builder->bindingInfo), blueprint(blueprint) {
+ }
+
+ BindGroupLayoutBase::~BindGroupLayoutBase() {
+ // Do not register the actual cached object if we are a blueprint
+ if (!blueprint) {
+ device->UncacheBindGroupLayout(this);
+ }
+ }
+
+ const BindGroupLayoutBase::LayoutBindingInfo& BindGroupLayoutBase::GetBindingInfo() const {
+ return bindingInfo;
+ }
+
+ // BindGroupLayoutBuilder
+
+ BindGroupLayoutBuilder::BindGroupLayoutBuilder(DeviceBase* device) : device(device) {
+ }
+
+ bool BindGroupLayoutBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ const BindGroupLayoutBase::LayoutBindingInfo& BindGroupLayoutBuilder::GetBindingInfo() const {
+ return bindingInfo;
+ }
+
+ BindGroupLayoutBase* BindGroupLayoutBuilder::GetResult() {
+ consumed = true;
+ BindGroupLayoutBase blueprint(this, true);
+
+ auto* result = device->GetOrCreateBindGroupLayout(&blueprint, this);
+ result->Reference();
+ return result;
+ }
+
+ void BindGroupLayoutBuilder::SetBindingsType(nxt::ShaderStageBit visibility, nxt::BindingType bindingType, uint32_t start, uint32_t count) {
+ if (start + count > kMaxBindingsPerGroup) {
+ device->HandleError("Setting bindings type over maximum number of bindings");
+ return;
+ }
+
+ for (size_t i = start; i < start + count; i++) {
+ if (bindingInfo.mask[i]) {
+ device->HandleError("Setting already set binding type");
+ return;
+ }
+ bindingInfo.mask.set(i);
+ bindingInfo.visibilities[i] = visibility;
+ bindingInfo.types[i] = bindingType;
+ }
+ }
+
+ // BindGroupLayoutCacheFuncs
+
+ size_t BindGroupLayoutCacheFuncs::operator() (const BindGroupLayoutBase* bgl) const {
+ return HashBindingInfo(bgl->GetBindingInfo());
+ }
+
+ bool BindGroupLayoutCacheFuncs::operator() (const BindGroupLayoutBase* a, const BindGroupLayoutBase* b) const {
+ return a->GetBindingInfo() == b->GetBindingInfo();
+ }
+
+}
diff --git a/src/backend/common/BindGroupLayout.h b/src/backend/common/BindGroupLayout.h
new file mode 100644
index 0000000..91f6115
--- /dev/null
+++ b/src/backend/common/BindGroupLayout.h
@@ -0,0 +1,76 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_BINDGROUPLAYOUT_H_
+#define BACKEND_COMMON_BINDGROUPLAYOUT_H_
+
+#include "Forward.h"
+#include "RefCounted.h"
+
+#include "nxt/nxtcpp.h"
+
+#include <array>
+#include <bitset>
+
+namespace backend {
+
+ class BindGroupLayoutBase : public RefCounted {
+ public:
+ BindGroupLayoutBase(BindGroupLayoutBuilder* builder, bool blueprint = false);
+ ~BindGroupLayoutBase() override;
+
+ struct LayoutBindingInfo {
+ std::array<nxt::ShaderStageBit, kMaxBindingsPerGroup> visibilities;
+ std::array<nxt::BindingType, kMaxBindingsPerGroup> types;
+ std::bitset<kMaxBindingsPerGroup> mask;
+ };
+ const LayoutBindingInfo& GetBindingInfo() const;
+
+ private:
+ DeviceBase* device;
+ LayoutBindingInfo bindingInfo;
+ bool blueprint = false;
+ };
+
+ class BindGroupLayoutBuilder : public RefCounted {
+ public:
+ BindGroupLayoutBuilder(DeviceBase* device);
+
+ bool WasConsumed() const;
+ const BindGroupLayoutBase::LayoutBindingInfo& GetBindingInfo() const;
+
+ // NXT API
+ BindGroupLayoutBase* GetResult();
+ void SetBindingsType(nxt::ShaderStageBit visibility, nxt::BindingType bindingType, uint32_t start, uint32_t count);
+
+ private:
+ friend class BindGroupLayoutBase;
+
+ DeviceBase* device;
+ BindGroupLayoutBase::LayoutBindingInfo bindingInfo;
+ bool consumed = false;
+ };
+
+ // Implements the functors necessary for the unordered_set<BGL*>-based cache.
+ struct BindGroupLayoutCacheFuncs {
+ // The hash function
+ size_t operator() (const BindGroupLayoutBase* bgl) const;
+
+ // The equality predicate
+ bool operator() (const BindGroupLayoutBase* a, const BindGroupLayoutBase* b) const;
+ };
+
+}
+
+#endif // BACKEND_COMMON_BINDGROUPLAYOUT_H_
diff --git a/src/backend/common/BitSetIterator.h b/src/backend/common/BitSetIterator.h
new file mode 100644
index 0000000..baeb862
--- /dev/null
+++ b/src/backend/common/BitSetIterator.h
@@ -0,0 +1,135 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_BITSETITERATOR_H_
+#define BACKEND_COMMON_BITSETITERATOR_H_
+
+#include "Forward.h"
+#include "Math.h"
+
+#include <bitset>
+#include <limits>
+
+// This is ANGLE's BitSetIterator class with a customizable return type
+// TODO(cwallez@chromium.org): it could be optimized, in particular when N <= 64
+
+namespace backend {
+
+ template <typename T>
+ T roundUp(const T value, const T alignment) {
+ auto temp = value + alignment - static_cast<T>(1);
+ return temp - temp % alignment;
+ }
+
+ template <size_t N, typename T>
+ class BitSetIterator final {
+ public:
+ BitSetIterator(const std::bitset<N>& bitset);
+ BitSetIterator(const BitSetIterator& other);
+ BitSetIterator &operator=(const BitSetIterator& other);
+
+ class Iterator final {
+ public:
+ Iterator(const std::bitset<N>& bits);
+ Iterator& operator++();
+
+ bool operator==(const Iterator& other) const;
+ bool operator!=(const Iterator& other) const;
+ T operator*() const { return static_cast<T>(mCurrentBit); }
+
+ private:
+ unsigned long getNextBit();
+
+ static const size_t BitsPerWord = sizeof(unsigned long) * 8;
+ std::bitset<N> mBits;
+ unsigned long mCurrentBit;
+ unsigned long mOffset;
+ };
+
+ Iterator begin() const { return Iterator(mBits); }
+ Iterator end() const { return Iterator(std::bitset<N>(0)); }
+
+ private:
+ const std::bitset<N> mBits;
+ };
+
+ template <size_t N, typename T>
+ BitSetIterator<N, T>::BitSetIterator(const std::bitset<N>& bitset)
+ : mBits(bitset) {
+ }
+
+ template <size_t N, typename T>
+ BitSetIterator<N, T>::BitSetIterator(const BitSetIterator& other)
+ : mBits(other.mBits) {
+ }
+
+ template <size_t N, typename T>
+ BitSetIterator<N, T>& BitSetIterator<N, T>::operator=(const BitSetIterator& other) {
+ mBits = other.mBits;
+ return *this;
+ }
+
+ template <size_t N, typename T>
+ BitSetIterator<N, T>::Iterator::Iterator(const std::bitset<N>& bits)
+ : mBits(bits), mCurrentBit(0), mOffset(0) {
+ if (bits.any()) {
+ mCurrentBit = getNextBit();
+ } else {
+ mOffset = static_cast<unsigned long>(roundUp(N, BitsPerWord));
+ }
+ }
+
+ template <size_t N, typename T>
+ typename BitSetIterator<N, T>::Iterator& BitSetIterator<N, T>::Iterator::operator++() {
+ ASSERT(mBits.any());
+ mBits.set(mCurrentBit - mOffset, 0);
+ mCurrentBit = getNextBit();
+ return *this;
+ }
+
+ template <size_t N, typename T>
+ bool BitSetIterator<N, T>::Iterator::operator==(const Iterator& other) const {
+ return mOffset == other.mOffset && mBits == other.mBits;
+ }
+
+ template <size_t N, typename T>
+ bool BitSetIterator<N, T>::Iterator::operator!=(const Iterator& other) const {
+ return !(*this == other);
+ }
+
+ template <size_t N, typename T>
+ unsigned long BitSetIterator<N, T>::Iterator::getNextBit() {
+ static std::bitset<N> wordMask(std::numeric_limits<unsigned long>::max());
+
+ while (mOffset < N) {
+ unsigned long wordBits = (mBits & wordMask).to_ulong();
+ if (wordBits != 0ul) {
+ return ScanForward(wordBits) + mOffset;
+ }
+
+ mBits >>= BitsPerWord;
+ mOffset += BitsPerWord;
+ }
+ return 0;
+ }
+
+ // Helper to avoid needing to specify the template parameter size
+ template <size_t N>
+ BitSetIterator<N, uint32_t> IterateBitSet(const std::bitset<N>& bitset) {
+ return BitSetIterator<N, uint32_t>(bitset);
+ }
+
+}
+
+#endif // BACKEND_COMMON_BITSETITERATOR_H_
diff --git a/src/backend/common/Buffer.cpp b/src/backend/common/Buffer.cpp
new file mode 100644
index 0000000..7592b04
--- /dev/null
+++ b/src/backend/common/Buffer.cpp
@@ -0,0 +1,233 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Buffer.h"
+
+#include "Device.h"
+
+#include <utility>
+#include <cstdio>
+
+namespace backend {
+
+ // Buffer
+
+ BufferBase::BufferBase(BufferBuilder* builder)
+ : device(builder->device),
+ size(builder->size),
+ allowedUsage(builder->allowedUsage),
+ currentUsage(builder->currentUsage) {
+ }
+
+ BufferViewBuilder* BufferBase::CreateBufferViewBuilder() {
+ return new BufferViewBuilder(device, this);
+ }
+
+ uint32_t BufferBase::GetSize() const {
+ return size;
+ }
+
+ nxt::BufferUsageBit BufferBase::GetAllowedUsage() const {
+ return allowedUsage;
+ }
+
+ nxt::BufferUsageBit BufferBase::GetUsage() const {
+ return currentUsage;
+ }
+
+ void BufferBase::SetSubData(uint32_t start, uint32_t count, const uint32_t* data) {
+ if ((start + count) * sizeof(uint32_t) > GetSize()) {
+ device->HandleError("Buffer subdata out of range");
+ return;
+ }
+
+ if (!(currentUsage & nxt::BufferUsageBit::Mapped)) {
+ device->HandleError("Buffer needs the mapped usage bit");
+ return;
+ }
+
+ SetSubDataImpl(start, count, data);
+ }
+
+ bool BufferBase::IsFrozen() const {
+ return frozen;
+ }
+
+ bool BufferBase::HasFrozenUsage(nxt::BufferUsageBit usage) const {
+ return frozen && (usage & allowedUsage);
+ }
+
+ bool BufferBase::IsUsagePossible(nxt::BufferUsageBit allowedUsage, nxt::BufferUsageBit usage) {
+ const nxt::BufferUsageBit allReadBits =
+ nxt::BufferUsageBit::TransferSrc |
+ nxt::BufferUsageBit::Index |
+ nxt::BufferUsageBit::Vertex |
+ nxt::BufferUsageBit::Uniform;
+ bool allowed = (usage & allowedUsage) == usage;
+ bool readOnly = (usage & allReadBits) == usage;
+ bool singleUse = nxt::HasZeroOrOneBits(usage);
+ return allowed && (readOnly || singleUse);
+ }
+
+ bool BufferBase::IsTransitionPossible(nxt::BufferUsageBit usage) const {
+ if (frozen) {
+ return false;
+ }
+ return IsUsagePossible(allowedUsage, usage);
+ }
+
+ void BufferBase::TransitionUsageImpl(nxt::BufferUsageBit usage) {
+ assert(IsTransitionPossible(usage));
+ currentUsage = usage;
+ }
+
+ void BufferBase::TransitionUsage(nxt::BufferUsageBit usage) {
+ if (!IsTransitionPossible(usage)) {
+ device->HandleError("Buffer frozen or usage not allowed");
+ return;
+ }
+ TransitionUsageImpl(usage);
+ }
+
+ void BufferBase::FreezeUsage(nxt::BufferUsageBit usage) {
+ if (!IsTransitionPossible(usage)) {
+ device->HandleError("Buffer frozen or usage not allowed");
+ return;
+ }
+ allowedUsage = usage;
+ currentUsage = usage;
+ frozen = true;
+ }
+
+ // BufferBuilder
+
+ enum BufferSetProperties {
+ BUFFER_PROPERTY_ALLOWED_USAGE = 0x1,
+ BUFFER_PROPERTY_INITIAL_USAGE = 0x2,
+ BUFFER_PROPERTY_SIZE = 0x4,
+ };
+
+ BufferBuilder::BufferBuilder(DeviceBase* device) : device(device) {
+ }
+
+ bool BufferBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ BufferBase* BufferBuilder::GetResult() {
+ constexpr int allProperties = BUFFER_PROPERTY_ALLOWED_USAGE | BUFFER_PROPERTY_SIZE;
+ if ((propertiesSet & allProperties) != allProperties) {
+ device->HandleError("Buffer missing properties");
+ return nullptr;
+ }
+
+ if (!BufferBase::IsUsagePossible(allowedUsage, currentUsage)) {
+ device->HandleError("Initial buffer usage is not allowed");
+ return nullptr;
+ }
+
+ consumed = true;
+ return device->CreateBuffer(this);
+ }
+
+ void BufferBuilder::SetAllowedUsage(nxt::BufferUsageBit usage) {
+ if ((propertiesSet & BUFFER_PROPERTY_ALLOWED_USAGE) != 0) {
+ device->HandleError("Buffer allowedUsage property set multiple times");
+ return;
+ }
+
+ this->allowedUsage = usage;
+ propertiesSet |= BUFFER_PROPERTY_ALLOWED_USAGE;
+ }
+
+ void BufferBuilder::SetInitialUsage(nxt::BufferUsageBit usage) {
+ if ((propertiesSet & BUFFER_PROPERTY_INITIAL_USAGE) != 0) {
+ device->HandleError("Buffer initialUsage property set multiple times");
+ return;
+ }
+
+ this->currentUsage = usage;
+ propertiesSet |= BUFFER_PROPERTY_INITIAL_USAGE;
+ }
+
+ void BufferBuilder::SetSize(uint32_t size) {
+ if ((propertiesSet & BUFFER_PROPERTY_SIZE) != 0) {
+ device->HandleError("Buffer size property set multiple times");
+ return;
+ }
+
+ this->size = size;
+ propertiesSet |= BUFFER_PROPERTY_SIZE;
+ }
+
+ // BufferViewBase
+
+ BufferViewBase::BufferViewBase(BufferViewBuilder* builder)
+ : buffer(std::move(builder->buffer)), size(builder->size), offset(builder->offset) {
+ }
+
+ BufferBase* BufferViewBase::GetBuffer() {
+ return buffer.Get();
+ }
+
+ uint32_t BufferViewBase::GetSize() const {
+ return size;
+ }
+
+ uint32_t BufferViewBase::GetOffset() const {
+ return offset;
+ }
+
+ // BufferViewBuilder
+
+ enum BufferViewSetProperties {
+ BUFFER_VIEW_PROPERTY_EXTENT = 0x1,
+ };
+
+ BufferViewBuilder::BufferViewBuilder(DeviceBase* device, BufferBase* buffer)
+ : device(device), buffer(buffer) {
+ }
+
+ bool BufferViewBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ BufferViewBase* BufferViewBuilder::GetResult() {
+ constexpr int allProperties = BUFFER_VIEW_PROPERTY_EXTENT;
+ if ((propertiesSet & allProperties) != allProperties) {
+ device->HandleError("Buffer view missing properties");
+ return nullptr;
+ }
+
+ return device->CreateBufferView(this);
+ }
+
+ void BufferViewBuilder::SetExtent(uint32_t offset, uint32_t size) {
+ if ((propertiesSet & BUFFER_VIEW_PROPERTY_EXTENT) != 0) {
+ device->HandleError("Buffer view extent property set multiple times");
+ return;
+ }
+
+ uint64_t viewEnd = static_cast<uint64_t>(offset) + static_cast<uint64_t>(size);
+ if (viewEnd > static_cast<uint64_t>(buffer->GetSize())) {
+ device->HandleError("Buffer view end is OOB");
+ return;
+ }
+
+ this->offset = offset;
+ this->size = size;
+ propertiesSet |= BUFFER_VIEW_PROPERTY_EXTENT;
+ }
+
+}
diff --git a/src/backend/common/Buffer.h b/src/backend/common/Buffer.h
new file mode 100644
index 0000000..d8216bc
--- /dev/null
+++ b/src/backend/common/Buffer.h
@@ -0,0 +1,114 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_BUFFER_H_
+#define BACKEND_COMMON_BUFFER_H_
+
+#include "Forward.h"
+#include "RefCounted.h"
+
+#include "nxt/nxtcpp.h"
+
+namespace backend {
+
+ class BufferBase : public RefCounted {
+ public:
+ BufferBase(BufferBuilder* builder);
+
+ uint32_t GetSize() const;
+ nxt::BufferUsageBit GetAllowedUsage() const;
+ nxt::BufferUsageBit GetUsage() const;
+ static bool IsUsagePossible(nxt::BufferUsageBit allowedUsage, nxt::BufferUsageBit usage);
+ bool IsTransitionPossible(nxt::BufferUsageBit usage) const;
+ bool IsFrozen() const;
+ bool HasFrozenUsage(nxt::BufferUsageBit usage) const;
+ void TransitionUsageImpl(nxt::BufferUsageBit usage);
+
+ // NXT API
+ BufferViewBuilder* CreateBufferViewBuilder();
+ void SetSubData(uint32_t start, uint32_t count, const uint32_t* data);
+ void TransitionUsage(nxt::BufferUsageBit usage);
+ void FreezeUsage(nxt::BufferUsageBit usage);
+
+ private:
+ virtual void SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) = 0;
+
+ DeviceBase* device;
+ uint32_t size;
+ nxt::BufferUsageBit allowedUsage = nxt::BufferUsageBit::None;
+ nxt::BufferUsageBit currentUsage = nxt::BufferUsageBit::None;
+ bool frozen = false;
+ };
+
+ class BufferBuilder : public RefCounted {
+ public:
+ BufferBuilder(DeviceBase* device);
+
+ bool WasConsumed() const;
+
+ // NXT API
+ BufferBase* GetResult();
+ void SetAllowedUsage(nxt::BufferUsageBit usage);
+ void SetInitialUsage(nxt::BufferUsageBit usage);
+ void SetSize(uint32_t size);
+
+ private:
+ friend class BufferBase;
+
+ DeviceBase* device;
+ uint32_t size;
+ nxt::BufferUsageBit allowedUsage = nxt::BufferUsageBit::None;
+ nxt::BufferUsageBit currentUsage = nxt::BufferUsageBit::None;
+ int propertiesSet = 0;
+ bool consumed = false;
+ };
+
+ class BufferViewBase : public RefCounted {
+ public:
+ BufferViewBase(BufferViewBuilder* builder);
+
+ BufferBase* GetBuffer();
+ uint32_t GetSize() const;
+ uint32_t GetOffset() const;
+
+ private:
+ Ref<BufferBase> buffer;
+ uint32_t size;
+ uint32_t offset;
+ };
+
+ class BufferViewBuilder : public RefCounted {
+ public:
+ BufferViewBuilder(DeviceBase* device, BufferBase* buffer);
+
+ bool WasConsumed() const;
+
+ // NXT API
+ BufferViewBase* GetResult();
+ void SetExtent(uint32_t offset, uint32_t size);
+
+ private:
+ friend class BufferViewBase;
+
+ DeviceBase* device;
+ Ref<BufferBase> buffer;
+ uint32_t offset = 0;
+ uint32_t size = 0;
+ int propertiesSet = 0;
+ bool consumed = false;
+ };
+
+}
+
+#endif // BACKEND_COMMON_BUFFER_H_
diff --git a/src/backend/common/CommandAllocator.cpp b/src/backend/common/CommandAllocator.cpp
new file mode 100644
index 0000000..0526e10
--- /dev/null
+++ b/src/backend/common/CommandAllocator.cpp
@@ -0,0 +1,219 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "CommandAllocator.h"
+
+#include "Math.h"
+
+#include <cassert>
+#include <climits>
+#include <cstdlib>
+#define ASSERT assert
+
+namespace backend {
+
+ constexpr uint32_t EndOfBlock = UINT_MAX;//std::numeric_limits<uint32_t>::max();
+ constexpr uint32_t AdditionalData = UINT_MAX - 1;//std::numeric_limits<uint32_t>::max();
+
+ // TODO(cwallez@chromium.org): figure out a way to have more type safety for the iterator
+
+ CommandIterator::CommandIterator()
+ : endOfBlock(EndOfBlock) {
+ Reset();
+ }
+
+ CommandIterator::~CommandIterator() {
+ ASSERT(dataWasDestroyed);
+
+ if (!IsEmpty()) {
+ for (auto& block : blocks) {
+ free(block.block);
+ }
+ }
+ }
+
+ CommandIterator::CommandIterator(CommandIterator&& other)
+ : endOfBlock(EndOfBlock) {
+ if (!other.IsEmpty()) {
+ blocks = std::move(other.blocks);
+ other.Reset();
+ }
+ other.DataWasDestroyed();
+ Reset();
+ }
+
+ CommandIterator& CommandIterator::operator=(CommandIterator&& other) {
+ if (!other.IsEmpty()) {
+ blocks = std::move(other.blocks);
+ other.Reset();
+ } else {
+ blocks.clear();
+ }
+ other.DataWasDestroyed();
+ Reset();
+ return *this;
+ }
+
+ CommandIterator::CommandIterator(CommandAllocator&& allocator)
+ : blocks(allocator.AcquireBlocks()), endOfBlock(EndOfBlock) {
+ Reset();
+ }
+
+ CommandIterator& CommandIterator::operator=(CommandAllocator&& allocator) {
+ blocks = allocator.AcquireBlocks();
+ Reset();
+ return *this;
+ }
+
+ void CommandIterator::Reset() {
+ currentBlock = 0;
+
+ if (blocks.empty()) {
+ // This will case the first NextCommandId call to try to move to the next
+ // block and stop the iteration immediately, without special casing the
+ // initialization.
+ currentPtr = reinterpret_cast<uint8_t*>(&endOfBlock);
+ blocks.emplace_back();
+ blocks[0].size = sizeof(endOfBlock);
+ blocks[0].block = currentPtr;
+ } else {
+ currentPtr = Align(blocks[0].block, alignof(uint32_t));
+ }
+ }
+
+ void CommandIterator::DataWasDestroyed() {
+ dataWasDestroyed = true;
+ }
+
+ bool CommandIterator::IsEmpty() const {
+ return blocks[0].block == reinterpret_cast<const uint8_t*>(&endOfBlock);
+ }
+
+ bool CommandIterator::NextCommandId(uint32_t* commandId) {
+ uint8_t* idPtr = Align(currentPtr, alignof(uint32_t));
+ ASSERT(idPtr + sizeof(uint32_t) <= blocks[currentBlock].block + blocks[currentBlock].size);
+
+ uint32_t id = *reinterpret_cast<uint32_t*>(idPtr);
+
+ if (id == EndOfBlock) {
+ currentBlock++;
+ if (currentBlock >= blocks.size()) {
+ Reset();
+ return false;
+ }
+ currentPtr = Align(blocks[currentBlock].block, alignof(uint32_t));
+ return NextCommandId(commandId);
+ }
+
+ currentPtr = idPtr + sizeof(uint32_t);
+ *commandId = id;
+ return true;
+ }
+
+ void* CommandIterator::NextCommand(size_t commandSize, size_t commandAlignment) {
+ uint8_t* commandPtr = Align(currentPtr, commandAlignment);
+ ASSERT(commandPtr + sizeof(commandSize) <= blocks[currentBlock].block + blocks[currentBlock].size);
+
+ currentPtr = commandPtr + commandSize;
+ return commandPtr;
+ }
+
+ void* CommandIterator::NextData(size_t dataSize, size_t dataAlignment) {
+ uint32_t id;
+ bool hasId = NextCommandId(&id);
+ ASSERT(hasId);
+ ASSERT(id == AdditionalData);
+
+ return NextCommand(dataSize, dataAlignment);
+ }
+
+ // Potential TODO(cwallez@chromium.org):
+ // - Host the size and pointer to next block in the block itself to avoid having an allocation in the vector
+ // - Assume T's alignof is, say 64bits, static assert it, and make commandAlignment a constant in Allocate
+ // - Be able to optimize allocation to one block, for command buffers expected to live long to avoid cache misses
+ // - Better block allocation, maybe have NXT API to say command buffer is going to have size close to another
+
+ CommandAllocator::CommandAllocator()
+ : currentPtr(reinterpret_cast<uint8_t*>(&dummyEnum[0])), endPtr(reinterpret_cast<uint8_t*>(&dummyEnum[1])) {
+ }
+
+ CommandAllocator::~CommandAllocator() {
+ ASSERT(blocks.empty());
+ }
+
+ CommandBlocks&& CommandAllocator::AcquireBlocks() {
+ ASSERT(currentPtr != nullptr && endPtr != nullptr);
+ ASSERT(IsAligned(currentPtr, alignof(uint32_t)));
+ ASSERT(currentPtr + sizeof(uint32_t) <= endPtr);
+ *reinterpret_cast<uint32_t*>(currentPtr) = EndOfBlock;
+
+ currentPtr = nullptr;
+ endPtr = nullptr;
+ return std::move(blocks);
+ }
+
+ uint8_t* CommandAllocator::Allocate(uint32_t commandId, size_t commandSize, size_t commandAlignment) {
+ ASSERT(currentPtr != nullptr);
+ ASSERT(endPtr != nullptr);
+ ASSERT(commandId != EndOfBlock);
+
+ // It should always be possible to allocate one id, for EndOfBlock tagging,
+ ASSERT(IsAligned(currentPtr, alignof(uint32_t)));
+ ASSERT(currentPtr + sizeof(uint32_t) <= endPtr);
+ uint32_t* idAlloc = reinterpret_cast<uint32_t*>(currentPtr);
+
+ uint8_t* commandAlloc = Align(currentPtr + sizeof(uint32_t), commandAlignment);
+ uint8_t* nextPtr = Align(commandAlloc + commandSize, alignof(uint32_t));
+
+ // When there is not enough space, we signal the EndOfBlock, so that the iterator nows to
+ // move to the next one. EndOfBlock on the last block means the end of the commands.
+ if (nextPtr + sizeof(uint32_t) > endPtr) {
+
+ // Even if we are not able to get another block, the list of commands will be well-formed
+ // and iterable as this block will be that last one.
+ *idAlloc = EndOfBlock;
+
+ // Make sure we have space for current allocation, plus end of block and alignment padding
+ // for the first id.
+ if (!GetNewBlock(nextPtr - currentPtr + sizeof(uint32_t) + alignof(uint32_t))) {
+ return nullptr;
+ }
+ return Allocate(commandId, commandSize, commandAlignment);
+ }
+
+ *idAlloc = commandId;
+ currentPtr = nextPtr;
+ return commandAlloc;
+ }
+
+ uint8_t* CommandAllocator::AllocateData(size_t commandSize, size_t commandAlignment) {
+ return Allocate(AdditionalData, commandSize, commandAlignment);
+ }
+
+ bool CommandAllocator::GetNewBlock(size_t minimumSize) {
+ // Allocate blocks doubling sizes each time, to a maximum of 16k (or at least minimumSize).
+ lastAllocationSize = std::max(minimumSize, std::min(lastAllocationSize * 2, size_t(16384)));
+
+ uint8_t* block = reinterpret_cast<uint8_t*>(malloc(lastAllocationSize));
+ if (block == nullptr) {
+ return false;
+ }
+
+ blocks.push_back({lastAllocationSize, block});
+ currentPtr = Align(block, alignof(uint32_t));
+ endPtr = block + lastAllocationSize;
+ return true;
+ }
+
+}
diff --git a/src/backend/common/CommandAllocator.h b/src/backend/common/CommandAllocator.h
new file mode 100644
index 0000000..3633d2e
--- /dev/null
+++ b/src/backend/common/CommandAllocator.h
@@ -0,0 +1,150 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_COMMAND_ALLOCATOR_H_
+#define BACKEND_COMMON_COMMAND_ALLOCATOR_H_
+
+#include <cstdint>
+#include <cstddef>
+#include <vector>
+
+namespace backend {
+
+ // Allocation for command buffers should be fast. To avoid doing an allocation per command
+ // or to avoid copying commands when reallocing, we use a linear allocator in a growing set
+ // of large memory blocks. We also use this to have the format to be (u32 commandId, command),
+ // so that iteration over the commands is easy.
+
+ // Usage of the allocator and iterator:
+ // CommandAllocator allocator;
+ // DrawCommand* cmd = allocator.Allocate<DrawCommand>(CommandType::Draw);
+ // // Fill command
+ // // Repeat allocation and filling commands
+ //
+ // CommandIterator commands(allocator);
+ // CommandType type;
+ // void* command;
+ // while(commands.NextCommandId(&e)) {
+ // switch(e) {
+ // case CommandType::Draw:
+ // DrawCommand* draw = commands.NextCommand<DrawCommand>();
+ // // Do the draw
+ // break;
+ // // other cases
+ // }
+ // }
+
+ // Note that you need to extract the commands from the CommandAllocator before destroying it
+ // and must tell the CommandIterator when the allocated commands have been processed for
+ // deletion.
+
+ // These are the lists of blocks, should not be used directly, only through CommandAllocator
+ // and CommandIterator
+ struct BlockDef {
+ size_t size;
+ uint8_t* block;
+ };
+ using CommandBlocks = std::vector<BlockDef>;
+
+ class CommandAllocator;
+
+ // TODO(cwallez@chromium.org): prevent copy for both iterator and allocator
+ class CommandIterator {
+ public:
+ CommandIterator();
+ ~CommandIterator();
+
+ CommandIterator(CommandIterator&& other);
+ CommandIterator& operator=(CommandIterator&& other);
+
+ CommandIterator(CommandAllocator&& allocator);
+ CommandIterator& operator=(CommandAllocator&& allocator);
+
+ template<typename E>
+ bool NextCommandId(E* commandId) {
+ return NextCommandId(reinterpret_cast<uint32_t*>(commandId));
+ }
+ template<typename T>
+ T* NextCommand() {
+ return reinterpret_cast<T*>(NextCommand(sizeof(T), alignof(T)));
+ }
+ template<typename T>
+ T* NextData(size_t count) {
+ return reinterpret_cast<T*>(NextData(sizeof(T) * count, alignof(T)));
+ }
+
+ // Needs to be called if iteration was stopped early.
+ void Reset();
+
+ void DataWasDestroyed();
+
+ private:
+ bool IsEmpty() const;
+
+ bool NextCommandId(uint32_t* commandId);
+ void* NextCommand(size_t commandSize, size_t commandAlignment);
+ void* NextData(size_t dataSize, size_t dataAlignment);
+
+ CommandBlocks blocks;
+ uint8_t* currentPtr = nullptr;
+ size_t currentBlock = 0;
+ // Used to avoid a special case for empty iterators.
+ uint32_t endOfBlock;
+ bool dataWasDestroyed = false;
+ };
+
+ class CommandAllocator {
+ public:
+ CommandAllocator();
+ ~CommandAllocator();
+
+ template<typename T, typename E>
+ T* Allocate(E commandId) {
+ static_assert(sizeof(E) == sizeof(uint32_t), "");
+ static_assert(alignof(E) == alignof(uint32_t), "");
+ return reinterpret_cast<T*>(Allocate(static_cast<uint32_t>(commandId), sizeof(T), alignof(T)));
+ }
+
+ template<typename T>
+ T* AllocateData(size_t count) {
+ return reinterpret_cast<T*>(AllocateData(sizeof(T) * count, alignof(T)));
+ }
+
+ private:
+ friend CommandIterator;
+ CommandBlocks&& AcquireBlocks();
+
+ uint8_t* Allocate(uint32_t commandId, size_t commandSize, size_t commandAlignment);
+ uint8_t* AllocateData(size_t dataSize, size_t dataAlignment);
+ bool GetNewBlock(size_t minimumSize);
+
+ CommandBlocks blocks;
+ size_t lastAllocationSize = 2048;
+
+ // Pointers to the current range of allocation in the block. Guaranteed to allow
+ // for at least one uint32_t is not nullptr, so that the special EndOfBlock command id
+ // can always be written.
+ // Nullptr iff the blocks were moved out.
+ uint8_t* currentPtr = nullptr;
+ uint8_t* endPtr = nullptr;
+
+ // Data used for the block range at initialization so that the first call to Allocate
+ // sees there is not enough space and calls GetNewBlock. This avoids having to special
+ // case the initialization in Allocate.
+ uint32_t dummyEnum[1] = {0};
+ };
+
+}
+
+#endif // BACKEND_COMMON_COMMAND_ALLOCATOR_H_
diff --git a/src/backend/common/CommandBuffer.cpp b/src/backend/common/CommandBuffer.cpp
new file mode 100644
index 0000000..d118e64
--- /dev/null
+++ b/src/backend/common/CommandBuffer.cpp
@@ -0,0 +1,623 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "CommandBuffer.h"
+
+#include "BindGroup.h"
+#include "BindGroupLayout.h"
+#include "Buffer.h"
+#include "Commands.h"
+#include "Device.h"
+#include "InputState.h"
+#include "Pipeline.h"
+#include "PipelineLayout.h"
+#include "Texture.h"
+
+#include <cstring>
+#include <map>
+
+namespace backend {
+
+ CommandBufferBase::CommandBufferBase(CommandBufferBuilder* builder)
+ : device(builder->device),
+ buffersTransitioned(std::move(builder->buffersTransitioned)),
+ texturesTransitioned(std::move(builder->texturesTransitioned)) {
+ }
+
+ bool CommandBufferBase::ValidateResourceUsagesImmediate() {
+ for (auto buffer : buffersTransitioned) {
+ if (buffer->IsFrozen()) {
+ device->HandleError("Command buffer: cannot transition buffer with frozen usage");
+ return false;
+ }
+ }
+ for (auto texture : texturesTransitioned) {
+ if (texture->IsFrozen()) {
+ device->HandleError("Command buffer: cannot transition texture with frozen usage");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void FreeCommands(CommandIterator* commands) {
+ Command type;
+ while(commands->NextCommandId(&type)) {
+ switch (type) {
+ case Command::CopyBufferToTexture:
+ {
+ CopyBufferToTextureCmd* copy = commands->NextCommand<CopyBufferToTextureCmd>();
+ copy->~CopyBufferToTextureCmd();
+ }
+ break;
+ case Command::Dispatch:
+ {
+ DispatchCmd* dispatch = commands->NextCommand<DispatchCmd>();
+ dispatch->~DispatchCmd();
+ }
+ break;
+ case Command::DrawArrays:
+ {
+ DrawArraysCmd* draw = commands->NextCommand<DrawArraysCmd>();
+ draw->~DrawArraysCmd();
+ }
+ break;
+ case Command::DrawElements:
+ {
+ DrawElementsCmd* draw = commands->NextCommand<DrawElementsCmd>();
+ draw->~DrawElementsCmd();
+ }
+ break;
+ case Command::SetPipeline:
+ {
+ SetPipelineCmd* cmd = commands->NextCommand<SetPipelineCmd>();
+ cmd->~SetPipelineCmd();
+ }
+ break;
+ case Command::SetPushConstants:
+ {
+ SetPushConstantsCmd* cmd = commands->NextCommand<SetPushConstantsCmd>();
+ commands->NextData<uint32_t>(cmd->count);
+ cmd->~SetPushConstantsCmd();
+ }
+ break;
+ case Command::SetBindGroup:
+ {
+ SetBindGroupCmd* cmd = commands->NextCommand<SetBindGroupCmd>();
+ cmd->~SetBindGroupCmd();
+ }
+ break;
+ case Command::SetIndexBuffer:
+ {
+ SetIndexBufferCmd* cmd = commands->NextCommand<SetIndexBufferCmd>();
+ cmd->~SetIndexBufferCmd();
+ }
+ break;
+ case Command::SetVertexBuffers:
+ {
+ SetVertexBuffersCmd* cmd = commands->NextCommand<SetVertexBuffersCmd>();
+ auto buffers = commands->NextData<Ref<BufferBase>>(cmd->count);
+ for (size_t i = 0; i < cmd->count; ++i) {
+ (&buffers[i])->~Ref<BufferBase>();
+ }
+ commands->NextData<uint32_t>(cmd->count);
+ cmd->~SetVertexBuffersCmd();
+ }
+ break;
+ case Command::TransitionBufferUsage:
+ {
+ TransitionBufferUsageCmd* cmd = commands->NextCommand<TransitionBufferUsageCmd>();
+ cmd->~TransitionBufferUsageCmd();
+ }
+ break;
+ case Command::TransitionTextureUsage:
+ {
+ TransitionTextureUsageCmd* cmd = commands->NextCommand<TransitionTextureUsageCmd>();
+ cmd->~TransitionTextureUsageCmd();
+ }
+ break;
+ }
+ }
+ commands->DataWasDestroyed();
+ }
+
+ CommandBufferBuilder::CommandBufferBuilder(DeviceBase* device) : device(device) {
+ }
+
+ CommandBufferBuilder::~CommandBufferBuilder() {
+ if (!consumed) {
+ MoveToIterator();
+ FreeCommands(&iterator);
+ }
+ }
+
+ bool CommandBufferBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ enum ValidationAspect {
+ VALIDATION_ASPECT_RENDER_PIPELINE,
+ VALIDATION_ASPECT_COMPUTE_PIPELINE,
+ VALIDATION_ASPECT_BINDGROUPS,
+ VALIDATION_ASPECT_VERTEX_BUFFERS,
+ VALIDATION_ASPECT_INDEX_BUFFER,
+
+ VALIDATION_ASPECT_COUNT,
+ };
+
+ using ValidationAspects = std::bitset<VALIDATION_ASPECT_COUNT>;
+
+ bool CommandBufferBuilder::ValidateGetResult() {
+ MoveToIterator();
+
+ ValidationAspects aspects;
+ std::bitset<kMaxBindGroups> bindgroupsSet;
+ std::bitset<kMaxVertexInputs> inputsSet;
+ PipelineBase* lastPipeline = nullptr;
+
+ std::map<BufferBase*, nxt::BufferUsageBit> mostRecentBufferUsages;
+ auto bufferHasGuaranteedUsageBit = [&](BufferBase* buffer, nxt::BufferUsageBit usage) -> bool {
+ assert(usage != nxt::BufferUsageBit::None && nxt::HasZeroOrOneBits(usage));
+ if (buffer->HasFrozenUsage(usage)) {
+ return true;
+ }
+ auto it = mostRecentBufferUsages.find(buffer);
+ return it != mostRecentBufferUsages.end() && (it->second & usage);
+ };
+
+ std::map<TextureBase*, nxt::TextureUsageBit> mostRecentTextureUsages;
+ auto textureHasGuaranteedUsageBit = [&](TextureBase* texture, nxt::TextureUsageBit usage) -> bool {
+ assert(usage != nxt::TextureUsageBit::None && nxt::HasZeroOrOneBits(usage));
+ if (texture->HasFrozenUsage(usage)) {
+ return true;
+ }
+ auto it = mostRecentTextureUsages.find(texture);
+ return it != mostRecentTextureUsages.end() && (it->second & usage);
+ };
+
+ auto validateBindGroupUsages = [&](BindGroupBase* group) -> bool {
+ const auto& layoutInfo = group->GetLayout()->GetBindingInfo();
+ for (size_t i = 0; i < kMaxBindingsPerGroup; ++i) {
+ if (!layoutInfo.mask[i]) {
+ continue;
+ }
+
+ nxt::BindingType type = layoutInfo.types[i];
+ switch (type) {
+ case nxt::BindingType::UniformBuffer:
+ case nxt::BindingType::StorageBuffer:
+ {
+ nxt::BufferUsageBit requiredUsage;
+ switch (type) {
+ case nxt::BindingType::UniformBuffer:
+ requiredUsage = nxt::BufferUsageBit::Uniform;
+ break;
+
+ case nxt::BindingType::StorageBuffer:
+ requiredUsage = nxt::BufferUsageBit::Storage;
+ break;
+
+ default:
+ assert(false);
+ return false;
+ }
+
+ auto buffer = group->GetBindingAsBufferView(i)->GetBuffer();
+ if (!bufferHasGuaranteedUsageBit(buffer, requiredUsage)) {
+ device->HandleError("Can't guarantee buffer usage needed by bind group");
+ return false;
+ }
+ }
+ break;
+ case nxt::BindingType::SampledTexture:
+ {
+ auto requiredUsage = nxt::TextureUsageBit::Sampled;
+
+ auto texture = group->GetBindingAsTextureView(i)->GetTexture();
+ if (!textureHasGuaranteedUsageBit(texture, requiredUsage)) {
+ device->HandleError("Can't guarantee texture usage needed by bind group");
+ return false;
+ }
+ }
+ break;
+ case nxt::BindingType::Sampler:
+ continue;
+ }
+ }
+ return true;
+ };
+
+ Command type;
+ while(iterator.NextCommandId(&type)) {
+ switch (type) {
+ case Command::CopyBufferToTexture:
+ {
+ CopyBufferToTextureCmd* copy = iterator.NextCommand<CopyBufferToTextureCmd>();
+ BufferBase* buffer = copy->buffer.Get();
+ TextureBase* texture = copy->texture.Get();
+ uint64_t width = copy->width;
+ uint64_t height = copy->height;
+ uint64_t depth = copy->depth;
+ uint64_t x = copy->x;
+ uint64_t y = copy->y;
+ uint64_t z = copy->z;
+ uint32_t level = copy->level;
+
+ if (!bufferHasGuaranteedUsageBit(buffer, nxt::BufferUsageBit::TransferSrc)) {
+ device->HandleError("Buffer needs the transfer source usage bit");
+ return false;
+ }
+
+ if (!textureHasGuaranteedUsageBit(texture, nxt::TextureUsageBit::TransferDst)) {
+ device->HandleError("Texture needs the transfer destination usage bit");
+ return false;
+ }
+
+ if (width == 0 || height == 0 || depth == 0) {
+ device->HandleError("Empty copy");
+ return false;
+ }
+
+ // TODO(cwallez@chromium.org): check for overflows
+ uint64_t pixelSize = TextureFormatPixelSize(texture->GetFormat());
+ uint64_t dataSize = width * height * depth * pixelSize;
+
+ // TODO(cwallez@chromium.org): handle buffer offset when it is in the command.
+ if (dataSize > static_cast<uint64_t>(buffer->GetSize())) {
+ device->HandleError("Copy would read after end of the buffer");
+ return false;
+ }
+
+ if (x + width > static_cast<uint64_t>(texture->GetWidth()) ||
+ y + height > static_cast<uint64_t>(texture->GetHeight()) ||
+ z + depth > static_cast<uint64_t>(texture->GetDepth()) ||
+ level > texture->GetNumMipLevels()) {
+ device->HandleError("Copy would write outside of the texture");
+ return false;
+ }
+ }
+ break;
+
+ case Command::Dispatch:
+ {
+ DispatchCmd* cmd = iterator.NextCommand<DispatchCmd>();
+
+ constexpr ValidationAspects requiredDispatchAspects =
+ 1 << VALIDATION_ASPECT_COMPUTE_PIPELINE |
+ 1 << VALIDATION_ASPECT_BINDGROUPS |
+ 1 << VALIDATION_ASPECT_VERTEX_BUFFERS;
+
+ if ((requiredDispatchAspects & ~aspects).any()) {
+ // Compute the lazily computed aspects
+ if (bindgroupsSet.all()) {
+ aspects.set(VALIDATION_ASPECT_BINDGROUPS);
+ }
+
+ auto requiredInputs = lastPipeline->GetInputState()->GetInputsSetMask();
+ if ((inputsSet & ~requiredInputs).none()) {
+ aspects.set(VALIDATION_ASPECT_VERTEX_BUFFERS);
+ }
+
+ // Check again if anything is missing
+ if ((requiredDispatchAspects & ~aspects).any()) {
+ device->HandleError("Some dispatch state is missing");
+ return false;
+ }
+ }
+ }
+ break;
+
+ case Command::DrawArrays:
+ case Command::DrawElements:
+ {
+ constexpr ValidationAspects requiredDrawAspects =
+ 1 << VALIDATION_ASPECT_RENDER_PIPELINE |
+ 1 << VALIDATION_ASPECT_BINDGROUPS |
+ 1 << VALIDATION_ASPECT_VERTEX_BUFFERS;
+
+ if ((requiredDrawAspects & ~aspects).any()) {
+ // Compute the lazily computed aspects
+ if (bindgroupsSet.all()) {
+ aspects.set(VALIDATION_ASPECT_BINDGROUPS);
+ }
+
+ auto requiredInputs = lastPipeline->GetInputState()->GetInputsSetMask();
+ if ((inputsSet & ~requiredInputs).none()) {
+ aspects.set(VALIDATION_ASPECT_VERTEX_BUFFERS);
+ }
+
+ // Check again if anything is missing
+ if ((requiredDrawAspects & ~aspects).any()) {
+ device->HandleError("Some draw state is missing");
+ return false;
+ }
+ }
+
+ if (type == Command::DrawArrays) {
+ DrawArraysCmd* draw = iterator.NextCommand<DrawArraysCmd>();
+ } else {
+ ASSERT(type == Command::DrawElements);
+ DrawElementsCmd* draw = iterator.NextCommand<DrawElementsCmd>();
+
+ if (!aspects[VALIDATION_ASPECT_INDEX_BUFFER]) {
+ device->HandleError("Draw elements requires an index buffer");
+ return false;
+ }
+ }
+ }
+ break;
+
+ case Command::SetPipeline:
+ {
+ SetPipelineCmd* cmd = iterator.NextCommand<SetPipelineCmd>();
+ PipelineBase* pipeline = cmd->pipeline.Get();
+ PipelineLayoutBase* layout = pipeline->GetLayout();
+
+ if (pipeline->IsCompute()) {
+ aspects.set(VALIDATION_ASPECT_COMPUTE_PIPELINE);
+ aspects.reset(VALIDATION_ASPECT_RENDER_PIPELINE);
+ } else {
+ aspects.set(VALIDATION_ASPECT_RENDER_PIPELINE);
+ aspects.reset(VALIDATION_ASPECT_COMPUTE_PIPELINE);
+ }
+ aspects.reset(VALIDATION_ASPECT_BINDGROUPS);
+ aspects.reset(VALIDATION_ASPECT_VERTEX_BUFFERS);
+ bindgroupsSet = ~layout->GetBindGroupsLayoutMask();
+
+ // Only bindgroups that were not the same layout in the last pipeline need to be set again.
+ if (lastPipeline) {
+ PipelineLayoutBase* lastLayout = lastPipeline->GetLayout();
+ for (uint32_t i = 0; i < kMaxBindGroups; ++i) {
+ if (lastLayout->GetBindGroupLayout(i) == layout->GetBindGroupLayout(i)) {
+ bindgroupsSet |= uint64_t(1) << i;
+ }
+ }
+ }
+
+ lastPipeline = pipeline;
+ }
+ break;
+
+ case Command::SetPushConstants:
+ {
+ SetPushConstantsCmd* cmd = iterator.NextCommand<SetPushConstantsCmd>();
+ iterator.NextData<uint32_t>(cmd->count);
+ if (cmd->count + cmd->offset > kMaxPushConstants) {
+ device->HandleError("Setting pushconstants past the limit");
+ return false;
+ }
+ }
+ break;
+
+ case Command::SetBindGroup:
+ {
+ SetBindGroupCmd* cmd = iterator.NextCommand<SetBindGroupCmd>();
+ uint32_t index = cmd->index;
+
+ if (cmd->group->GetLayout() != lastPipeline->GetLayout()->GetBindGroupLayout(index)) {
+ device->HandleError("Bind group layout mismatch");
+ return false;
+ }
+ if (!validateBindGroupUsages(cmd->group.Get())) {
+ return false;
+ }
+ bindgroupsSet |= uint64_t(1) << index;
+ }
+ break;
+
+ case Command::SetIndexBuffer:
+ {
+ SetIndexBufferCmd* cmd = iterator.NextCommand<SetIndexBufferCmd>();
+ auto buffer = cmd->buffer;
+ auto usage = nxt::BufferUsageBit::Index;
+ if (!bufferHasGuaranteedUsageBit(buffer.Get(), usage)) {
+ device->HandleError("Buffer needs the index usage bit to be guaranteed");
+ return false;
+ }
+
+ aspects.set(VALIDATION_ASPECT_INDEX_BUFFER);
+ }
+ break;
+
+ case Command::SetVertexBuffers:
+ {
+ SetVertexBuffersCmd* cmd = iterator.NextCommand<SetVertexBuffersCmd>();
+ auto buffers = iterator.NextData<Ref<BufferBase>>(cmd->count);
+ iterator.NextData<uint32_t>(cmd->count);
+
+ for (uint32_t i = 0; i < cmd->count; ++i) {
+ auto buffer = buffers[i];
+ auto usage = nxt::BufferUsageBit::Vertex;
+ if (!bufferHasGuaranteedUsageBit(buffer.Get(), usage)) {
+ device->HandleError("Buffer needs vertex usage bit to be guaranteed");
+ return false;
+ }
+ inputsSet.set(cmd->startSlot + i);
+ }
+ }
+ break;
+
+ case Command::TransitionBufferUsage:
+ {
+ TransitionBufferUsageCmd* cmd = iterator.NextCommand<TransitionBufferUsageCmd>();
+ auto buffer = cmd->buffer.Get();
+ auto usage = cmd->usage;
+
+ if (!cmd->buffer->IsTransitionPossible(cmd->usage)) {
+ device->HandleError("Buffer frozen or usage not allowed");
+ return false;
+ }
+
+ mostRecentBufferUsages[buffer] = usage;
+
+ buffersTransitioned.insert(buffer);
+ }
+ break;
+
+ case Command::TransitionTextureUsage:
+ {
+ TransitionTextureUsageCmd* cmd = iterator.NextCommand<TransitionTextureUsageCmd>();
+ auto texture = cmd->texture.Get();
+ auto usage = cmd->usage;
+
+ if (!cmd->texture->IsTransitionPossible(cmd->usage)) {
+ device->HandleError("Texture frozen or usage not allowed");
+ return false;
+ }
+
+ mostRecentTextureUsages[texture] = usage;
+
+ texturesTransitioned.insert(texture);
+ }
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ CommandIterator CommandBufferBuilder::AcquireCommands() {
+ return std::move(iterator);
+ }
+
+ CommandBufferBase* CommandBufferBuilder::GetResult() {
+ MoveToIterator();
+ consumed = true;
+ return device->CreateCommandBuffer(this);
+ }
+
+ void CommandBufferBuilder::CopyBufferToTexture(BufferBase* buffer, TextureBase* texture, uint32_t x, uint32_t y, uint32_t z,
+ uint32_t width, uint32_t height, uint32_t depth, uint32_t level) {
+ CopyBufferToTextureCmd* copy = allocator.Allocate<CopyBufferToTextureCmd>(Command::CopyBufferToTexture);
+ new(copy) CopyBufferToTextureCmd;
+ copy->buffer = buffer;
+ copy->texture = texture;
+ copy->x = x;
+ copy->y = y;
+ copy->z = z;
+ copy->width = width;
+ copy->height = height;
+ copy->depth = depth;
+ copy->level = level;
+ }
+
+ void CommandBufferBuilder::Dispatch(uint32_t x, uint32_t y, uint32_t z) {
+ DispatchCmd* dispatch = allocator.Allocate<DispatchCmd>(Command::Dispatch);
+ new(dispatch) DispatchCmd;
+ dispatch->x = x;
+ dispatch->y = y;
+ dispatch->z = z;
+ }
+
+ void CommandBufferBuilder::DrawArrays(uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance) {
+ DrawArraysCmd* draw = allocator.Allocate<DrawArraysCmd>(Command::DrawArrays);
+ new(draw) DrawArraysCmd;
+ draw->vertexCount = vertexCount;
+ draw->instanceCount = instanceCount;
+ draw->firstVertex = firstVertex;
+ draw->firstInstance = firstInstance;
+ }
+
+ void CommandBufferBuilder::DrawElements(uint32_t indexCount, uint32_t instanceCount, uint32_t firstIndex, uint32_t firstInstance) {
+ DrawElementsCmd* draw = allocator.Allocate<DrawElementsCmd>(Command::DrawElements);
+ new(draw) DrawElementsCmd;
+ draw->indexCount = indexCount;
+ draw->instanceCount = instanceCount;
+ draw->firstIndex = firstIndex;
+ draw->firstInstance = firstInstance;
+ }
+
+ void CommandBufferBuilder::SetPipeline(PipelineBase* pipeline) {
+ SetPipelineCmd* cmd = allocator.Allocate<SetPipelineCmd>(Command::SetPipeline);
+ new(cmd) SetPipelineCmd;
+ cmd->pipeline = pipeline;
+ }
+
+ void CommandBufferBuilder::SetPushConstants(nxt::ShaderStageBit stage, uint32_t offset, uint32_t count, const void* data) {
+ if (offset + count > kMaxPushConstants) {
+ device->HandleError("Setting too many push constants");
+ return;
+ }
+
+ SetPushConstantsCmd* cmd = allocator.Allocate<SetPushConstantsCmd>(Command::SetPushConstants);
+ new(cmd) SetPushConstantsCmd;
+ cmd->stage = stage;
+ cmd->offset = offset;
+ cmd->count = count;
+
+ uint32_t* values = allocator.AllocateData<uint32_t>(count);
+ memcpy(values, data, count * sizeof(uint32_t));
+ }
+
+ void CommandBufferBuilder::SetBindGroup(uint32_t groupIndex, BindGroupBase* group) {
+ if (groupIndex >= kMaxBindGroups) {
+ device->HandleError("Setting bind group over the max");
+ return;
+ }
+
+ SetBindGroupCmd* cmd = allocator.Allocate<SetBindGroupCmd>(Command::SetBindGroup);
+ new(cmd) SetBindGroupCmd;
+ cmd->index = groupIndex;
+ cmd->group = group;
+ }
+
+ void CommandBufferBuilder::SetIndexBuffer(BufferBase* buffer, uint32_t offset, nxt::IndexFormat format) {
+ // TODO(kainino@chromium.org): validation
+
+ SetIndexBufferCmd* cmd = allocator.Allocate<SetIndexBufferCmd>(Command::SetIndexBuffer);
+ new(cmd) SetIndexBufferCmd;
+ cmd->buffer = buffer;
+ cmd->offset = offset;
+ cmd->format = format;
+ }
+
+ void CommandBufferBuilder::SetVertexBuffers(uint32_t startSlot, uint32_t count, BufferBase* const* buffers, uint32_t const* offsets){
+ // TODO(kainino@chromium.org): validation
+
+ SetVertexBuffersCmd* cmd = allocator.Allocate<SetVertexBuffersCmd>(Command::SetVertexBuffers);
+ new(cmd) SetVertexBuffersCmd;
+ cmd->startSlot = startSlot;
+ cmd->count = count;
+
+ Ref<BufferBase>* cmdBuffers = allocator.AllocateData<Ref<BufferBase>>(count);
+ for (size_t i = 0; i < count; ++i) {
+ new(&cmdBuffers[i]) Ref<BufferBase>(buffers[i]);
+ }
+
+ uint32_t* cmdOffsets = allocator.AllocateData<uint32_t>(count);
+ memcpy(cmdOffsets, offsets, count * sizeof(uint32_t));
+ }
+
+ void CommandBufferBuilder::TransitionBufferUsage(BufferBase* buffer, nxt::BufferUsageBit usage) {
+ TransitionBufferUsageCmd* cmd = allocator.Allocate<TransitionBufferUsageCmd>(Command::TransitionBufferUsage);
+ new(cmd) TransitionBufferUsageCmd;
+ cmd->buffer = buffer;
+ cmd->usage = usage;
+ }
+
+ void CommandBufferBuilder::TransitionTextureUsage(TextureBase* texture, nxt::TextureUsageBit usage) {
+ TransitionTextureUsageCmd* cmd = allocator.Allocate<TransitionTextureUsageCmd>(Command::TransitionTextureUsage);
+ new(cmd) TransitionTextureUsageCmd;
+ cmd->texture = texture;
+ cmd->usage = usage;
+ }
+
+ void CommandBufferBuilder::MoveToIterator() {
+ if (!movedToIterator) {
+ iterator = std::move(allocator);
+ movedToIterator = true;
+ }
+ }
+
+}
diff --git a/src/backend/common/CommandBuffer.h b/src/backend/common/CommandBuffer.h
new file mode 100644
index 0000000..1642d54
--- /dev/null
+++ b/src/backend/common/CommandBuffer.h
@@ -0,0 +1,98 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_COMMANDBUFFERGL_H_
+#define BACKEND_COMMON_COMMANDBUFFERGL_H_
+
+#include "nxt/nxtcpp.h"
+
+#include "CommandAllocator.h"
+#include "RefCounted.h"
+
+#include <set>
+#include <utility>
+
+namespace backend {
+
+ class BindGroupBase;
+ class BufferBase;
+ class DeviceBase;
+ class PipelineBase;
+ class TextureBase;
+
+ class CommandBufferBuilder;
+
+ class CommandBufferBase : public RefCounted {
+ public:
+ CommandBufferBase(CommandBufferBuilder* builder);
+ bool ValidateResourceUsagesImmediate();
+
+ private:
+ DeviceBase* device;
+ std::set<BufferBase*> buffersTransitioned;
+ std::set<TextureBase*> texturesTransitioned;
+ };
+
+ class CommandBufferBuilder : public RefCounted {
+ public:
+ CommandBufferBuilder(DeviceBase* device);
+ ~CommandBufferBuilder();
+
+ bool WasConsumed() const;
+ bool ValidateGetResult();
+
+ CommandIterator AcquireCommands();
+
+ // NXT API
+ CommandBufferBase* GetResult();
+
+ void CopyBufferToTexture(BufferBase* buffer, TextureBase* texture, uint32_t x, uint32_t y, uint32_t z,
+ uint32_t width, uint32_t height, uint32_t depth, uint32_t level);
+ void Dispatch(uint32_t x, uint32_t y, uint32_t z);
+ void DrawArrays(uint32_t vertexCount, uint32_t instanceCount, uint32_t firstVertex, uint32_t firstInstance);
+ void DrawElements(uint32_t vertexCount, uint32_t instanceCount, uint32_t firstIndex, uint32_t firstInstance);
+ void SetPushConstants(nxt::ShaderStageBit stage, uint32_t offset, uint32_t count, const void* data);
+ void SetPipeline(PipelineBase* pipeline);
+ void SetBindGroup(uint32_t groupIndex, BindGroupBase* group);
+ void SetIndexBuffer(BufferBase* buffer, uint32_t offset, nxt::IndexFormat format);
+
+ template<typename T>
+ void SetVertexBuffers(uint32_t startSlot, uint32_t count, T* const* buffers, uint32_t const* offsets) {
+ static_assert(std::is_base_of<BufferBase, T>::value, "");
+ SetVertexBuffers(startSlot, count, reinterpret_cast<BufferBase* const*>(buffers), offsets);
+ }
+ void SetVertexBuffers(uint32_t startSlot, uint32_t count, BufferBase* const* buffers, uint32_t const* offsets);
+
+ void TransitionBufferUsage(BufferBase* buffer, nxt::BufferUsageBit usage);
+ void TransitionTextureUsage(TextureBase* texture, nxt::TextureUsageBit usage);
+
+ private:
+ friend class CommandBufferBase;
+
+ void MoveToIterator();
+
+ DeviceBase* device;
+ CommandAllocator allocator;
+ CommandIterator iterator;
+ bool consumed = false;
+ bool movedToIterator = false;
+ // These pointers will remain valid since they are referenced by
+ // the bind groups which are referenced by this command buffer.
+ std::set<BufferBase*> buffersTransitioned;
+ std::set<TextureBase*> texturesTransitioned;
+ };
+
+}
+
+#endif // BACKEND_COMMON_COMMANDBUFFERGL_H_
diff --git a/src/backend/common/Commands.h b/src/backend/common/Commands.h
new file mode 100644
index 0000000..f9eebd0
--- /dev/null
+++ b/src/backend/common/Commands.h
@@ -0,0 +1,114 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_COMMANDS_H_
+#define BACKEND_COMMON_COMMANDS_H_
+
+#include "Texture.h"
+
+#include "nxt/nxtcpp.h"
+
+namespace backend {
+
+ // Definition of the commands that are present in the CommandIterator given by the
+ // CommandBufferBuilder. There are not defined in CommandBuffer.h to break some header
+ // dependencies: Ref<Object> needs Object to be defined.
+
+ enum class Command {
+ CopyBufferToTexture,
+ Dispatch,
+ DrawArrays,
+ DrawElements,
+ SetPipeline,
+ SetPushConstants,
+ SetBindGroup,
+ SetIndexBuffer,
+ SetVertexBuffers,
+ TransitionBufferUsage,
+ TransitionTextureUsage,
+ };
+
+ struct CopyBufferToTextureCmd {
+ Ref<BufferBase> buffer;
+ Ref<TextureBase> texture;
+ uint32_t x, y, z;
+ uint32_t width, height, depth;
+ uint32_t level;
+ };
+
+ struct DispatchCmd {
+ uint32_t x;
+ uint32_t y;
+ uint32_t z;
+ };
+
+ struct DrawArraysCmd {
+ uint32_t vertexCount;
+ uint32_t instanceCount;
+ uint32_t firstVertex;
+ uint32_t firstInstance;
+ };
+
+ struct DrawElementsCmd {
+ uint32_t indexCount;
+ uint32_t instanceCount;
+ uint32_t firstIndex;
+ uint32_t firstInstance;
+ };
+
+ struct SetPipelineCmd {
+ Ref<PipelineBase> pipeline;
+ };
+
+ struct SetPushConstantsCmd {
+ nxt::ShaderStageBit stage;
+ uint32_t offset;
+ uint32_t count;
+ };
+
+ struct SetBindGroupCmd {
+ uint32_t index;
+ Ref<BindGroupBase> group;
+ };
+
+ struct SetIndexBufferCmd {
+ Ref<BufferBase> buffer;
+ uint32_t offset;
+ nxt::IndexFormat format;
+ };
+
+ struct SetVertexBuffersCmd {
+ uint32_t startSlot;
+ uint32_t count;
+ };
+
+ struct TransitionBufferUsageCmd {
+ Ref<BufferBase> buffer;
+ nxt::BufferUsageBit usage;
+ };
+
+ struct TransitionTextureUsageCmd {
+ Ref<TextureBase> texture;
+ uint32_t startLevel;
+ uint32_t levelCount;
+ nxt::TextureUsageBit usage;
+ };
+
+ // This needs to be called before the CommandIterator is freed so that the Ref<> present in
+ // the commands have a chance to run their destructor and remove internal references.
+ void FreeCommands(CommandIterator* commands);
+
+}
+
+#endif // BACKEND_COMMON_COMMANDS_H_
diff --git a/src/backend/common/Device.cpp b/src/backend/common/Device.cpp
new file mode 100644
index 0000000..9dea47d
--- /dev/null
+++ b/src/backend/common/Device.cpp
@@ -0,0 +1,126 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Device.h"
+
+#include "BindGroup.h"
+#include "BindGroupLayout.h"
+#include "Buffer.h"
+#include "CommandBuffer.h"
+#include "InputState.h"
+#include "Pipeline.h"
+#include "PipelineLayout.h"
+#include "Queue.h"
+#include "Sampler.h"
+#include "ShaderModule.h"
+#include "Texture.h"
+
+#include <unordered_set>
+
+namespace backend {
+
+ void RegisterSynchronousErrorCallback(nxtDevice device, ErrorCallback callback, void* userData) {
+ auto deviceBase = reinterpret_cast<DeviceBase*>(device);
+ deviceBase->RegisterErrorCallback(callback, userData);
+ }
+
+ // DeviceBase::Caches
+
+ // The caches are unordered_sets of pointers with special hash and compare functions
+ // to compare the value of the objects, instead of the pointers.
+ using BindGroupLayoutCache = std::unordered_set<BindGroupLayoutBase*, BindGroupLayoutCacheFuncs, BindGroupLayoutCacheFuncs>;
+
+ struct DeviceBase::Caches {
+ BindGroupLayoutCache bindGroupLayouts;
+ };
+
+ // DeviceBase
+
+ DeviceBase::DeviceBase() {
+ caches = new DeviceBase::Caches();
+ }
+
+ DeviceBase::~DeviceBase() {
+ delete caches;
+ }
+
+ void DeviceBase::HandleError(const char* message) {
+ if (errorCallback) {
+ errorCallback(message, errorUserData);
+ }
+ }
+
+ void DeviceBase::RegisterErrorCallback(ErrorCallback callback, void* userData) {
+ this->errorCallback = callback;
+ this->errorUserData = userData;
+ }
+
+ BindGroupLayoutBase* DeviceBase::GetOrCreateBindGroupLayout(const BindGroupLayoutBase* blueprint, BindGroupLayoutBuilder* builder) {
+ // The blueprint is only used to search in the cache and is not modified. However cached
+ // objects can be modified, and unordered_set cannot search for a const pointer in a non
+ // const pointer set. That's why we do a const_cast here, but the blueprint won't be
+ // modified.
+ auto iter = caches->bindGroupLayouts.find(const_cast<BindGroupLayoutBase*>(blueprint));
+ if (iter != caches->bindGroupLayouts.end()) {
+ return *iter;
+ }
+
+ BindGroupLayoutBase* backendObj = CreateBindGroupLayout(builder);
+ caches->bindGroupLayouts.insert(backendObj);
+ return backendObj;
+ }
+
+ void DeviceBase::UncacheBindGroupLayout(BindGroupLayoutBase* obj) {
+ caches->bindGroupLayouts.erase(obj);
+ }
+
+ BindGroupBuilder* DeviceBase::CreateBindGroupBuilder() {
+ return new BindGroupBuilder(this);
+ }
+ BindGroupLayoutBuilder* DeviceBase::CreateBindGroupLayoutBuilder() {
+ return new BindGroupLayoutBuilder(this);
+ }
+ BufferBuilder* DeviceBase::CreateBufferBuilder() {
+ return new BufferBuilder(this);
+ }
+ CommandBufferBuilder* DeviceBase::CreateCommandBufferBuilder() {
+ return new CommandBufferBuilder(this);
+ }
+ InputStateBuilder* DeviceBase::CreateInputStateBuilder() {
+ return new InputStateBuilder(this);
+ }
+ PipelineBuilder* DeviceBase::CreatePipelineBuilder() {
+ return new PipelineBuilder(this);
+ }
+ PipelineLayoutBuilder* DeviceBase::CreatePipelineLayoutBuilder() {
+ return new PipelineLayoutBuilder(this);
+ }
+ QueueBuilder* DeviceBase::CreateQueueBuilder() {
+ return new QueueBuilder(this);
+ }
+ SamplerBuilder* DeviceBase::CreateSamplerBuilder() {
+ return new SamplerBuilder(this);
+ }
+ ShaderModuleBuilder* DeviceBase::CreateShaderModuleBuilder() {
+ return new ShaderModuleBuilder(this);
+ }
+ TextureBuilder* DeviceBase::CreateTextureBuilder() {
+ return new TextureBuilder(this);
+ }
+
+ void DeviceBase::CopyBindGroups(uint32_t start, uint32_t count, BindGroupBase* source, BindGroupBase* target) {
+ // TODO(cwallez@chromium.org): update state tracking then call the backend
+ }
+
+}
diff --git a/src/backend/common/Device.h b/src/backend/common/Device.h
new file mode 100644
index 0000000..a7b8e4c
--- /dev/null
+++ b/src/backend/common/Device.h
@@ -0,0 +1,94 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_DEVICEBASE_H_
+#define BACKEND_COMMON_DEVICEBASE_H_
+
+#include "common/Forward.h"
+#include "common/RefCounted.h"
+
+#include "nxt/nxtcpp.h"
+
+namespace backend {
+
+ using ErrorCallback = void (*)(const char* errorMessage, void* userData);
+
+ class DeviceBase {
+ public:
+ DeviceBase();
+ ~DeviceBase();
+
+ void HandleError(const char* message);
+ void RegisterErrorCallback(ErrorCallback callback, void* userData);
+
+ virtual BindGroupBase* CreateBindGroup(BindGroupBuilder* builder) = 0;
+ virtual BindGroupLayoutBase* CreateBindGroupLayout(BindGroupLayoutBuilder* builder) = 0;
+ virtual BufferBase* CreateBuffer(BufferBuilder* builder) = 0;
+ virtual BufferViewBase* CreateBufferView(BufferViewBuilder* builder) = 0;
+ virtual CommandBufferBase* CreateCommandBuffer(CommandBufferBuilder* builder) = 0;
+ virtual InputStateBase* CreateInputState(InputStateBuilder* builder) = 0;
+ virtual PipelineBase* CreatePipeline(PipelineBuilder* builder) = 0;
+ virtual PipelineLayoutBase* CreatePipelineLayout(PipelineLayoutBuilder* builder) = 0;
+ virtual QueueBase* CreateQueue(QueueBuilder* builder) = 0;
+ virtual SamplerBase* CreateSampler(SamplerBuilder* builder) = 0;
+ virtual ShaderModuleBase* CreateShaderModule(ShaderModuleBuilder* builder) = 0;
+ virtual TextureBase* CreateTexture(TextureBuilder* builder) = 0;
+ virtual TextureViewBase* CreateTextureView(TextureViewBuilder* builder) = 0;
+
+ // Many NXT objects are completely immutable once created which means that if two
+ // builders are given the same arguments, they can return the same object. Reusing
+ // objects will help make comparisons between objects by a single pointer comparison.
+ //
+ // Technically no object is immutable as they have a reference count, and an
+ // application with reference-counting issues could "see" that objects are reused.
+ // This is solved by automatic-reference counting, and also the fact that when using
+ // the client-server wire every creation will get a different proxy object, with a
+ // different reference count.
+ //
+ // When trying to create an object, we give both the builder and an example of what
+ // the built object will be, the "blueprint". The blueprint is just a FooBase object
+ // instead of a backend Foo object. If the blueprint doesn't match an object in the
+ // cache, then the builder is used to make a new object.
+ BindGroupLayoutBase* GetOrCreateBindGroupLayout(const BindGroupLayoutBase* blueprint, BindGroupLayoutBuilder* builder);
+ void UncacheBindGroupLayout(BindGroupLayoutBase* obj);
+
+ // NXT API
+ BindGroupBuilder* CreateBindGroupBuilder();
+ BindGroupLayoutBuilder* CreateBindGroupLayoutBuilder();
+ BufferBuilder* CreateBufferBuilder();
+ BufferViewBuilder* CreateBufferViewBuilder();
+ CommandBufferBuilder* CreateCommandBufferBuilder();
+ InputStateBuilder* CreateInputStateBuilder();
+ PipelineBuilder* CreatePipelineBuilder();
+ PipelineLayoutBuilder* CreatePipelineLayoutBuilder();
+ QueueBuilder* CreateQueueBuilder();
+ SamplerBuilder* CreateSamplerBuilder();
+ ShaderModuleBuilder* CreateShaderModuleBuilder();
+ TextureBuilder* CreateTextureBuilder();
+
+ void CopyBindGroups(uint32_t start, uint32_t count, BindGroupBase* source, BindGroupBase* target);
+
+ private:
+ // The object caches aren't exposed in the header as they would require a lot of
+ // additional includes.
+ struct Caches;
+ Caches* caches = nullptr;
+
+ ErrorCallback errorCallback = nullptr;
+ void* errorUserData = nullptr;
+ };
+
+}
+
+#endif // BACKEND_COMMON_DEVICEBASE_H_
diff --git a/src/backend/common/Forward.h b/src/backend/common/Forward.h
new file mode 100644
index 0000000..94e64ce
--- /dev/null
+++ b/src/backend/common/Forward.h
@@ -0,0 +1,71 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_FORWARD_H_
+#define BACKEND_COMMON_FORWARD_H_
+
+#include <cassert>
+#include <cstdint>
+
+#define ASSERT assert
+
+namespace backend {
+
+ class BindGroupBase;
+ class BindGroupBuilder;
+ class BindGroupLayoutBase;
+ class BindGroupLayoutBuilder;
+ class BufferBase;
+ class BufferBuilder;
+ class BufferViewBase;
+ class BufferViewBuilder;
+ class CommandBufferBase;
+ class CommandBufferBuilder;
+ class InputStateBase;
+ class InputStateBuilder;
+ class PipelineBase;
+ class PipelineBuilder;
+ class PipelineLayoutBase;
+ class PipelineLayoutBuilder;
+ class QueueBase;
+ class QueueBuilder;
+ class SamplerBase;
+ class SamplerBuilder;
+ class ShaderModuleBase;
+ class ShaderModuleBuilder;
+ class TextureBase;
+ class TextureBuilder;
+ class TextureViewBase;
+ class TextureViewBuilder;
+
+ class DeviceBase;
+
+ template<typename T>
+ class Ref;
+
+ template<typename T>
+ class PerStage;
+
+ // TODO(cwallez@chromium.org): where should constants live?
+ static constexpr uint32_t kMaxPushConstants = 32u;
+ static constexpr uint32_t kMaxBindGroups = 4u;
+ static constexpr uint32_t kMaxBindingsPerGroup = 16u; // TODO(cwallez@chromium.org): investigate bindgroup limits
+ static constexpr uint32_t kMaxVertexAttributes = 16u;
+ static constexpr uint32_t kMaxVertexInputs = 16u;
+ static constexpr uint32_t kNumStages = 3;
+
+ enum PushConstantType : uint8_t;
+}
+
+#endif // BACKEND_COMMON_FORWARD_H_
diff --git a/src/backend/common/InputState.cpp b/src/backend/common/InputState.cpp
new file mode 100644
index 0000000..2b2da0c
--- /dev/null
+++ b/src/backend/common/InputState.cpp
@@ -0,0 +1,139 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "InputState.h"
+
+#include "Device.h"
+
+namespace backend {
+
+ // InputState helpers
+
+ size_t IndexFormatSize(nxt::IndexFormat format) {
+ switch (format) {
+ case nxt::IndexFormat::Uint16:
+ return sizeof(uint16_t);
+ case nxt::IndexFormat::Uint32:
+ return sizeof(uint32_t);
+ }
+ }
+
+ uint32_t VertexFormatNumComponents(nxt::VertexFormat format) {
+ switch (format) {
+ case nxt::VertexFormat::FloatR32G32B32A32:
+ return 4;
+ case nxt::VertexFormat::FloatR32G32B32:
+ return 3;
+ case nxt::VertexFormat::FloatR32G32:
+ return 2;
+ }
+ }
+
+ size_t VertexFormatSize(nxt::VertexFormat format) {
+ switch (format) {
+ case nxt::VertexFormat::FloatR32G32B32A32:
+ case nxt::VertexFormat::FloatR32G32B32:
+ case nxt::VertexFormat::FloatR32G32:
+ return VertexFormatNumComponents(format) * sizeof(float);
+ }
+ }
+
+ // InputStateBase
+
+ InputStateBase::InputStateBase(InputStateBuilder* builder) {
+ attributesSetMask = builder->attributesSetMask;
+ attributeInfos = builder->attributeInfos;
+ inputsSetMask = builder->inputsSetMask;
+ inputInfos = builder->inputInfos;
+ }
+
+ const std::bitset<kMaxVertexAttributes>& InputStateBase::GetAttributesSetMask() const {
+ return attributesSetMask;
+ }
+
+ const InputStateBase::AttributeInfo& InputStateBase::GetAttribute(uint32_t location) const {
+ ASSERT(attributesSetMask[location]);
+ return attributeInfos[location];
+ }
+
+ const std::bitset<kMaxVertexInputs>& InputStateBase::GetInputsSetMask() const {
+ return inputsSetMask;
+ }
+
+ const InputStateBase::InputInfo& InputStateBase::GetInput(uint32_t slot) const {
+ ASSERT(inputsSetMask[slot]);
+ return inputInfos[slot];
+ }
+
+ // InputStateBuilder
+
+ InputStateBuilder::InputStateBuilder(DeviceBase* device) : device(device) {
+ }
+
+ bool InputStateBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ InputStateBase* InputStateBuilder::GetResult() {
+ for (uint32_t location = 0; location < kMaxVertexAttributes; ++location) {
+ if (attributesSetMask[location] &&
+ !inputsSetMask[attributeInfos[location].bindingSlot]) {
+ device->HandleError("Attribute uses unset input");
+ return nullptr;
+ }
+ }
+ consumed = true;
+ return device->CreateInputState(this);
+ }
+
+ void InputStateBuilder::SetAttribute(uint32_t shaderLocation,
+ uint32_t bindingSlot, nxt::VertexFormat format, uint32_t offset) {
+ if (shaderLocation >= kMaxVertexAttributes) {
+ device->HandleError("Setting attribute out of bounds");
+ return;
+ }
+ if (bindingSlot >= kMaxVertexInputs) {
+ device->HandleError("Binding slot out of bounds");
+ return;
+ }
+ if (attributesSetMask[shaderLocation]) {
+ device->HandleError("Setting already set attribute");
+ return;
+ }
+
+ attributesSetMask.set(shaderLocation);
+ auto& info = attributeInfos[shaderLocation];
+ info.bindingSlot = bindingSlot;
+ info.format = format;
+ info.offset = offset;
+ }
+
+ void InputStateBuilder::SetInput(uint32_t bindingSlot, uint32_t stride,
+ nxt::InputStepMode stepMode) {
+ if (bindingSlot >= kMaxVertexInputs) {
+ device->HandleError("Setting input out of bounds");
+ return;
+ }
+ if (inputsSetMask[bindingSlot]) {
+ device->HandleError("Setting already set input");
+ return;
+ }
+
+ inputsSetMask.set(bindingSlot);
+ auto& info = inputInfos[bindingSlot];
+ info.stride = stride;
+ info.stepMode = stepMode;
+ }
+
+}
diff --git a/src/backend/common/InputState.h b/src/backend/common/InputState.h
new file mode 100644
index 0000000..3cf29c2
--- /dev/null
+++ b/src/backend/common/InputState.h
@@ -0,0 +1,85 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_INPUTSTATE_H_
+#define BACKEND_COMMON_INPUTSTATE_H_
+
+#include "Forward.h"
+#include "RefCounted.h"
+
+#include "nxt/nxtcpp.h"
+
+#include <array>
+#include <bitset>
+
+namespace backend {
+
+ size_t IndexFormatSize(nxt::IndexFormat format);
+ uint32_t VertexFormatNumComponents(nxt::VertexFormat format);
+ size_t VertexFormatSize(nxt::VertexFormat format);
+
+ class InputStateBase : public RefCounted {
+ public:
+ InputStateBase(InputStateBuilder* builder);
+
+ struct AttributeInfo {
+ uint32_t bindingSlot;
+ nxt::VertexFormat format;
+ uint32_t offset;
+ };
+
+ struct InputInfo {
+ uint32_t stride;
+ nxt::InputStepMode stepMode;
+ };
+
+ const std::bitset<kMaxVertexAttributes>& GetAttributesSetMask() const;
+ const AttributeInfo& GetAttribute(uint32_t location) const;
+ const std::bitset<kMaxVertexInputs>& GetInputsSetMask() const;
+ const InputInfo& GetInput(uint32_t slot) const;
+
+ private:
+ std::bitset<kMaxVertexAttributes> attributesSetMask;
+ std::array<AttributeInfo, kMaxVertexAttributes> attributeInfos;
+ std::bitset<kMaxVertexInputs> inputsSetMask;
+ std::array<InputInfo, kMaxVertexInputs> inputInfos;
+ };
+
+ class InputStateBuilder : public RefCounted {
+ public:
+ InputStateBuilder(DeviceBase* device);
+
+ bool WasConsumed() const;
+
+ // NXT API
+ InputStateBase* GetResult();
+ void SetAttribute(uint32_t shaderLocation, uint32_t bindingSlot,
+ nxt::VertexFormat format, uint32_t offset);
+ void SetInput(uint32_t bindingSlot, uint32_t stride,
+ nxt::InputStepMode stepMode);
+
+ private:
+ friend class InputStateBase;
+
+ DeviceBase* device;
+ std::bitset<kMaxVertexAttributes> attributesSetMask;
+ std::array<InputStateBase::AttributeInfo, kMaxVertexAttributes> attributeInfos;
+ std::bitset<kMaxVertexInputs> inputsSetMask;
+ std::array<InputStateBase::InputInfo, kMaxVertexInputs> inputInfos;
+ bool consumed = false;
+ };
+
+}
+
+#endif // BACKEND_COMMON_INPUTSTATE_H_
diff --git a/src/backend/common/Math.cpp b/src/backend/common/Math.cpp
new file mode 100644
index 0000000..483f524
--- /dev/null
+++ b/src/backend/common/Math.cpp
@@ -0,0 +1,52 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Math.h"
+
+#include "Forward.h"
+
+namespace backend {
+
+ unsigned long ScanForward(unsigned long bits) {
+ ASSERT(bits != 0);
+ // TODO(cwallez@chromium.org): handle non-posix platforms
+ // unsigned long firstBitIndex = 0ul;
+ // unsigned char ret = _BitScanForward(&firstBitIndex, bits);
+ // ASSERT(ret != 0);
+ // return firstBitIndex;
+ return static_cast<unsigned long>(__builtin_ctzl(bits));
+ }
+
+ uint32_t Log2(uint32_t value) {
+ ASSERT(value != 0);
+ return 31 - __builtin_clz(value);
+ }
+
+ bool IsPowerOfTwo(size_t n) {
+ ASSERT(n != 0);
+ return (n & (n - 1)) == 0;
+ }
+
+ bool IsAligned(const void* ptr, size_t alignment) {
+ ASSERT(IsPowerOfTwo(alignment));
+ ASSERT(alignment != 0);
+ return (reinterpret_cast<intptr_t>(ptr) & (alignment - 1)) == 0;
+ }
+
+ void* AlignVoidPtr(void* ptr, size_t alignment) {
+ ASSERT(alignment != 0);
+ return reinterpret_cast<void*>((reinterpret_cast<intptr_t>(ptr) + (alignment - 1)) & ~(alignment - 1));
+ }
+
+}
diff --git a/src/backend/common/Math.h b/src/backend/common/Math.h
new file mode 100644
index 0000000..4e9e9fb
--- /dev/null
+++ b/src/backend/common/Math.h
@@ -0,0 +1,43 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_MATH_H_
+#define BACKEND_COMMON_MATH_H_
+
+#include <cstddef>
+#include "cstdint"
+
+namespace backend {
+
+ // The following are not valid for 0
+ unsigned long ScanForward(unsigned long bits);
+ uint32_t Log2(uint32_t value);
+ bool IsPowerOfTwo(size_t n);
+
+ bool IsAligned(const void* ptr, size_t alignment);
+ void* AlignVoidPtr(void* ptr, size_t alignment);
+
+ template<typename T>
+ T* Align(T* ptr, size_t alignment) {
+ return reinterpret_cast<T*>(AlignVoidPtr(ptr, alignment));
+ }
+
+ template<typename T>
+ const T* Align(const T* ptr, size_t alignment) {
+ return reinterpret_cast<const T*>(AlignVoidPtr(const_cast<T*>(ptr), alignment));
+ }
+
+}
+
+#endif // BACKEND_COMMON_MATH_H_
diff --git a/src/backend/common/PerStage.cpp b/src/backend/common/PerStage.cpp
new file mode 100644
index 0000000..cdccac5
--- /dev/null
+++ b/src/backend/common/PerStage.cpp
@@ -0,0 +1,29 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "PerStage.h"
+
+namespace backend {
+
+ BitSetIterator<kNumStages, nxt::ShaderStage> IterateStages(nxt::ShaderStageBit stages) {
+ std::bitset<kNumStages> bits(static_cast<uint32_t>(stages));
+ return BitSetIterator<kNumStages, nxt::ShaderStage>(bits);
+ }
+
+ nxt::ShaderStageBit StageBit(nxt::ShaderStage stage) {
+ ASSERT(static_cast<uint32_t>(stage) < kNumStages);
+ return static_cast<nxt::ShaderStageBit>(1 << static_cast<uint32_t>(stage));
+ }
+
+}
diff --git a/src/backend/common/PerStage.h b/src/backend/common/PerStage.h
new file mode 100644
index 0000000..c14dde8
--- /dev/null
+++ b/src/backend/common/PerStage.h
@@ -0,0 +1,68 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_PERSTAGE_H_
+#define BACKEND_COMMON_PERSTAGE_H_
+
+#include "BitSetIterator.h"
+
+#include "nxt/nxtcpp.h"
+
+#include <array>
+
+namespace backend {
+
+ static_assert(static_cast<uint32_t>(nxt::ShaderStage::Vertex) < kNumStages, "");
+ static_assert(static_cast<uint32_t>(nxt::ShaderStage::Fragment) < kNumStages, "");
+ static_assert(static_cast<uint32_t>(nxt::ShaderStage::Compute) < kNumStages, "");
+
+ static_assert(static_cast<uint32_t>(nxt::ShaderStageBit::Vertex) == (1 << static_cast<uint32_t>(nxt::ShaderStage::Vertex)), "");
+ static_assert(static_cast<uint32_t>(nxt::ShaderStageBit::Fragment) == (1 << static_cast<uint32_t>(nxt::ShaderStage::Fragment)), "");
+ static_assert(static_cast<uint32_t>(nxt::ShaderStageBit::Compute) == (1 << static_cast<uint32_t>(nxt::ShaderStage::Compute)), "");
+
+ BitSetIterator<kNumStages, nxt::ShaderStage> IterateStages(nxt::ShaderStageBit stages);
+ nxt::ShaderStageBit StageBit(nxt::ShaderStage stage);
+
+ static constexpr nxt::ShaderStageBit kAllStages = static_cast<nxt::ShaderStageBit>((1 << kNumStages) - 1);
+
+ template<typename T>
+ class PerStage {
+ public:
+ T& operator[](nxt::ShaderStage stage) {
+ ASSERT(static_cast<uint32_t>(stage) < kNumStages);
+ return data[static_cast<uint32_t>(stage)];
+ }
+ const T& operator[](nxt::ShaderStage stage) const {
+ ASSERT(static_cast<uint32_t>(stage) < kNumStages);
+ return data[static_cast<uint32_t>(stage)];
+ }
+
+ T& operator[](nxt::ShaderStageBit stageBit) {
+ uint32_t bit = static_cast<uint32_t>(stageBit);
+ ASSERT(bit != 0 && IsPowerOfTwo(bit) && bit <= (1 << kNumStages));
+ return data[Log2(bit)];
+ }
+ const T& operator[](nxt::ShaderStageBit stageBit) const {
+ uint32_t bit = static_cast<uint32_t>(stageBit);
+ ASSERT(bit != 0 && IsPowerOfTwo(bit) && bit <= (1 << kNumStages));
+ return data[Log2(bit)];
+ }
+
+ private:
+ std::array<T, kNumStages> data;
+ };
+
+}
+
+#endif // BACKEND_COMMON_PERSTAGE_H_
diff --git a/src/backend/common/Pipeline.cpp b/src/backend/common/Pipeline.cpp
new file mode 100644
index 0000000..f9901af
--- /dev/null
+++ b/src/backend/common/Pipeline.cpp
@@ -0,0 +1,149 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Pipeline.h"
+
+#include "Device.h"
+#include "InputState.h"
+#include "PipelineLayout.h"
+#include "ShaderModule.h"
+
+namespace backend {
+
+ // PipelineBase
+
+ PipelineBase::PipelineBase(PipelineBuilder* builder)
+ : device(builder->device), stageMask(builder->stageMask), layout(std::move(builder->layout)),
+ inputState(std::move(builder->inputState)) {
+
+ if (stageMask != (nxt::ShaderStageBit::Vertex | nxt::ShaderStageBit::Fragment) &&
+ stageMask != nxt::ShaderStageBit::Compute) {
+ device->HandleError("Wrong combination of stage for pipeline");
+ return;
+ }
+
+ auto FillPushConstants = [](const ShaderModuleBase* module, PushConstantInfo* info) {
+ const auto& moduleInfo = module->GetPushConstants();
+ info->mask = moduleInfo.mask;
+
+ for (uint32_t i = 0; i < moduleInfo.names.size(); i++) {
+ unsigned int size = moduleInfo.sizes[i];
+ if (size == 0) {
+ continue;
+ }
+
+ for (uint32_t offset = 0; offset < size; offset++) {
+ info->types[i + offset] = moduleInfo.types[i];
+ }
+ i += size - 1;
+ }
+ };
+
+ for (auto stageBit : IterateStages(builder->stageMask)) {
+ if (!builder->stages[stageBit].module->IsCompatibleWithPipelineLayout(layout.Get())) {
+ device->HandleError("Stage not compatible with layout");
+ return;
+ }
+
+ FillPushConstants(builder->stages[stageBit].module.Get(), &pushConstants[stageBit]);
+ }
+
+ if (!IsCompute()) {
+ if ((builder->stages[nxt::ShaderStage::Vertex].module->GetUsedVertexAttributes() & ~inputState->GetAttributesSetMask()).any()) {
+ device->HandleError("Pipeline vertex stage uses inputs not in the input state");
+ return;
+ }
+ }
+ }
+
+ const PipelineBase::PushConstantInfo& PipelineBase::GetPushConstants(nxt::ShaderStage stage) const {
+ return pushConstants[stage];
+ }
+
+ nxt::ShaderStageBit PipelineBase::GetStageMask() const {
+ return stageMask;
+ }
+
+ PipelineLayoutBase* PipelineBase::GetLayout() {
+ return layout.Get();
+ }
+
+ InputStateBase* PipelineBase::GetInputState() {
+ return inputState.Get();
+ }
+
+ bool PipelineBase::IsCompute() const {
+ return stageMask == nxt::ShaderStageBit::Compute;
+ }
+
+ // PipelineBuilder
+
+ PipelineBuilder::PipelineBuilder(DeviceBase* device)
+ : device(device), stageMask(static_cast<nxt::ShaderStageBit>(0)) {
+ }
+
+ bool PipelineBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ const PipelineBuilder::StageInfo& PipelineBuilder::GetStageInfo(nxt::ShaderStage stage) const {
+ ASSERT(stageMask & StageBit(stage));
+ return stages[stage];
+ }
+
+ PipelineBase* PipelineBuilder::GetResult() {
+ // TODO(cwallez@chromium.org): the layout should be required, and put the default objects in the device
+ if (!layout) {
+ layout = device->CreatePipelineLayoutBuilder()->GetResult();
+ }
+ if (!inputState) {
+ inputState = device->CreateInputStateBuilder()->GetResult();
+ }
+
+ consumed = true;
+ return device->CreatePipeline(this);
+ }
+
+ void PipelineBuilder::SetLayout(PipelineLayoutBase* layout) {
+ this->layout = layout;
+ }
+
+ void PipelineBuilder::SetStage(nxt::ShaderStage stage, ShaderModuleBase* module, const char* entryPoint) {
+ if (entryPoint != std::string("main")) {
+ device->HandleError("Currently the entry point has to be main()");
+ return;
+ }
+
+ if (stage != module->GetExecutionModel()) {
+ device->HandleError("Setting module with wrong execution model");
+ return;
+ }
+
+ nxt::ShaderStageBit bit = StageBit(stage);
+ if (stageMask & bit) {
+ device->HandleError("Setting already set stage");
+ return;
+ }
+ stageMask |= bit;
+
+ stages[stage].module = module;
+ stages[stage].entryPoint = entryPoint;
+ }
+
+ void PipelineBuilder::SetInputState(InputStateBase* inputState) {
+ this->inputState = inputState;
+ }
+
+
+}
diff --git a/src/backend/common/Pipeline.h b/src/backend/common/Pipeline.h
new file mode 100644
index 0000000..761ec5a
--- /dev/null
+++ b/src/backend/common/Pipeline.h
@@ -0,0 +1,92 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_PIPELINE_H_
+#define BACKEND_COMMON_PIPELINE_H_
+
+#include "Forward.h"
+#include "PerStage.h"
+#include "RefCounted.h"
+
+#include "nxt/nxtcpp.h"
+
+#include <array>
+#include <bitset>
+
+namespace backend {
+
+ enum PushConstantType : uint8_t {
+ Int,
+ UInt,
+ Float,
+ };
+
+ class PipelineBase : public RefCounted {
+ public:
+ PipelineBase(PipelineBuilder* builder);
+
+ struct PushConstantInfo {
+ std::bitset<kMaxPushConstants> mask;
+ std::array<PushConstantType, kMaxPushConstants> types;
+ };
+ const PushConstantInfo& GetPushConstants(nxt::ShaderStage stage) const;
+ nxt::ShaderStageBit GetStageMask() const;
+
+ PipelineLayoutBase* GetLayout();
+ InputStateBase* GetInputState();
+
+ // TODO(cwallez@chromium.org): split compute and render pipelines
+ bool IsCompute() const;
+
+ private:
+ DeviceBase* device;
+
+ nxt::ShaderStageBit stageMask;
+ Ref<PipelineLayoutBase> layout;
+ PerStage<PushConstantInfo> pushConstants;
+ Ref<InputStateBase> inputState;
+ };
+
+ class PipelineBuilder : public RefCounted {
+ public:
+ PipelineBuilder(DeviceBase* device);
+
+ bool WasConsumed() const;
+
+ struct StageInfo {
+ std::string entryPoint;
+ Ref<ShaderModuleBase> module;
+ };
+ const StageInfo& GetStageInfo(nxt::ShaderStage stage) const;
+
+ // NXT API
+ PipelineBase* GetResult();
+ void SetLayout(PipelineLayoutBase* layout);
+ void SetStage(nxt::ShaderStage stage, ShaderModuleBase* module, const char* entryPoint);
+ void SetInputState(InputStateBase* inputState);
+
+ private:
+ friend class PipelineBase;
+
+ DeviceBase* device;
+ Ref<PipelineLayoutBase> layout;
+ nxt::ShaderStageBit stageMask;
+ PerStage<StageInfo> stages;
+ Ref<InputStateBase> inputState;
+ bool consumed = false;
+ };
+
+}
+
+#endif // BACKEND_COMMON_PIPELINE_H_
diff --git a/src/backend/common/PipelineLayout.cpp b/src/backend/common/PipelineLayout.cpp
new file mode 100644
index 0000000..3a01640
--- /dev/null
+++ b/src/backend/common/PipelineLayout.cpp
@@ -0,0 +1,73 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "PipelineLayout.h"
+
+#include "BindGroupLayout.h"
+#include "Device.h"
+
+namespace backend {
+
+ // PipelineLayoutBase
+
+ PipelineLayoutBase::PipelineLayoutBase(PipelineLayoutBuilder* builder)
+ : bindGroupLayouts(std::move(builder->bindGroupLayouts)), mask(builder->mask) {
+ }
+
+ const BindGroupLayoutBase* PipelineLayoutBase::GetBindGroupLayout(size_t group) const {
+ ASSERT(group < kMaxBindGroups);
+ return bindGroupLayouts[group].Get();
+ }
+
+ const std::bitset<kMaxBindGroups> PipelineLayoutBase::GetBindGroupsLayoutMask() const {
+ return mask;
+ }
+
+ // PipelineLayoutBuilder
+
+ PipelineLayoutBuilder::PipelineLayoutBuilder(DeviceBase* device) : device(device) {
+ }
+
+ bool PipelineLayoutBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ PipelineLayoutBase* PipelineLayoutBuilder::GetResult() {
+ // TODO(cwallez@chromium.org): this is a hack, have the null bind group layout somewhere in the device
+ // once we have a cache of BGL
+ for (size_t group = 0; group < kMaxBindGroups; ++group) {
+ if (!bindGroupLayouts[group]) {
+ bindGroupLayouts[group] = device->CreateBindGroupLayoutBuilder()->GetResult();
+ }
+ }
+
+ consumed = true;
+ return device->CreatePipelineLayout(this);
+ }
+
+ void PipelineLayoutBuilder::SetBindGroupLayout(uint32_t groupIndex, BindGroupLayoutBase* layout) {
+ if (groupIndex >= kMaxBindGroups) {
+ device->HandleError("groupIndex is over the maximum allowed");
+ return;
+ }
+ if (mask[groupIndex]) {
+ device->HandleError("Bind group layout already specified");
+ return;
+ }
+
+ bindGroupLayouts[groupIndex] = layout;
+ mask.set(groupIndex);
+ }
+
+}
diff --git a/src/backend/common/PipelineLayout.h b/src/backend/common/PipelineLayout.h
new file mode 100644
index 0000000..79e3626
--- /dev/null
+++ b/src/backend/common/PipelineLayout.h
@@ -0,0 +1,63 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_PIPELINELAYOUT_H_
+#define BACKEND_COMMON_PIPELINELAYOUT_H_
+
+#include "Forward.h"
+#include "RefCounted.h"
+
+#include "nxt/nxtcpp.h"
+
+#include <array>
+#include <bitset>
+
+namespace backend {
+
+ using BindGroupLayoutArray = std::array<Ref<BindGroupLayoutBase>, kMaxBindGroups>;
+
+ class PipelineLayoutBase : public RefCounted {
+ public:
+ PipelineLayoutBase(PipelineLayoutBuilder* builder);
+
+ const BindGroupLayoutBase* GetBindGroupLayout(size_t group) const;
+ const std::bitset<kMaxBindGroups> GetBindGroupsLayoutMask() const;
+
+ protected:
+ BindGroupLayoutArray bindGroupLayouts;
+ std::bitset<kMaxBindGroups> mask;
+ };
+
+ class PipelineLayoutBuilder : public RefCounted {
+ public:
+ PipelineLayoutBuilder(DeviceBase* device);
+
+ bool WasConsumed() const;
+
+ // NXT API
+ PipelineLayoutBase* GetResult();
+ void SetBindGroupLayout(uint32_t groupIndex, BindGroupLayoutBase* layout);
+
+ private:
+ friend class PipelineLayoutBase;
+
+ DeviceBase* device;
+ BindGroupLayoutArray bindGroupLayouts;
+ std::bitset<kMaxBindGroups> mask;
+ bool consumed = false;
+ };
+
+}
+
+#endif // BACKEND_COMMON_PIPELINELAYOUT_H_
diff --git a/src/backend/common/Queue.cpp b/src/backend/common/Queue.cpp
new file mode 100644
index 0000000..f5483fb
--- /dev/null
+++ b/src/backend/common/Queue.cpp
@@ -0,0 +1,42 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Queue.h"
+
+#include "Device.h"
+#include "CommandBuffer.h"
+
+namespace backend {
+
+ // QueueBase
+
+ bool QueueBase::ValidateSubmitCommand(CommandBufferBase* command) {
+ return command->ValidateResourceUsagesImmediate();
+ }
+
+ // QueueBuilder
+
+ QueueBuilder::QueueBuilder(DeviceBase* device) : device(device) {
+ }
+
+ bool QueueBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ QueueBase* QueueBuilder::GetResult() {
+ consumed = true;
+ return device->CreateQueue(this);
+ }
+
+}
diff --git a/src/backend/common/Queue.h b/src/backend/common/Queue.h
new file mode 100644
index 0000000..0c0ce6c
--- /dev/null
+++ b/src/backend/common/Queue.h
@@ -0,0 +1,59 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_QUEUE_H_
+#define BACKEND_COMMON_QUEUE_H_
+
+#include "Forward.h"
+#include "RefCounted.h"
+
+#include "nxt/nxtcpp.h"
+
+namespace backend {
+
+ class QueueBase : public RefCounted {
+ private:
+ bool ValidateSubmitCommand(CommandBufferBase* command);
+
+ public:
+ template<typename T>
+ bool ValidateSubmit(uint32_t numCommands, T* const * commands) {
+ static_assert(std::is_base_of<CommandBufferBase, T>::value, "invalid command buffer type");
+
+ for (uint32_t i = 0; i < numCommands; ++i) {
+ if (!ValidateSubmitCommand(commands[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ };
+
+ class QueueBuilder : public RefCounted {
+ public:
+ QueueBuilder(DeviceBase* device);
+
+ bool WasConsumed() const;
+
+ // NXT API
+ QueueBase* GetResult();
+
+ private:
+ DeviceBase* device;
+ bool consumed = false;
+ };
+
+}
+
+#endif // BACKEND_COMMON_QUEUE_H_
diff --git a/src/backend/common/RefCounted.cpp b/src/backend/common/RefCounted.cpp
new file mode 100644
index 0000000..076b57f
--- /dev/null
+++ b/src/backend/common/RefCounted.cpp
@@ -0,0 +1,66 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "RefCounted.h"
+
+#include <cassert>
+#define ASSERT assert
+
+namespace backend {
+
+ RefCounted::RefCounted() {
+ }
+
+ RefCounted::~RefCounted() {
+ }
+
+ void RefCounted::ReferenceInternal() {
+ ASSERT(internalRefs != 0);
+ // TODO(cwallez@chromium.org): what to do on overflow?
+ internalRefs ++;
+ }
+
+ void RefCounted::ReleaseInternal() {
+ ASSERT(internalRefs != 0);
+ internalRefs --;
+ if (internalRefs == 0) {
+ ASSERT(externalRefs == 0);
+ // TODO(cwallez@chromium.org): would this work with custom allocators?
+ delete this;
+ }
+ }
+
+ uint32_t RefCounted::GetExternalRefs() const {
+ return externalRefs;
+ }
+
+ uint32_t RefCounted::GetInternalRefs() const {
+ return internalRefs;
+ }
+
+ void RefCounted::Reference() {
+ ASSERT(externalRefs != 0);
+ // TODO(cwallez@chromium.org): what to do on overflow?
+ externalRefs ++;
+ }
+
+ void RefCounted::Release() {
+ ASSERT(externalRefs != 0);
+ externalRefs --;
+ if (externalRefs == 0) {
+ ReleaseInternal();
+ }
+ }
+
+}
diff --git a/src/backend/common/RefCounted.h b/src/backend/common/RefCounted.h
new file mode 100644
index 0000000..a9aa304
--- /dev/null
+++ b/src/backend/common/RefCounted.h
@@ -0,0 +1,126 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_REFCOUNTED_H_
+#define BACKEND_COMMON_REFCOUNTED_H_
+
+#include <cstdint>
+
+namespace backend {
+
+ class RefCounted {
+ public:
+ RefCounted();
+ virtual ~RefCounted();
+
+ void ReferenceInternal();
+ void ReleaseInternal();
+
+ uint32_t GetExternalRefs() const;
+ uint32_t GetInternalRefs() const;
+
+ // NXT API
+ void Reference();
+ void Release();
+
+ protected:
+ uint32_t externalRefs = 1;
+ uint32_t internalRefs = 1;
+ };
+
+ template<typename T>
+ class Ref {
+ public:
+ Ref() {}
+
+ Ref(T* p): pointee(p) {
+ Reference();
+ }
+
+ Ref(Ref<T>& other): pointee(other.pointee) {
+ Reference();
+ }
+ Ref<T>& operator=(const Ref<T>& other) {
+ if (&other == this) return *this;
+
+ other.Reference();
+ Release();
+ pointee = other.pointee;
+
+ return *this;
+ }
+
+ Ref(Ref<T>&& other) {
+ pointee = other.pointee;
+ other.pointee = nullptr;
+ }
+ Ref<T>& operator=(Ref<T>&& other) {
+ if (&other == this) return *this;
+
+ Release();
+ pointee = other.pointee;
+ other.pointee = nullptr;
+
+ return *this;
+ }
+
+ ~Ref() {
+ Release();
+ pointee = nullptr;
+ }
+
+ operator bool() {
+ return pointee != nullptr;
+ }
+
+ const T& operator*() const {
+ return *pointee;
+ }
+ T& operator*() {
+ return *pointee;
+ }
+
+ const T* operator->() const {
+ return pointee;
+ }
+ T* operator->() {
+ return pointee;
+ }
+
+ const T* Get() const {
+ return pointee;
+ }
+ T* Get() {
+ return pointee;
+ }
+
+ private:
+ void Reference() const {
+ if (pointee != nullptr) {
+ pointee->ReferenceInternal();
+ }
+ }
+ void Release() const {
+ if (pointee != nullptr) {
+ pointee->ReleaseInternal();
+ }
+ }
+
+ //static_assert(std::is_base_of<RefCounted, T>::value, "");
+ T* pointee = nullptr;
+ };
+
+}
+
+#endif // BACKEND_COMMON_REFCOUNTED_H_
diff --git a/src/backend/common/Sampler.cpp b/src/backend/common/Sampler.cpp
new file mode 100644
index 0000000..8a4f2fa
--- /dev/null
+++ b/src/backend/common/Sampler.cpp
@@ -0,0 +1,67 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Sampler.h"
+
+#include "Device.h"
+
+namespace backend {
+
+ // SamplerBase
+
+ SamplerBase::SamplerBase(SamplerBuilder* builder) {
+ }
+
+ // SamplerBuilder
+
+ enum SamplerSetProperties {
+ SAMPLER_PROPERTY_FILTER = 0x1,
+ };
+ SamplerBuilder::SamplerBuilder(DeviceBase* device)
+ :device(device) {
+ }
+
+ nxt::FilterMode SamplerBuilder::GetMagFilter() const {
+ return magFilter;
+ }
+
+ nxt::FilterMode SamplerBuilder::GetMinFilter() const {
+ return minFilter;
+ }
+
+ nxt::FilterMode SamplerBuilder::GetMipMapFilter() const {
+ return mipMapFilter;
+ }
+
+ bool SamplerBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ SamplerBase* SamplerBuilder::GetResult() {
+ consumed = true;
+ return device->CreateSampler(this);
+ }
+
+ void SamplerBuilder::SetFilterMode(nxt::FilterMode magFilter, nxt::FilterMode minFilter, nxt::FilterMode mipMapFilter) {
+ if ((propertiesSet & SAMPLER_PROPERTY_FILTER) != 0) {
+ device->HandleError("Sampler filter property set multiple times");
+ return;
+ }
+
+ this->magFilter = magFilter;
+ this->minFilter = minFilter;
+ this->mipMapFilter = mipMapFilter;
+ propertiesSet |= SAMPLER_PROPERTY_FILTER;
+ }
+}
diff --git a/src/backend/common/Sampler.h b/src/backend/common/Sampler.h
new file mode 100644
index 0000000..bbd5c58
--- /dev/null
+++ b/src/backend/common/Sampler.h
@@ -0,0 +1,58 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_SAMPLER_H_
+#define BACKEND_COMMON_SAMPLER_H_
+
+#include "Forward.h"
+#include "RefCounted.h"
+
+#include "nxt/nxtcpp.h"
+
+namespace backend {
+
+ class SamplerBase : public RefCounted {
+ public:
+ SamplerBase(SamplerBuilder* builder);
+ };
+
+ class SamplerBuilder : public RefCounted {
+ public:
+ SamplerBuilder(DeviceBase* device);
+
+ nxt::FilterMode GetMagFilter() const;
+ nxt::FilterMode GetMinFilter() const;
+ nxt::FilterMode GetMipMapFilter() const;
+
+ bool WasConsumed() const;
+
+ // NXT API
+ SamplerBase* GetResult();
+ void SetFilterMode(nxt::FilterMode magFilter, nxt::FilterMode minFilter, nxt::FilterMode mipMapFilter);
+
+ private:
+ friend class SamplerBase;
+
+ DeviceBase* device;
+ int propertiesSet = 0;
+ bool consumed = false;
+
+ nxt::FilterMode magFilter = nxt::FilterMode::Nearest;
+ nxt::FilterMode minFilter = nxt::FilterMode::Nearest;
+ nxt::FilterMode mipMapFilter = nxt::FilterMode::Nearest;
+ };
+
+}
+
+#endif // BACKEND_COMMON_SAMPLER_H_
diff --git a/src/backend/common/ShaderModule.cpp b/src/backend/common/ShaderModule.cpp
new file mode 100644
index 0000000..831dcb8
--- /dev/null
+++ b/src/backend/common/ShaderModule.cpp
@@ -0,0 +1,217 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ShaderModule.h"
+
+#include "BindGroupLayout.h"
+#include "Device.h"
+#include "Pipeline.h"
+#include "PipelineLayout.h"
+
+#include <spirv-cross/spirv_cross.hpp>
+
+namespace backend {
+
+ ShaderModuleBase::ShaderModuleBase(ShaderModuleBuilder* builder)
+ : device(builder->device) {
+ }
+
+ void ShaderModuleBase::ExtractSpirvInfo(const spirv_cross::Compiler& compiler) {
+ const auto& resources = compiler.get_shader_resources();
+
+ switch (compiler.get_execution_model()) {
+ case spv::ExecutionModelVertex:
+ executionModel = nxt::ShaderStage::Vertex;
+ break;
+ case spv::ExecutionModelFragment:
+ executionModel = nxt::ShaderStage::Fragment;
+ break;
+ case spv::ExecutionModelGLCompute:
+ executionModel = nxt::ShaderStage::Compute;
+ break;
+ default:
+ ASSERT(false);
+ break;
+ }
+
+ // Extract push constants
+ pushConstants.mask.reset();
+ pushConstants.sizes.fill(0);
+ pushConstants.types.fill(PushConstantType::Int);
+
+ if (resources.push_constant_buffers.size() > 0) {
+ auto interfaceBlock = resources.push_constant_buffers[0];
+
+ const auto& blockType = compiler.get_type(interfaceBlock.type_id);
+ ASSERT(blockType.basetype == spirv_cross::SPIRType::Struct);
+
+ for (uint32_t i = 0; i < blockType.member_types.size(); i++) {
+ ASSERT(compiler.get_member_decoration_mask(blockType.self, i) & 1ull << spv::DecorationOffset);
+ uint32_t offset = compiler.get_member_decoration(blockType.self, i, spv::DecorationOffset);
+ ASSERT(offset % 4 == 0);
+ offset /= 4;
+ ASSERT(offset < kMaxPushConstants);
+
+ auto memberType = compiler.get_type(blockType.member_types[i]);
+ PushConstantType constantType;
+ if (memberType.basetype == spirv_cross::SPIRType::Int) {
+ constantType = PushConstantType::Int;
+ } else if (memberType.basetype == spirv_cross::SPIRType::UInt) {
+ constantType = PushConstantType::UInt;
+ } else {
+ ASSERT(memberType.basetype == spirv_cross::SPIRType::Float);
+ constantType = PushConstantType::Float;
+ }
+
+ pushConstants.mask.set(offset);
+ pushConstants.names[offset] = interfaceBlock.name + "." + compiler.get_member_name(blockType.self, i);
+ pushConstants.sizes[offset] = memberType.vecsize * memberType.columns;
+ pushConstants.types[offset] = constantType;
+ }
+ }
+
+ // Fill in bindingInfo with the SPIRV bindings
+ auto ExtractResourcesBinding = [this](const std::vector<spirv_cross::Resource>& resources,
+ const spirv_cross::Compiler& compiler, nxt::BindingType type) {
+ constexpr uint64_t requiredBindingDecorationMask = (1ull << spv::DecorationBinding) | (1ull << spv::DecorationDescriptorSet);
+
+ for (const auto& resource : resources) {
+ ASSERT((compiler.get_decoration_mask(resource.id) & requiredBindingDecorationMask) == requiredBindingDecorationMask);
+ uint32_t binding = compiler.get_decoration(resource.id, spv::DecorationBinding);
+ uint32_t set = compiler.get_decoration(resource.id, spv::DecorationDescriptorSet);
+
+ if (binding >= kMaxBindingsPerGroup || set >= kMaxBindGroups) {
+ device->HandleError("Binding over limits in the SPIRV");
+ continue;
+ }
+
+ auto& info = bindingInfo[set][binding];
+ info.used = true;
+ info.id = resource.id;
+ info.base_type_id = resource.base_type_id;
+ info.type = type;
+ }
+ };
+
+ ExtractResourcesBinding(resources.uniform_buffers, compiler, nxt::BindingType::UniformBuffer);
+ ExtractResourcesBinding(resources.separate_images, compiler, nxt::BindingType::SampledTexture);
+ ExtractResourcesBinding(resources.separate_samplers, compiler, nxt::BindingType::Sampler);
+ ExtractResourcesBinding(resources.storage_buffers, compiler, nxt::BindingType::StorageBuffer);
+
+ // Extract the vertex attributes
+ if (executionModel == nxt::ShaderStage::Vertex) {
+ for (const auto& attrib : resources.stage_inputs) {
+ ASSERT(compiler.get_decoration_mask(attrib.id) & (1ull << spv::DecorationLocation));
+ uint32_t location = compiler.get_decoration(attrib.id, spv::DecorationLocation);
+
+ if (location >= kMaxVertexAttributes) {
+ device->HandleError("Attribute location over limits in the SPIRV");
+ return;
+ }
+
+ usedVertexAttributes.set(location);
+ }
+
+ // Without a location qualifier on vertex outputs, spirv_cross::CompilerMSL gives them all
+ // the location 0, causing a compile error.
+ for (const auto& attrib : resources.stage_outputs) {
+ if (!(compiler.get_decoration_mask(attrib.id) & (1ull << spv::DecorationLocation))) {
+ device->HandleError("Need location qualifier on vertex output");
+ return;
+ }
+ }
+ }
+
+ if (executionModel == nxt::ShaderStage::Fragment) {
+ // Without a location qualifier on vertex inputs, spirv_cross::CompilerMSL gives them all
+ // the location 0, causing a compile error.
+ for (const auto& attrib : resources.stage_inputs) {
+ if (!(compiler.get_decoration_mask(attrib.id) & (1ull << spv::DecorationLocation))) {
+ device->HandleError("Need location qualifier on fragment input");
+ return;
+ }
+ }
+ }
+ }
+
+ const ShaderModuleBase::PushConstantInfo& ShaderModuleBase::GetPushConstants() const {
+ return pushConstants;
+ }
+
+ const ShaderModuleBase::ModuleBindingInfo& ShaderModuleBase::GetBindingInfo() const {
+ return bindingInfo;
+ }
+
+ const std::bitset<kMaxVertexAttributes>& ShaderModuleBase::GetUsedVertexAttributes() const {
+ return usedVertexAttributes;
+ }
+
+ nxt::ShaderStage ShaderModuleBase::GetExecutionModel() const {
+ return executionModel;
+ }
+
+ bool ShaderModuleBase::IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout) {
+ for (size_t group = 0; group < kMaxBindGroups; ++group) {
+ if (!IsCompatibleWithBindGroupLayout(group, layout->GetBindGroupLayout(group))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool ShaderModuleBase::IsCompatibleWithBindGroupLayout(size_t group, const BindGroupLayoutBase* layout) {
+ const auto& layoutInfo = layout->GetBindingInfo();
+ for (size_t i = 0; i < kMaxBindingsPerGroup; ++i) {
+ const auto& moduleInfo = bindingInfo[group][i];
+
+ if (!moduleInfo.used) {
+ continue;
+ }
+
+ if (moduleInfo.type != layoutInfo.types[i]) {
+ return false;
+ }
+ if ((layoutInfo.visibilities[i] & StageBit(executionModel)) == 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ShaderModuleBuilder::ShaderModuleBuilder(DeviceBase* device) : device(device) {}
+
+ bool ShaderModuleBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ std::vector<uint32_t> ShaderModuleBuilder::AcquireSpirv() {
+ return std::move(spirv);
+ }
+
+ ShaderModuleBase* ShaderModuleBuilder::GetResult() {
+ if (spirv.size() == 0) {
+ device->HandleError("Shader module needs to have the source set");
+ return nullptr;
+ }
+
+ consumed = true;
+ return device->CreateShaderModule(this);
+ }
+
+ void ShaderModuleBuilder::SetSource(uint32_t codeSize, const uint32_t* code) {
+ spirv.assign(code, code + codeSize);
+ }
+
+}
diff --git a/src/backend/common/ShaderModule.h b/src/backend/common/ShaderModule.h
new file mode 100644
index 0000000..1407fd9
--- /dev/null
+++ b/src/backend/common/ShaderModule.h
@@ -0,0 +1,95 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_SHADERMODULE_H_
+#define BACKEND_COMMON_SHADERMODULE_H_
+
+#include "Forward.h"
+#include "RefCounted.h"
+
+#include "nxt/nxtcpp.h"
+
+#include <array>
+#include <bitset>
+#include <vector>
+
+namespace spirv_cross {
+ class Compiler;
+}
+
+namespace backend {
+
+ class ShaderModuleBase : public RefCounted {
+ public:
+ ShaderModuleBase(ShaderModuleBuilder* builder);
+
+ void ExtractSpirvInfo(const spirv_cross::Compiler& compiler);
+
+ struct PushConstantInfo {
+ std::bitset<kMaxPushConstants> mask;
+
+ std::array<std::string, kMaxPushConstants> names;
+ std::array<int, kMaxPushConstants> sizes;
+ std::array<PushConstantType, kMaxPushConstants> types;
+ };
+
+ struct BindingInfo {
+ // The SPIRV ID of the resource.
+ uint32_t id;
+ uint32_t base_type_id;
+ nxt::BindingType type;
+ bool used = false;
+ };
+ using ModuleBindingInfo = std::array<std::array<BindingInfo, kMaxBindingsPerGroup>, kMaxBindGroups>;
+
+ const PushConstantInfo& GetPushConstants() const;
+ const ModuleBindingInfo& GetBindingInfo() const;
+ const std::bitset<kMaxVertexAttributes>& GetUsedVertexAttributes() const;
+ nxt::ShaderStage GetExecutionModel() const;
+
+ bool IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout);
+
+ private:
+ bool IsCompatibleWithBindGroupLayout(size_t group, const BindGroupLayoutBase* layout);
+
+ DeviceBase* device;
+ PushConstantInfo pushConstants = {};
+ ModuleBindingInfo bindingInfo;
+ std::bitset<kMaxVertexAttributes> usedVertexAttributes;
+ nxt::ShaderStage executionModel;
+ };
+
+ class ShaderModuleBuilder : public RefCounted {
+ public:
+ ShaderModuleBuilder(DeviceBase* device);
+
+ bool WasConsumed() const;
+
+ std::vector<uint32_t> AcquireSpirv();
+
+ // NXT API
+ ShaderModuleBase* GetResult();
+ void SetSource(uint32_t codeSize, const uint32_t* code);
+
+ private:
+ friend class ShaderModuleBase;
+
+ DeviceBase* device;
+ std::vector<uint32_t> spirv;
+ bool consumed = false;
+ };
+
+}
+
+#endif // BACKEND_COMMON_SHADERMODULE_H_
diff --git a/src/backend/common/Texture.cpp b/src/backend/common/Texture.cpp
new file mode 100644
index 0000000..4551dbf
--- /dev/null
+++ b/src/backend/common/Texture.cpp
@@ -0,0 +1,239 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "Texture.h"
+
+#include "Device.h"
+
+namespace backend {
+
+ size_t TextureFormatPixelSize(nxt::TextureFormat format) {
+ switch (format) {
+ case nxt::TextureFormat::R8G8B8A8Unorm:
+ return 4;
+ }
+ }
+
+ // TextureBase
+
+ TextureBase::TextureBase(TextureBuilder* builder)
+ : device(builder->device), dimension(builder->dimension), format(builder->format), width(builder->width),
+ height(builder->height), depth(builder->depth), numMipLevels(builder->numMipLevels),
+ allowedUsage(builder->allowedUsage), currentUsage(builder->currentUsage) {
+ }
+
+ nxt::TextureDimension TextureBase::GetDimension() const {
+ return dimension;
+ }
+ nxt::TextureFormat TextureBase::GetFormat() const {
+ return format;
+ }
+ uint32_t TextureBase::GetWidth() const {
+ return width;
+ }
+ uint32_t TextureBase::GetHeight() const {
+ return height;
+ }
+ uint32_t TextureBase::GetDepth() const {
+ return depth;
+ }
+ uint32_t TextureBase::GetNumMipLevels() const {
+ return numMipLevels;
+ }
+ nxt::TextureUsageBit TextureBase::GetAllowedUsage() const {
+ return allowedUsage;
+ }
+ nxt::TextureUsageBit TextureBase::GetUsage() const {
+ return currentUsage;
+ }
+
+ TextureViewBuilder* TextureBase::CreateTextureViewBuilder() {
+ return new TextureViewBuilder(device, this);
+ }
+
+ bool TextureBase::IsFrozen() const {
+ return frozen;
+ }
+
+ bool TextureBase::HasFrozenUsage(nxt::TextureUsageBit usage) const {
+ return frozen && (usage & allowedUsage);
+ }
+
+ bool TextureBase::IsUsagePossible(nxt::TextureUsageBit allowedUsage, nxt::TextureUsageBit usage) {
+ bool allowed = (usage & allowedUsage) == usage;
+ bool singleUse = nxt::HasZeroOrOneBits(usage);
+ return allowed && singleUse;
+ }
+
+ bool TextureBase::IsTransitionPossible(nxt::TextureUsageBit usage) const {
+ if (frozen) {
+ return false;
+ }
+ return IsUsagePossible(allowedUsage, usage);
+ }
+
+ void TextureBase::TransitionUsageImpl(nxt::TextureUsageBit usage) {
+ assert(IsTransitionPossible(usage));
+ currentUsage = usage;
+ }
+
+ void TextureBase::TransitionUsage(nxt::TextureUsageBit usage) {
+ if (!IsTransitionPossible(usage)) {
+ device->HandleError("Texture frozen or usage not allowed");
+ return;
+ }
+ TransitionUsageImpl(usage);
+ }
+
+ void TextureBase::FreezeUsage(nxt::TextureUsageBit usage) {
+ if (!IsTransitionPossible(usage)) {
+ device->HandleError("Texture frozen or usage not allowed");
+ return;
+ }
+ allowedUsage = usage;
+ currentUsage = usage;
+ frozen = true;
+ }
+
+ // TextureBuilder
+
+ enum TextureSetProperties {
+ TEXTURE_PROPERTY_DIMENSION = 0x1,
+ TEXTURE_PROPERTY_EXTENT = 0x2,
+ TEXTURE_PROPERTY_FORMAT = 0x4,
+ TEXTURE_PROPERTY_MIP_LEVELS = 0x8,
+ TEXTURE_PROPERTY_ALLOWED_USAGE = 0x10,
+ TEXTURE_PROPERTY_INITIAL_USAGE = 0x20,
+ };
+
+ TextureBuilder::TextureBuilder(DeviceBase* device)
+ : device(device) {
+ }
+
+ bool TextureBuilder::WasConsumed() const {
+ return consumed;
+ }
+
+ TextureBase* TextureBuilder::GetResult() {
+ constexpr int allProperties = TEXTURE_PROPERTY_DIMENSION | TEXTURE_PROPERTY_EXTENT |
+ TEXTURE_PROPERTY_FORMAT | TEXTURE_PROPERTY_MIP_LEVELS | TEXTURE_PROPERTY_ALLOWED_USAGE;
+ if ((propertiesSet & allProperties) != allProperties) {
+ device->HandleError("Texture missing properties");
+ return nullptr;
+ }
+
+ if (!TextureBase::IsUsagePossible(allowedUsage, currentUsage)) {
+ device->HandleError("Initial texture usage is not allowed");
+ return nullptr;
+ }
+
+ // TODO(cwallez@chromium.org): check stuff based on the dimension
+
+ consumed = true;
+ return device->CreateTexture(this);
+ }
+
+ void TextureBuilder::SetDimension(nxt::TextureDimension dimension) {
+ if ((propertiesSet & TEXTURE_PROPERTY_DIMENSION) != 0) {
+ device->HandleError("Texture dimension property set multiple times");
+ return;
+ }
+
+ propertiesSet |= TEXTURE_PROPERTY_DIMENSION;
+ this->dimension = dimension;
+ }
+
+ void TextureBuilder::SetExtent(uint32_t width, uint32_t height, uint32_t depth) {
+ if ((propertiesSet & TEXTURE_PROPERTY_EXTENT) != 0) {
+ device->HandleError("Texture extent property set multiple times");
+ return;
+ }
+
+ if (width == 0 || height == 0 || depth == 0) {
+ device->HandleError("Cannot create an empty texture");
+ return;
+ }
+
+ propertiesSet |= TEXTURE_PROPERTY_EXTENT;
+ this->width = width;
+ this->height = height;
+ this->depth = depth;
+ }
+
+ void TextureBuilder::SetFormat(nxt::TextureFormat format) {
+ if ((propertiesSet & TEXTURE_PROPERTY_FORMAT) != 0) {
+ device->HandleError("Texture format property set multiple times");
+ return;
+ }
+
+ propertiesSet |= TEXTURE_PROPERTY_FORMAT;
+ this->format = format;
+ }
+
+ void TextureBuilder::SetMipLevels(uint32_t numMipLevels) {
+ if ((propertiesSet & TEXTURE_PROPERTY_MIP_LEVELS) != 0) {
+ device->HandleError("Texture mip levels property set multiple times");
+ return;
+ }
+
+ propertiesSet |= TEXTURE_PROPERTY_MIP_LEVELS;
+ this->numMipLevels = numMipLevels;
+ }
+
+ void TextureBuilder::SetAllowedUsage(nxt::TextureUsageBit usage) {
+ if ((propertiesSet & TEXTURE_PROPERTY_ALLOWED_USAGE) != 0) {
+ device->HandleError("Texture allowed usage property set multiple times");
+ return;
+ }
+
+ propertiesSet |= TEXTURE_PROPERTY_ALLOWED_USAGE;
+ this->allowedUsage = usage;
+ }
+
+ void TextureBuilder::SetInitialUsage(nxt::TextureUsageBit usage) {
+ if ((propertiesSet & TEXTURE_PROPERTY_INITIAL_USAGE) != 0) {
+ device->HandleError("Texture initial usage property set multiple times");
+ return;
+ }
+
+ propertiesSet |= TEXTURE_PROPERTY_INITIAL_USAGE;
+ this->currentUsage = usage;
+ }
+
+ // TextureViewBase
+
+ TextureViewBase::TextureViewBase(TextureViewBuilder* builder)
+ : texture(builder->texture) {
+ }
+
+ TextureBase* TextureViewBase::GetTexture() {
+ return texture.Get();
+ }
+
+ // TextureViewBuilder
+
+ TextureViewBuilder::TextureViewBuilder(DeviceBase* device, TextureBase* texture)
+ : device(device), texture(texture) {
+ }
+
+ bool TextureViewBuilder::WasConsumed() const {
+ return false;
+ }
+
+ TextureViewBase* TextureViewBuilder::GetResult() {
+ consumed = true;
+ return device->CreateTextureView(this);
+ }
+
+}
diff --git a/src/backend/common/Texture.h b/src/backend/common/Texture.h
new file mode 100644
index 0000000..d76fe80
--- /dev/null
+++ b/src/backend/common/Texture.h
@@ -0,0 +1,121 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_TEXTURE_H_
+#define BACKEND_COMMON_TEXTURE_H_
+
+#include "Forward.h"
+#include "RefCounted.h"
+
+#include "nxt/nxtcpp.h"
+
+namespace backend {
+
+ size_t TextureFormatPixelSize(nxt::TextureFormat format);
+
+ class TextureBase : public RefCounted {
+ public:
+ TextureBase(TextureBuilder* builder);
+
+ nxt::TextureDimension GetDimension() const;
+ nxt::TextureFormat GetFormat() const;
+ uint32_t GetWidth() const;
+ uint32_t GetHeight() const;
+ uint32_t GetDepth() const;
+ uint32_t GetNumMipLevels() const;
+ nxt::TextureUsageBit GetAllowedUsage() const;
+ nxt::TextureUsageBit GetUsage() const;
+ bool IsFrozen() const;
+ bool HasFrozenUsage(nxt::TextureUsageBit usage) const;
+ static bool IsUsagePossible(nxt::TextureUsageBit allowedUsage, nxt::TextureUsageBit usage);
+ bool IsTransitionPossible(nxt::TextureUsageBit usage) const;
+ void TransitionUsageImpl(nxt::TextureUsageBit usage);
+
+ // NXT API
+ TextureViewBuilder* CreateTextureViewBuilder();
+ void TransitionUsage(nxt::TextureUsageBit usage);
+ void FreezeUsage(nxt::TextureUsageBit usage);
+
+ private:
+ DeviceBase* device;
+
+ nxt::TextureDimension dimension;
+ nxt::TextureFormat format;
+ uint32_t width, height, depth;
+ uint32_t numMipLevels;
+ nxt::TextureUsageBit allowedUsage = nxt::TextureUsageBit::None;
+ nxt::TextureUsageBit currentUsage = nxt::TextureUsageBit::None;
+ bool frozen = false;
+ };
+
+ class TextureBuilder : public RefCounted {
+ public:
+ TextureBuilder(DeviceBase* device);
+
+ bool WasConsumed() const;
+
+ // NXT API
+ TextureBase* GetResult();
+ void SetDimension(nxt::TextureDimension dimension);
+ void SetExtent(uint32_t width, uint32_t height, uint32_t depth);
+ void SetFormat(nxt::TextureFormat format);
+ void SetMipLevels(uint32_t numMipLevels);
+ void SetAllowedUsage(nxt::TextureUsageBit usage);
+ void SetInitialUsage(nxt::TextureUsageBit usage);
+
+ private:
+ friend class TextureBase;
+
+ DeviceBase* device;
+ int propertiesSet = 0;
+ bool consumed = false;
+
+ nxt::TextureDimension dimension;
+ uint32_t width, height, depth;
+ nxt::TextureFormat format;
+ uint32_t numMipLevels;
+ nxt::TextureUsageBit allowedUsage = nxt::TextureUsageBit::None;
+ nxt::TextureUsageBit currentUsage = nxt::TextureUsageBit::None;
+ };
+
+ class TextureViewBase : public RefCounted {
+ public:
+ TextureViewBase(TextureViewBuilder* builder);
+
+ TextureBase* GetTexture();
+
+ private:
+ Ref<TextureBase> texture;
+ };
+
+ class TextureViewBuilder : public RefCounted {
+ public:
+ TextureViewBuilder(DeviceBase* device, TextureBase* texture);
+
+ bool WasConsumed() const;
+
+ // NXT API
+ TextureViewBase* GetResult();
+
+ private:
+ friend class TextureViewBase;
+
+ DeviceBase* device;
+ bool consumed = false;
+ Ref<TextureBase> texture;
+ };
+
+}
+
+#endif // BACKEND_COMMON_TEXTURE_H_
diff --git a/src/backend/common/ToBackend.h b/src/backend/common/ToBackend.h
new file mode 100644
index 0000000..ff7bd0b
--- /dev/null
+++ b/src/backend/common/ToBackend.h
@@ -0,0 +1,120 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_COMMON_TOBACKEND_H_
+#define BACKEND_COMMON_TOBACKEND_H_
+
+#include "Forward.h"
+
+namespace backend {
+
+ // ToBackendTraits implements the mapping from base type to member type of BackendTraits
+ template<typename T, typename BackendTraits>
+ struct ToBackendTraits;
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<BindGroupBase, BackendTraits> {
+ using BackendType = typename BackendTraits::BindGroupType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<BindGroupLayoutBase, BackendTraits> {
+ using BackendType = typename BackendTraits::BindGroupLayoutType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<BufferBase, BackendTraits> {
+ using BackendType = typename BackendTraits::BufferType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<BufferViewBase, BackendTraits> {
+ using BackendType = typename BackendTraits::BufferViewType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<CommandBufferBase, BackendTraits> {
+ using BackendType = typename BackendTraits::CommandBufferType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<InputStateBase, BackendTraits> {
+ using BackendType = typename BackendTraits::InputStateType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<PipelineBase, BackendTraits> {
+ using BackendType = typename BackendTraits::PipelineType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<PipelineLayoutBase, BackendTraits> {
+ using BackendType = typename BackendTraits::PipelineLayoutType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<QueueBase, BackendTraits> {
+ using BackendType = typename BackendTraits::QueueType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<SamplerBase, BackendTraits> {
+ using BackendType = typename BackendTraits::SamplerType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<ShaderModuleBase, BackendTraits> {
+ using BackendType = typename BackendTraits::ShaderModuleType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<TextureBase, BackendTraits> {
+ using BackendType = typename BackendTraits::TextureType;
+ };
+
+ template<typename BackendTraits>
+ struct ToBackendTraits<TextureViewBase, BackendTraits> {
+ using BackendType = typename BackendTraits::TextureViewType;
+ };
+
+ // ToBackendBase implements conversion to the given BackendTraits
+ // To use it in a backend, use the following:
+ // template<typename T>
+ // auto ToBackend(T&& common) -> decltype(ToBackendBase<MyBackendTraits>(common)) {
+ // return ToBackendBase<MyBackendTraits>(common);
+ // }
+
+ template<typename BackendTraits, typename T>
+ Ref<typename ToBackendTraits<T, BackendTraits>::BackendType>& ToBackendBase(Ref<T>& common) {
+ return reinterpret_cast<Ref<typename ToBackendTraits<T, BackendTraits>::BackendType>&>(common);
+ }
+
+ template<typename BackendTraits, typename T>
+ const Ref<typename ToBackendTraits<T, BackendTraits>::BackendType>& ToBackendBase(const Ref<T>& common) {
+ return reinterpret_cast<const Ref<typename ToBackendTraits<T, BackendTraits>::BackendType>&>(common);
+ }
+
+ template<typename BackendTraits, typename T>
+ typename ToBackendTraits<T, BackendTraits>::BackendType* ToBackendBase(T* common) {
+ return reinterpret_cast<typename ToBackendTraits<T, BackendTraits>::BackendType*>(common);
+ }
+
+ template<typename BackendTraits, typename T>
+ const typename ToBackendTraits<T, BackendTraits>::BackendType* ToBackendBase(const T* common) {
+ return reinterpret_cast<const typename ToBackendTraits<T, BackendTraits>::BackendType*>(common);
+ }
+
+}
+
+#endif // BACKEND_COMMON_TOBACKEND_H_
diff --git a/src/backend/metal/GeneratedCodeIncludes.h b/src/backend/metal/GeneratedCodeIncludes.h
new file mode 100644
index 0000000..16d9bfe
--- /dev/null
+++ b/src/backend/metal/GeneratedCodeIncludes.h
@@ -0,0 +1,18 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "MetalBackend.h"
+
+#include "common/Device.h"
+#include "common/CommandBuffer.h"
diff --git a/src/backend/metal/MetalBackend.h b/src/backend/metal/MetalBackend.h
new file mode 100644
index 0000000..4ebf82b
--- /dev/null
+++ b/src/backend/metal/MetalBackend.h
@@ -0,0 +1,282 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_METAL_METALBACKEND_H_
+#define BACKEND_METAL_METALBACKEND_H_
+
+#include "nxt/nxtcpp.h"
+
+#include <map>
+#include <mutex>
+#include <unordered_set>
+
+#include "common/Buffer.h"
+#include "common/BindGroup.h"
+#include "common/BindGroupLayout.h"
+#include "common/Device.h"
+#include "common/CommandBuffer.h"
+#include "common/InputState.h"
+#include "common/Pipeline.h"
+#include "common/PipelineLayout.h"
+#include "common/Queue.h"
+#include "common/Sampler.h"
+#include "common/ShaderModule.h"
+#include "common/Texture.h"
+#include "common/ToBackend.h"
+
+#include <type_traits>
+#import <Metal/Metal.h>
+#import <QuartzCore/CAMetalLayer.h>
+
+namespace spirv_cross {
+ class CompilerMSL;
+}
+
+namespace backend {
+namespace metal {
+
+ class BindGroup;
+ class BindGroupLayout;
+ class Buffer;
+ class BufferView;
+ class CommandBuffer;
+ class InputState;
+ class Pipeline;
+ class PipelineLayout;
+ class Queue;
+ class Sampler;
+ class ShaderModule;
+ class Texture;
+ class TextureView;
+
+ struct MetalBackendTraits {
+ using BindGroupType = BindGroup;
+ using BindGroupLayoutType = BindGroupLayout;
+ using BufferType = Buffer;
+ using BufferViewType = BufferView;
+ using CommandBufferType = CommandBuffer;
+ using InputStateType = InputState;
+ using PipelineType = Pipeline;
+ using PipelineLayoutType = PipelineLayout;
+ using QueueType = Queue;
+ using SamplerType = Sampler;
+ using ShaderModuleType = ShaderModule;
+ using TextureType = Texture;
+ using TextureViewType = TextureView;
+ };
+
+ template<typename T>
+ auto ToBackend(T&& common) -> decltype(ToBackendBase<MetalBackendTraits>(common)) {
+ return ToBackendBase<MetalBackendTraits>(common);
+ }
+
+ class Device : public DeviceBase {
+ public:
+ Device(id<MTLDevice> mtlDevice);
+ ~Device();
+
+ BindGroupBase* CreateBindGroup(BindGroupBuilder* builder) override;
+ BindGroupLayoutBase* CreateBindGroupLayout(BindGroupLayoutBuilder* builder) override;
+ BufferBase* CreateBuffer(BufferBuilder* builder) override;
+ BufferViewBase* CreateBufferView(BufferViewBuilder* builder) override;
+ CommandBufferBase* CreateCommandBuffer(CommandBufferBuilder* builder) override;
+ InputStateBase* CreateInputState(InputStateBuilder* builder) override;
+ PipelineBase* CreatePipeline(PipelineBuilder* builder) override;
+ PipelineLayoutBase* CreatePipelineLayout(PipelineLayoutBuilder* builder) override;
+ QueueBase* CreateQueue(QueueBuilder* builder) override;
+ SamplerBase* CreateSampler(SamplerBuilder* builder) override;
+ ShaderModuleBase* CreateShaderModule(ShaderModuleBuilder* builder) override;
+ TextureBase* CreateTexture(TextureBuilder* builder) override;
+ TextureViewBase* CreateTextureView(TextureViewBuilder* builder) override;
+
+ void SetNextDrawable(id<CAMetalDrawable> drawable);
+ void Present();
+
+ id<MTLDevice> GetMTLDevice();
+ id<MTLTexture> GetCurrentTexture();
+ id<MTLTexture> GetCurrentDepthTexture();
+
+ // NXT API
+ void Reference();
+ void Release();
+
+ private:
+ id<MTLDevice> mtlDevice = nil;
+ id<MTLCommandQueue> commandQueue = nil;
+
+ id<CAMetalDrawable> currentDrawable = nil;
+ id<MTLTexture> currentTexture = nil;
+ id<MTLTexture> currentDepthTexture = nil;
+ };
+
+ class BindGroup : public BindGroupBase {
+ public:
+ BindGroup(Device* device, BindGroupBuilder* builder);
+
+ private:
+ Device* device;
+ };
+
+ class BindGroupLayout : public BindGroupLayoutBase {
+ public:
+ BindGroupLayout(Device* device, BindGroupLayoutBuilder* builder);
+
+ private:
+ Device* device;
+ };
+
+ class Buffer : public BufferBase {
+ public:
+ Buffer(Device* device, BufferBuilder* builder);
+ ~Buffer();
+
+ id<MTLBuffer> GetMTLBuffer();
+ std::mutex& GetMutex();
+
+ private:
+ void SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) override;
+
+ Device* device;
+ std::mutex mutex;
+ id<MTLBuffer> mtlBuffer = nil;
+ };
+
+ class BufferView : public BufferViewBase {
+ public:
+ BufferView(Device* device, BufferViewBuilder* builder);
+
+ private:
+ Device* device;
+ };
+
+ class CommandBuffer : public CommandBufferBase {
+ public:
+ CommandBuffer(Device* device, CommandBufferBuilder* builder);
+ ~CommandBuffer();
+
+ void FillCommands(id<MTLCommandBuffer> commandBuffer, std::unordered_set<std::mutex*>* mutexes);
+
+ private:
+ Device* device;
+ CommandIterator commands;
+ };
+
+ class InputState : public InputStateBase {
+ public:
+ InputState(Device* device, InputStateBuilder* builder);
+ ~InputState();
+
+ MTLVertexDescriptor* GetMTLVertexDescriptor();
+
+ private:
+ Device* device;
+ MTLVertexDescriptor* mtlVertexDescriptor = nil;
+ };
+
+ class Pipeline : public PipelineBase {
+ public:
+ Pipeline(Device* device, PipelineBuilder* builder);
+ ~Pipeline();
+
+ void Encode(id<MTLRenderCommandEncoder> encoder);
+ void Encode(id<MTLComputeCommandEncoder> encoder);
+ MTLSize GetLocalWorkGroupSize() const;
+
+ private:
+ Device* device;
+
+ id<MTLRenderPipelineState> mtlRenderPipelineState = nil;
+ id<MTLDepthStencilState> mtlDepthStencilState = nil;
+
+ id<MTLComputePipelineState> mtlComputePipelineState = nil;
+ MTLSize localWorkgroupSize;
+ };
+
+ class PipelineLayout : public PipelineLayoutBase {
+ public:
+ PipelineLayout(Device* device, PipelineLayoutBuilder* builder);
+
+ using BindingIndexInfo = std::array<std::array<uint32_t, kMaxBindingsPerGroup>, kMaxBindGroups>;
+ const BindingIndexInfo& GetBindingIndexInfo(nxt::ShaderStage stage) const;
+
+ private:
+ Device* device;
+ PerStage<BindingIndexInfo> indexInfo;
+ };
+
+ class Queue : public QueueBase {
+ public:
+ Queue(Device* device, QueueBuilder* builder);
+ ~Queue();
+
+ id<MTLCommandQueue> GetMTLCommandQueue();
+
+ // NXT API
+ void Submit(uint32_t numCommands, CommandBuffer* const * commands);
+
+ private:
+ Device* device;
+ id<MTLCommandQueue> commandQueue = nil;
+ };
+
+ class Sampler : public SamplerBase {
+ public:
+ Sampler(Device* device, SamplerBuilder* builder);
+ ~Sampler();
+
+ id<MTLSamplerState> GetMTLSamplerState();
+
+ private:
+ Device* device;
+ id<MTLSamplerState> mtlSamplerState = nil;
+ };
+
+ class ShaderModule : public ShaderModuleBase {
+ public:
+ ShaderModule(Device* device, ShaderModuleBuilder* builder);
+ ~ShaderModule();
+
+ id<MTLFunction> GetFunction(const char* functionName) const;
+ MTLSize GetLocalWorkGroupSize(const std::string& entryPoint) const;
+
+ private:
+ Device* device;
+ id<MTLLibrary> mtlLibrary = nil;
+ spirv_cross::CompilerMSL* compiler = nullptr;
+ };
+
+ class Texture : public TextureBase {
+ public:
+ Texture(Device* device, TextureBuilder* builder);
+ ~Texture();
+
+ id<MTLTexture> GetMTLTexture();
+
+ private:
+ Device* device;
+ id<MTLTexture> mtlTexture = nil;
+ };
+
+ class TextureView : public TextureViewBase {
+ public:
+ TextureView(Device* device, TextureViewBuilder* builder);
+
+ private:
+ Device* device;
+ };
+
+}
+}
+
+#endif // BACKEND_METAL_METALBACKEND_H_
diff --git a/src/backend/metal/MetalBackend.mm b/src/backend/metal/MetalBackend.mm
new file mode 100644
index 0000000..46a444e
--- /dev/null
+++ b/src/backend/metal/MetalBackend.mm
@@ -0,0 +1,968 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "MetalBackend.h"
+
+#include <spirv-cross/spirv_msl.hpp>
+
+#include <sstream>
+
+#include "common/Commands.h"
+
+namespace backend {
+namespace metal {
+ nxtProcTable GetNonValidatingProcs();
+ nxtProcTable GetValidatingProcs();
+
+ void Init(id<MTLDevice> metalDevice, nxtProcTable* procs, nxtDevice* device) {
+ *device = nullptr;
+
+ *procs = GetValidatingProcs();
+ *device = reinterpret_cast<nxtDevice>(new Device(metalDevice));
+ }
+
+ void SetNextDrawable(nxtDevice device, id<CAMetalDrawable> drawable) {
+ Device* backendDevice = reinterpret_cast<Device*>(device);
+ backendDevice->SetNextDrawable(drawable);
+ }
+
+ void Present(nxtDevice device) {
+ Device* backendDevice = reinterpret_cast<Device*>(device);
+ backendDevice->Present();
+ }
+
+ // Device
+
+ Device::Device(id<MTLDevice> mtlDevice) : mtlDevice(mtlDevice) {
+ [mtlDevice retain];
+ commandQueue = [mtlDevice newCommandQueue];
+ }
+
+ Device::~Device() {
+ [mtlDevice release];
+ mtlDevice = nil;
+
+ [commandQueue release];
+ commandQueue = nil;
+
+ [currentTexture release];
+ currentTexture = nil;
+
+ [currentDepthTexture release];
+ currentDepthTexture = nil;
+ }
+
+ BindGroupBase* Device::CreateBindGroup(BindGroupBuilder* builder) {
+ return new BindGroup(this, builder);
+ }
+ BindGroupLayoutBase* Device::CreateBindGroupLayout(BindGroupLayoutBuilder* builder) {
+ return new BindGroupLayout(this, builder);
+ }
+ BufferBase* Device::CreateBuffer(BufferBuilder* builder) {
+ return new Buffer(this, builder);
+ }
+ BufferViewBase* Device::CreateBufferView(BufferViewBuilder* builder) {
+ return new BufferView(this, builder);
+ }
+ CommandBufferBase* Device::CreateCommandBuffer(CommandBufferBuilder* builder) {
+ return new CommandBuffer(this, builder);
+ }
+ InputStateBase* Device::CreateInputState(InputStateBuilder* builder) {
+ return new InputState(this, builder);
+ }
+ PipelineBase* Device::CreatePipeline(PipelineBuilder* builder) {
+ return new Pipeline(this, builder);
+ }
+ PipelineLayoutBase* Device::CreatePipelineLayout(PipelineLayoutBuilder* builder) {
+ return new PipelineLayout(this, builder);
+ }
+ QueueBase* Device::CreateQueue(QueueBuilder* builder) {
+ return new Queue(this, builder);
+ }
+ SamplerBase* Device::CreateSampler(SamplerBuilder* builder) {
+ return new Sampler(this, builder);
+ }
+ ShaderModuleBase* Device::CreateShaderModule(ShaderModuleBuilder* builder) {
+ return new ShaderModule(this, builder);
+ }
+ TextureBase* Device::CreateTexture(TextureBuilder* builder) {
+ return new Texture(this, builder);
+ }
+ TextureViewBase* Device::CreateTextureView(TextureViewBuilder* builder) {
+ return new TextureView(this, builder);
+ }
+
+ void Device::SetNextDrawable(id<CAMetalDrawable> drawable) {
+ [currentDrawable release];
+ currentDrawable = drawable;
+ [currentDrawable retain];
+
+ [currentTexture release];
+ currentTexture = drawable.texture;
+ [currentTexture retain];
+
+ if (currentDepthTexture == nil ||
+ currentTexture.width != currentDepthTexture.width ||
+ currentTexture.height != currentDepthTexture.height) {
+ if (currentDepthTexture != nil) {
+ [currentDepthTexture release];
+ }
+ MTLTextureDescriptor* depthDescriptor = [MTLTextureDescriptor
+ texture2DDescriptorWithPixelFormat:MTLPixelFormatDepth32Float
+ width:currentTexture.width
+ height:currentTexture.height
+ mipmapped:NO];
+ depthDescriptor.textureType = MTLTextureType2D;
+ depthDescriptor.usage = MTLTextureUsageRenderTarget;
+ depthDescriptor.storageMode = MTLStorageModePrivate;
+ currentDepthTexture = [mtlDevice newTextureWithDescriptor:depthDescriptor];
+ }
+
+ MTLRenderPassDescriptor* passDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
+ passDescriptor.colorAttachments[0].texture = currentTexture;
+ passDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
+ passDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
+ passDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0);
+ passDescriptor.depthAttachment.texture = currentDepthTexture;
+ passDescriptor.depthAttachment.loadAction = MTLLoadActionClear;
+ passDescriptor.depthAttachment.storeAction = MTLStoreActionStore;
+ passDescriptor.depthAttachment.clearDepth = 1.0;
+
+
+ id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
+ id<MTLRenderCommandEncoder> commandEncoder = [commandBuffer
+ renderCommandEncoderWithDescriptor:passDescriptor];
+ [commandEncoder endEncoding];
+ [commandBuffer commit];
+ }
+
+ void Device::Present() {
+ id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
+ [commandBuffer presentDrawable: currentDrawable];
+ [commandBuffer commit];
+ }
+
+ id<MTLDevice> Device::GetMTLDevice() {
+ return mtlDevice;
+ }
+
+ id<MTLTexture> Device::GetCurrentTexture() {
+ return currentTexture;
+ }
+
+ id<MTLTexture> Device::GetCurrentDepthTexture() {
+ return currentDepthTexture;
+ }
+
+ void Device::Reference() {
+ }
+
+ void Device::Release() {
+ }
+
+ // Bind Group
+
+ BindGroup::BindGroup(Device* device, BindGroupBuilder* builder)
+ : BindGroupBase(builder), device(device) {
+ }
+
+ // Bind Group Layout
+
+ BindGroupLayout::BindGroupLayout(Device* device, BindGroupLayoutBuilder* builder)
+ : BindGroupLayoutBase(builder), device(device) {
+ }
+
+ // Buffer
+
+ Buffer::Buffer(Device* device, BufferBuilder* builder)
+ : BufferBase(builder), device(device) {
+ mtlBuffer = [device->GetMTLDevice() newBufferWithLength:GetSize()
+ options:MTLResourceStorageModeManaged];
+ }
+
+ Buffer::~Buffer() {
+ std::lock_guard<std::mutex> lock(mutex);
+ [mtlBuffer release];
+ mtlBuffer = nil;
+ }
+
+ id<MTLBuffer> Buffer::GetMTLBuffer() {
+ return mtlBuffer;
+ }
+
+ std::mutex& Buffer::GetMutex() {
+ return mutex;
+ }
+
+ void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) {
+ uint32_t* dest = reinterpret_cast<uint32_t*>([mtlBuffer contents]);
+ {
+ std::lock_guard<std::mutex> lock(mutex);
+ memcpy(&dest[start], data, count * sizeof(uint32_t));
+ }
+ [mtlBuffer didModifyRange:NSMakeRange(start * sizeof(uint32_t), count * sizeof(uint32_t))];
+ }
+
+ // BufferView
+
+ BufferView::BufferView(Device* device, BufferViewBuilder* builder)
+ : BufferViewBase(builder), device(device) {
+ }
+
+ // CommandBuffer
+
+ static MTLIndexType IndexFormatType(nxt::IndexFormat format) {
+ switch (format) {
+ case nxt::IndexFormat::Uint16:
+ return MTLIndexTypeUInt16;
+ case nxt::IndexFormat::Uint32:
+ return MTLIndexTypeUInt32;
+ }
+ }
+
+ CommandBuffer::CommandBuffer(Device* device, CommandBufferBuilder* builder)
+ : CommandBufferBase(builder), device(device), commands(builder->AcquireCommands()) {
+ }
+
+ CommandBuffer::~CommandBuffer() {
+ FreeCommands(&commands);
+ }
+
+ namespace {
+
+ struct CurrentEncoders {
+ Device* device;
+
+ id<MTLBlitCommandEncoder> blit = nil;
+ id<MTLComputeCommandEncoder> compute = nil;
+ id<MTLRenderCommandEncoder> render = nil;
+
+ void FinishEncoders() {
+ if (blit != nil) {
+ [blit endEncoding];
+ blit = nil;
+ }
+ if (compute != nil) {
+ [compute endEncoding];
+ compute = nil;
+ }
+ if (render != nil) {
+ [render endEncoding];
+ render = nil;
+ }
+ }
+
+ void EnsureBlit(id<MTLCommandBuffer> commandBuffer) {
+ if (blit == nil) {
+ FinishEncoders();
+ blit = [commandBuffer blitCommandEncoder];
+ }
+ }
+ void EnsureCompute(id<MTLCommandBuffer> commandBuffer) {
+ if (compute == nil) {
+ FinishEncoders();
+ compute = [commandBuffer computeCommandEncoder];
+ // TODO(cwallez@chromium.org): does any state need to be reset?
+ }
+ }
+ void EnsureRender(id<MTLCommandBuffer> commandBuffer) {
+ if (render == nil) {
+ FinishEncoders();
+
+ // TODO(cwallez@chromium.org): this should be created from a renderpass subpass
+ MTLRenderPassDescriptor* descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
+ descriptor.colorAttachments[0].texture = device->GetCurrentTexture();
+ descriptor.colorAttachments[0].loadAction = MTLLoadActionLoad;
+ descriptor.colorAttachments[0].storeAction = MTLStoreActionStore;
+ descriptor.depthAttachment.texture = device->GetCurrentDepthTexture();
+ descriptor.depthAttachment.loadAction = MTLLoadActionLoad;
+ descriptor.depthAttachment.storeAction = MTLStoreActionStore;
+
+ render = [commandBuffer renderCommandEncoderWithDescriptor:descriptor];
+ // TODO(cwallez@chromium.org): does any state need to be reset?
+ }
+ }
+ };
+
+ }
+
+ void CommandBuffer::FillCommands(id<MTLCommandBuffer> commandBuffer, std::unordered_set<std::mutex*>* mutexes) {
+ Command type;
+ Pipeline* lastPipeline = nullptr;
+ id<MTLBuffer> indexBuffer = nil;
+ uint32_t indexBufferOffset = 0;
+ MTLIndexType indexType = MTLIndexTypeUInt32;
+
+ CurrentEncoders encoders;
+ encoders.device = device;
+
+ while (commands.NextCommandId(&type)) {
+ switch (type) {
+ case Command::CopyBufferToTexture:
+ {
+ CopyBufferToTextureCmd* copy = commands.NextCommand<CopyBufferToTextureCmd>();
+ Buffer* buffer = ToBackend(copy->buffer.Get());
+ Texture* texture = ToBackend(copy->texture.Get());
+
+ // TODO(kainino@chromium.org): this has to be in a Blit encoder, not a Render encoder, so ordering is lost here
+ unsigned rowSize = copy->width * TextureFormatPixelSize(texture->GetFormat());
+ MTLOrigin origin;
+ origin.x = copy->x;
+ origin.y = copy->y;
+ origin.z = copy->z;
+
+ MTLSize size;
+ size.width = copy->width;
+ size.height = copy->height;
+ size.depth = copy->depth;
+
+ encoders.EnsureBlit(commandBuffer);
+ [encoders.blit
+ copyFromBuffer:buffer->GetMTLBuffer()
+ sourceOffset:0
+ sourceBytesPerRow:rowSize
+ sourceBytesPerImage:(rowSize * copy->height)
+ sourceSize:size
+ toTexture:texture->GetMTLTexture()
+ destinationSlice:0
+ destinationLevel:copy->level
+ destinationOrigin:origin];
+ }
+ break;
+
+ case Command::Dispatch:
+ {
+ DispatchCmd* dispatch = commands.NextCommand<DispatchCmd>();
+ encoders.EnsureCompute(commandBuffer);
+ ASSERT(lastPipeline->IsCompute());
+
+ [encoders.compute dispatchThreadgroups:MTLSizeMake(dispatch->x, dispatch->y, dispatch->z)
+ threadsPerThreadgroup: lastPipeline->GetLocalWorkGroupSize()];
+ }
+ break;
+
+ case Command::DrawArrays:
+ {
+ DrawArraysCmd* draw = commands.NextCommand<DrawArraysCmd>();
+
+ encoders.EnsureRender(commandBuffer);
+ [encoders.render
+ drawPrimitives:MTLPrimitiveTypeTriangle
+ vertexStart:draw->firstVertex
+ vertexCount:draw->vertexCount
+ instanceCount:draw->instanceCount
+ baseInstance:draw->firstInstance];
+ }
+ break;
+
+ case Command::DrawElements:
+ {
+ DrawElementsCmd* draw = commands.NextCommand<DrawElementsCmd>();
+
+ encoders.EnsureRender(commandBuffer);
+ [encoders.render
+ drawIndexedPrimitives:MTLPrimitiveTypeTriangle
+ indexCount:draw->indexCount
+ indexType:indexType
+ indexBuffer:indexBuffer
+ indexBufferOffset:indexBufferOffset
+ instanceCount:draw->instanceCount
+ baseVertex:0
+ baseInstance:draw->firstInstance];
+ }
+ break;
+
+ case Command::SetPipeline:
+ {
+ SetPipelineCmd* cmd = commands.NextCommand<SetPipelineCmd>();
+ lastPipeline = ToBackend(cmd->pipeline).Get();
+
+ if (lastPipeline->IsCompute()) {
+ encoders.EnsureCompute(commandBuffer);
+ lastPipeline->Encode(encoders.compute);
+ } else {
+ encoders.EnsureRender(commandBuffer);
+ lastPipeline->Encode(encoders.render);
+ }
+ }
+ break;
+
+ case Command::SetPushConstants:
+ {
+ SetPushConstantsCmd* cmd = commands.NextCommand<SetPushConstantsCmd>();
+ uint32_t* valuesUInt = commands.NextData<uint32_t>(cmd->count);
+ int32_t* valuesInt = reinterpret_cast<int32_t*>(valuesUInt);
+ float* valuesFloat = reinterpret_cast<float*>(valuesUInt);
+
+ // TODO(kainino@chromium.org): implement SetPushConstants
+ }
+ break;
+
+ case Command::SetBindGroup:
+ {
+ SetBindGroupCmd* cmd = commands.NextCommand<SetBindGroupCmd>();
+ BindGroup* group = ToBackend(cmd->group.Get());
+ uint32_t groupIndex = cmd->index;
+
+ const auto& layout = group->GetLayout()->GetBindingInfo();
+
+ if (lastPipeline->IsCompute()) {
+ encoders.EnsureCompute(commandBuffer);
+ } else {
+ encoders.EnsureRender(commandBuffer);
+ }
+
+ // TODO(kainino@chromium.org): Maintain buffers and offsets arrays in BindGroup so that we
+ // only have to do one setVertexBuffers and one setFragmentBuffers call here.
+ for (size_t binding = 0; binding < layout.mask.size(); ++binding) {
+ if (!layout.mask[binding]) {
+ continue;
+ }
+
+ auto stage = layout.visibilities[binding];
+ bool vertStage = stage & nxt::ShaderStageBit::Vertex;
+ bool fragStage = stage & nxt::ShaderStageBit::Fragment;
+ bool computeStage = stage & nxt::ShaderStageBit::Compute;
+ uint32_t vertIndex = 0;
+ uint32_t fragIndex = 0;
+ uint32_t computeIndex = 0;
+ if (vertStage) {
+ vertIndex = ToBackend(lastPipeline->GetLayout())->
+ GetBindingIndexInfo(nxt::ShaderStage::Vertex)[groupIndex][binding];
+ }
+ if (fragStage) {
+ fragIndex = ToBackend(lastPipeline->GetLayout())->
+ GetBindingIndexInfo(nxt::ShaderStage::Fragment)[groupIndex][binding];
+ }
+ if (computeStage) {
+ computeIndex = ToBackend(lastPipeline->GetLayout())->
+ GetBindingIndexInfo(nxt::ShaderStage::Compute)[groupIndex][binding];
+ }
+
+ switch (layout.types[binding]) {
+ case nxt::BindingType::UniformBuffer:
+ case nxt::BindingType::StorageBuffer:
+ {
+ BufferView* view = ToBackend(group->GetBindingAsBufferView(binding));
+ auto b = ToBackend(view->GetBuffer());
+ mutexes->insert(&b->GetMutex());
+ const id<MTLBuffer> buffer = b->GetMTLBuffer();
+ const NSUInteger offset = view->GetOffset();
+ if (vertStage) {
+ [encoders.render
+ setVertexBuffers:&buffer
+ offsets:&offset
+ withRange:NSMakeRange(vertIndex, 1)];
+ }
+ if (fragStage) {
+ [encoders.render
+ setFragmentBuffers:&buffer
+ offsets:&offset
+ withRange:NSMakeRange(fragIndex, 1)];
+ }
+ if (computeStage) {
+ [encoders.compute
+ setBuffers:&buffer
+ offsets:&offset
+ withRange:NSMakeRange(computeIndex, 1)];
+ }
+
+ }
+ break;
+
+ case nxt::BindingType::Sampler:
+ {
+ auto sampler = ToBackend(group->GetBindingAsSampler(binding));
+ if (vertStage) {
+ [encoders.render
+ setVertexSamplerState:sampler->GetMTLSamplerState()
+ atIndex:vertIndex];
+ }
+ if (fragStage) {
+ [encoders.render
+ setFragmentSamplerState:sampler->GetMTLSamplerState()
+ atIndex:fragIndex];
+ }
+ if (computeStage) {
+ [encoders.compute
+ setSamplerState:sampler->GetMTLSamplerState()
+ atIndex:computeIndex];
+ }
+ }
+ break;
+
+ case nxt::BindingType::SampledTexture:
+ {
+ auto texture = ToBackend(group->GetBindingAsTextureView(binding)->GetTexture());
+ if (vertStage) {
+ [encoders.render
+ setVertexTexture:texture->GetMTLTexture()
+ atIndex:vertIndex];
+ }
+ if (fragStage) {
+ [encoders.render
+ setFragmentTexture:texture->GetMTLTexture()
+ atIndex:fragIndex];
+ }
+ if (computeStage) {
+ [encoders.compute
+ setTexture:texture->GetMTLTexture()
+ atIndex:computeIndex];
+ }
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ case Command::SetIndexBuffer:
+ {
+ SetIndexBufferCmd* cmd = commands.NextCommand<SetIndexBufferCmd>();
+ auto b = ToBackend(cmd->buffer.Get());
+ mutexes->insert(&b->GetMutex());
+ indexBuffer = b->GetMTLBuffer();
+ indexBufferOffset = cmd->offset;
+ indexType = IndexFormatType(cmd->format);
+ }
+ break;
+
+ case Command::SetVertexBuffers:
+ {
+ SetVertexBuffersCmd* cmd = commands.NextCommand<SetVertexBuffersCmd>();
+ auto buffers = commands.NextData<Ref<BufferBase>>(cmd->count);
+ auto offsets = commands.NextData<uint32_t>(cmd->count);
+
+ auto inputState = lastPipeline->GetInputState();
+
+ std::array<id<MTLBuffer>, kMaxVertexInputs> mtlBuffers;
+ std::array<NSUInteger, kMaxVertexInputs> mtlOffsets;
+
+ // Perhaps an "array of vertex buffers(+offsets?)" should be
+ // a NXT API primitive to avoid reconstructing this array?
+ for (uint32_t i = 0; i < cmd->count; ++i) {
+ Buffer* buffer = ToBackend(buffers[i].Get());
+ mutexes->insert(&buffer->GetMutex());
+ mtlBuffers[i] = buffer->GetMTLBuffer();
+ mtlOffsets[i] = offsets[i];
+ }
+
+ encoders.EnsureRender(commandBuffer);
+ [encoders.render
+ setVertexBuffers:mtlBuffers.data()
+ offsets:mtlOffsets.data()
+ withRange:NSMakeRange(kMaxBindingsPerGroup + cmd->startSlot, cmd->count)];
+ }
+ break;
+
+ case Command::TransitionBufferUsage:
+ {
+ TransitionBufferUsageCmd* cmd = commands.NextCommand<TransitionBufferUsageCmd>();
+
+ cmd->buffer->TransitionUsageImpl(cmd->usage);
+ }
+ break;
+
+ case Command::TransitionTextureUsage:
+ {
+ TransitionTextureUsageCmd* cmd = commands.NextCommand<TransitionTextureUsageCmd>();
+
+ cmd->texture->TransitionUsageImpl(cmd->usage);
+ }
+ break;
+;
+ }
+ }
+
+ encoders.FinishEncoders();
+ }
+
+ // InputState
+
+ static MTLVertexFormat VertexFormatType(nxt::VertexFormat format) {
+ switch (format) {
+ case nxt::VertexFormat::FloatR32G32B32A32:
+ return MTLVertexFormatFloat4;
+ case nxt::VertexFormat::FloatR32G32B32:
+ return MTLVertexFormatFloat3;
+ case nxt::VertexFormat::FloatR32G32:
+ return MTLVertexFormatFloat2;
+ }
+ }
+
+ static MTLVertexStepFunction InputStepModeFunction(nxt::InputStepMode mode) {
+ switch (mode) {
+ case nxt::InputStepMode::Vertex:
+ return MTLVertexStepFunctionPerVertex;
+ case nxt::InputStepMode::Instance:
+ return MTLVertexStepFunctionPerInstance;
+ }
+ }
+
+ InputState::InputState(Device* device, InputStateBuilder* builder)
+ : InputStateBase(builder), device(device) {
+ mtlVertexDescriptor = [MTLVertexDescriptor new];
+
+ const auto& attributesSetMask = GetAttributesSetMask();
+ for (size_t i = 0; i < attributesSetMask.size(); ++i) {
+ if (!attributesSetMask[i]) {
+ continue;
+ }
+ const AttributeInfo& info = GetAttribute(i);
+
+ auto attribDesc = [MTLVertexAttributeDescriptor new];
+ attribDesc.format = VertexFormatType(info.format);
+ attribDesc.offset = info.offset;
+ attribDesc.bufferIndex = kMaxBindingsPerGroup + info.bindingSlot;
+ mtlVertexDescriptor.attributes[i] = attribDesc;
+ [attribDesc release];
+ }
+
+ const auto& inputsSetMask = GetInputsSetMask();
+ for (size_t i = 0; i < inputsSetMask.size(); ++i) {
+ if (!inputsSetMask[i]) {
+ continue;
+ }
+ const InputInfo& info = GetInput(i);
+
+ auto layoutDesc = [MTLVertexBufferLayoutDescriptor new];
+ if (info.stride == 0) {
+ // For MTLVertexStepFunctionConstant, the stepRate must be 0,
+ // but the stride must NOT be 0, so I made up a value (256).
+ layoutDesc.stepFunction = MTLVertexStepFunctionConstant;
+ layoutDesc.stepRate = 0;
+ layoutDesc.stride = 256;
+ } else {
+ layoutDesc.stepFunction = InputStepModeFunction(info.stepMode);
+ layoutDesc.stepRate = 1;
+ layoutDesc.stride = info.stride;
+ }
+ mtlVertexDescriptor.layouts[kMaxBindingsPerGroup + i] = layoutDesc;
+ [layoutDesc release];
+ }
+ }
+
+ InputState::~InputState() {
+ [mtlVertexDescriptor release];
+ mtlVertexDescriptor = nil;
+ }
+
+ MTLVertexDescriptor* InputState::GetMTLVertexDescriptor() {
+ return mtlVertexDescriptor;
+ }
+
+ // Pipeline
+
+ Pipeline::Pipeline(Device* device, PipelineBuilder* builder)
+ : PipelineBase(builder), device(device) {
+
+ if (IsCompute()) {
+ const auto& module = ToBackend(builder->GetStageInfo(nxt::ShaderStage::Compute).module);
+ const auto& entryPoint = builder->GetStageInfo(nxt::ShaderStage::Compute).entryPoint;
+
+ id<MTLFunction> function = module->GetFunction(entryPoint.c_str());
+
+ NSError *error = nil;
+ mtlComputePipelineState = [device->GetMTLDevice()
+ newComputePipelineStateWithFunction:function error:&error];
+ if (error != nil) {
+ NSLog(@" error => %@", error);
+ device->HandleError("Error creating pipeline state");
+ return;
+ }
+
+ // Copy over the local workgroup size as it is passed to dispatch explicitly in Metal
+ localWorkgroupSize = module->GetLocalWorkGroupSize(entryPoint);
+
+ } else {
+ MTLRenderPipelineDescriptor* descriptor = [MTLRenderPipelineDescriptor new];
+
+ for (auto stage : IterateStages(GetStageMask())) {
+ const auto& module = ToBackend(builder->GetStageInfo(stage).module);
+
+ const auto& entryPoint = builder->GetStageInfo(stage).entryPoint;
+ id<MTLFunction> function = module->GetFunction(entryPoint.c_str());
+
+ switch (stage) {
+ case nxt::ShaderStage::Vertex:
+ descriptor.vertexFunction = function;
+ break;
+ case nxt::ShaderStage::Fragment:
+ descriptor.fragmentFunction = function;
+ break;
+ case nxt::ShaderStage::Compute:
+ ASSERT(false);
+ break;
+ }
+ }
+
+ descriptor.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
+ descriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float;
+
+ InputState* inputState = ToBackend(GetInputState());
+ descriptor.vertexDescriptor = inputState->GetMTLVertexDescriptor();
+
+ // TODO(kainino@chromium.org): push constants, textures, samplers
+
+ NSError *error = nil;
+ mtlRenderPipelineState = [device->GetMTLDevice()
+ newRenderPipelineStateWithDescriptor:descriptor error:&error];
+ if (error != nil) {
+ NSLog(@" error => %@", error);
+ device->HandleError("Error creating pipeline state");
+ return;
+ }
+
+ MTLDepthStencilDescriptor* dsDesc = [MTLDepthStencilDescriptor new];
+ dsDesc.depthWriteEnabled = true;
+ dsDesc.depthCompareFunction = MTLCompareFunctionLess;
+ mtlDepthStencilState = [device->GetMTLDevice()
+ newDepthStencilStateWithDescriptor:dsDesc];
+
+ [dsDesc release];
+ [descriptor release];
+ }
+ }
+
+ Pipeline::~Pipeline() {
+ [mtlRenderPipelineState release];
+ [mtlDepthStencilState release];
+ [mtlComputePipelineState release];
+ }
+
+ void Pipeline::Encode(id<MTLRenderCommandEncoder> encoder) {
+ ASSERT(!IsCompute());
+ [encoder setDepthStencilState:mtlDepthStencilState];
+ [encoder setRenderPipelineState:mtlRenderPipelineState];
+ }
+
+ void Pipeline::Encode(id<MTLComputeCommandEncoder> encoder) {
+ ASSERT(IsCompute());
+ [encoder setComputePipelineState:mtlComputePipelineState];
+ }
+
+ MTLSize Pipeline::GetLocalWorkGroupSize() const {
+ return localWorkgroupSize;
+ }
+
+ // PipelineLayout
+
+ PipelineLayout::PipelineLayout(Device* device, PipelineLayoutBuilder* builder)
+ : PipelineLayoutBase(builder), device(device) {
+ // Each stage has its own numbering namespace in CompilerMSL.
+ for (auto stage : IterateStages(kAllStages)) {
+ uint32_t bufferIndex = 0;
+ uint32_t samplerIndex = 0;
+ uint32_t textureIndex = 0;
+
+ for (size_t group = 0; group < kMaxBindGroups; ++group) {
+ const auto& groupInfo = GetBindGroupLayout(group)->GetBindingInfo();
+ for (size_t binding = 0; binding < kMaxBindingsPerGroup; ++binding) {
+ if (!(groupInfo.visibilities[binding] & StageBit(stage))) {
+ continue;
+ }
+ if (!groupInfo.mask[binding]) {
+ continue;
+ }
+
+ switch (groupInfo.types[binding]) {
+ case nxt::BindingType::UniformBuffer:
+ case nxt::BindingType::StorageBuffer:
+ indexInfo[stage][group][binding] = bufferIndex;
+ bufferIndex++;
+ break;
+ case nxt::BindingType::Sampler:
+ indexInfo[stage][group][binding] = samplerIndex;
+ samplerIndex++;
+ break;
+ case nxt::BindingType::SampledTexture:
+ indexInfo[stage][group][binding] = textureIndex;
+ textureIndex++;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ const PipelineLayout::BindingIndexInfo& PipelineLayout::GetBindingIndexInfo(nxt::ShaderStage stage) const {
+ return indexInfo[stage];
+ }
+
+ // Queue
+
+ Queue::Queue(Device* device, QueueBuilder* builder)
+ : device(device) {
+ commandQueue = [device->GetMTLDevice() newCommandQueue];
+ }
+
+ Queue::~Queue() {
+ [commandQueue release];
+ commandQueue = nil;
+ }
+
+ id<MTLCommandQueue> Queue::GetMTLCommandQueue() {
+ return commandQueue;
+ }
+
+ void Queue::Submit(uint32_t numCommands, CommandBuffer* const * commands) {
+ id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
+
+ // Mutexes are necessary to prevent buffers from being written from the
+ // CPU before their previous value has been read from the GPU.
+ // https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/TripleBuffering.html
+ // TODO(kainino@chromium.org): When we have resource transitions, all of these mutexes will be replaced.
+ std::unordered_set<std::mutex*> mutexes;
+
+ for (uint32_t i = 0; i < numCommands; ++i) {
+ commands[i]->FillCommands(commandBuffer, &mutexes);
+ }
+
+ for (auto mutex : mutexes) {
+ mutex->lock();
+ }
+ [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> commandBuffer) {
+ // 'mutexes' is copied into this Block
+ for (auto mutex : mutexes) {
+ mutex->unlock();
+ }
+ }];
+
+ [commandBuffer commit];
+ }
+
+ // Sampler
+
+ MTLSamplerMinMagFilter FilterModeToMinMagFilter(nxt::FilterMode mode) {
+ switch (mode) {
+ case nxt::FilterMode::Nearest:
+ return MTLSamplerMinMagFilterNearest;
+ case nxt::FilterMode::Linear:
+ return MTLSamplerMinMagFilterLinear;
+ }
+ }
+
+ MTLSamplerMipFilter FilterModeToMipFilter(nxt::FilterMode mode) {
+ switch (mode) {
+ case nxt::FilterMode::Nearest:
+ return MTLSamplerMipFilterNearest;
+ case nxt::FilterMode::Linear:
+ return MTLSamplerMipFilterLinear;
+ }
+ }
+
+ Sampler::Sampler(Device* device, SamplerBuilder* builder)
+ : SamplerBase(builder), device(device) {
+ auto desc = [MTLSamplerDescriptor new];
+ [desc autorelease];
+ desc.minFilter = FilterModeToMinMagFilter(builder->GetMinFilter());
+ desc.magFilter = FilterModeToMinMagFilter(builder->GetMagFilter());
+ desc.mipFilter = FilterModeToMipFilter(builder->GetMipMapFilter());
+ // TODO(kainino@chromium.org): wrap modes
+ mtlSamplerState = [device->GetMTLDevice() newSamplerStateWithDescriptor:desc];
+ }
+
+ Sampler::~Sampler() {
+ [mtlSamplerState release];
+ }
+
+ id<MTLSamplerState> Sampler::GetMTLSamplerState() {
+ return mtlSamplerState;
+ }
+
+ // ShaderModule
+
+ ShaderModule::ShaderModule(Device* device, ShaderModuleBuilder* builder)
+ : ShaderModuleBase(builder), device(device) {
+ compiler = new spirv_cross::CompilerMSL(builder->AcquireSpirv());
+ ExtractSpirvInfo(*compiler);
+
+ spirv_cross::MSLConfiguration mslConfig;
+ mslConfig.flip_vert_y = false;
+ mslConfig.flip_frag_y = false;
+ std::string msl = compiler->compile(mslConfig);
+
+ NSString* mslSource = [NSString stringWithFormat:@"%s", msl.c_str()];
+ NSError *error = nil;
+ mtlLibrary = [device->GetMTLDevice() newLibraryWithSource:mslSource options:nil error:&error];
+ if (error != nil) {
+ NSLog(@"MTLDevice newLibraryWithSource => %@", error);
+ device->HandleError("Error creating MTLLibrary from MSL source");
+ }
+ }
+
+ ShaderModule::~ShaderModule() {
+ delete compiler;
+ }
+
+ id<MTLFunction> ShaderModule::GetFunction(const char* functionName) const {
+ // TODO(kainino@chromium.org): make this somehow more robust; it needs to behave like clean_func_name:
+ // https://github.com/KhronosGroup/SPIRV-Cross/blob/4e915e8c483e319d0dd7a1fa22318bef28f8cca3/spirv_msl.cpp#L1213
+ if (strcmp(functionName, "main") == 0) {
+ functionName = "main0";
+ }
+ NSString* name = [NSString stringWithFormat:@"%s", functionName];
+ return [mtlLibrary newFunctionWithName:name];
+ }
+
+ MTLSize ShaderModule::GetLocalWorkGroupSize(const std::string& entryPoint) const {
+ auto size = compiler->get_entry_point(entryPoint).workgroup_size;
+ return MTLSizeMake(size.x, size.y, size.z);
+ }
+
+ // Texture
+
+ MTLPixelFormat TextureFormatPixelFormat(nxt::TextureFormat format) {
+ switch (format) {
+ case nxt::TextureFormat::R8G8B8A8Unorm:
+ return MTLPixelFormatRGBA8Unorm;
+ }
+ }
+
+ Texture::Texture(Device* device, TextureBuilder* builder)
+ : TextureBase(builder), device(device) {
+ auto desc = [MTLTextureDescriptor new];
+ [desc autorelease];
+ switch (GetDimension()) {
+ case nxt::TextureDimension::e2D:
+ desc.textureType = MTLTextureType2D;
+ break;
+ }
+ desc.usage = MTLTextureUsageShaderRead;
+ desc.pixelFormat = TextureFormatPixelFormat(GetFormat());
+ desc.width = GetWidth();
+ desc.height = GetHeight();
+ desc.depth = GetDepth();
+ desc.mipmapLevelCount = GetNumMipLevels();
+ desc.arrayLength = 1;
+
+ mtlTexture = [device->GetMTLDevice() newTextureWithDescriptor:desc];
+ }
+
+ Texture::~Texture() {
+ [mtlTexture release];
+ }
+
+ id<MTLTexture> Texture::GetMTLTexture() {
+ return mtlTexture;
+ }
+
+ // TextureView
+
+ TextureView::TextureView(Device* device, TextureViewBuilder* builder)
+ : TextureViewBase(builder), device(device) {
+ }
+
+}
+}
diff --git a/src/backend/opengl/CommandBufferGL.cpp b/src/backend/opengl/CommandBufferGL.cpp
new file mode 100644
index 0000000..a62da6e
--- /dev/null
+++ b/src/backend/opengl/CommandBufferGL.cpp
@@ -0,0 +1,303 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "CommandBufferGL.h"
+
+#include "common/Commands.h"
+#include "OpenGLBackend.h"
+#include "PipelineGL.h"
+#include "PipelineLayoutGL.h"
+#include "SamplerGL.h"
+#include "TextureGL.h"
+
+#include <cstring>
+
+namespace backend {
+namespace opengl {
+
+ CommandBuffer::CommandBuffer(Device* device, CommandBufferBuilder* builder)
+ : CommandBufferBase(builder), device(device), commands(builder->AcquireCommands()) {
+ }
+
+ CommandBuffer::~CommandBuffer() {
+ FreeCommands(&commands);
+ }
+
+ static GLenum IndexFormatType(nxt::IndexFormat format) {
+ switch (format) {
+ case nxt::IndexFormat::Uint16:
+ return GL_UNSIGNED_SHORT;
+ case nxt::IndexFormat::Uint32:
+ return GL_UNSIGNED_INT;
+ }
+ }
+
+ static GLenum VertexFormatType(nxt::VertexFormat format) {
+ switch (format) {
+ case nxt::VertexFormat::FloatR32G32B32A32:
+ case nxt::VertexFormat::FloatR32G32B32:
+ case nxt::VertexFormat::FloatR32G32:
+ return GL_FLOAT;
+ }
+ }
+
+ void CommandBuffer::Execute() {
+ Command type;
+ Pipeline* lastPipeline = nullptr;
+ uint32_t indexBufferOffset = 0;
+ nxt::IndexFormat indexBufferFormat = nxt::IndexFormat::Uint16;
+
+ while(commands.NextCommandId(&type)) {
+ switch (type) {
+
+ case Command::CopyBufferToTexture:
+ {
+ CopyBufferToTextureCmd* copy = commands.NextCommand<CopyBufferToTextureCmd>();
+ Buffer* buffer = ToBackend(copy->buffer.Get());
+ Texture* texture = ToBackend(copy->texture.Get());
+ GLenum target = texture->GetGLTarget();
+ auto format = texture->GetGLFormat();
+
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->GetHandle());
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(target, texture->GetHandle());
+
+ glTexSubImage2D(target, copy->level, copy->x, copy->y, copy->width, copy->height,
+ format.format, format.type, nullptr);
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+ break;
+
+ case Command::Dispatch:
+ {
+ DispatchCmd* dispatch = commands.NextCommand<DispatchCmd>();
+ glDispatchCompute(dispatch->x, dispatch->y, dispatch->z);
+ // TODO(cwallez@chromium.org): add barriers to the API
+ glMemoryBarrier(GL_ALL_BARRIER_BITS);
+ }
+ break;
+
+ case Command::DrawArrays:
+ {
+ DrawArraysCmd* draw = commands.NextCommand<DrawArraysCmd>();
+ if (draw->firstInstance > 0) {
+ glDrawArraysInstancedBaseInstance(GL_TRIANGLES,
+ draw->firstVertex, draw->vertexCount, draw->instanceCount, draw->firstInstance);
+ } else {
+ // This branch is only needed on OpenGL < 4.2
+ glDrawArraysInstanced(GL_TRIANGLES,
+ draw->firstVertex, draw->vertexCount, draw->instanceCount);
+ }
+ }
+ break;
+
+ case Command::DrawElements:
+ {
+ DrawElementsCmd* draw = commands.NextCommand<DrawElementsCmd>();
+ size_t formatSize = IndexFormatSize(indexBufferFormat);
+ GLenum formatType = IndexFormatType(indexBufferFormat);
+
+ if (draw->firstInstance > 0) {
+ glDrawElementsInstancedBaseInstance(GL_TRIANGLES,
+ draw->indexCount, formatType,
+ reinterpret_cast<void*>(draw->firstIndex * formatSize + indexBufferOffset),
+ draw->instanceCount, draw->firstInstance);
+ } else {
+ // This branch is only needed on OpenGL < 4.2
+ glDrawElementsInstanced(GL_TRIANGLES,
+ draw->indexCount, formatType,
+ reinterpret_cast<void*>(draw->firstIndex * formatSize + indexBufferOffset),
+ draw->instanceCount);
+ }
+ }
+ break;
+
+ case Command::SetPipeline:
+ {
+ SetPipelineCmd* cmd = commands.NextCommand<SetPipelineCmd>();
+ ToBackend(cmd->pipeline)->ApplyNow();
+ lastPipeline = ToBackend(cmd->pipeline).Get();
+ }
+ break;
+
+ case Command::SetPushConstants:
+ {
+ SetPushConstantsCmd* cmd = commands.NextCommand<SetPushConstantsCmd>();
+ uint32_t* valuesUInt = commands.NextData<uint32_t>(cmd->count);
+ int32_t* valuesInt = reinterpret_cast<int32_t*>(valuesUInt);
+ float* valuesFloat = reinterpret_cast<float*>(valuesUInt);
+
+ for (auto stage : IterateStages(cmd->stage)) {
+ const auto& pushConstants = lastPipeline->GetPushConstants(stage);
+ const auto& glPushConstants = lastPipeline->GetGLPushConstants(stage);
+ for (size_t i = 0; i < cmd->count; i++) {
+ GLint location = glPushConstants[cmd->offset + i];
+
+ switch (pushConstants.types[cmd->offset + i]) {
+ case PushConstantType::Int:
+ glUniform1i(location, valuesInt[i]);
+ break;
+ case PushConstantType::UInt:
+ glUniform1ui(location, valuesUInt[i]);
+ break;
+ case PushConstantType::Float:
+ glUniform1f(location, valuesFloat[i]);
+ break;
+ }
+ }
+ }
+ }
+ break;
+
+ case Command::SetBindGroup:
+ {
+ SetBindGroupCmd* cmd = commands.NextCommand<SetBindGroupCmd>();
+ size_t index = cmd->index;
+ BindGroup* group = ToBackend(cmd->group.Get());
+
+ const auto& indices = ToBackend(lastPipeline->GetLayout())->GetBindingIndexInfo()[index];
+ const auto& layout = group->GetLayout()->GetBindingInfo();
+
+ // TODO(cwallez@chromium.org): iterate over the layout bitmask instead
+ for (size_t binding = 0; binding < kMaxBindingsPerGroup; ++binding) {
+ if (!layout.mask[binding]) {
+ continue;
+ }
+
+ switch (layout.types[binding]) {
+ case nxt::BindingType::UniformBuffer:
+ {
+ BufferView* view = ToBackend(group->GetBindingAsBufferView(binding));
+ GLuint buffer = ToBackend(view->GetBuffer())->GetHandle();
+ GLuint index = indices[binding];
+
+ glBindBufferRange(GL_UNIFORM_BUFFER, index, buffer, view->GetOffset(), view->GetSize());
+ }
+ break;
+
+ case nxt::BindingType::Sampler:
+ {
+ GLuint sampler = ToBackend(group->GetBindingAsSampler(binding))->GetHandle();
+ GLuint index = indices[binding];
+
+ for (auto unit : lastPipeline->GetTextureUnitsForSampler(index)) {
+ glBindSampler(unit, sampler);
+ }
+ }
+ break;
+
+ case nxt::BindingType::SampledTexture:
+ {
+ TextureView* view = ToBackend(group->GetBindingAsTextureView(binding));
+ Texture* texture = ToBackend(view->GetTexture());
+ GLuint handle = texture->GetHandle();
+ GLenum target = texture->GetGLTarget();
+ GLuint index = indices[binding];
+
+ for (auto unit : lastPipeline->GetTextureUnitsForTexture(index)) {
+ glActiveTexture(GL_TEXTURE0 + unit);
+ glBindTexture(target, handle);
+ }
+ }
+ break;
+
+ case nxt::BindingType::StorageBuffer:
+ {
+ BufferView* view = ToBackend(group->GetBindingAsBufferView(binding));
+ GLuint buffer = ToBackend(view->GetBuffer())->GetHandle();
+ GLuint index = indices[binding];
+
+ glBindBufferRange(GL_SHADER_STORAGE_BUFFER, index, buffer, view->GetOffset(), view->GetSize());
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ case Command::SetIndexBuffer:
+ {
+ SetIndexBufferCmd* cmd = commands.NextCommand<SetIndexBufferCmd>();
+
+ GLuint buffer = ToBackend(cmd->buffer.Get())->GetHandle();
+ indexBufferOffset = cmd->offset;
+ indexBufferFormat = cmd->format;
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
+ }
+ break;
+
+ case Command::SetVertexBuffers:
+ {
+ SetVertexBuffersCmd* cmd = commands.NextCommand<SetVertexBuffersCmd>();
+ auto buffers = commands.NextData<Ref<BufferBase>>(cmd->count);
+ auto offsets = commands.NextData<uint32_t>(cmd->count);
+
+ auto inputState = lastPipeline->GetInputState();
+
+ auto& attributesSetMask = inputState->GetAttributesSetMask();
+ for (uint32_t location = 0; location < attributesSetMask.size(); ++location) {
+ if (!attributesSetMask[location]) {
+ // This slot is not used in the input state
+ continue;
+ }
+ auto attribute = inputState->GetAttribute(location);
+ auto slot = attribute.bindingSlot;
+ ASSERT(slot < kMaxVertexInputs);
+ if (slot < cmd->startSlot || slot >= cmd->startSlot + cmd->count) {
+ // This slot is not affected by this call
+ continue;
+ }
+ size_t bufferIndex = slot - cmd->startSlot;
+ GLuint buffer = ToBackend(buffers[bufferIndex])->GetHandle();
+ uint32_t bufferOffset = offsets[bufferIndex];
+
+ auto input = inputState->GetInput(slot);
+
+ auto components = VertexFormatNumComponents(attribute.format);
+ auto formatType = VertexFormatType(attribute.format);
+
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ glVertexAttribPointer(
+ location, components, formatType, GL_FALSE,
+ input.stride,
+ reinterpret_cast<void*>(static_cast<intptr_t>(bufferOffset + attribute.offset)));
+ }
+ }
+ break;
+
+ case Command::TransitionBufferUsage:
+ {
+ TransitionBufferUsageCmd* cmd = commands.NextCommand<TransitionBufferUsageCmd>();
+
+ cmd->buffer->TransitionUsageImpl(cmd->usage);
+ }
+ break;
+
+ case Command::TransitionTextureUsage:
+ {
+ TransitionTextureUsageCmd* cmd = commands.NextCommand<TransitionTextureUsageCmd>();
+
+ cmd->texture->TransitionUsageImpl(cmd->usage);
+ }
+ break;
+ }
+ }
+
+ // HACK: cleanup a tiny bit of state to make this work with
+ // virtualized contexts enabled in Chromium
+ glBindSampler(0, 0);
+ }
+
+}
+}
diff --git a/src/backend/opengl/CommandBufferGL.h b/src/backend/opengl/CommandBufferGL.h
new file mode 100644
index 0000000..1492551
--- /dev/null
+++ b/src/backend/opengl/CommandBufferGL.h
@@ -0,0 +1,45 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_COMMANDBUFFER_H_
+#define BACKEND_OPENGL_COMMANDBUFFER_H_
+
+#include "common/CommandAllocator.h"
+#include "common/CommandBuffer.h"
+
+namespace backend {
+ class CommandBufferBuilder;
+}
+
+namespace backend {
+namespace opengl {
+
+ class Device;
+
+ class CommandBuffer : public CommandBufferBase {
+ public:
+ CommandBuffer(Device* device, CommandBufferBuilder* builder);
+ ~CommandBuffer();
+
+ void Execute();
+
+ private:
+ Device* device;
+ CommandIterator commands;
+ };
+
+}
+}
+
+#endif // BACKEND_OPENGL_COMMANDBUFFER_H_
diff --git a/src/backend/opengl/GeneratedCodeIncludes.h b/src/backend/opengl/GeneratedCodeIncludes.h
new file mode 100644
index 0000000..f757618
--- /dev/null
+++ b/src/backend/opengl/GeneratedCodeIncludes.h
@@ -0,0 +1,21 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "OpenGLBackend.h"
+#include "CommandBufferGL.h"
+#include "PipelineGL.h"
+#include "PipelineLayoutGL.h"
+#include "SamplerGL.h"
+#include "ShaderModuleGL.h"
+#include "TextureGL.h"
diff --git a/src/backend/opengl/OpenGLBackend.cpp b/src/backend/opengl/OpenGLBackend.cpp
new file mode 100644
index 0000000..7455626
--- /dev/null
+++ b/src/backend/opengl/OpenGLBackend.cpp
@@ -0,0 +1,180 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "OpenGLBackend.h"
+
+#include "CommandBufferGL.h"
+#include "PipelineGL.h"
+#include "PipelineLayoutGL.h"
+#include "ShaderModuleGL.h"
+#include "SamplerGL.h"
+#include "TextureGL.h"
+
+namespace backend {
+namespace opengl {
+ nxtProcTable GetNonValidatingProcs();
+ nxtProcTable GetValidatingProcs();
+
+ void HACKCLEAR() {
+ glClearColor(0, 0, 0, 1);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ }
+
+ void Init(void* (*getProc)(const char*), nxtProcTable* procs, nxtDevice* device) {
+ *device = nullptr;
+
+ gladLoadGLLoader(reinterpret_cast<GLADloadproc>(getProc));
+
+ glEnable(GL_DEPTH_TEST);
+ HACKCLEAR();
+
+ *procs = GetValidatingProcs();
+ *device = reinterpret_cast<nxtDevice>(new Device);
+ }
+
+ // Device
+
+ BindGroupBase* Device::CreateBindGroup(BindGroupBuilder* builder) {
+ return new BindGroup(this, builder);
+ }
+ BindGroupLayoutBase* Device::CreateBindGroupLayout(BindGroupLayoutBuilder* builder) {
+ return new BindGroupLayout(this, builder);
+ }
+ BufferBase* Device::CreateBuffer(BufferBuilder* builder) {
+ return new Buffer(this, builder);
+ }
+ BufferViewBase* Device::CreateBufferView(BufferViewBuilder* builder) {
+ return new BufferView(this, builder);
+ }
+ CommandBufferBase* Device::CreateCommandBuffer(CommandBufferBuilder* builder) {
+ return new CommandBuffer(this, builder);
+ }
+ InputStateBase* Device::CreateInputState(InputStateBuilder* builder) {
+ return new InputState(this, builder);
+ }
+ PipelineBase* Device::CreatePipeline(PipelineBuilder* builder) {
+ return new Pipeline(this, builder);
+ }
+ PipelineLayoutBase* Device::CreatePipelineLayout(PipelineLayoutBuilder* builder) {
+ return new PipelineLayout(this, builder);
+ }
+ QueueBase* Device::CreateQueue(QueueBuilder* builder) {
+ return new Queue(this, builder);
+ }
+ SamplerBase* Device::CreateSampler(SamplerBuilder* builder) {
+ return new Sampler(this, builder);
+ }
+ ShaderModuleBase* Device::CreateShaderModule(ShaderModuleBuilder* builder) {
+ return new ShaderModule(this, builder);
+ }
+ TextureBase* Device::CreateTexture(TextureBuilder* builder) {
+ return new Texture(this, builder);
+ }
+ TextureViewBase* Device::CreateTextureView(TextureViewBuilder* builder) {
+ return new TextureView(this, builder);
+ }
+
+ void Device::Reference() {
+ }
+
+ void Device::Release() {
+ }
+
+ // Bind Group
+
+ BindGroup::BindGroup(Device* device, BindGroupBuilder* builder)
+ : BindGroupBase(builder), device(device) {
+ }
+
+ // Bind Group Layout
+
+ BindGroupLayout::BindGroupLayout(Device* device, BindGroupLayoutBuilder* builder)
+ : BindGroupLayoutBase(builder), device(device) {
+ }
+
+ // Buffer
+
+ Buffer::Buffer(Device* device, BufferBuilder* builder)
+ : BufferBase(builder), device(device) {
+ glGenBuffers(1, &buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ glBufferData(GL_ARRAY_BUFFER, GetSize(), nullptr, GL_STATIC_DRAW);
+ }
+
+ GLuint Buffer::GetHandle() const {
+ return buffer;
+ }
+
+ void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) {
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ glBufferSubData(GL_ARRAY_BUFFER, start * sizeof(uint32_t), count * sizeof(uint32_t), data);
+ }
+
+ // BufferView
+
+ BufferView::BufferView(Device* device, BufferViewBuilder* builder)
+ : BufferViewBase(builder), device(device) {
+ }
+
+ // InputState
+
+ InputState::InputState(Device* device, InputStateBuilder* builder)
+ : InputStateBase(builder), device(device) {
+ glGenVertexArrays(1, &vertexArrayObject);
+ glBindVertexArray(vertexArrayObject);
+ auto& attributesSetMask = GetAttributesSetMask();
+ for (uint32_t location = 0; location < attributesSetMask.size(); ++location) {
+ if (!attributesSetMask[location]) {
+ continue;
+ }
+ auto attribute = GetAttribute(location);
+ glEnableVertexAttribArray(location);
+
+ auto input = GetInput(attribute.bindingSlot);
+ if (input.stride == 0) {
+ // Emulate a stride of zero (constant vertex attribute) by
+ // setting the attribute instance divisor to a huge number.
+ glVertexAttribDivisor(location, 0xffffffff);
+ } else {
+ switch (input.stepMode) {
+ case nxt::InputStepMode::Vertex:
+ break;
+ case nxt::InputStepMode::Instance:
+ glVertexAttribDivisor(location, 1);
+ break;
+ default:
+ ASSERT(false);
+ break;
+ }
+ }
+ }
+ }
+
+ GLuint InputState::GetVAO() {
+ return vertexArrayObject;
+ }
+
+ // Queue
+
+ Queue::Queue(Device* device, QueueBuilder* builder) : device(device) {
+ }
+
+ void Queue::Submit(uint32_t numCommands, CommandBuffer* const * commands) {
+ for (uint32_t i = 0; i < numCommands; ++i) {
+ commands[i]->Execute();
+ }
+ }
+
+}
+}
diff --git a/src/backend/opengl/OpenGLBackend.h b/src/backend/opengl/OpenGLBackend.h
new file mode 100644
index 0000000..41742ea
--- /dev/null
+++ b/src/backend/opengl/OpenGLBackend.h
@@ -0,0 +1,151 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_OPENGLBACKEND_H_
+#define BACKEND_OPENGL_OPENGLBACKEND_H_
+
+#include "nxt/nxtcpp.h"
+
+#include "common/Buffer.h"
+#include "common/BindGroup.h"
+#include "common/BindGroupLayout.h"
+#include "common/Device.h"
+#include "common/InputState.h"
+#include "common/Queue.h"
+#include "common/ToBackend.h"
+
+#include "glad/glad.h"
+
+namespace backend {
+namespace opengl {
+
+ class BindGroup;
+ class BindGroupLayout;
+ class Buffer;
+ class BufferView;
+ class CommandBuffer;
+ class InputState;
+ class Pipeline;
+ class PipelineLayout;
+ class Queue;
+ class Sampler;
+ class ShaderModule;
+ class Texture;
+ class TextureView;
+
+ struct OpenGLBackendTraits {
+ using BindGroupType = BindGroup;
+ using BindGroupLayoutType = BindGroupLayout;
+ using BufferType = Buffer;
+ using BufferViewType = BufferView;
+ using CommandBufferType = CommandBuffer;
+ using InputStateType = InputState;
+ using PipelineType = Pipeline;
+ using PipelineLayoutType = PipelineLayout;
+ using QueueType = Queue;
+ using SamplerType = Sampler;
+ using ShaderModuleType = ShaderModule;
+ using TextureType = Texture;
+ using TextureViewType = TextureView;
+ };
+
+ template<typename T>
+ auto ToBackend(T&& common) -> decltype(ToBackendBase<OpenGLBackendTraits>(common)) {
+ return ToBackendBase<OpenGLBackendTraits>(common);
+ }
+
+ // Definition of backend types
+ class Device : public DeviceBase {
+ public:
+ BindGroupBase* CreateBindGroup(BindGroupBuilder* builder) override;
+ BindGroupLayoutBase* CreateBindGroupLayout(BindGroupLayoutBuilder* builder) override;
+ BufferBase* CreateBuffer(BufferBuilder* builder) override;
+ BufferViewBase* CreateBufferView(BufferViewBuilder* builder) override;
+ CommandBufferBase* CreateCommandBuffer(CommandBufferBuilder* builder) override;
+ InputStateBase* CreateInputState(InputStateBuilder* builder) override;
+ PipelineBase* CreatePipeline(PipelineBuilder* builder) override;
+ PipelineLayoutBase* CreatePipelineLayout(PipelineLayoutBuilder* builder) override;
+ QueueBase* CreateQueue(QueueBuilder* builder) override;
+ SamplerBase* CreateSampler(SamplerBuilder* builder) override;
+ ShaderModuleBase* CreateShaderModule(ShaderModuleBuilder* builder) override;
+ TextureBase* CreateTexture(TextureBuilder* builder) override;
+ TextureViewBase* CreateTextureView(TextureViewBuilder* builder) override;
+
+ // NXT API
+ void Reference();
+ void Release();
+ };
+
+ class BindGroup : public BindGroupBase {
+ public:
+ BindGroup(Device* device, BindGroupBuilder* builder);
+
+ private:
+ Device* device;
+ };
+
+ class BindGroupLayout : public BindGroupLayoutBase {
+ public:
+ BindGroupLayout(Device* device, BindGroupLayoutBuilder* builder);
+
+ private:
+ Device* device;
+ };
+
+ class Buffer : public BufferBase {
+ public:
+ Buffer(Device* device, BufferBuilder* builder);
+
+ GLuint GetHandle() const;
+
+ private:
+ void SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) override;
+
+ Device* device;
+ GLuint buffer = 0;
+ };
+
+ class BufferView : public BufferViewBase {
+ public:
+ BufferView(Device* device, BufferViewBuilder* builder);
+
+ private:
+ Device* device;
+ };
+
+ class InputState : public InputStateBase {
+ public:
+ InputState(Device* device, InputStateBuilder* builder);
+ GLuint GetVAO();
+
+ private:
+ Device* device;
+ GLuint vertexArrayObject;
+ };
+
+ class Queue : public QueueBase {
+ public:
+ Queue(Device* device, QueueBuilder* builder);
+
+ // NXT API
+ void Submit(uint32_t numCommands, CommandBuffer* const * commands);
+
+ private:
+ Device* device;
+ };
+
+}
+}
+
+#endif // BACKEND_OPENGL_OPENGLBACKEND_H_
diff --git a/src/backend/opengl/PipelineGL.cpp b/src/backend/opengl/PipelineGL.cpp
new file mode 100644
index 0000000..50b9f08
--- /dev/null
+++ b/src/backend/opengl/PipelineGL.cpp
@@ -0,0 +1,213 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "PipelineGL.h"
+
+#include "OpenGLBackend.h"
+#include "PipelineLayoutGL.h"
+#include "ShaderModuleGL.h"
+
+#include <iostream>
+#include <set>
+
+namespace backend {
+namespace opengl {
+
+ namespace {
+
+ GLenum GLShaderType(nxt::ShaderStage stage) {
+ switch (stage) {
+ case nxt::ShaderStage::Vertex:
+ return GL_VERTEX_SHADER;
+ case nxt::ShaderStage::Fragment:
+ return GL_FRAGMENT_SHADER;
+ case nxt::ShaderStage::Compute:
+ return GL_COMPUTE_SHADER;
+ }
+ }
+
+ }
+
+ Pipeline::Pipeline(Device* device, PipelineBuilder* builder) : PipelineBase(builder), device(device) {
+ auto CreateShader = [](GLenum type, const char* source) -> GLuint {
+ GLuint shader = glCreateShader(type);
+ glShaderSource(shader, 1, &source, nullptr);
+ glCompileShader(shader);
+
+ GLint compileStatus = GL_FALSE;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
+ if (compileStatus == GL_FALSE) {
+ GLint infoLogLength = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+ if (infoLogLength > 1) {
+ std::vector<char> buffer(infoLogLength);
+ glGetShaderInfoLog(shader, infoLogLength, nullptr, &buffer[0]);
+ std::cout << source << std::endl;
+ std::cout << "Program compilation failed:\n";
+ std::cout << buffer.data() << std::endl;
+ }
+ }
+ return shader;
+ };
+
+ auto FillPushConstants = [](const ShaderModule* module, GLPushConstantInfo* info, GLuint program) {
+ const auto& moduleInfo = module->GetPushConstants();
+ for (uint32_t i = 0; i < moduleInfo.names.size(); i++) {
+ (*info)[i] = -1;
+
+ unsigned int size = moduleInfo.sizes[i];
+ if (size == 0) {
+ continue;
+ }
+
+ GLint location = glGetUniformLocation(program, moduleInfo.names[i].c_str());
+ if (location == -1) {
+ continue;
+ }
+
+ for (uint32_t offset = 0; offset < size; offset++) {
+ (*info)[i + offset] = location + offset;
+ }
+ i += size - 1;
+ }
+ };
+
+ program = glCreateProgram();
+
+ for (auto stage : IterateStages(GetStageMask())) {
+ const ShaderModule* module = ToBackend(builder->GetStageInfo(stage).module.Get());
+
+ GLuint shader = CreateShader(GLShaderType(stage), module->GetSource());
+ glAttachShader(program, shader);
+ }
+
+ glLinkProgram(program);
+
+ GLint linkStatus = GL_FALSE;
+ glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+ if (linkStatus == GL_FALSE) {
+ GLint infoLogLength = 0;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+ if (infoLogLength > 1) {
+ std::vector<char> buffer(infoLogLength);
+ glGetProgramInfoLog(program, infoLogLength, nullptr, &buffer[0]);
+ std::cout << "Program link failed:\n";
+ std::cout << buffer.data() << std::endl;
+ }
+ }
+
+ for (auto stage : IterateStages(GetStageMask())) {
+ const ShaderModule* module = ToBackend(builder->GetStageInfo(stage).module.Get());
+ FillPushConstants(module, &glPushConstants[stage], program);
+ }
+
+ glUseProgram(program);
+
+ // The uniforms are part of the program state so we can pre-bind buffer units, texture units etc.
+ const auto& layout = ToBackend(GetLayout());
+ const auto& indices = layout->GetBindingIndexInfo();
+
+ for (uint32_t group = 0; group < kMaxBindGroups; ++group) {
+ const auto& groupInfo = layout->GetBindGroupLayout(group)->GetBindingInfo();
+
+ for (uint32_t binding = 0; binding < kMaxBindingsPerGroup; ++binding) {
+ if (!groupInfo.mask[binding]) {
+ continue;
+ }
+
+ std::string name = GetBindingName(group, binding);
+ switch (groupInfo.types[binding]) {
+ case nxt::BindingType::UniformBuffer:
+ {
+ GLint location = glGetUniformBlockIndex(program, name.c_str());
+ glUniformBlockBinding(program, location, indices[group][binding]);
+ }
+ break;
+
+ case nxt::BindingType::StorageBuffer:
+ {
+ GLuint location = glGetProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, name.c_str());
+ glShaderStorageBlockBinding(program, location, indices[group][binding]);
+ }
+ break;
+
+ case nxt::BindingType::Sampler:
+ case nxt::BindingType::SampledTexture:
+ // These binding types are handled in the separate sampler and texture emulation
+ break;
+
+ }
+ }
+ }
+
+ // Compute links between stages for combined samplers, then bind them to texture units
+ {
+ std::set<CombinedSampler> combinedSamplersSet;
+ for (auto stage : IterateStages(GetStageMask())) {
+ const auto& module = ToBackend(builder->GetStageInfo(stage).module);
+
+ for (const auto& combined : module->GetCombinedSamplerInfo()) {
+ combinedSamplersSet.insert(combined);
+ }
+ }
+
+ unitsForSamplers.resize(layout->GetNumSamplers());
+ unitsForTextures.resize(layout->GetNumSampledTextures());
+
+ GLuint textureUnit = layout->GetTextureUnitsUsed();
+ for (const auto& combined : combinedSamplersSet) {
+ std::string name = combined.GetName();
+ GLint location = glGetUniformLocation(program, name.c_str());
+ glUniform1i(location, textureUnit);
+
+ GLuint samplerIndex = indices[combined.samplerLocation.group][combined.samplerLocation.binding];
+ unitsForSamplers[samplerIndex].push_back(textureUnit);
+
+ GLuint textureIndex = indices[combined.textureLocation.group][combined.textureLocation.binding];
+ unitsForTextures[textureIndex].push_back(textureUnit);
+
+ textureUnit ++;
+ }
+ }
+ }
+
+ const Pipeline::GLPushConstantInfo& Pipeline::GetGLPushConstants(nxt::ShaderStage stage) const {
+ return glPushConstants[stage];
+ }
+
+ const std::vector<GLuint>& Pipeline::GetTextureUnitsForSampler(GLuint index) const {
+ ASSERT(index >= 0 && index < unitsForSamplers.size());
+ return unitsForSamplers[index];
+ }
+
+ const std::vector<GLuint>& Pipeline::GetTextureUnitsForTexture(GLuint index) const {
+ ASSERT(index >= 0 && index < unitsForSamplers.size());
+ return unitsForTextures[index];
+ }
+
+ GLuint Pipeline::GetProgramHandle() const {
+ return program;
+ }
+
+ void Pipeline::ApplyNow() {
+ glUseProgram(program);
+
+ auto inputState = ToBackend(GetInputState());
+ glBindVertexArray(inputState->GetVAO());
+ }
+
+}
+}
diff --git a/src/backend/opengl/PipelineGL.h b/src/backend/opengl/PipelineGL.h
new file mode 100644
index 0000000..ec0c0c5
--- /dev/null
+++ b/src/backend/opengl/PipelineGL.h
@@ -0,0 +1,55 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_PIPELINEGL_H_
+#define BACKEND_OPENGL_PIPELINEGL_H_
+
+#include "common/Pipeline.h"
+
+#include "glad/glad.h"
+
+#include <vector>
+
+namespace backend {
+namespace opengl {
+
+ class Device;
+ class ShaderModule;
+
+ class Pipeline : public PipelineBase {
+ public:
+ Pipeline(Device* device, PipelineBuilder* builder);
+
+ using GLPushConstantInfo = std::array<GLint, kMaxPushConstants>;
+ using BindingLocations = std::array<std::array<GLint, kMaxBindingsPerGroup>, kMaxBindGroups>;
+
+ const GLPushConstantInfo& GetGLPushConstants(nxt::ShaderStage stage) const;
+ const std::vector<GLuint>& GetTextureUnitsForSampler(GLuint index) const;
+ const std::vector<GLuint>& GetTextureUnitsForTexture(GLuint index) const;
+ GLuint GetProgramHandle() const;
+
+ void ApplyNow();
+
+ private:
+ GLuint program;
+ PerStage<GLPushConstantInfo> glPushConstants;
+ std::vector<std::vector<GLuint>> unitsForSamplers;
+ std::vector<std::vector<GLuint>> unitsForTextures;
+ Device* device;
+ };
+
+}
+}
+
+#endif // BACKEND_OPENGL_PIPELINEGL_H_
diff --git a/src/backend/opengl/PipelineLayoutGL.cpp b/src/backend/opengl/PipelineLayoutGL.cpp
new file mode 100644
index 0000000..1f3cb5e
--- /dev/null
+++ b/src/backend/opengl/PipelineLayoutGL.cpp
@@ -0,0 +1,80 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "PipelineLayoutGL.h"
+
+#include "OpenGLBackend.h"
+
+namespace backend {
+namespace opengl {
+
+ PipelineLayout::PipelineLayout(Device* device, PipelineLayoutBuilder* builder)
+ : PipelineLayoutBase(builder), device(device) {
+ GLuint uboIndex = 0;
+ GLuint samplerIndex = 0;
+ GLuint sampledTextureIndex = 0;
+ GLuint ssboIndex = 0;
+
+ for (size_t group = 0; group < kMaxBindGroups; ++group) {
+ const auto& groupInfo = GetBindGroupLayout(group)->GetBindingInfo();
+
+ for (size_t binding = 0; binding < kMaxBindingsPerGroup; ++binding) {
+ if (!groupInfo.mask[binding]) {
+ continue;
+ }
+
+ switch (groupInfo.types[binding]) {
+ case nxt::BindingType::UniformBuffer:
+ indexInfo[group][binding] = uboIndex;
+ uboIndex ++;
+ break;
+ case nxt::BindingType::Sampler:
+ indexInfo[group][binding] = samplerIndex;
+ samplerIndex ++;
+ break;
+ case nxt::BindingType::SampledTexture:
+ indexInfo[group][binding] = sampledTextureIndex;
+ sampledTextureIndex ++;
+ break;
+
+ case nxt::BindingType::StorageBuffer:
+ indexInfo[group][binding] = ssboIndex;
+ ssboIndex ++;
+ break;
+ }
+ }
+ }
+
+ numSamplers = samplerIndex;
+ numSampledTextures = sampledTextureIndex;
+ }
+
+ const PipelineLayout::BindingIndexInfo& PipelineLayout::GetBindingIndexInfo() const {
+ return indexInfo;
+ }
+
+ GLuint PipelineLayout::GetTextureUnitsUsed() const {
+ return 0;
+ }
+
+ size_t PipelineLayout::GetNumSamplers() const {
+ return numSamplers;
+ }
+
+ size_t PipelineLayout::GetNumSampledTextures() const {
+ return numSampledTextures;
+ }
+
+}
+}
diff --git a/src/backend/opengl/PipelineLayoutGL.h b/src/backend/opengl/PipelineLayoutGL.h
new file mode 100644
index 0000000..fc35099
--- /dev/null
+++ b/src/backend/opengl/PipelineLayoutGL.h
@@ -0,0 +1,48 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_PIPELINELAYOUTGL_H_
+#define BACKEND_OPENGL_PIPELINELAYOUTGL_H_
+
+#include "common/PipelineLayout.h"
+
+#include "glad/glad.h"
+
+namespace backend {
+namespace opengl {
+
+ class Device;
+
+ class PipelineLayout : public PipelineLayoutBase {
+ public:
+ PipelineLayout(Device* device, PipelineLayoutBuilder* builder);
+
+ using BindingIndexInfo = std::array<std::array<GLuint, kMaxBindingsPerGroup>, kMaxBindGroups>;
+ const BindingIndexInfo& GetBindingIndexInfo() const;
+
+ GLuint GetTextureUnitsUsed() const;
+ size_t GetNumSamplers() const;
+ size_t GetNumSampledTextures() const;
+
+ private:
+ Device* device;
+ BindingIndexInfo indexInfo;
+ size_t numSamplers;
+ size_t numSampledTextures;
+ };
+
+}
+}
+
+#endif // BACKEND_OPENGL_PIPELINELAYOUTGL_H_
diff --git a/src/backend/opengl/SamplerGL.cpp b/src/backend/opengl/SamplerGL.cpp
new file mode 100644
index 0000000..73d418b
--- /dev/null
+++ b/src/backend/opengl/SamplerGL.cpp
@@ -0,0 +1,62 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "SamplerGL.h"
+
+namespace backend {
+namespace opengl {
+
+ namespace {
+ GLenum MagFilterMode(nxt::FilterMode filter) {
+ switch (filter) {
+ case nxt::FilterMode::Nearest:
+ return GL_NEAREST;
+ case nxt::FilterMode::Linear:
+ return GL_LINEAR;
+ }
+ }
+
+ GLenum MinFilterMode(nxt::FilterMode minFilter, nxt::FilterMode mipMapFilter) {
+ switch (minFilter) {
+ case nxt::FilterMode::Nearest:
+ switch (mipMapFilter) {
+ case nxt::FilterMode::Nearest:
+ return GL_NEAREST_MIPMAP_NEAREST;
+ case nxt::FilterMode::Linear:
+ return GL_NEAREST_MIPMAP_LINEAR;
+ }
+ case nxt::FilterMode::Linear:
+ switch (mipMapFilter) {
+ case nxt::FilterMode::Nearest:
+ return GL_LINEAR_MIPMAP_NEAREST;
+ case nxt::FilterMode::Linear:
+ return GL_LINEAR_MIPMAP_LINEAR;
+ }
+ }
+ }
+ }
+
+ Sampler::Sampler(Device* device, SamplerBuilder* builder)
+ : SamplerBase(builder), device(device) {
+ glGenSamplers(1, &handle);
+ glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, MagFilterMode(builder->GetMagFilter()));
+ glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, MinFilterMode(builder->GetMinFilter(), builder->GetMipMapFilter()));
+ }
+
+ GLuint Sampler::GetHandle() const {
+ return handle;
+ }
+
+}
+}
diff --git a/src/backend/opengl/SamplerGL.h b/src/backend/opengl/SamplerGL.h
new file mode 100644
index 0000000..c238e8a
--- /dev/null
+++ b/src/backend/opengl/SamplerGL.h
@@ -0,0 +1,41 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_SAMPLERGL_H_
+#define BACKEND_OPENGL_SAMPLERGL_H_
+
+#include "common/Sampler.h"
+
+#include "glad/glad.h"
+
+namespace backend {
+namespace opengl {
+
+ class Device;
+
+ class Sampler : public SamplerBase {
+ public:
+ Sampler(Device* device, SamplerBuilder* builder);
+
+ GLuint GetHandle() const;
+
+ private:
+ Device* device;
+ GLuint handle;
+ };
+
+}
+}
+
+#endif // BACKEND_OPENGL_SAMPLERGL_H_
diff --git a/src/backend/opengl/ShaderModuleGL.cpp b/src/backend/opengl/ShaderModuleGL.cpp
new file mode 100644
index 0000000..afb5706
--- /dev/null
+++ b/src/backend/opengl/ShaderModuleGL.cpp
@@ -0,0 +1,105 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ShaderModuleGL.h"
+
+#include <spirv-cross/spirv_glsl.hpp>
+
+#include <sstream>
+
+namespace backend {
+namespace opengl {
+
+ std::string GetBindingName(uint32_t group, uint32_t binding) {
+ std::ostringstream o;
+ o << "nxt_binding_" << group << "_" << binding;
+ return o.str();
+ }
+
+ bool operator < (const BindingLocation& a, const BindingLocation& b) {
+ return std::tie(a.group, a.binding) < std::tie(b.group, b.binding);
+ }
+
+ bool operator < (const CombinedSampler& a, const CombinedSampler& b) {
+ return std::tie(a.samplerLocation, a.textureLocation) < std::tie(b.samplerLocation, b.textureLocation);
+ }
+
+ std::string CombinedSampler::GetName() const {
+ std::ostringstream o;
+ o << "nxt_combined";
+ o << "_" << samplerLocation.group << "_" << samplerLocation.binding;
+ o << "_with_" << textureLocation.group << "_" << textureLocation.binding;
+ return o.str();
+ }
+
+ ShaderModule::ShaderModule(Device* device, ShaderModuleBuilder* builder)
+ : ShaderModuleBase(builder), device(device) {
+ spirv_cross::CompilerGLSL compiler(builder->AcquireSpirv());
+ spirv_cross::CompilerGLSL::Options options;
+
+ // TODO(cwallez@chromium.org): discover the backing context version and use that.
+#if defined(__APPLE__)
+ options.version = 410;
+#else
+ options.version = 450;
+#endif
+ compiler.set_options(options);
+
+ ExtractSpirvInfo(compiler);
+
+ const auto& bindingInfo = GetBindingInfo();
+
+ // Extract bindings names so that it can be used to get its location in program.
+ // Now translate the separate sampler / textures into combined ones and store their info.
+ // We need to do this before removing the set and binding decorations.
+ compiler.build_combined_image_samplers();
+
+ for (const auto& combined : compiler.get_combined_image_samplers()) {
+ combinedInfo.emplace_back();
+
+ auto& info = combinedInfo.back();
+ info.samplerLocation.group = compiler.get_decoration(combined.sampler_id, spv::DecorationDescriptorSet);
+ info.samplerLocation.binding = compiler.get_decoration(combined.sampler_id, spv::DecorationBinding);
+ info.textureLocation.group = compiler.get_decoration(combined.image_id, spv::DecorationDescriptorSet);
+ info.textureLocation.binding = compiler.get_decoration(combined.image_id, spv::DecorationBinding);
+ compiler.set_name(combined.combined_id, info.GetName());
+ }
+
+ // Change binding names to be "nxt_binding_<group>_<binding>".
+ // Also unsets the SPIRV "Binding" decoration as it outputs "layout(binding=)" which
+ // isn't supported on OSX's OpenGL.
+ for (uint32_t group = 0; group < kMaxBindGroups; ++group) {
+ for (uint32_t binding = 0; binding < kMaxBindingsPerGroup; ++binding) {
+ const auto& info = bindingInfo[group][binding];
+ if (info.used) {
+ compiler.set_name(info.base_type_id, GetBindingName(group, binding));
+ compiler.unset_decoration(info.id, spv::DecorationBinding);
+ compiler.unset_decoration(info.id, spv::DecorationDescriptorSet);
+ }
+ }
+ }
+
+ glslSource = compiler.compile();
+ }
+
+ const char* ShaderModule::GetSource() const {
+ return reinterpret_cast<const char*>(glslSource.data());
+ }
+
+ const ShaderModule::CombinedSamplerInfo& ShaderModule::GetCombinedSamplerInfo() const {
+ return combinedInfo;
+ }
+
+}
+}
diff --git a/src/backend/opengl/ShaderModuleGL.h b/src/backend/opengl/ShaderModuleGL.h
new file mode 100644
index 0000000..d985527
--- /dev/null
+++ b/src/backend/opengl/ShaderModuleGL.h
@@ -0,0 +1,60 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_SHADERMODULEGL_H_
+#define BACKEND_OPENGL_SHADERMODULEGL_H_
+
+#include "common/ShaderModule.h"
+
+#include "glad/glad.h"
+
+namespace backend {
+namespace opengl {
+
+ class Device;
+
+ std::string GetBindingName(uint32_t group, uint32_t binding);
+
+ struct BindingLocation {
+ uint32_t group;
+ uint32_t binding;
+ };
+ bool operator < (const BindingLocation& a, const BindingLocation& b);
+
+ struct CombinedSampler {
+ BindingLocation samplerLocation;
+ BindingLocation textureLocation;
+ std::string GetName() const;
+ };
+ bool operator < (const CombinedSampler& a, const CombinedSampler& b);
+
+ class ShaderModule : public ShaderModuleBase {
+ public:
+ ShaderModule(Device* device, ShaderModuleBuilder* builder);
+
+ using CombinedSamplerInfo = std::vector<CombinedSampler>;
+
+ const char* GetSource() const;
+ const CombinedSamplerInfo& GetCombinedSamplerInfo() const;
+
+ private:
+ Device* device;
+ CombinedSamplerInfo combinedInfo;
+ std::string glslSource;
+ };
+
+}
+}
+
+#endif // BACKEND_OPENGL_SHADERMODULEGL_H_
diff --git a/src/backend/opengl/TextureGL.cpp b/src/backend/opengl/TextureGL.cpp
new file mode 100644
index 0000000..fc1a733
--- /dev/null
+++ b/src/backend/opengl/TextureGL.cpp
@@ -0,0 +1,86 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "TextureGL.h"
+
+#include <algorithm>
+#include <vector>
+
+namespace backend {
+namespace opengl {
+
+ namespace {
+
+ GLenum TargetForDimension(nxt::TextureDimension dimension) {
+ switch (dimension) {
+ case nxt::TextureDimension::e2D:
+ return GL_TEXTURE_2D;
+ }
+ }
+
+ TextureFormatInfo GetGLFormatInfo(nxt::TextureFormat format) {
+ switch (format) {
+ case nxt::TextureFormat::R8G8B8A8Unorm:
+ return {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
+ }
+ }
+
+ }
+
+ // Texture
+
+ Texture::Texture(Device* device, TextureBuilder* builder)
+ : TextureBase(builder), device(device) {
+ target = TargetForDimension(GetDimension());
+
+ uint32_t width = GetWidth();
+ uint32_t height = GetHeight();
+ uint32_t levels = GetNumMipLevels();
+
+ auto formatInfo = GetGLFormatInfo(GetFormat());
+
+ glGenTextures(1, &handle);
+ glBindTexture(target, handle);
+
+ for (uint32_t i = 0; i < levels; ++i) {
+ glTexImage2D(target, i, formatInfo.internalFormat, width, height, 0, formatInfo.format, formatInfo.type, nullptr);
+ width = std::max(uint32_t(1), width / 2);
+ height = std::max(uint32_t(1), height / 2);
+ }
+
+ // The texture is not complete if it uses mipmapping and not all levels up to
+ // MAX_LEVEL have been defined.
+ glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, levels - 1);
+ }
+
+ GLuint Texture::GetHandle() const {
+ return handle;
+ }
+
+ GLenum Texture::GetGLTarget() const {
+ return target;
+ }
+
+ TextureFormatInfo Texture::GetGLFormat() const {
+ return GetGLFormatInfo(GetFormat());
+ }
+
+ // TextureView
+
+ TextureView::TextureView(Device* device, TextureViewBuilder* builder)
+ : TextureViewBase(builder), device(device) {
+ }
+
+}
+}
diff --git a/src/backend/opengl/TextureGL.h b/src/backend/opengl/TextureGL.h
new file mode 100644
index 0000000..6bdf534
--- /dev/null
+++ b/src/backend/opengl/TextureGL.h
@@ -0,0 +1,59 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_TEXTUREGL_H_
+#define BACKEND_OPENGL_TEXTUREGL_H_
+
+#include "common/Texture.h"
+
+#include "glad/glad.h"
+
+namespace backend {
+namespace opengl {
+
+ class Device;
+
+ struct TextureFormatInfo {
+ GLenum internalFormat;
+ GLenum format;
+ GLenum type;
+ };
+
+ class Texture : public TextureBase {
+ public:
+ Texture(Device* device, TextureBuilder* builder);
+
+ GLuint GetHandle() const;
+ GLenum GetGLTarget() const;
+ TextureFormatInfo GetGLFormat() const;
+
+ private:
+ Device* device;
+ GLuint handle;
+ GLenum target;
+ };
+
+ class TextureView : public TextureViewBase {
+ public:
+ TextureView(Device* device, TextureViewBuilder* builder);
+
+ private:
+ Device* device;
+ };
+
+
+}
+}
+
+#endif // BACKEND_OPENGL_TEXTUREGL_H_
diff --git a/src/backend/tests/BitSetIteratorTests.cpp b/src/backend/tests/BitSetIteratorTests.cpp
new file mode 100644
index 0000000..b1ac2e7
--- /dev/null
+++ b/src/backend/tests/BitSetIteratorTests.cpp
@@ -0,0 +1,85 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "common/BitSetIterator.h"
+
+// This is ANGLE's BitSetIterator_unittests.cpp file.
+
+using namespace backend;
+
+class BitSetIteratorTest : public testing::Test {
+ protected:
+ std::bitset<40> mStateBits;
+};
+
+// Simple iterator test.
+TEST_F(BitSetIteratorTest, Iterator) {
+ std::set<unsigned long> originalValues;
+ originalValues.insert(2);
+ originalValues.insert(6);
+ originalValues.insert(8);
+ originalValues.insert(35);
+
+ for (unsigned long value : originalValues) {
+ mStateBits.set(value);
+ }
+
+ std::set<unsigned long> readValues;
+ for (unsigned long bit : IterateBitSet(mStateBits)) {
+ EXPECT_EQ(1u, originalValues.count(bit));
+ EXPECT_EQ(0u, readValues.count(bit));
+ readValues.insert(bit);
+ }
+
+ EXPECT_EQ(originalValues.size(), readValues.size());
+}
+
+// Test an empty iterator.
+TEST_F(BitSetIteratorTest, EmptySet) {
+ // We don't use the FAIL gtest macro here since it returns immediately,
+ // causing an unreachable code warning in MSVS
+ bool sawBit = false;
+ for (unsigned long bit : IterateBitSet(mStateBits)) {
+ sawBit = true;
+ }
+ EXPECT_FALSE(sawBit);
+}
+
+// Test iterating a result of combining two bitsets.
+TEST_F(BitSetIteratorTest, NonLValueBitset) {
+ std::bitset<40> otherBits;
+
+ mStateBits.set(1);
+ mStateBits.set(2);
+ mStateBits.set(3);
+ mStateBits.set(4);
+
+ otherBits.set(0);
+ otherBits.set(1);
+ otherBits.set(3);
+ otherBits.set(5);
+
+ std::set<unsigned long> seenBits;
+
+ for (unsigned long bit : IterateBitSet(mStateBits & otherBits)) {
+ EXPECT_EQ(0u, seenBits.count(bit));
+ seenBits.insert(bit);
+ EXPECT_TRUE(mStateBits[bit]);
+ EXPECT_TRUE(otherBits[bit]);
+ }
+
+ EXPECT_EQ((mStateBits & otherBits).count(), seenBits.size());
+}
diff --git a/src/backend/tests/CommandAllocatorTests.cpp b/src/backend/tests/CommandAllocatorTests.cpp
new file mode 100644
index 0000000..9ebb1d7
--- /dev/null
+++ b/src/backend/tests/CommandAllocatorTests.cpp
@@ -0,0 +1,361 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "common/CommandAllocator.h"
+
+using namespace backend;
+
+// Definition of the command types used in the tests
+enum class CommandType {
+ Draw,
+ Pipeline,
+ PushConstants,
+ Big,
+ Small,
+};
+
+struct CommandDraw {
+ uint32_t first;
+ uint32_t count;
+};
+
+struct CommandPipeline {
+ uint64_t pipeline;
+ uint32_t attachmentPoint;
+};
+
+struct CommandPushConstants {
+ uint8_t size;
+ uint8_t offset;
+};
+
+constexpr int kBigBufferSize = 65536;
+
+struct CommandBig {
+ uint32_t buffer[kBigBufferSize];
+};
+
+struct CommandSmall {
+ uint16_t data;
+};
+
+// Test allocating nothing works
+TEST(CommandAllocator, DoNothingAllocator) {
+ CommandAllocator allocator;
+}
+
+// Test iterating over nothing works
+TEST(CommandAllocator, DoNothingAllocatorWithIterator) {
+ CommandAllocator allocator;
+ CommandIterator iterator(std::move(allocator));
+ iterator.DataWasDestroyed();
+}
+
+// Test basic usage of allocator + iterator
+TEST(CommandAllocator, Basic) {
+ CommandAllocator allocator;
+
+ uint64_t myPipeline = 0xDEADBEEFBEEFDEAD;
+ uint32_t myAttachmentPoint = 2;
+ uint32_t myFirst = 42;
+ uint32_t myCount = 16;
+
+ {
+ CommandPipeline* pipeline = allocator.Allocate<CommandPipeline>(CommandType::Pipeline);
+ pipeline->pipeline = myPipeline;
+ pipeline->attachmentPoint = myAttachmentPoint;
+
+ CommandDraw* draw = allocator.Allocate<CommandDraw>(CommandType::Draw);
+ draw->first = myFirst;
+ draw->count = myCount;
+ }
+
+ {
+ CommandIterator iterator(std::move(allocator));
+ CommandType type;
+
+ bool hasNext = iterator.NextCommandId(&type);
+ ASSERT_TRUE(hasNext);
+ ASSERT_EQ(type, CommandType::Pipeline);
+
+ CommandPipeline* pipeline = iterator.NextCommand<CommandPipeline>();
+ ASSERT_EQ(pipeline->pipeline, myPipeline);
+ ASSERT_EQ(pipeline->attachmentPoint, myAttachmentPoint);
+
+ hasNext = iterator.NextCommandId(&type);
+ ASSERT_TRUE(hasNext);
+ ASSERT_EQ(type, CommandType::Draw);
+
+ CommandDraw* draw = iterator.NextCommand<CommandDraw>();
+ ASSERT_EQ(draw->first, myFirst);
+ ASSERT_EQ(draw->count, myCount);
+
+ hasNext = iterator.NextCommandId(&type);
+ ASSERT_FALSE(hasNext);
+
+ iterator.DataWasDestroyed();
+ }
+}
+
+// Test basic usage of allocator + iterator with data
+TEST(CommandAllocator, BasicWithData) {
+ CommandAllocator allocator;
+
+ uint8_t mySize = 8;
+ uint8_t myOffset = 3;
+ uint32_t myValues[5] = {6, 42, 0xFFFFFFFF, 0, 54};
+
+ {
+ CommandPushConstants* pushConstants = allocator.Allocate<CommandPushConstants>(CommandType::PushConstants);
+ pushConstants->size = mySize;
+ pushConstants->offset = myOffset;
+
+ uint32_t* values = allocator.AllocateData<uint32_t>(5);
+ for (size_t i = 0; i < 5; i++) {
+ values[i] = myValues[i];
+ }
+ }
+
+ {
+ CommandIterator iterator(std::move(allocator));
+ CommandType type;
+
+ bool hasNext = iterator.NextCommandId(&type);
+ ASSERT_TRUE(hasNext);
+ ASSERT_EQ(type, CommandType::PushConstants);
+
+ CommandPushConstants* pushConstants = iterator.NextCommand<CommandPushConstants>();
+ ASSERT_EQ(pushConstants->size, mySize);
+ ASSERT_EQ(pushConstants->offset, myOffset);
+
+ uint32_t* values = iterator.NextData<uint32_t>(5);
+ for (size_t i = 0; i < 5; i++) {
+ ASSERT_EQ(values[i], myValues[i]);
+ }
+
+ hasNext = iterator.NextCommandId(&type);
+ ASSERT_FALSE(hasNext);
+
+ iterator.DataWasDestroyed();
+ }
+}
+
+// Test basic iterating several times
+TEST(CommandAllocator, MultipleIterations) {
+ CommandAllocator allocator;
+
+ uint32_t myFirst = 42;
+ uint32_t myCount = 16;
+
+ CommandDraw* draw = allocator.Allocate<CommandDraw>(CommandType::Draw);
+ draw->first = myFirst;
+ draw->count = myCount;
+
+ {
+ CommandIterator iterator(std::move(allocator));
+ CommandType type;
+
+ // First iteration
+ bool hasNext = iterator.NextCommandId(&type);
+ ASSERT_TRUE(hasNext);
+ ASSERT_EQ(type, CommandType::Draw);
+
+ CommandDraw* draw = iterator.NextCommand<CommandDraw>();
+ ASSERT_EQ(draw->first, myFirst);
+ ASSERT_EQ(draw->count, myCount);
+
+ hasNext = iterator.NextCommandId(&type);
+ ASSERT_FALSE(hasNext);
+
+ // Second iteration
+ hasNext = iterator.NextCommandId(&type);
+ ASSERT_TRUE(hasNext);
+ ASSERT_EQ(type, CommandType::Draw);
+
+ draw = iterator.NextCommand<CommandDraw>();
+ ASSERT_EQ(draw->first, myFirst);
+ ASSERT_EQ(draw->count, myCount);
+
+ hasNext = iterator.NextCommandId(&type);
+ ASSERT_FALSE(hasNext);
+
+ iterator.DataWasDestroyed();
+ }
+}
+// Test large commands work
+TEST(CommandAllocator, LargeCommands) {
+ CommandAllocator allocator;
+
+ const int kCommandCount = 5;
+
+ int count = 0;
+ for (int i = 0; i < kCommandCount; i++) {
+ CommandBig* big = allocator.Allocate<CommandBig>(CommandType::Big);
+ for (int j = 0; j < kBigBufferSize; j++) {
+ big->buffer[j] = count ++;
+ }
+ }
+
+ CommandIterator iterator(std::move(allocator));
+ CommandType type;
+ count = 0;
+ int numCommands = 0;
+ while (iterator.NextCommandId(&type)) {
+ ASSERT_EQ(type, CommandType::Big);
+
+ CommandBig* big = iterator.NextCommand<CommandBig>();
+ for (int i = 0; i < kBigBufferSize; i++) {
+ ASSERT_EQ(big->buffer[i], count);
+ count ++;
+ }
+ numCommands ++;
+ }
+ ASSERT_EQ(numCommands, kCommandCount);
+
+ iterator.DataWasDestroyed();
+}
+
+// Test many small commands work
+TEST(CommandAllocator, ManySmallCommands) {
+ CommandAllocator allocator;
+
+ // Stay under max representable uint16_t
+ const int kCommandCount = 50000;
+
+ int count = 0;
+ for (int i = 0; i < kCommandCount; i++) {
+ CommandSmall* small = allocator.Allocate<CommandSmall>(CommandType::Small);
+ small->data = count ++;
+ }
+
+ CommandIterator iterator(std::move(allocator));
+ CommandType type;
+ count = 0;
+ int numCommands = 0;
+ while (iterator.NextCommandId(&type)) {
+ ASSERT_EQ(type, CommandType::Small);
+
+ CommandSmall* small = iterator.NextCommand<CommandSmall>();
+ ASSERT_EQ(small->data, count);
+ count ++;
+ numCommands ++;
+ }
+ ASSERT_EQ(numCommands, kCommandCount);
+
+ iterator.DataWasDestroyed();
+}
+
+// ________
+// / \
+// | POUIC! |
+// \_ ______/
+// v
+// ()_()
+// (O.o)
+// (> <)o
+
+// Test usage of iterator.Reset
+TEST(CommandAllocator, IteratorReset) {
+ CommandAllocator allocator;
+
+ uint64_t myPipeline = 0xDEADBEEFBEEFDEAD;
+ uint32_t myAttachmentPoint = 2;
+ uint32_t myFirst = 42;
+ uint32_t myCount = 16;
+
+ {
+ CommandPipeline* pipeline = allocator.Allocate<CommandPipeline>(CommandType::Pipeline);
+ pipeline->pipeline = myPipeline;
+ pipeline->attachmentPoint = myAttachmentPoint;
+
+ CommandDraw* draw = allocator.Allocate<CommandDraw>(CommandType::Draw);
+ draw->first = myFirst;
+ draw->count = myCount;
+ }
+
+ {
+ CommandIterator iterator(std::move(allocator));
+ CommandType type;
+
+ bool hasNext = iterator.NextCommandId(&type);
+ ASSERT_TRUE(hasNext);
+ ASSERT_EQ(type, CommandType::Pipeline);
+
+ CommandPipeline* pipeline = iterator.NextCommand<CommandPipeline>();
+ ASSERT_EQ(pipeline->pipeline, myPipeline);
+ ASSERT_EQ(pipeline->attachmentPoint, myAttachmentPoint);
+
+ iterator.Reset();
+
+ hasNext = iterator.NextCommandId(&type);
+ ASSERT_TRUE(hasNext);
+ ASSERT_EQ(type, CommandType::Pipeline);
+
+ pipeline = iterator.NextCommand<CommandPipeline>();
+ ASSERT_EQ(pipeline->pipeline, myPipeline);
+ ASSERT_EQ(pipeline->attachmentPoint, myAttachmentPoint);
+
+ hasNext = iterator.NextCommandId(&type);
+ ASSERT_TRUE(hasNext);
+ ASSERT_EQ(type, CommandType::Draw);
+
+ CommandDraw* draw = iterator.NextCommand<CommandDraw>();
+ ASSERT_EQ(draw->first, myFirst);
+ ASSERT_EQ(draw->count, myCount);
+
+ hasNext = iterator.NextCommandId(&type);
+ ASSERT_FALSE(hasNext);
+
+ iterator.DataWasDestroyed();
+ }
+}
+
+// Test iterating empty iterators
+TEST(CommandAllocator, EmptyIterator) {
+ {
+ CommandAllocator allocator;
+ CommandIterator iterator(std::move(allocator));
+
+ CommandType type;
+ bool hasNext = iterator.NextCommandId(&type);
+ ASSERT_FALSE(hasNext);
+
+ iterator.DataWasDestroyed();
+ }
+ {
+ CommandAllocator allocator;
+ CommandIterator iterator1(std::move(allocator));
+ CommandIterator iterator2(std::move(iterator1));
+
+ CommandType type;
+ bool hasNext = iterator2.NextCommandId(&type);
+ ASSERT_FALSE(hasNext);
+
+ iterator1.DataWasDestroyed();
+ iterator2.DataWasDestroyed();
+ }
+ {
+ CommandIterator iterator1;
+ CommandIterator iterator2(std::move(iterator1));
+
+ CommandType type;
+ bool hasNext = iterator2.NextCommandId(&type);
+ ASSERT_FALSE(hasNext);
+
+ iterator1.DataWasDestroyed();
+ iterator2.DataWasDestroyed();
+ }
+}
diff --git a/src/backend/tests/MathTests.cpp b/src/backend/tests/MathTests.cpp
new file mode 100644
index 0000000..3264a8f
--- /dev/null
+++ b/src/backend/tests/MathTests.cpp
@@ -0,0 +1,85 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "common/Math.h"
+
+using namespace backend;
+
+// Tests for ScanForward
+TEST(Math, ScanForward) {
+ // Test extrema
+ ASSERT_EQ(ScanForward(1), 0);
+ ASSERT_EQ(ScanForward(0x8000000000000000), 63);
+
+ // Test with more than one bit set.
+ ASSERT_EQ(ScanForward(256), 8);
+ ASSERT_EQ(ScanForward(256 + 32), 5);
+ ASSERT_EQ(ScanForward(1024 + 256 + 32), 5);
+}
+
+// Tests for Log2
+TEST(Math, Log2) {
+ // Test extrema
+ ASSERT_EQ(Log2(1), 0);
+ ASSERT_EQ(Log2(0xFFFFFFFF), 31);
+
+ // Test boundary between two logs
+ ASSERT_EQ(Log2(0x80000000), 31);
+ ASSERT_EQ(Log2(0x7FFFFFFF), 30);
+
+ ASSERT_EQ(Log2(16), 4);
+ ASSERT_EQ(Log2(15), 3);
+}
+
+// Tests for IsPowerOfTwo
+TEST(Math, IsPowerOfTwo) {
+ ASSERT_TRUE(IsPowerOfTwo(1));
+ ASSERT_TRUE(IsPowerOfTwo(2));
+ ASSERT_FALSE(IsPowerOfTwo(3));
+
+ ASSERT_TRUE(IsPowerOfTwo(0x8000000));
+ ASSERT_FALSE(IsPowerOfTwo(0x8000400));
+}
+
+// Tests for Align
+TEST(Math, Align) {
+ constexpr size_t kTestAlignment = 8;
+
+ char buffer[kTestAlignment * 4];
+
+ for (size_t i = 0; i < 2 * kTestAlignment; ++i) {
+ char* unaligned = &buffer[i];
+ char* aligned = Align(unaligned, kTestAlignment);
+
+ ASSERT_GE(aligned - unaligned, 0);
+ ASSERT_LT(aligned - unaligned, kTestAlignment);
+ ASSERT_EQ(reinterpret_cast<intptr_t>(aligned) & (kTestAlignment -1), 0);
+ }
+}
+
+// Tests for IsAligned
+TEST(Math, IsAligned) {
+ constexpr size_t kTestAlignment = 8;
+
+ char buffer[kTestAlignment * 4];
+
+ for (size_t i = 0; i < 2 * kTestAlignment; ++i) {
+ char* unaligned = &buffer[i];
+ char* aligned = Align(unaligned, kTestAlignment);
+
+ ASSERT_EQ(IsAligned(unaligned, kTestAlignment), unaligned == aligned);
+ }
+}
diff --git a/src/backend/tests/PerStageTests.cpp b/src/backend/tests/PerStageTests.cpp
new file mode 100644
index 0000000..c9496bc
--- /dev/null
+++ b/src/backend/tests/PerStageTests.cpp
@@ -0,0 +1,89 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "common/PerStage.h"
+
+using namespace backend;
+
+// Tests for StageBit
+TEST(PerStage, StageBit) {
+ ASSERT_EQ(StageBit(nxt::ShaderStage::Vertex), nxt::ShaderStageBit::Vertex);
+ ASSERT_EQ(StageBit(nxt::ShaderStage::Fragment), nxt::ShaderStageBit::Fragment);
+ ASSERT_EQ(StageBit(nxt::ShaderStage::Compute), nxt::ShaderStageBit::Compute);
+}
+
+// Basic test for the PerStage container
+TEST(PerStage, PerStage) {
+ PerStage<int> data;
+
+ // Store data using nxt::ShaderStage
+ data[nxt::ShaderStage::Vertex] = 42;
+ data[nxt::ShaderStage::Fragment] = 3;
+ data[nxt::ShaderStage::Compute] = -1;
+
+ // Load it using nxt::ShaderStageBit
+ ASSERT_EQ(data[nxt::ShaderStageBit::Vertex], 42);
+ ASSERT_EQ(data[nxt::ShaderStageBit::Fragment], 3);
+ ASSERT_EQ(data[nxt::ShaderStageBit::Compute], -1);
+}
+
+// Test IterateStages with kAllStages
+TEST(PerStage, IterateAllStages) {
+ PerStage<int> counts;
+ counts[nxt::ShaderStage::Vertex] = 0;
+ counts[nxt::ShaderStage::Fragment] = 0;
+ counts[nxt::ShaderStage::Compute] = 0;
+
+ for (auto stage : IterateStages(kAllStages)) {
+ counts[stage] ++;
+ }
+
+ ASSERT_EQ(counts[nxt::ShaderStageBit::Vertex], 1);
+ ASSERT_EQ(counts[nxt::ShaderStageBit::Fragment], 1);
+ ASSERT_EQ(counts[nxt::ShaderStageBit::Compute], 1);
+}
+
+// Test IterateStages with one stage
+TEST(PerStage, IterateOneStage) {
+ PerStage<int> counts;
+ counts[nxt::ShaderStage::Vertex] = 0;
+ counts[nxt::ShaderStage::Fragment] = 0;
+ counts[nxt::ShaderStage::Compute] = 0;
+
+ for (auto stage : IterateStages(nxt::ShaderStageBit::Fragment)) {
+ counts[stage] ++;
+ }
+
+ ASSERT_EQ(counts[nxt::ShaderStageBit::Vertex], 0);
+ ASSERT_EQ(counts[nxt::ShaderStageBit::Fragment], 1);
+ ASSERT_EQ(counts[nxt::ShaderStageBit::Compute], 0);
+}
+
+// Test IterateStages with no stage
+TEST(PerStage, IterateNoStages) {
+ PerStage<int> counts;
+ counts[nxt::ShaderStage::Vertex] = 0;
+ counts[nxt::ShaderStage::Fragment] = 0;
+ counts[nxt::ShaderStage::Compute] = 0;
+
+ for (auto stage : IterateStages(nxt::ShaderStageBit::Fragment & nxt::ShaderStageBit::Vertex)) {
+ counts[stage] ++;
+ }
+
+ ASSERT_EQ(counts[nxt::ShaderStageBit::Vertex], 0);
+ ASSERT_EQ(counts[nxt::ShaderStageBit::Fragment], 0);
+ ASSERT_EQ(counts[nxt::ShaderStageBit::Compute], 0);
+}
diff --git a/src/backend/tests/RefCountedTests.cpp b/src/backend/tests/RefCountedTests.cpp
new file mode 100644
index 0000000..0136491
--- /dev/null
+++ b/src/backend/tests/RefCountedTests.cpp
@@ -0,0 +1,178 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "common/RefCounted.h"
+
+using namespace backend;
+
+struct RCTest : public RefCounted {
+ RCTest() {
+ }
+
+ RCTest(bool* deleted): deleted(deleted) {
+ }
+
+ ~RCTest() override {
+ if (deleted != nullptr) {
+ *deleted = true;
+ }
+ }
+
+ RCTest* GetThis() {
+ return this;
+ }
+
+ bool* deleted = nullptr;
+};
+
+// Test that RCs start with one external ref, and removing it destroys the object.
+TEST(RefCounted, StartsWithOneExternalRef) {
+ bool deleted = false;
+ auto test = new RCTest(&deleted);
+
+ test->Release();
+ ASSERT_TRUE(deleted);
+}
+
+// Test internal refs keep the RC alive.
+TEST(RefCounted, InternalRefKeepsAlive) {
+ bool deleted = false;
+ auto test = new RCTest(&deleted);
+
+ test->ReferenceInternal();
+ test->Release();
+ ASSERT_FALSE(deleted);
+
+ test->ReleaseInternal();
+ ASSERT_TRUE(deleted);
+}
+
+// Test Ref remove internal reference when going out of scope
+TEST(Ref, EndOfScopeRemovesInternalRef) {
+ bool deleted = false;
+ {
+ Ref<RCTest> test(new RCTest(&deleted));
+ test->Release();
+ }
+ ASSERT_TRUE(deleted);
+}
+
+// Test getting pointer out of the Ref
+TEST(Ref, Gets) {
+ RCTest* original = new RCTest;
+ Ref<RCTest> test(original);
+ test->Release();
+
+ ASSERT_EQ(test.Get(), original);
+ ASSERT_EQ(&*test, original);
+ ASSERT_EQ(test->GetThis(), original);
+}
+
+// Test Refs default to null
+TEST(Ref, DefaultsToNull) {
+ Ref<RCTest> test;
+
+ ASSERT_EQ(test.Get(), nullptr);
+ ASSERT_EQ(&*test, nullptr);
+ ASSERT_EQ(test->GetThis(), nullptr);
+}
+
+// Test Refs can be used inside ifs
+TEST(Ref, BoolConversion) {
+ Ref<RCTest> empty;
+ Ref<RCTest> full(new RCTest);
+ full->Release();
+
+ if (!full || empty) {
+ ASSERT_TRUE(false);
+ }
+}
+
+// Test Ref's copy constructor
+TEST(Ref, CopyConstructor) {
+ bool deleted;
+ RCTest* original = new RCTest(&deleted);
+
+ Ref<RCTest> source(original);
+ Ref<RCTest> destination(source);
+ original->Release();
+
+ ASSERT_EQ(source.Get(), original);
+ ASSERT_EQ(destination.Get(), original);
+
+ source = nullptr;
+ ASSERT_FALSE(deleted);
+ destination = nullptr;
+ ASSERT_TRUE(deleted);
+}
+
+// Test Ref's copy assignment
+TEST(Ref, CopyAssignment) {
+ bool deleted;
+ RCTest* original = new RCTest(&deleted);
+
+ Ref<RCTest> source(original);
+ original->Release();
+
+ Ref<RCTest> destination;
+ destination = source;
+
+ ASSERT_EQ(source.Get(), original);
+ ASSERT_EQ(destination.Get(), original);
+
+ source = nullptr;
+ // This fails when address sanitizer is turned on
+ ASSERT_FALSE(deleted);
+
+ destination = nullptr;
+ ASSERT_TRUE(deleted);
+}
+
+// Test Ref's move constructor
+TEST(Ref, MoveConstructor) {
+ bool deleted;
+ RCTest* original = new RCTest(&deleted);
+
+ Ref<RCTest> source(original);
+ Ref<RCTest> destination(std::move(source));
+ original->Release();
+
+ ASSERT_EQ(source.Get(), nullptr);
+ ASSERT_EQ(destination.Get(), original);
+ ASSERT_FALSE(deleted);
+
+ destination = nullptr;
+ ASSERT_TRUE(deleted);
+}
+
+// Test Ref's move assignment
+TEST(Ref, MoveAssignment) {
+ bool deleted;
+ RCTest* original = new RCTest(&deleted);
+
+ Ref<RCTest> source(original);
+ original->Release();
+
+ Ref<RCTest> destination;
+ destination = std::move(source);
+
+ ASSERT_EQ(source.Get(), nullptr);
+ ASSERT_EQ(destination.Get(), original);
+ ASSERT_FALSE(deleted);
+
+ destination = nullptr;
+ ASSERT_TRUE(deleted);
+}
diff --git a/src/backend/tests/ToBackendTests.cpp b/src/backend/tests/ToBackendTests.cpp
new file mode 100644
index 0000000..3ce65cb
--- /dev/null
+++ b/src/backend/tests/ToBackendTests.cpp
@@ -0,0 +1,89 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+#include "common/RefCounted.h"
+#include "common/ToBackend.h"
+
+#include <type_traits>
+
+// Make our own Base - Backend object pair, reusing the CommandBuffer name
+namespace backend {
+ class CommandBufferBase : public RefCounted {
+ };
+}
+
+using namespace backend;
+
+class MyCommandBuffer : public CommandBufferBase {
+};
+
+struct MyBackendTraits {
+ using CommandBufferType = MyCommandBuffer;
+};
+
+// Instanciate ToBackend for our "backend"
+template<typename T>
+auto ToBackend(T&& common) -> decltype(ToBackendBase<MyBackendTraits>(common)) {
+ return ToBackendBase<MyBackendTraits>(common);
+}
+
+// Test that ToBackend correctly converts pointers to base classes.
+TEST(ToBackend, Pointers) {
+ {
+ MyCommandBuffer* cmdBuf = new MyCommandBuffer;
+ const CommandBufferBase* base = cmdBuf;
+
+ auto backendCmdBuf = ToBackend(base);
+ static_assert(std::is_same<decltype(backendCmdBuf), const MyCommandBuffer*>::value, "");
+ ASSERT_EQ(cmdBuf, backendCmdBuf);
+
+ cmdBuf->Release();
+ }
+ {
+ MyCommandBuffer* cmdBuf = new MyCommandBuffer;
+ CommandBufferBase* base = cmdBuf;
+
+ auto backendCmdBuf = ToBackend(base);
+ static_assert(std::is_same<decltype(backendCmdBuf), MyCommandBuffer*>::value, "");
+ ASSERT_EQ(cmdBuf, backendCmdBuf);
+
+ cmdBuf->Release();
+ }
+}
+
+// Test that ToBackend correctly converts Refs to base classes.
+TEST(ToBackend, Ref) {
+ {
+ MyCommandBuffer* cmdBuf = new MyCommandBuffer;
+ const Ref<CommandBufferBase> base(cmdBuf);
+
+ const auto& backendCmdBuf = ToBackend(base);
+ static_assert(std::is_same<decltype(ToBackend(base)), const Ref<MyCommandBuffer>&>::value, "");
+ ASSERT_EQ(cmdBuf, backendCmdBuf.Get());
+
+ cmdBuf->Release();
+ }
+ {
+ MyCommandBuffer* cmdBuf = new MyCommandBuffer;
+ Ref<CommandBufferBase> base(cmdBuf);
+
+ auto backendCmdBuf = ToBackend(base);
+ static_assert(std::is_same<decltype(ToBackend(base)), Ref<MyCommandBuffer>&>::value, "");
+ ASSERT_EQ(cmdBuf, backendCmdBuf.Get());
+
+ cmdBuf->Release();
+ }
+}
diff --git a/src/backend/tests/UnittestsMain.cpp b/src/backend/tests/UnittestsMain.cpp
new file mode 100644
index 0000000..73870aa
--- /dev/null
+++ b/src/backend/tests/UnittestsMain.cpp
@@ -0,0 +1,20 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/src/include/nxt/EnumClassBitmasks.h b/src/include/nxt/EnumClassBitmasks.h
new file mode 100644
index 0000000..63804a4
--- /dev/null
+++ b/src/include/nxt/EnumClassBitmasks.h
@@ -0,0 +1,150 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef NXT_ENUM_CLASS_BITMASKS_H_
+#define NXT_ENUM_CLASS_BITMASKS_H_
+
+#include <type_traits>
+
+namespace nxt {
+
+// std::underlying_type doesn't work in old GLIBC still used in Chrome
+#define CR_GLIBCXX_4_7_0 20120322
+#define CR_GLIBCXX_4_5_4 20120702
+#define CR_GLIBCXX_4_6_4 20121127
+#if defined(__GLIBCXX__) && \
+ (__GLIBCXX__ < CR_GLIBCXX_4_7_0 || __GLIBCXX__ == CR_GLIBCXX_4_5_4 || \
+ __GLIBCXX__ == CR_GLIBCXX_4_6_4)
+#define CR_USE_FALLBACKS_FOR_OLD_GLIBCXX
+#endif
+
+#if defined(CR_USE_FALLBACKS_FOR_OLD_GLIBCXX)
+ template <typename T>
+ struct UnderlyingType {
+ using type = __underlying_type(T);
+ };
+#else
+ template <typename T>
+ using UnderlyingType = std::underlying_type<T>;
+#endif
+
+ template<typename T>
+ struct IsNXTBitmask {
+ static constexpr bool enable = false;
+ };
+
+ template<typename T, typename Enable = void>
+ struct LowerBitmask {
+ static constexpr bool enable = false;
+ };
+
+ template<typename T>
+ struct LowerBitmask<T, typename std::enable_if<IsNXTBitmask<T>::enable>::type> {
+ static constexpr bool enable = true;
+ using type = T;
+ static T Lower(T t) {return t;}
+ };
+
+ template<typename T>
+ struct BoolConvertible {
+ using Integral = typename UnderlyingType<T>::type;
+
+ BoolConvertible(Integral value) : value(value) {}
+ operator bool() const {return value != 0;}
+ operator T() const {return static_cast<T>(value);}
+
+ Integral value;
+ };
+
+ template<typename T>
+ struct LowerBitmask<BoolConvertible<T>> {
+ static constexpr bool enable = true;
+ using type = T;
+ static type Lower(BoolConvertible<T> t) {return t;}
+ };
+
+ template<typename T>
+ constexpr bool HasZeroOrOneBits(T value) {
+ using Integral = typename UnderlyingType<T>::type;
+ Integral v = static_cast<Integral>(value);
+ return (v & (v - 1)) == 0;
+ }
+
+ template<typename T1, typename T2, typename = typename std::enable_if<
+ LowerBitmask<T1>::enable && LowerBitmask<T2>::enable
+ >::type>
+ constexpr BoolConvertible<typename LowerBitmask<T1>::type> operator | (T1 left, T2 right) {
+ using T = typename LowerBitmask<T1>::type;
+ using Integral = typename UnderlyingType<T>::type;
+ return static_cast<Integral>(LowerBitmask<T1>::Lower(left)) |
+ static_cast<Integral>(LowerBitmask<T2>::Lower(right));
+ }
+
+ template<typename T1, typename T2, typename = typename std::enable_if<
+ LowerBitmask<T1>::enable && LowerBitmask<T2>::enable
+ >::type>
+ constexpr BoolConvertible<typename LowerBitmask<T1>::type> operator & (T1 left, T2 right) {
+ using T = typename LowerBitmask<T1>::type;
+ using Integral = typename UnderlyingType<T>::type;
+ return static_cast<Integral>(LowerBitmask<T1>::Lower(left)) &
+ static_cast<Integral>(LowerBitmask<T2>::Lower(right));
+ }
+
+ template<typename T1, typename T2, typename = typename std::enable_if<
+ LowerBitmask<T1>::enable && LowerBitmask<T2>::enable
+ >::type>
+ constexpr BoolConvertible<typename LowerBitmask<T1>::type> operator ^ (T1 left, T2 right) {
+ using T = typename LowerBitmask<T1>::type;
+ using Integral = typename UnderlyingType<T>::type;
+ return static_cast<Integral>(LowerBitmask<T1>::Lower(left)) ^
+ static_cast<Integral>(LowerBitmask<T2>::Lower(right));
+ }
+
+ template<typename T1>
+ constexpr BoolConvertible<typename LowerBitmask<T1>::type> operator ~ (T1 t) {
+ using T = typename LowerBitmask<T1>::type;
+ using Integral = typename UnderlyingType<T>::type;
+ return ~static_cast<Integral>(LowerBitmask<T1>::Lower(t));
+ }
+
+ template<typename T, typename T2, typename = typename std::enable_if<
+ IsNXTBitmask<T>::enable && LowerBitmask<T2>::enable
+ >::type>
+ T& operator &= (T& l, T2 right) {
+ T r = LowerBitmask<T2>::Lower(right);
+ l = l & r;
+ return l;
+ }
+
+ template<typename T, typename T2, typename = typename std::enable_if<
+ IsNXTBitmask<T>::enable && LowerBitmask<T2>::enable
+ >::type>
+ T& operator |= (T& l, T2 right) {
+ T r = LowerBitmask<T2>::Lower(right);
+ l = l | r;
+ return l;
+ }
+
+ template<typename T, typename T2, typename = typename std::enable_if<
+ IsNXTBitmask<T>::enable && LowerBitmask<T2>::enable
+ >::type>
+ T& operator ^= (T& l, T2 right) {
+ T r = LowerBitmask<T2>::Lower(right);
+ l = l ^ r;
+ return l;
+ }
+
+}
+
+#endif // NXT_ENUM_CLASS_BITMASKS_H_
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
new file mode 100644
index 0000000..8a42014
--- /dev/null
+++ b/src/tests/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright 2017 The NXT Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+add_executable(nxt_unittests
+ ${TESTS_DIR}/EnumClassBitmasksTests.cpp
+ ${TESTS_DIR}/UnittestsMain.cpp
+)
+target_link_libraries(nxt_unittests gtest nxtcpp)
+SetCXX14(nxt_unittests)
diff --git a/src/tests/EnumClassBitmasksTests.cpp b/src/tests/EnumClassBitmasksTests.cpp
new file mode 100644
index 0000000..7851047
--- /dev/null
+++ b/src/tests/EnumClassBitmasksTests.cpp
@@ -0,0 +1,93 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gtest/gtest.h"
+
+#include "nxt/EnumClassBitmasks.h"
+
+namespace nxt {
+
+ enum class Color : uint32_t {
+ R = 1,
+ G = 2,
+ B = 4,
+ A = 8,
+ };
+
+ template<>
+ struct IsNXTBitmask<Color> {
+ static constexpr bool enable = true;
+ };
+
+ TEST(BitmaskTests, BasicOperations) {
+ Color test1 = Color::R | Color::G;
+ ASSERT_EQ(1 | 2, static_cast<uint32_t>(test1));
+
+ Color test2 = test1 ^ (Color::R | Color::A);
+ ASSERT_EQ(2 | 8, static_cast<uint32_t>(test2));
+
+ Color test3 = test2 & Color::A;
+ ASSERT_EQ(8, static_cast<uint32_t>(test3));
+
+ Color test4 = ~test3;
+ ASSERT_EQ(~uint32_t(8), static_cast<uint32_t>(test4));
+ }
+
+ TEST(BitmaskTests, AssignOperations) {
+ Color test1 = Color::R;
+ test1 |= Color::G;
+ ASSERT_EQ(1 | 2, static_cast<uint32_t>(test1));
+
+ Color test2 = test1;
+ test2 ^= (Color::R | Color::A);
+ ASSERT_EQ(2 | 8, static_cast<uint32_t>(test2));
+
+ Color test3 = test2;
+ test3 &= Color::A;
+ ASSERT_EQ(8, static_cast<uint32_t>(test3));
+ }
+
+ TEST(BitmaskTests, BoolConversion) {
+ bool test1 = Color::R | Color::G;
+ ASSERT_TRUE(test1);
+
+ bool test2 = Color::R & Color::G;
+ ASSERT_FALSE(test2);
+
+ bool test3 = Color::R ^ Color::G;
+ ASSERT_TRUE(test3);
+
+ if (Color::R & ~Color::R) {
+ ASSERT_TRUE(false);
+ }
+ }
+
+ TEST(BitmaskTests, ThreeOrs) {
+ Color c = Color::R | Color::G | Color::B;
+ ASSERT_EQ(7, static_cast<uint32_t>(c));
+ }
+
+ TEST(BitmaskTests, ZeroOrOneBits) {
+ Color zero = static_cast<Color>(0);
+ ASSERT_TRUE(HasZeroOrOneBits(zero));
+ ASSERT_TRUE(HasZeroOrOneBits(Color::R));
+ ASSERT_TRUE(HasZeroOrOneBits(Color::G));
+ ASSERT_TRUE(HasZeroOrOneBits(Color::B));
+ ASSERT_TRUE(HasZeroOrOneBits(Color::A));
+ ASSERT_FALSE(HasZeroOrOneBits(static_cast<Color>(Color::R | Color::G)));
+ ASSERT_FALSE(HasZeroOrOneBits(static_cast<Color>(Color::G | Color::B)));
+ ASSERT_FALSE(HasZeroOrOneBits(static_cast<Color>(Color::B | Color::A)));
+ }
+
+}
diff --git a/src/tests/UnittestsMain.cpp b/src/tests/UnittestsMain.cpp
new file mode 100644
index 0000000..73870aa
--- /dev/null
+++ b/src/tests/UnittestsMain.cpp
@@ -0,0 +1,20 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/src/wire/CMakeLists.txt b/src/wire/CMakeLists.txt
new file mode 100644
index 0000000..5ade76a
--- /dev/null
+++ b/src/wire/CMakeLists.txt
@@ -0,0 +1,43 @@
+# Copyright 2017 The NXT Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(WIRE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+set(TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests)
+
+Generate(
+ LIB_NAME wire_autogen
+ LIB_TYPE STATIC
+ PRINT_NAME "Wire serialization/deserialization autogenerated files"
+ COMMAND_LINE_ARGS
+ ${GENERATOR_COMMON_ARGS}
+ -T wire
+)
+target_include_directories(wire_autogen PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+target_include_directories(wire_autogen PUBLIC ${GENERATED_DIR})
+SetCXX14(wire_autogen)
+SetPic(wire_autogen)
+
+add_library(nxt_wire SHARED
+ ${WIRE_DIR}/TerribleCommandBuffer.h
+)
+target_link_libraries(nxt_wire wire_autogen)
+SetCXX14(nxt_wire)
+
+add_executable(wire_unittests
+ ${TESTS_DIR}/UnittestsMain.cpp
+ ${TESTS_DIR}/WireTests.cpp
+)
+target_link_libraries(wire_unittests mock_nxt nxt_wire)
+target_include_directories(wire_unittests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
+SetCXX14(wire_unittests)
diff --git a/src/wire/TerribleCommandBuffer.h b/src/wire/TerribleCommandBuffer.h
new file mode 100644
index 0000000..408e418
--- /dev/null
+++ b/src/wire/TerribleCommandBuffer.h
@@ -0,0 +1,58 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef WIRE_TERRIBLE_COMMAND_BUFFER_H_
+#define WIRE_TERRIBLE_COMMAND_BUFFER_H_
+
+#include <vector>
+
+#include "Wire.h"
+
+namespace nxt {
+namespace wire {
+
+class TerribleCommandBuffer : public CommandSerializer {
+ public:
+ TerribleCommandBuffer(CommandHandler* handler) : handler(handler) {
+ }
+ void* GetCmdSpace(size_t size) {
+ if (size > sizeof(buffer)) {
+ return nullptr;
+ }
+
+ uint8_t* result = &buffer[offset];
+ offset += size;
+
+ if (offset > sizeof(buffer)) {
+ Flush();
+ return GetCmdSpace(size);
+ }
+
+ return result;
+ }
+ void Flush() {
+ handler->HandleCommands(buffer, offset);
+ offset = 0;
+ }
+
+ private:
+ CommandHandler* handler = nullptr;
+ size_t offset;
+ uint8_t buffer[10000000];
+};
+
+}
+}
+
+#endif // WIRE_TERRIBLE_COMMAND_BUFFER_H_
diff --git a/src/wire/Wire.h b/src/wire/Wire.h
new file mode 100644
index 0000000..bf3f30f
--- /dev/null
+++ b/src/wire/Wire.h
@@ -0,0 +1,47 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef WIRE_WIRE_H_
+#define WIRE_WIRE_H_
+
+#include <cstdint>
+
+#include "nxt/nxt.h"
+
+namespace nxt {
+namespace wire {
+
+ class CommandSerializer {
+ public:
+ virtual ~CommandSerializer() = default;
+ virtual void* GetCmdSpace(size_t size) = 0;
+ virtual void Flush() = 0;
+ };
+
+ void NewClientDevice(nxtProcTable* procs, nxtDevice* device, CommandSerializer* serializer);
+
+ class CommandHandler {
+ public:
+ virtual ~CommandHandler() = default;
+ virtual const uint8_t* HandleCommands(const uint8_t* commands, size_t size) = 0;
+
+ virtual void OnSynchronousError() = 0;
+ };
+
+ CommandHandler* CreateCommandHandler(nxtDevice device, const nxtProcTable& procs);
+
+}
+}
+
+#endif // WIRE_WIRE_H_
diff --git a/src/wire/WireCmd.h b/src/wire/WireCmd.h
new file mode 100644
index 0000000..7114f40
--- /dev/null
+++ b/src/wire/WireCmd.h
@@ -0,0 +1,20 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef WIRE_WIRECMD_H_
+#define WIRE_WIRECMD_H_
+
+#include "wire/WireCmd_autogen.h"
+
+#endif // WIRE_WIRECMD_H_
diff --git a/src/wire/tests/UnittestsMain.cpp b/src/wire/tests/UnittestsMain.cpp
new file mode 100644
index 0000000..73870aa
--- /dev/null
+++ b/src/wire/tests/UnittestsMain.cpp
@@ -0,0 +1,20 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <gtest/gtest.h>
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/src/wire/tests/WireTests.cpp b/src/wire/tests/WireTests.cpp
new file mode 100644
index 0000000..0e3139b
--- /dev/null
+++ b/src/wire/tests/WireTests.cpp
@@ -0,0 +1,182 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gtest/gtest.h"
+#include "mock/mock_nxt.h"
+
+#include "TerribleCommandBuffer.h"
+#include "Wire.h"
+
+using namespace testing;
+using namespace nxt::wire;
+
+class WireTests : public Test {
+ protected:
+ void SetUp() override {
+ nxtProcTable mockProcs;
+ nxtDevice mockDevice;
+ api.GetProcTableAndDevice(&mockProcs, &mockDevice);
+
+ wireServer = CreateCommandHandler(mockDevice, mockProcs);
+
+ cmdBuf = new TerribleCommandBuffer(wireServer);
+
+ nxtDevice clientDevice;
+ nxtProcTable clientProcs;
+ NewClientDevice(&clientProcs, &clientDevice, cmdBuf);
+
+ nxtSetProcs(&clientProcs);
+ device = clientDevice;
+ apiDevice = mockDevice;
+ }
+
+ void TearDown() override {
+ nxtSetProcs(nullptr);
+ delete wireServer;
+ delete cmdBuf;
+ }
+
+ void Flush() {
+ cmdBuf->Flush();
+ }
+
+ MockProcTable api;
+ nxtDevice apiDevice;
+ nxtDevice device;
+
+ private:
+ CommandHandler* wireServer = nullptr;
+ TerribleCommandBuffer* cmdBuf = nullptr;
+};
+
+// One call gets forwarded correctly.
+TEST_F(WireTests, CallForwarded) {
+ nxtCommandBufferBuilder builder = nxtDeviceCreateCommandBufferBuilder(device);
+
+ nxtCommandBufferBuilder apiCmdBufBuilder = api.GetNewCommandBufferBuilder();
+ EXPECT_CALL(api, DeviceCreateCommandBufferBuilder(apiDevice))
+ .WillOnce(Return(apiCmdBufBuilder));
+
+ Flush();
+}
+
+// Test that calling methods on a new object works as expected.
+TEST_F(WireTests, CreateThenCall) {
+ nxtCommandBufferBuilder builder = nxtDeviceCreateCommandBufferBuilder(device);
+ nxtCommandBuffer cmdBuf = nxtCommandBufferBuilderGetResult(builder);
+
+ nxtCommandBufferBuilder apiCmdBufBuilder = api.GetNewCommandBufferBuilder();
+ EXPECT_CALL(api, DeviceCreateCommandBufferBuilder(apiDevice))
+ .WillOnce(Return(apiCmdBufBuilder));
+
+ nxtCommandBuffer apiCmdBuf = api.GetNewCommandBuffer();
+ EXPECT_CALL(api, CommandBufferBuilderGetResult(apiCmdBufBuilder))
+ .WillOnce(Return(apiCmdBuf));
+
+ Flush();
+}
+
+// Test that client reference/release do not call the backend API.
+TEST_F(WireTests, RefCountKeptInClient) {
+ nxtCommandBufferBuilder builder = nxtDeviceCreateCommandBufferBuilder(device);
+
+ nxtCommandBufferBuilderReference(builder);
+ nxtCommandBufferBuilderRelease(builder);
+
+ nxtCommandBufferBuilder apiCmdBufBuilder = api.GetNewCommandBufferBuilder();
+ EXPECT_CALL(api, DeviceCreateCommandBufferBuilder(apiDevice))
+ .WillOnce(Return(apiCmdBufBuilder));
+
+ Flush();
+}
+
+// Test that client reference/release do not call the backend API.
+TEST_F(WireTests, ReleaseCalledOnRefCount0) {
+ nxtCommandBufferBuilder builder = nxtDeviceCreateCommandBufferBuilder(device);
+
+ nxtCommandBufferBuilderRelease(builder);
+
+ nxtCommandBufferBuilder apiCmdBufBuilder = api.GetNewCommandBufferBuilder();
+ EXPECT_CALL(api, DeviceCreateCommandBufferBuilder(apiDevice))
+ .WillOnce(Return(apiCmdBufBuilder));
+
+ EXPECT_CALL(api, CommandBufferBuilderRelease(apiCmdBufBuilder));
+
+ Flush();
+}
+
+TEST_F(WireTests, ObjectAsValueArgument) {
+ // Create pipeline
+ nxtPipelineBuilder pipelineBuilder = nxtDeviceCreatePipelineBuilder(device);
+ nxtPipeline pipeline = nxtPipelineBuilderGetResult(pipelineBuilder);
+
+ nxtPipelineBuilder apiPipelineBuilder = api.GetNewPipelineBuilder();
+ EXPECT_CALL(api, DeviceCreatePipelineBuilder(apiDevice))
+ .WillOnce(Return(apiPipelineBuilder));
+
+ nxtPipeline apiPipeline = api.GetNewPipeline();
+ EXPECT_CALL(api, PipelineBuilderGetResult(apiPipelineBuilder))
+ .WillOnce(Return(apiPipeline));
+
+ // Create command buffer builder, setting pipeline
+ nxtCommandBufferBuilder cmdBufBuilder = nxtDeviceCreateCommandBufferBuilder(device);
+ nxtCommandBufferBuilderSetPipeline(cmdBufBuilder, pipeline);
+
+ nxtCommandBufferBuilder apiCmdBufBuilder = api.GetNewCommandBufferBuilder();
+ EXPECT_CALL(api, DeviceCreateCommandBufferBuilder(apiDevice))
+ .WillOnce(Return(apiCmdBufBuilder));
+
+ EXPECT_CALL(api, CommandBufferBuilderSetPipeline(apiCmdBufBuilder, apiPipeline));
+
+ Flush();
+}
+
+TEST_F(WireTests, OneObjectAsPointerArgument) {
+ // Create command buffer
+ nxtCommandBufferBuilder cmdBufBuilder = nxtDeviceCreateCommandBufferBuilder(device);
+ nxtCommandBuffer cmdBuf = nxtCommandBufferBuilderGetResult(cmdBufBuilder);
+
+ nxtCommandBufferBuilder apiCmdBufBuilder = api.GetNewCommandBufferBuilder();
+ EXPECT_CALL(api, DeviceCreateCommandBufferBuilder(apiDevice))
+ .WillOnce(Return(apiCmdBufBuilder));
+
+ nxtCommandBuffer apiCmdBuf = api.GetNewCommandBuffer();
+ EXPECT_CALL(api, CommandBufferBuilderGetResult(apiCmdBufBuilder))
+ .WillOnce(Return(apiCmdBuf));
+
+ // Create queue
+ nxtQueueBuilder queueBuilder = nxtDeviceCreateQueueBuilder(device);
+ nxtQueue queue = nxtQueueBuilderGetResult(queueBuilder);
+
+ nxtQueueBuilder apiQueueBuilder = api.GetNewQueueBuilder();
+ EXPECT_CALL(api, DeviceCreateQueueBuilder(apiDevice))
+ .WillOnce(Return(apiQueueBuilder));
+
+ nxtQueue apiQueue = api.GetNewQueue();
+ EXPECT_CALL(api, QueueBuilderGetResult(apiQueueBuilder))
+ .WillOnce(Return(apiQueue));
+
+ // Submit command buffer
+ nxtQueueSubmit(queue, 1, &cmdBuf);
+
+ EXPECT_CALL(api, QueueSubmit(apiQueue, 1, Pointee(apiCmdBuf)));
+
+ Flush();
+}
+
+// TODO
+// - Test values work
+// - Test multiple objects as value work
+// - Object creation, then calls do nothing after error on builder
+// - Object creation then error then create object, then should do nothing.