Metal: split non-trivial objects in their own file.

No functional changes intended, but there are a couple additional
cleanups:
 - Use anonymous namespaces instead of static functions
 - Don't store an extra Device pointer in objects
diff --git a/src/backend/CMakeLists.txt b/src/backend/CMakeLists.txt
index 5dd0e06..49ef85f 100644
--- a/src/backend/CMakeLists.txt
+++ b/src/backend/CMakeLists.txt
@@ -143,8 +143,26 @@
     SetPIC(metal_autogen)
 
     list(APPEND BACKEND_SOURCES
+        ${METAL_DIR}/BufferMTL.mm
+        ${METAL_DIR}/BufferMTL.h
+        ${METAL_DIR}/CommandBufferMTL.mm
+        ${METAL_DIR}/CommandBufferMTL.h
+        ${METAL_DIR}/DepthStencilStateMTL.mm
+        ${METAL_DIR}/DepthStencilStateMTL.h
+        ${METAL_DIR}/InputStateMTL.mm
+        ${METAL_DIR}/InputStateMTL.h
         ${METAL_DIR}/MetalBackend.mm
         ${METAL_DIR}/MetalBackend.h
+        ${METAL_DIR}/PipelineMTL.mm
+        ${METAL_DIR}/PipelineMTL.h
+        ${METAL_DIR}/PipelineLayoutMTL.mm
+        ${METAL_DIR}/PipelineLayoutMTL.h
+        ${METAL_DIR}/SamplerMTL.mm
+        ${METAL_DIR}/SamplerMTL.h
+        ${METAL_DIR}/ShaderModuleMTL.mm
+        ${METAL_DIR}/ShaderModuleMTL.h
+        ${METAL_DIR}/TextureMTL.mm
+        ${METAL_DIR}/TextureMTL.h
     )
 endif()
 
diff --git a/src/backend/metal/BufferMTL.h b/src/backend/metal/BufferMTL.h
new file mode 100644
index 0000000..f519dfb
--- /dev/null
+++ b/src/backend/metal/BufferMTL.h
@@ -0,0 +1,53 @@
+// 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_BUFFERMTL_H_
+#define BACKEND_METAL_BUFFERMTL_H_
+
+#include "common/Buffer.h"
+
+#import <Metal/Metal.h>
+
+#include <mutex>
+
+namespace backend {
+namespace metal {
+
+    class Buffer : public BufferBase {
+        public:
+            Buffer(BufferBuilder* builder);
+            ~Buffer();
+
+            id<MTLBuffer> GetMTLBuffer();
+            std::mutex& GetMutex();
+
+        private:
+            void SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) override;
+            void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
+            void UnmapImpl() override;
+            void TransitionUsageImpl(nxt::BufferUsageBit currentUsage, nxt::BufferUsageBit targetUsage) override;
+
+            std::mutex mutex;
+            id<MTLBuffer> mtlBuffer = nil;
+    };
+
+    class BufferView : public BufferViewBase {
+        public:
+            BufferView(BufferViewBuilder* builder);
+    };
+
+}
+}
+
+#endif // BACKEND_METAL_BUFFERMTL_H_
diff --git a/src/backend/metal/BufferMTL.mm b/src/backend/metal/BufferMTL.mm
new file mode 100644
index 0000000..425770e
--- /dev/null
+++ b/src/backend/metal/BufferMTL.mm
@@ -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 "BufferMTL.h"
+
+#include "MetalBackend.h"
+
+namespace backend {
+namespace metal {
+
+    Buffer::Buffer(BufferBuilder* builder)
+        : BufferBase(builder) {
+        mtlBuffer = [ToBackend(GetDevice())->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))];
+    }
+
+    void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) {
+        // TODO(cwallez@chromium.org): Implement Map Read for the metal backend
+    }
+
+    void Buffer::UnmapImpl() {
+        // TODO(cwallez@chromium.org): Implement Map Read for the metal backend
+    }
+
+    void Buffer::TransitionUsageImpl(nxt::BufferUsageBit currentUsage, nxt::BufferUsageBit targetUsage) {
+    }
+
+    BufferView::BufferView(BufferViewBuilder* builder)
+        : BufferViewBase(builder) {
+    }
+
+}
+}
diff --git a/src/backend/metal/CommandBufferMTL.h b/src/backend/metal/CommandBufferMTL.h
new file mode 100644
index 0000000..67c3a07
--- /dev/null
+++ b/src/backend/metal/CommandBufferMTL.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_METAL_COMMANDBUFFERMTL_H_
+#define BACKEND_METAL_COMMANDBUFFERMTL_H_
+
+#include "common/CommandBuffer.h"
+
+#import <Metal/Metal.h>
+
+#include <mutex>
+#include <unordered_set>
+
+namespace backend {
+namespace metal {
+
+    class 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;
+    };
+
+}
+}
+
+#endif // BACKEND_METAL_COMMANDBUFFERMTL_H_
diff --git a/src/backend/metal/CommandBufferMTL.mm b/src/backend/metal/CommandBufferMTL.mm
new file mode 100644
index 0000000..3d49dbd
--- /dev/null
+++ b/src/backend/metal/CommandBufferMTL.mm
@@ -0,0 +1,474 @@
+// 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 "CommandBufferMTL.h"
+
+#include "common/Commands.h"
+#include "BufferMTL.h"
+#include "DepthStencilStateMTL.h"
+#include "InputStateMTL.h"
+#include "MetalBackend.h"
+#include "PipelineMTL.h"
+#include "PipelineLayoutMTL.h"
+#include "SamplerMTL.h"
+#include "TextureMTL.h"
+
+namespace backend {
+namespace metal {
+
+    namespace {
+        MTLIndexType IndexFormatType(nxt::IndexFormat format) {
+            switch (format) {
+                case nxt::IndexFormat::Uint16:
+                    return MTLIndexTypeUInt16;
+                case nxt::IndexFormat::Uint32:
+                    return MTLIndexTypeUInt32;
+            }
+        }
+
+        struct CurrentEncoders {
+            Device* device;
+
+            id<MTLBlitCommandEncoder> blit = nil;
+            id<MTLComputeCommandEncoder> compute = nil;
+            id<MTLRenderCommandEncoder> render = nil;
+
+            RenderPass* currentRenderPass = nullptr;
+            Framebuffer* currentFramebuffer = nullptr;
+
+            void FinishEncoders() {
+                ASSERT(render == nil);
+                if (blit != nil) {
+                    [blit endEncoding];
+                    blit = nil;
+                }
+                if (compute != nil) {
+                    [compute endEncoding];
+                    compute = 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 BeginSubpass(id<MTLCommandBuffer> commandBuffer, uint32_t subpass) {
+                ASSERT(currentRenderPass);
+                if (render != nil) {
+                    [render endEncoding];
+                    render = nil;
+                }
+
+                const auto& info = currentRenderPass->GetSubpassInfo(subpass);
+
+                MTLRenderPassDescriptor* descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
+                bool usingBackbuffer = false; // HACK(kainino@chromium.org): workaround for not having depth attachments
+                for (uint32_t index = 0; index < info.colorAttachments.size(); ++index) {
+                    uint32_t attachment = info.colorAttachments[index];
+
+                    // TODO(kainino@chromium.org): currently a 'null' texture view
+                    // falls back to the 'back buffer' but this should go away
+                    // when we have WSI.
+                    id<MTLTexture> texture = nil;
+                    if (auto textureView = currentFramebuffer->GetTextureView(attachment)) {
+                        texture = ToBackend(textureView->GetTexture())->GetMTLTexture();
+                    } else {
+                        texture = device->GetCurrentTexture();
+                        usingBackbuffer = true;
+                    }
+                    descriptor.colorAttachments[index].texture = texture;
+                    descriptor.colorAttachments[index].loadAction = MTLLoadActionLoad;
+                    descriptor.colorAttachments[index].storeAction = MTLStoreActionStore;
+                }
+                // TODO(kainino@chromium.org): load depth attachment from subpass
+                if (usingBackbuffer) {
+                    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 EndRenderPass() {
+                ASSERT(render != nil);
+                [render endEncoding];
+                render = nil;
+            }
+        };
+    }
+
+    CommandBuffer::CommandBuffer(Device* device, CommandBufferBuilder* builder)
+        : CommandBufferBase(builder), device(device), commands(builder->AcquireCommands()) {
+    }
+
+    CommandBuffer::~CommandBuffer() {
+        FreeCommands(&commands);
+    }
+
+    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;
+
+        uint32_t currentSubpass = 0;
+        id<MTLRenderCommandEncoder> renderEncoder = nil;
+
+        while (commands.NextCommandId(&type)) {
+            switch (type) {
+                case Command::AdvanceSubpass:
+                    {
+                        commands.NextCommand<AdvanceSubpassCmd>();
+                        currentSubpass += 1;
+                        encoders.BeginSubpass(commandBuffer, currentSubpass);
+                    }
+                    break;
+
+                case Command::BeginRenderPass:
+                    {
+                        BeginRenderPassCmd* beginRenderPassCmd = commands.NextCommand<BeginRenderPassCmd>();
+                        encoders.currentRenderPass = ToBackend(beginRenderPassCmd->renderPass.Get());
+                        encoders.currentFramebuffer = ToBackend(beginRenderPassCmd->framebuffer.Get());
+                        encoders.FinishEncoders();
+                        currentSubpass = 0;
+                        encoders.BeginSubpass(commandBuffer, currentSubpass);
+                    }
+                    break;
+
+                case Command::CopyBufferToBuffer:
+                    {
+                        CopyBufferToBufferCmd* copy = commands.NextCommand<CopyBufferToBufferCmd>();
+
+                        encoders.EnsureBlit(commandBuffer);
+                        [encoders.blit
+                            copyFromBuffer:ToBackend(copy->source)->GetMTLBuffer()
+                            sourceOffset:copy->sourceOffset
+                            toBuffer:ToBackend(copy->destination)->GetMTLBuffer()
+                            destinationOffset:copy->destinationOffset
+                            size:copy->size];
+                    }
+                    break;
+
+                case Command::CopyBufferToTexture:
+                    {
+                        CopyBufferToTextureCmd* copy = commands.NextCommand<CopyBufferToTextureCmd>();
+                        Buffer* buffer = ToBackend(copy->buffer.Get());
+                        Texture* texture = ToBackend(copy->texture.Get());
+
+                        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:copy->bufferOffset
+                            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>();
+
+                        ASSERT(encoders.render);
+                        [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>();
+
+                        ASSERT(encoders.render);
+                        [encoders.render
+                            drawIndexedPrimitives:MTLPrimitiveTypeTriangle
+                            indexCount:draw->indexCount
+                            indexType:indexType
+                            indexBuffer:indexBuffer
+                            indexBufferOffset:indexBufferOffset
+                            instanceCount:draw->instanceCount
+                            baseVertex:0
+                            baseInstance:draw->firstInstance];
+                    }
+                    break;
+
+                case Command::EndRenderPass:
+                    {
+                        commands.NextCommand<EndRenderPassCmd>();
+                        encoders.EndRenderPass();
+                    }
+                    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 {
+                            ASSERT(encoders.render);
+                            DepthStencilState* depthStencilState = ToBackend(lastPipeline->GetDepthStencilState());
+                            [encoders.render setDepthStencilState:depthStencilState->GetMTLDepthStencilState()];
+                            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::SetStencilReference:
+                    {
+                        SetStencilReferenceCmd* cmd = commands.NextCommand<SetStencilReferenceCmd>();
+
+                        ASSERT(encoders.render);
+
+                        [encoders.render setStencilReferenceValue:cmd->reference];
+                    }
+                    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 {
+                            ASSERT(encoders.render);
+                        }
+
+                        // 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];
+                        }
+
+                        ASSERT(encoders.render);
+                        [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->UpdateUsageInternal(cmd->usage);
+                    }
+                    break;
+
+                case Command::TransitionTextureUsage:
+                    {
+                        TransitionTextureUsageCmd* cmd = commands.NextCommand<TransitionTextureUsageCmd>();
+
+                        cmd->texture->UpdateUsageInternal(cmd->usage);
+                    }
+                    break;
+            }
+        }
+
+        encoders.FinishEncoders();
+    }
+
+}
+}
diff --git a/src/backend/metal/DepthStencilStateMTL.h b/src/backend/metal/DepthStencilStateMTL.h
new file mode 100644
index 0000000..e0dc97f
--- /dev/null
+++ b/src/backend/metal/DepthStencilStateMTL.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_METAL_DEPTHSTENCILSTATEMTL_H_
+#define BACKEND_METAL_DEPTHSTENCILSTATEMTL_H_
+
+#include "common/DepthStencilState.h"
+
+#import <Metal/Metal.h>
+
+namespace backend {
+namespace metal {
+
+    class Device;
+
+    class DepthStencilState : public DepthStencilStateBase {
+        public:
+            DepthStencilState(DepthStencilStateBuilder* builder);
+            ~DepthStencilState();
+
+            id<MTLDepthStencilState> GetMTLDepthStencilState();
+
+        private:
+            id<MTLDepthStencilState> mtlDepthStencilState = nil;
+    };
+
+}
+}
+
+#endif // BACKEND_METAL_DEPTHSTENCILSTATEMTL_H_
diff --git a/src/backend/metal/DepthStencilStateMTL.mm b/src/backend/metal/DepthStencilStateMTL.mm
new file mode 100644
index 0000000..3c727f9
--- /dev/null
+++ b/src/backend/metal/DepthStencilStateMTL.mm
@@ -0,0 +1,117 @@
+// 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 "DepthStencilStateMTL.h"
+
+#include "MetalBackend.h"
+
+namespace backend {
+namespace metal {
+
+    namespace {
+        MTLCompareFunction MetalDepthStencilCompareFunction(nxt::CompareFunction compareFunction) {
+            switch (compareFunction) {
+                case nxt::CompareFunction::Never:
+                    return MTLCompareFunctionNever;
+                case nxt::CompareFunction::Less:
+                    return MTLCompareFunctionLess;
+                case nxt::CompareFunction::LessEqual:
+                    return MTLCompareFunctionLessEqual;
+                case nxt::CompareFunction::Greater:
+                    return MTLCompareFunctionGreater;
+                case nxt::CompareFunction::GreaterEqual:
+                    return MTLCompareFunctionGreaterEqual;
+                case nxt::CompareFunction::NotEqual:
+                    return MTLCompareFunctionNotEqual;
+                case nxt::CompareFunction::Equal:
+                    return MTLCompareFunctionEqual;
+                case nxt::CompareFunction::Always:
+                    return MTLCompareFunctionAlways;
+            }
+        }
+
+        MTLStencilOperation MetalStencilOperation(nxt::StencilOperation stencilOperation) {
+            switch (stencilOperation) {
+                case nxt::StencilOperation::Keep:
+                    return MTLStencilOperationKeep;
+                case nxt::StencilOperation::Zero:
+                    return MTLStencilOperationZero;
+                case nxt::StencilOperation::Replace:
+                    return MTLStencilOperationReplace;
+                case nxt::StencilOperation::Invert:
+                    return MTLStencilOperationInvert;
+                case nxt::StencilOperation::IncrementClamp:
+                    return MTLStencilOperationIncrementClamp;
+                case nxt::StencilOperation::DecrementClamp:
+                    return MTLStencilOperationDecrementClamp;
+                case nxt::StencilOperation::IncrementWrap:
+                    return MTLStencilOperationIncrementWrap;
+                case nxt::StencilOperation::DecrementWrap:
+                    return MTLStencilOperationDecrementWrap;
+            }
+        }
+    }
+
+    DepthStencilState::DepthStencilState(DepthStencilStateBuilder* builder)
+        : DepthStencilStateBase(builder) {
+        MTLDepthStencilDescriptor* mtlDepthStencilDescriptor = [MTLDepthStencilDescriptor new];
+
+        if (DepthTestEnabled()) {
+            auto& depth = GetDepth();
+            mtlDepthStencilDescriptor.depthCompareFunction = MetalDepthStencilCompareFunction(depth.compareFunction);
+            mtlDepthStencilDescriptor.depthWriteEnabled = depth.depthWriteEnabled;
+        }
+
+        auto& stencil = GetStencil();
+
+        if (StencilTestEnabled()) {
+            MTLStencilDescriptor* backFaceStencil = [MTLStencilDescriptor new];
+            MTLStencilDescriptor* frontFaceStencil = [MTLStencilDescriptor new];
+
+            backFaceStencil.stencilCompareFunction = MetalDepthStencilCompareFunction(stencil.back.compareFunction);
+            backFaceStencil.stencilFailureOperation = MetalStencilOperation(stencil.back.stencilFail);
+            backFaceStencil.depthFailureOperation = MetalStencilOperation(stencil.back.depthFail);
+            backFaceStencil.depthStencilPassOperation = MetalStencilOperation(stencil.back.depthStencilPass);
+            backFaceStencil.readMask = stencil.readMask;
+            backFaceStencil.writeMask = stencil.writeMask;
+
+            frontFaceStencil.stencilCompareFunction = MetalDepthStencilCompareFunction(stencil.front.compareFunction);
+            frontFaceStencil.stencilFailureOperation = MetalStencilOperation(stencil.front.stencilFail);
+            frontFaceStencil.depthFailureOperation = MetalStencilOperation(stencil.front.depthFail);
+            frontFaceStencil.depthStencilPassOperation = MetalStencilOperation(stencil.front.depthStencilPass);
+            frontFaceStencil.readMask = stencil.readMask;
+            frontFaceStencil.writeMask = stencil.writeMask;
+
+            mtlDepthStencilDescriptor.backFaceStencil = backFaceStencil;
+            mtlDepthStencilDescriptor.frontFaceStencil = frontFaceStencil;
+            [backFaceStencil release];
+            [frontFaceStencil release];
+        }
+
+        auto mtlDevice = ToBackend(builder->GetDevice())->GetMTLDevice();
+        mtlDepthStencilState = [mtlDevice newDepthStencilStateWithDescriptor:mtlDepthStencilDescriptor];
+        [mtlDepthStencilDescriptor release];
+    }
+
+    DepthStencilState::~DepthStencilState() {
+        [mtlDepthStencilState release];
+        mtlDepthStencilState = nil;
+    }
+
+    id<MTLDepthStencilState> DepthStencilState::GetMTLDepthStencilState() {
+        return mtlDepthStencilState;
+    }
+
+}
+}
diff --git a/src/backend/metal/GeneratedCodeIncludes.h b/src/backend/metal/GeneratedCodeIncludes.h
index 16d9bfe..808462e 100644
--- a/src/backend/metal/GeneratedCodeIncludes.h
+++ b/src/backend/metal/GeneratedCodeIncludes.h
@@ -13,6 +13,12 @@
 // limitations under the License.
 
 #include "MetalBackend.h"
-
-#include "common/Device.h"
-#include "common/CommandBuffer.h"
+#include "BufferMTL.h"
+#include "CommandBufferMTL.h"
+#include "DepthStencilStateMTL.h"
+#include "InputStateMTL.h"
+#include "PipelineMTL.h"
+#include "PipelineLayoutMTL.h"
+#include "SamplerMTL.h"
+#include "ShaderModuleMTL.h"
+#include "TextureMTL.h"
diff --git a/src/backend/metal/InputStateMTL.h b/src/backend/metal/InputStateMTL.h
new file mode 100644
index 0000000..8011f40
--- /dev/null
+++ b/src/backend/metal/InputStateMTL.h
@@ -0,0 +1,39 @@
+// 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_INPUTSTATEMTL_H_
+#define BACKEND_METAL_INPUTSTATEMTL_H_
+
+#include "common/InputState.h"
+
+#import <Metal/Metal.h>
+
+namespace backend {
+namespace metal {
+
+    class InputState : public InputStateBase {
+        public:
+            InputState(InputStateBuilder* builder);
+            ~InputState();
+
+            MTLVertexDescriptor* GetMTLVertexDescriptor();
+
+        private:
+            MTLVertexDescriptor* mtlVertexDescriptor = nil;
+    };
+
+}
+}
+
+#endif // BACKEND_METAL_COMMANDINPUTSTATEMTL_H_
diff --git a/src/backend/metal/InputStateMTL.mm b/src/backend/metal/InputStateMTL.mm
new file mode 100644
index 0000000..852ddb5
--- /dev/null
+++ b/src/backend/metal/InputStateMTL.mm
@@ -0,0 +1,97 @@
+// 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 "InputStateMTL.h"
+
+#include "MetalBackend.h"
+
+namespace backend {
+namespace metal {
+
+    namespace {
+        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;
+            }
+        }
+
+        MTLVertexStepFunction InputStepModeFunction(nxt::InputStepMode mode) {
+            switch (mode) {
+                case nxt::InputStepMode::Vertex:
+                    return MTLVertexStepFunctionPerVertex;
+                case nxt::InputStepMode::Instance:
+                    return MTLVertexStepFunctionPerInstance;
+            }
+        }
+    }
+
+    InputState::InputState(InputStateBuilder* builder)
+        : InputStateBase(builder) {
+        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;
+    }
+
+}
+}
diff --git a/src/backend/metal/MetalBackend.h b/src/backend/metal/MetalBackend.h
index f0f582d..968dedf 100644
--- a/src/backend/metal/MetalBackend.h
+++ b/src/backend/metal/MetalBackend.h
@@ -17,35 +17,18 @@
 
 #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/DepthStencilState.h"
-#include "common/InputState.h"
 #include "common/Framebuffer.h"
-#include "common/Pipeline.h"
-#include "common/PipelineLayout.h"
 #include "common/Queue.h"
 #include "common/RenderPass.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 {
 
@@ -152,70 +135,6 @@
             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;
-            void MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) override;
-            void UnmapImpl() override;
-            void TransitionUsageImpl(nxt::BufferUsageBit currentUsage, nxt::BufferUsageBit targetUsage) 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 DepthStencilState : public DepthStencilStateBase {
-        public:
-            DepthStencilState(Device* device, DepthStencilStateBuilder* builder);
-            ~DepthStencilState();
-
-            id<MTLDepthStencilState> GetMTLDepthStencilState();
-
-        private:
-            Device* device;
-
-            id<MTLDepthStencilState> mtlDepthStencilState = nil;
-    };
-
-    class InputState : public InputStateBase {
-        public:
-            InputState(Device* device, InputStateBuilder* builder);
-            ~InputState();
-
-            MTLVertexDescriptor* GetMTLVertexDescriptor();
-
-        private:
-            Device* device;
-            MTLVertexDescriptor* mtlVertexDescriptor = nil;
-    };
-
     class Framebuffer : public FramebufferBase {
         public:
             Framebuffer(Device* device, FramebufferBuilder* builder);
@@ -225,35 +144,6 @@
             Device* device;
     };
 
-    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<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);
@@ -278,54 +168,6 @@
             Device* device;
     };
 
-    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:
-            void TransitionUsageImpl(nxt::TextureUsageBit currentUsage, nxt::TextureUsageBit targetUsage) override;
-
-            Device* device;
-            id<MTLTexture> mtlTexture = nil;
-    };
-
-    class TextureView : public TextureViewBase {
-        public:
-            TextureView(Device* device, TextureViewBuilder* builder);
-
-        private:
-            Device* device;
-    };
-
 }
 }
 
diff --git a/src/backend/metal/MetalBackend.mm b/src/backend/metal/MetalBackend.mm
index e406bbf..5251603 100644
--- a/src/backend/metal/MetalBackend.mm
+++ b/src/backend/metal/MetalBackend.mm
@@ -16,11 +16,15 @@
 
 #include "MetalBackend.h"
 
-#include <spirv-cross/spirv_msl.hpp>
-
-#include <sstream>
-
-#include "common/Commands.h"
+#include "BufferMTL.h"
+#include "CommandBufferMTL.h"
+#include "DepthStencilStateMTL.h"
+#include "InputStateMTL.h"
+#include "PipelineMTL.h"
+#include "PipelineLayoutMTL.h"
+#include "SamplerMTL.h"
+#include "ShaderModuleMTL.h"
+#include "TextureMTL.h"
 
 namespace backend {
 namespace metal {
@@ -72,28 +76,28 @@
         return new BindGroupLayout(this, builder);
     }
     BufferBase* Device::CreateBuffer(BufferBuilder* builder) {
-        return new Buffer(this, builder);
+        return new Buffer(builder);
     }
     BufferViewBase* Device::CreateBufferView(BufferViewBuilder* builder) {
-        return new BufferView(this, builder);
+        return new BufferView(builder);
     }
     CommandBufferBase* Device::CreateCommandBuffer(CommandBufferBuilder* builder) {
         return new CommandBuffer(this, builder);
     }
     DepthStencilStateBase* Device::CreateDepthStencilState(DepthStencilStateBuilder* builder) {
-        return new DepthStencilState(this, builder);
+        return new DepthStencilState(builder);
     }
     InputStateBase* Device::CreateInputState(InputStateBuilder* builder) {
-        return new InputState(this, builder);
+        return new InputState(builder);
     }
     FramebufferBase* Device::CreateFramebuffer(FramebufferBuilder* builder) {
         return new Framebuffer(this, builder);
     }
     PipelineBase* Device::CreatePipeline(PipelineBuilder* builder) {
-        return new Pipeline(this, builder);
+        return new Pipeline(builder);
     }
     PipelineLayoutBase* Device::CreatePipelineLayout(PipelineLayoutBuilder* builder) {
-        return new PipelineLayout(this, builder);
+        return new PipelineLayout(builder);
     }
     QueueBase* Device::CreateQueue(QueueBuilder* builder) {
         return new Queue(this, builder);
@@ -102,16 +106,16 @@
         return new RenderPass(this, builder);
     }
     SamplerBase* Device::CreateSampler(SamplerBuilder* builder) {
-        return new Sampler(this, builder);
+        return new Sampler(builder);
     }
     ShaderModuleBase* Device::CreateShaderModule(ShaderModuleBuilder* builder) {
-        return new ShaderModule(this, builder);
+        return new ShaderModule(builder);
     }
     TextureBase* Device::CreateTexture(TextureBuilder* builder) {
-        return new Texture(this, builder);
+        return new Texture(builder);
     }
     TextureViewBase* Device::CreateTextureView(TextureViewBuilder* builder) {
-        return new TextureView(this, builder);
+        return new TextureView(builder);
     }
 
     void Device::TickImpl() {
@@ -197,669 +201,6 @@
         : 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))];
-    }
-
-    void Buffer::MapReadAsyncImpl(uint32_t serial, uint32_t start, uint32_t count) {
-        // TODO(cwallez@chromium.org): Implement Map Read for the metal backend
-    }
-
-    void Buffer::UnmapImpl() {
-        // TODO(cwallez@chromium.org): Implement Map Read for the metal backend
-    }
-
-    void Buffer::TransitionUsageImpl(nxt::BufferUsageBit currentUsage, nxt::BufferUsageBit targetUsage) {
-    }
-
-    // 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;
-
-            RenderPass* currentRenderPass = nullptr;
-            Framebuffer* currentFramebuffer = nullptr;
-
-            void FinishEncoders() {
-                ASSERT(render == nil);
-                if (blit != nil) {
-                    [blit endEncoding];
-                    blit = nil;
-                }
-                if (compute != nil) {
-                    [compute endEncoding];
-                    compute = 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 BeginSubpass(id<MTLCommandBuffer> commandBuffer, uint32_t subpass) {
-                ASSERT(currentRenderPass);
-                if (render != nil) {
-                    [render endEncoding];
-                    render = nil;
-                }
-
-                const auto& info = currentRenderPass->GetSubpassInfo(subpass);
-
-                MTLRenderPassDescriptor* descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
-                bool usingBackbuffer = false; // HACK(kainino@chromium.org): workaround for not having depth attachments
-                for (uint32_t index = 0; index < info.colorAttachments.size(); ++index) {
-                    uint32_t attachment = info.colorAttachments[index];
-
-                    // TODO(kainino@chromium.org): currently a 'null' texture view
-                    // falls back to the 'back buffer' but this should go away
-                    // when we have WSI.
-                    id<MTLTexture> texture = nil;
-                    if (auto textureView = currentFramebuffer->GetTextureView(attachment)) {
-                        texture = ToBackend(textureView->GetTexture())->GetMTLTexture();
-                    } else {
-                        texture = device->GetCurrentTexture();
-                        usingBackbuffer = true;
-                    }
-                    descriptor.colorAttachments[index].texture = texture;
-                    descriptor.colorAttachments[index].loadAction = MTLLoadActionLoad;
-                    descriptor.colorAttachments[index].storeAction = MTLStoreActionStore;
-                }
-                // TODO(kainino@chromium.org): load depth attachment from subpass
-                if (usingBackbuffer) {
-                    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 EndRenderPass() {
-                ASSERT(render != nil);
-                [render endEncoding];
-                render = nil;
-            }
-        };
-
-    }
-
-    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;
-
-        uint32_t currentSubpass = 0;
-        id<MTLRenderCommandEncoder> renderEncoder = nil;
-
-        while (commands.NextCommandId(&type)) {
-            switch (type) {
-                case Command::AdvanceSubpass:
-                    {
-                        commands.NextCommand<AdvanceSubpassCmd>();
-                        currentSubpass += 1;
-                        encoders.BeginSubpass(commandBuffer, currentSubpass);
-                    }
-                    break;
-
-                case Command::BeginRenderPass:
-                    {
-                        BeginRenderPassCmd* beginRenderPassCmd = commands.NextCommand<BeginRenderPassCmd>();
-                        encoders.currentRenderPass = ToBackend(beginRenderPassCmd->renderPass.Get());
-                        encoders.currentFramebuffer = ToBackend(beginRenderPassCmd->framebuffer.Get());
-                        encoders.FinishEncoders();
-                        currentSubpass = 0;
-                        encoders.BeginSubpass(commandBuffer, currentSubpass);
-                    }
-                    break;
-
-                case Command::CopyBufferToBuffer:
-                    {
-                        CopyBufferToBufferCmd* copy = commands.NextCommand<CopyBufferToBufferCmd>();
-
-                        encoders.EnsureBlit(commandBuffer);
-                        [encoders.blit
-                            copyFromBuffer:ToBackend(copy->source)->GetMTLBuffer()
-                            sourceOffset:copy->sourceOffset
-                            toBuffer:ToBackend(copy->destination)->GetMTLBuffer()
-                            destinationOffset:copy->destinationOffset
-                            size:copy->size];
-                    }
-                    break;
-
-                case Command::CopyBufferToTexture:
-                    {
-                        CopyBufferToTextureCmd* copy = commands.NextCommand<CopyBufferToTextureCmd>();
-                        Buffer* buffer = ToBackend(copy->buffer.Get());
-                        Texture* texture = ToBackend(copy->texture.Get());
-
-                        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:copy->bufferOffset
-                            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>();
-
-                        ASSERT(encoders.render);
-                        [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>();
-
-                        ASSERT(encoders.render);
-                        [encoders.render
-                            drawIndexedPrimitives:MTLPrimitiveTypeTriangle
-                            indexCount:draw->indexCount
-                            indexType:indexType
-                            indexBuffer:indexBuffer
-                            indexBufferOffset:indexBufferOffset
-                            instanceCount:draw->instanceCount
-                            baseVertex:0
-                            baseInstance:draw->firstInstance];
-                    }
-                    break;
-
-                case Command::EndRenderPass:
-                    {
-                        commands.NextCommand<EndRenderPassCmd>();
-                        encoders.EndRenderPass();
-                    }
-                    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 {
-                            ASSERT(encoders.render);
-                            DepthStencilState* depthStencilState = ToBackend(lastPipeline->GetDepthStencilState());
-                            [encoders.render setDepthStencilState:depthStencilState->GetMTLDepthStencilState()];
-                            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::SetStencilReference:
-                    {
-                        SetStencilReferenceCmd* cmd = commands.NextCommand<SetStencilReferenceCmd>();
-
-                        ASSERT(encoders.render);
-
-                        [encoders.render setStencilReferenceValue:cmd->reference];
-                    }
-                    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 {
-                            ASSERT(encoders.render);
-                        }
-
-                        // 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];
-                        }
-
-                        ASSERT(encoders.render);
-                        [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->UpdateUsageInternal(cmd->usage);
-                    }
-                    break;
-
-                case Command::TransitionTextureUsage:
-                    {
-                        TransitionTextureUsageCmd* cmd = commands.NextCommand<TransitionTextureUsageCmd>();
-
-                        cmd->texture->UpdateUsageInternal(cmd->usage);
-                    }
-                    break;
-;
-            }
-        }
-
-        encoders.FinishEncoders();
-    }
-
-    // DepthStencilState
-
-    static MTLCompareFunction MetalDepthStencilCompareFunction(nxt::CompareFunction compareFunction) {
-        switch (compareFunction) {
-            case nxt::CompareFunction::Never:
-                return MTLCompareFunctionNever;
-            case nxt::CompareFunction::Less:
-                return MTLCompareFunctionLess;
-            case nxt::CompareFunction::LessEqual:
-                return MTLCompareFunctionLessEqual;
-            case nxt::CompareFunction::Greater:
-                return MTLCompareFunctionGreater;
-            case nxt::CompareFunction::GreaterEqual:
-                return MTLCompareFunctionGreaterEqual;
-            case nxt::CompareFunction::NotEqual:
-                return MTLCompareFunctionNotEqual;
-            case nxt::CompareFunction::Equal:
-                return MTLCompareFunctionEqual;
-            case nxt::CompareFunction::Always:
-                return MTLCompareFunctionAlways;
-        }
-    }
-
-    static MTLStencilOperation MetalStencilOperation(nxt::StencilOperation stencilOperation) {
-        switch (stencilOperation) {
-            case nxt::StencilOperation::Keep:
-                return MTLStencilOperationKeep;
-            case nxt::StencilOperation::Zero:
-                return MTLStencilOperationZero;
-            case nxt::StencilOperation::Replace:
-                return MTLStencilOperationReplace;
-            case nxt::StencilOperation::Invert:
-                return MTLStencilOperationInvert;
-            case nxt::StencilOperation::IncrementClamp:
-                return MTLStencilOperationIncrementClamp;
-            case nxt::StencilOperation::DecrementClamp:
-                return MTLStencilOperationDecrementClamp;
-            case nxt::StencilOperation::IncrementWrap:
-                return MTLStencilOperationIncrementWrap;
-            case nxt::StencilOperation::DecrementWrap:
-                return MTLStencilOperationDecrementWrap;
-        }
-    }
-
-    DepthStencilState::DepthStencilState(Device* device, DepthStencilStateBuilder* builder)
-        : DepthStencilStateBase(builder), device(device) {
-        MTLDepthStencilDescriptor* mtlDepthStencilDescriptor = [MTLDepthStencilDescriptor new];
-
-        if (DepthTestEnabled()) {
-            auto& depth = GetDepth();
-            mtlDepthStencilDescriptor.depthCompareFunction = MetalDepthStencilCompareFunction(depth.compareFunction);
-            mtlDepthStencilDescriptor.depthWriteEnabled = depth.depthWriteEnabled;
-        }
-
-        auto& stencil = GetStencil();
-
-        if (StencilTestEnabled()) {
-            MTLStencilDescriptor* backFaceStencil = [MTLStencilDescriptor new];
-            MTLStencilDescriptor* frontFaceStencil = [MTLStencilDescriptor new];
-
-            backFaceStencil.stencilCompareFunction = MetalDepthStencilCompareFunction(stencil.back.compareFunction);
-            backFaceStencil.stencilFailureOperation = MetalStencilOperation(stencil.back.stencilFail);
-            backFaceStencil.depthFailureOperation = MetalStencilOperation(stencil.back.depthFail);
-            backFaceStencil.depthStencilPassOperation = MetalStencilOperation(stencil.back.depthStencilPass);
-            backFaceStencil.readMask = stencil.readMask;
-            backFaceStencil.writeMask = stencil.writeMask;
-
-            frontFaceStencil.stencilCompareFunction = MetalDepthStencilCompareFunction(stencil.front.compareFunction);
-            frontFaceStencil.stencilFailureOperation = MetalStencilOperation(stencil.front.stencilFail);
-            frontFaceStencil.depthFailureOperation = MetalStencilOperation(stencil.front.depthFail);
-            frontFaceStencil.depthStencilPassOperation = MetalStencilOperation(stencil.front.depthStencilPass);
-            frontFaceStencil.readMask = stencil.readMask;
-            frontFaceStencil.writeMask = stencil.writeMask;
-
-            mtlDepthStencilDescriptor.backFaceStencil = backFaceStencil;
-            mtlDepthStencilDescriptor.frontFaceStencil = frontFaceStencil;
-            [backFaceStencil release];
-            [frontFaceStencil release];
-        }
-
-        mtlDepthStencilState = [device->GetMTLDevice() newDepthStencilStateWithDescriptor:mtlDepthStencilDescriptor];
-        [mtlDepthStencilDescriptor release];
-    }
-
-    DepthStencilState::~DepthStencilState() {
-        [mtlDepthStencilState release];
-        mtlDepthStencilState = nil;
-    }
-
-    id<MTLDepthStencilState> DepthStencilState::GetMTLDepthStencilState() {
-        return mtlDepthStencilState;
-    }
-
-    // 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;
-    }
-
     // Framebuffer
 
     Framebuffer::Framebuffer(Device* device, FramebufferBuilder* builder)
@@ -869,135 +210,6 @@
     Framebuffer::~Framebuffer() {
     }
 
-    // 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);
-                builder->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);
-                builder->HandleError("Error creating pipeline state");
-                return;
-            }
-
-            [descriptor release];
-        }
-    }
-
-    Pipeline::~Pipeline() {
-        [mtlRenderPipelineState release];
-        [mtlComputePipelineState release];
-    }
-
-    void Pipeline::Encode(id<MTLRenderCommandEncoder> encoder) {
-        ASSERT(!IsCompute());
-        [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)
@@ -1049,127 +261,5 @@
     RenderPass::~RenderPass() {
     }
 
-    // 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);
-
-        std::string msl = compiler->compile();
-
-        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);
-            builder->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;
-    }
-
-    void Texture::TransitionUsageImpl(nxt::TextureUsageBit currentUsage, nxt::TextureUsageBit targetUsage) {
-    }
-
-    // TextureView
-
-    TextureView::TextureView(Device* device, TextureViewBuilder* builder)
-        : TextureViewBase(builder), device(device) {
-    }
-
 }
 }
diff --git a/src/backend/metal/PipelineLayoutMTL.h b/src/backend/metal/PipelineLayoutMTL.h
new file mode 100644
index 0000000..e944e33
--- /dev/null
+++ b/src/backend/metal/PipelineLayoutMTL.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_METAL_PIPELINELAYOUTMTL_H_
+#define BACKEND_METAL_PIPELINELAYOUTMTL_H_
+
+#include "common/PipelineLayout.h"
+
+#include "common/PerStage.h"
+
+#import <Metal/Metal.h>
+
+namespace spirv_cross {
+    class CompilerMSL;
+}
+
+namespace backend {
+namespace metal {
+
+    class PipelineLayout : public PipelineLayoutBase {
+        public:
+            PipelineLayout(PipelineLayoutBuilder* builder);
+
+            using BindingIndexInfo = std::array<std::array<uint32_t, kMaxBindingsPerGroup>, kMaxBindGroups>;
+            const BindingIndexInfo& GetBindingIndexInfo(nxt::ShaderStage stage) const;
+
+        private:
+            PerStage<BindingIndexInfo> indexInfo;
+    };
+
+}
+}
+
+#endif // BACKEND_METAL_PIPELINELAYOUTMTL_H_
diff --git a/src/backend/metal/PipelineLayoutMTL.mm b/src/backend/metal/PipelineLayoutMTL.mm
new file mode 100644
index 0000000..5fcadb9
--- /dev/null
+++ b/src/backend/metal/PipelineLayoutMTL.mm
@@ -0,0 +1,65 @@
+// 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 "PipelineLayoutMTL.h"
+
+#include "MetalBackend.h"
+
+namespace backend {
+namespace metal {
+
+    PipelineLayout::PipelineLayout(PipelineLayoutBuilder* builder)
+        : PipelineLayoutBase(builder) {
+        // 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];
+    }
+
+}
+}
diff --git a/src/backend/metal/PipelineMTL.h b/src/backend/metal/PipelineMTL.h
new file mode 100644
index 0000000..d3e97f2
--- /dev/null
+++ b/src/backend/metal/PipelineMTL.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_METAL_PIPELINEMTL_H_
+#define BACKEND_METAL_PIPELINEMTL_H_
+
+#include "common/Pipeline.h"
+
+#import <Metal/Metal.h>
+
+namespace backend {
+namespace metal {
+
+    class Pipeline : public PipelineBase {
+        public:
+            Pipeline(PipelineBuilder* builder);
+            ~Pipeline();
+
+            void Encode(id<MTLRenderCommandEncoder> encoder);
+            void Encode(id<MTLComputeCommandEncoder> encoder);
+            MTLSize GetLocalWorkGroupSize() const;
+
+        private:
+            id<MTLRenderPipelineState> mtlRenderPipelineState = nil;
+            id<MTLComputePipelineState> mtlComputePipelineState = nil;
+            MTLSize localWorkgroupSize;
+    };
+
+}
+}
+
+#endif // BACKEND_METAL_PIPELINEMTL_H_
diff --git a/src/backend/metal/PipelineMTL.mm b/src/backend/metal/PipelineMTL.mm
new file mode 100644
index 0000000..3732c9d
--- /dev/null
+++ b/src/backend/metal/PipelineMTL.mm
@@ -0,0 +1,112 @@
+// 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 "PipelineMTL.h"
+
+#include "DepthStencilStateMTL.h"
+#include "InputStateMTL.h"
+#include "MetalBackend.h"
+#include "PipelineLayoutMTL.h"
+#include "ShaderModuleMTL.h"
+
+namespace backend {
+namespace metal {
+
+    Pipeline::Pipeline(PipelineBuilder* builder)
+        : PipelineBase(builder) {
+
+        auto mtlDevice = ToBackend(builder->GetDevice())->GetMTLDevice();
+
+        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 = [mtlDevice
+                newComputePipelineStateWithFunction:function error:&error];
+            if (error != nil) {
+                NSLog(@" error => %@", error);
+                builder->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 = [mtlDevice
+                newRenderPipelineStateWithDescriptor:descriptor error:&error];
+            if (error != nil) {
+                NSLog(@" error => %@", error);
+                builder->HandleError("Error creating pipeline state");
+                return;
+            }
+
+            [descriptor release];
+        }
+    }
+
+    Pipeline::~Pipeline() {
+        [mtlRenderPipelineState release];
+        [mtlComputePipelineState release];
+    }
+
+    void Pipeline::Encode(id<MTLRenderCommandEncoder> encoder) {
+        ASSERT(!IsCompute());
+        [encoder setRenderPipelineState:mtlRenderPipelineState];
+    }
+
+    void Pipeline::Encode(id<MTLComputeCommandEncoder> encoder) {
+        ASSERT(IsCompute());
+        [encoder setComputePipelineState:mtlComputePipelineState];
+    }
+
+    MTLSize Pipeline::GetLocalWorkGroupSize() const {
+        return localWorkgroupSize;
+    }
+
+}
+}
diff --git a/src/backend/metal/SamplerMTL.h b/src/backend/metal/SamplerMTL.h
new file mode 100644
index 0000000..5990e55
--- /dev/null
+++ b/src/backend/metal/SamplerMTL.h
@@ -0,0 +1,39 @@
+// 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_SAMPLERMTL_H_
+#define BACKEND_METAL_SAMPLERMTL_H_
+
+#include "common/Sampler.h"
+
+#import <Metal/Metal.h>
+
+namespace backend {
+namespace metal {
+
+    class Sampler : public SamplerBase {
+        public:
+            Sampler(SamplerBuilder* builder);
+            ~Sampler();
+
+            id<MTLSamplerState> GetMTLSamplerState();
+
+        private:
+            id<MTLSamplerState> mtlSamplerState = nil;
+    };
+
+}
+}
+
+#endif // BACKEND_METAL_SAMPLERMTL_H_
diff --git a/src/backend/metal/SamplerMTL.mm b/src/backend/metal/SamplerMTL.mm
new file mode 100644
index 0000000..fbaa8b2
--- /dev/null
+++ b/src/backend/metal/SamplerMTL.mm
@@ -0,0 +1,64 @@
+// 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 "SamplerMTL.h"
+
+#include "MetalBackend.h"
+
+namespace backend {
+namespace metal {
+
+    namespace {
+        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(SamplerBuilder* builder)
+        : SamplerBase(builder) {
+        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
+        auto mtlDevice = ToBackend(builder->GetDevice())->GetMTLDevice();
+        mtlSamplerState = [mtlDevice newSamplerStateWithDescriptor:desc];
+    }
+
+    Sampler::~Sampler() {
+        [mtlSamplerState release];
+    }
+
+    id<MTLSamplerState> Sampler::GetMTLSamplerState() {
+        return mtlSamplerState;
+    }
+
+}
+}
diff --git a/src/backend/metal/ShaderModuleMTL.h b/src/backend/metal/ShaderModuleMTL.h
new file mode 100644
index 0000000..6b7c3f7
--- /dev/null
+++ b/src/backend/metal/ShaderModuleMTL.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_METAL_SHADERMODULEMTL_H_
+#define BACKEND_METAL_SHADERMODULEMTL_H_
+
+#include "common/ShaderModule.h"
+
+#import <Metal/Metal.h>
+
+namespace spirv_cross {
+    class CompilerMSL;
+}
+
+namespace backend {
+namespace metal {
+
+    class ShaderModule : public ShaderModuleBase {
+        public:
+            ShaderModule(ShaderModuleBuilder* builder);
+            ~ShaderModule();
+
+            id<MTLFunction> GetFunction(const char* functionName) const;
+            MTLSize GetLocalWorkGroupSize(const std::string& entryPoint) const;
+
+        private:
+            id<MTLLibrary> mtlLibrary = nil;
+            spirv_cross::CompilerMSL* compiler = nullptr;
+    };
+
+}
+}
+
+#endif // BACKEND_METAL_SHADERMODULEMTL_H_
diff --git a/src/backend/metal/ShaderModuleMTL.mm b/src/backend/metal/ShaderModuleMTL.mm
new file mode 100644
index 0000000..5247eaf
--- /dev/null
+++ b/src/backend/metal/ShaderModuleMTL.mm
@@ -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.
+
+#include "ShaderModuleMTL.h"
+
+#include "MetalBackend.h"
+
+#include <spirv-cross/spirv_msl.hpp>
+
+#include <sstream>
+
+namespace backend {
+namespace metal {
+
+    ShaderModule::ShaderModule(ShaderModuleBuilder* builder)
+        : ShaderModuleBase(builder) {
+        compiler = new spirv_cross::CompilerMSL(builder->AcquireSpirv());
+        ExtractSpirvInfo(*compiler);
+
+        std::string msl = compiler->compile();
+        NSString* mslSource = [NSString stringWithFormat:@"%s", msl.c_str()];
+
+        auto mtlDevice = ToBackend(builder->GetDevice())->GetMTLDevice();
+        NSError *error = nil;
+        mtlLibrary = [mtlDevice newLibraryWithSource:mslSource options:nil error:&error];
+        if (error != nil) {
+            NSLog(@"MTLDevice newLibraryWithSource => %@", error);
+            builder->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);
+    }
+
+}
+}
diff --git a/src/backend/metal/TextureMTL.h b/src/backend/metal/TextureMTL.h
new file mode 100644
index 0000000..bc72cd1
--- /dev/null
+++ b/src/backend/metal/TextureMTL.h
@@ -0,0 +1,46 @@
+// 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_TEXTUREMTL_H_
+#define BACKEND_METAL_TEXTUREMTL_H_
+
+#include "common/Texture.h"
+
+#import <Metal/Metal.h>
+
+namespace backend {
+namespace metal {
+
+    class Texture : public TextureBase {
+        public:
+            Texture(TextureBuilder* builder);
+            ~Texture();
+
+            id<MTLTexture> GetMTLTexture();
+
+        private:
+            void TransitionUsageImpl(nxt::TextureUsageBit currentUsage, nxt::TextureUsageBit targetUsage) override;
+
+            id<MTLTexture> mtlTexture = nil;
+    };
+
+    class TextureView : public TextureViewBase {
+        public:
+            TextureView(TextureViewBuilder* builder);
+    };
+
+}
+}
+
+#endif // BACKEND_METAL_TEXTUREMTL_H_
diff --git a/src/backend/metal/TextureMTL.mm b/src/backend/metal/TextureMTL.mm
new file mode 100644
index 0000000..36b8a45
--- /dev/null
+++ b/src/backend/metal/TextureMTL.mm
@@ -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 "TextureMTL.h"
+
+#include "MetalBackend.h"
+
+namespace backend {
+namespace metal {
+
+    namespace {
+        MTLPixelFormat TextureFormatPixelFormat(nxt::TextureFormat format) {
+            switch (format) {
+                case nxt::TextureFormat::R8G8B8A8Unorm:
+                    return MTLPixelFormatRGBA8Unorm;
+            }
+        }
+    }
+
+    Texture::Texture(TextureBuilder* builder)
+        : TextureBase(builder) {
+        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;
+
+        auto mtlDevice = ToBackend(builder->GetDevice())->GetMTLDevice();
+        mtlTexture = [mtlDevice newTextureWithDescriptor:desc];
+    }
+
+    Texture::~Texture() {
+        [mtlTexture release];
+    }
+
+    id<MTLTexture> Texture::GetMTLTexture() {
+        return mtlTexture;
+    }
+
+    void Texture::TransitionUsageImpl(nxt::TextureUsageBit currentUsage, nxt::TextureUsageBit targetUsage) {
+    }
+
+    TextureView::TextureView(TextureViewBuilder* builder)
+        : TextureViewBase(builder) {
+    }
+}
+}
diff --git a/src/backend/opengl/CommandBufferGL.h b/src/backend/opengl/CommandBufferGL.h
index 1492551..d380f42 100644
--- a/src/backend/opengl/CommandBufferGL.h
+++ b/src/backend/opengl/CommandBufferGL.h
@@ -12,20 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef BACKEND_OPENGL_COMMANDBUFFER_H_
-#define BACKEND_OPENGL_COMMANDBUFFER_H_
+#ifndef BACKEND_OPENGL_COMMANDBUFFERGL_H_
+#define BACKEND_OPENGL_COMMANDBUFFERGL_H_
 
 #include "common/CommandAllocator.h"
 #include "common/CommandBuffer.h"
 
 namespace backend {
-    class CommandBufferBuilder;
-}
-
-namespace backend {
 namespace opengl {
 
-    class  Device;
+    class Device;
 
     class CommandBuffer : public CommandBufferBase {
         public:
@@ -42,4 +38,4 @@
 }
 }
 
-#endif // BACKEND_OPENGL_COMMANDBUFFER_H_
+#endif // BACKEND_OPENGL_COMMANDBUFFERGL_H_