Initial commit of all the NXT integration.

More like squashed history, contributors were:
 - Kai Ninomiya
 - Corentin Wallez
diff --git a/src/backend/opengl/CommandBufferGL.cpp b/src/backend/opengl/CommandBufferGL.cpp
new file mode 100644
index 0000000..a62da6e
--- /dev/null
+++ b/src/backend/opengl/CommandBufferGL.cpp
@@ -0,0 +1,303 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "CommandBufferGL.h"
+
+#include "common/Commands.h"
+#include "OpenGLBackend.h"
+#include "PipelineGL.h"
+#include "PipelineLayoutGL.h"
+#include "SamplerGL.h"
+#include "TextureGL.h"
+
+#include <cstring>
+
+namespace backend {
+namespace opengl {
+
+    CommandBuffer::CommandBuffer(Device* device, CommandBufferBuilder* builder)
+        : CommandBufferBase(builder), device(device), commands(builder->AcquireCommands()) {
+    }
+
+    CommandBuffer::~CommandBuffer() {
+        FreeCommands(&commands);
+    }
+
+    static GLenum IndexFormatType(nxt::IndexFormat format) {
+        switch (format) {
+            case nxt::IndexFormat::Uint16:
+                return GL_UNSIGNED_SHORT;
+            case nxt::IndexFormat::Uint32:
+                return GL_UNSIGNED_INT;
+        }
+    }
+
+    static GLenum VertexFormatType(nxt::VertexFormat format) {
+        switch (format) {
+            case nxt::VertexFormat::FloatR32G32B32A32:
+            case nxt::VertexFormat::FloatR32G32B32:
+            case nxt::VertexFormat::FloatR32G32:
+                return GL_FLOAT;
+        }
+    }
+
+    void CommandBuffer::Execute() {
+        Command type;
+        Pipeline* lastPipeline = nullptr;
+        uint32_t indexBufferOffset = 0;
+        nxt::IndexFormat indexBufferFormat = nxt::IndexFormat::Uint16;
+
+        while(commands.NextCommandId(&type)) {
+            switch (type) {
+
+                case Command::CopyBufferToTexture:
+                    {
+                        CopyBufferToTextureCmd* copy = commands.NextCommand<CopyBufferToTextureCmd>();
+                        Buffer* buffer = ToBackend(copy->buffer.Get());
+                        Texture* texture = ToBackend(copy->texture.Get());
+                        GLenum target = texture->GetGLTarget();
+                        auto format = texture->GetGLFormat();
+
+                        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->GetHandle());
+                        glActiveTexture(GL_TEXTURE0);
+                        glBindTexture(target, texture->GetHandle());
+
+                        glTexSubImage2D(target, copy->level, copy->x, copy->y, copy->width, copy->height,
+                                        format.format, format.type, nullptr);
+                        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+                    }
+                    break;
+
+                case Command::Dispatch:
+                    {
+                        DispatchCmd* dispatch = commands.NextCommand<DispatchCmd>();
+                        glDispatchCompute(dispatch->x, dispatch->y, dispatch->z);
+                        // TODO(cwallez@chromium.org): add barriers to the API
+                        glMemoryBarrier(GL_ALL_BARRIER_BITS);
+                    }
+                    break;
+
+                case Command::DrawArrays:
+                    {
+                        DrawArraysCmd* draw = commands.NextCommand<DrawArraysCmd>();
+                        if (draw->firstInstance > 0) {
+                            glDrawArraysInstancedBaseInstance(GL_TRIANGLES,
+                                draw->firstVertex, draw->vertexCount, draw->instanceCount, draw->firstInstance);
+                        } else {
+                            // This branch is only needed on OpenGL < 4.2
+                            glDrawArraysInstanced(GL_TRIANGLES,
+                                draw->firstVertex, draw->vertexCount, draw->instanceCount);
+                        }
+                    }
+                    break;
+
+                case Command::DrawElements:
+                    {
+                        DrawElementsCmd* draw = commands.NextCommand<DrawElementsCmd>();
+                        size_t formatSize = IndexFormatSize(indexBufferFormat);
+                        GLenum formatType = IndexFormatType(indexBufferFormat);
+
+                        if (draw->firstInstance > 0) {
+                            glDrawElementsInstancedBaseInstance(GL_TRIANGLES,
+                                draw->indexCount, formatType,
+                                reinterpret_cast<void*>(draw->firstIndex * formatSize + indexBufferOffset),
+                                draw->instanceCount, draw->firstInstance);
+                        } else {
+                            // This branch is only needed on OpenGL < 4.2
+                            glDrawElementsInstanced(GL_TRIANGLES,
+                                draw->indexCount, formatType,
+                                reinterpret_cast<void*>(draw->firstIndex * formatSize + indexBufferOffset),
+                                draw->instanceCount);
+                        }
+                    }
+                    break;
+
+                case Command::SetPipeline:
+                    {
+                        SetPipelineCmd* cmd = commands.NextCommand<SetPipelineCmd>();
+                        ToBackend(cmd->pipeline)->ApplyNow();
+                        lastPipeline = ToBackend(cmd->pipeline).Get();
+                    }
+                    break;
+
+                case Command::SetPushConstants:
+                    {
+                        SetPushConstantsCmd* cmd = commands.NextCommand<SetPushConstantsCmd>();
+                        uint32_t* valuesUInt = commands.NextData<uint32_t>(cmd->count);
+                        int32_t* valuesInt = reinterpret_cast<int32_t*>(valuesUInt);
+                        float* valuesFloat = reinterpret_cast<float*>(valuesUInt);
+
+                        for (auto stage : IterateStages(cmd->stage)) {
+                            const auto& pushConstants = lastPipeline->GetPushConstants(stage);
+                            const auto& glPushConstants = lastPipeline->GetGLPushConstants(stage);
+                            for (size_t i = 0; i < cmd->count; i++) {
+                                GLint location = glPushConstants[cmd->offset + i];
+
+                                switch (pushConstants.types[cmd->offset + i]) {
+                                    case PushConstantType::Int:
+                                        glUniform1i(location, valuesInt[i]);
+                                        break;
+                                    case PushConstantType::UInt:
+                                        glUniform1ui(location, valuesUInt[i]);
+                                        break;
+                                    case PushConstantType::Float:
+                                        glUniform1f(location, valuesFloat[i]);
+                                        break;
+                                }
+                            }
+                        }
+                    }
+                    break;
+
+                case Command::SetBindGroup:
+                    {
+                        SetBindGroupCmd* cmd = commands.NextCommand<SetBindGroupCmd>();
+                        size_t index = cmd->index;
+                        BindGroup* group = ToBackend(cmd->group.Get());
+
+                        const auto& indices = ToBackend(lastPipeline->GetLayout())->GetBindingIndexInfo()[index];
+                        const auto& layout = group->GetLayout()->GetBindingInfo();
+
+                        // TODO(cwallez@chromium.org): iterate over the layout bitmask instead
+                        for (size_t binding = 0; binding < kMaxBindingsPerGroup; ++binding) {
+                            if (!layout.mask[binding]) {
+                                continue;
+                            }
+
+                            switch (layout.types[binding]) {
+                                case nxt::BindingType::UniformBuffer:
+                                    {
+                                        BufferView* view = ToBackend(group->GetBindingAsBufferView(binding));
+                                        GLuint buffer = ToBackend(view->GetBuffer())->GetHandle();
+                                        GLuint index = indices[binding];
+
+                                        glBindBufferRange(GL_UNIFORM_BUFFER, index, buffer, view->GetOffset(), view->GetSize());
+                                    }
+                                    break;
+
+                                case nxt::BindingType::Sampler:
+                                    {
+                                        GLuint sampler = ToBackend(group->GetBindingAsSampler(binding))->GetHandle();
+                                        GLuint index = indices[binding];
+
+                                        for (auto unit : lastPipeline->GetTextureUnitsForSampler(index)) {
+                                            glBindSampler(unit, sampler);
+                                        }
+                                    }
+                                    break;
+
+                                case nxt::BindingType::SampledTexture:
+                                    {
+                                        TextureView* view = ToBackend(group->GetBindingAsTextureView(binding));
+                                        Texture* texture = ToBackend(view->GetTexture());
+                                        GLuint handle = texture->GetHandle();
+                                        GLenum target = texture->GetGLTarget();
+                                        GLuint index = indices[binding];
+
+                                        for (auto unit : lastPipeline->GetTextureUnitsForTexture(index)) {
+                                            glActiveTexture(GL_TEXTURE0 + unit);
+                                            glBindTexture(target, handle);
+                                        }
+                                    }
+                                    break;
+
+                                case nxt::BindingType::StorageBuffer:
+                                    {
+                                        BufferView* view = ToBackend(group->GetBindingAsBufferView(binding));
+                                        GLuint buffer = ToBackend(view->GetBuffer())->GetHandle();
+                                        GLuint index = indices[binding];
+
+                                        glBindBufferRange(GL_SHADER_STORAGE_BUFFER, index, buffer, view->GetOffset(), view->GetSize());
+                                    }
+                                    break;
+                            }
+                        }
+                    }
+                    break;
+
+                case Command::SetIndexBuffer:
+                    {
+                        SetIndexBufferCmd* cmd = commands.NextCommand<SetIndexBufferCmd>();
+
+                        GLuint buffer = ToBackend(cmd->buffer.Get())->GetHandle();
+                        indexBufferOffset = cmd->offset;
+                        indexBufferFormat = cmd->format;
+                        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
+                    }
+                    break;
+
+                case Command::SetVertexBuffers:
+                    {
+                        SetVertexBuffersCmd* cmd = commands.NextCommand<SetVertexBuffersCmd>();
+                        auto buffers = commands.NextData<Ref<BufferBase>>(cmd->count);
+                        auto offsets = commands.NextData<uint32_t>(cmd->count);
+
+                        auto inputState = lastPipeline->GetInputState();
+
+                        auto& attributesSetMask = inputState->GetAttributesSetMask();
+                        for (uint32_t location = 0; location < attributesSetMask.size(); ++location) {
+                            if (!attributesSetMask[location]) {
+                                // This slot is not used in the input state
+                                continue;
+                            }
+                            auto attribute = inputState->GetAttribute(location);
+                            auto slot = attribute.bindingSlot;
+                            ASSERT(slot < kMaxVertexInputs);
+                            if (slot < cmd->startSlot || slot >= cmd->startSlot + cmd->count) {
+                                // This slot is not affected by this call
+                                continue;
+                            }
+                            size_t bufferIndex = slot - cmd->startSlot;
+                            GLuint buffer = ToBackend(buffers[bufferIndex])->GetHandle();
+                            uint32_t bufferOffset = offsets[bufferIndex];
+
+                            auto input = inputState->GetInput(slot);
+
+                            auto components = VertexFormatNumComponents(attribute.format);
+                            auto formatType = VertexFormatType(attribute.format);
+
+                            glBindBuffer(GL_ARRAY_BUFFER, buffer);
+                            glVertexAttribPointer(
+                                    location, components, formatType, GL_FALSE,
+                                    input.stride,
+                                    reinterpret_cast<void*>(static_cast<intptr_t>(bufferOffset + attribute.offset)));
+                        }
+                    }
+                    break;
+
+                case Command::TransitionBufferUsage:
+                    {
+                        TransitionBufferUsageCmd* cmd = commands.NextCommand<TransitionBufferUsageCmd>();
+
+                        cmd->buffer->TransitionUsageImpl(cmd->usage);
+                    }
+                    break;
+
+                case Command::TransitionTextureUsage:
+                    {
+                        TransitionTextureUsageCmd* cmd = commands.NextCommand<TransitionTextureUsageCmd>();
+
+                        cmd->texture->TransitionUsageImpl(cmd->usage);
+                    }
+                    break;
+            }
+        }
+
+        // HACK: cleanup a tiny bit of state to make this work with
+        // virtualized contexts enabled in Chromium
+        glBindSampler(0, 0);
+    }
+
+}
+}
diff --git a/src/backend/opengl/CommandBufferGL.h b/src/backend/opengl/CommandBufferGL.h
new file mode 100644
index 0000000..1492551
--- /dev/null
+++ b/src/backend/opengl/CommandBufferGL.h
@@ -0,0 +1,45 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_COMMANDBUFFER_H_
+#define BACKEND_OPENGL_COMMANDBUFFER_H_
+
+#include "common/CommandAllocator.h"
+#include "common/CommandBuffer.h"
+
+namespace backend {
+    class CommandBufferBuilder;
+}
+
+namespace backend {
+namespace opengl {
+
+    class  Device;
+
+    class CommandBuffer : public CommandBufferBase {
+        public:
+            CommandBuffer(Device* device, CommandBufferBuilder* builder);
+            ~CommandBuffer();
+
+            void Execute();
+
+        private:
+            Device* device;
+            CommandIterator commands;
+    };
+
+}
+}
+
+#endif // BACKEND_OPENGL_COMMANDBUFFER_H_
diff --git a/src/backend/opengl/GeneratedCodeIncludes.h b/src/backend/opengl/GeneratedCodeIncludes.h
new file mode 100644
index 0000000..f757618
--- /dev/null
+++ b/src/backend/opengl/GeneratedCodeIncludes.h
@@ -0,0 +1,21 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "OpenGLBackend.h"
+#include "CommandBufferGL.h"
+#include "PipelineGL.h"
+#include "PipelineLayoutGL.h"
+#include "SamplerGL.h"
+#include "ShaderModuleGL.h"
+#include "TextureGL.h"
diff --git a/src/backend/opengl/OpenGLBackend.cpp b/src/backend/opengl/OpenGLBackend.cpp
new file mode 100644
index 0000000..7455626
--- /dev/null
+++ b/src/backend/opengl/OpenGLBackend.cpp
@@ -0,0 +1,180 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "OpenGLBackend.h"
+
+#include "CommandBufferGL.h"
+#include "PipelineGL.h"
+#include "PipelineLayoutGL.h"
+#include "ShaderModuleGL.h"
+#include "SamplerGL.h"
+#include "TextureGL.h"
+
+namespace backend {
+namespace opengl {
+    nxtProcTable GetNonValidatingProcs();
+    nxtProcTable GetValidatingProcs();
+
+    void HACKCLEAR() {
+        glClearColor(0, 0, 0, 1);
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+    }
+
+    void Init(void* (*getProc)(const char*), nxtProcTable* procs, nxtDevice* device) {
+        *device = nullptr;
+
+        gladLoadGLLoader(reinterpret_cast<GLADloadproc>(getProc));
+
+        glEnable(GL_DEPTH_TEST);
+        HACKCLEAR();
+
+        *procs = GetValidatingProcs();
+        *device = reinterpret_cast<nxtDevice>(new Device);
+    }
+
+    // Device
+
+    BindGroupBase* Device::CreateBindGroup(BindGroupBuilder* builder) {
+        return new BindGroup(this, builder);
+    }
+    BindGroupLayoutBase* Device::CreateBindGroupLayout(BindGroupLayoutBuilder* builder) {
+        return new BindGroupLayout(this, builder);
+    }
+    BufferBase* Device::CreateBuffer(BufferBuilder* builder) {
+        return new Buffer(this, builder);
+    }
+    BufferViewBase* Device::CreateBufferView(BufferViewBuilder* builder) {
+        return new BufferView(this, builder);
+    }
+    CommandBufferBase* Device::CreateCommandBuffer(CommandBufferBuilder* builder) {
+        return new CommandBuffer(this, builder);
+    }
+    InputStateBase* Device::CreateInputState(InputStateBuilder* builder) {
+        return new InputState(this, builder);
+    }
+    PipelineBase* Device::CreatePipeline(PipelineBuilder* builder) {
+        return new Pipeline(this, builder);
+    }
+    PipelineLayoutBase* Device::CreatePipelineLayout(PipelineLayoutBuilder* builder) {
+        return new PipelineLayout(this, builder);
+    }
+    QueueBase* Device::CreateQueue(QueueBuilder* builder) {
+        return new Queue(this, builder);
+    }
+    SamplerBase* Device::CreateSampler(SamplerBuilder* builder) {
+        return new Sampler(this, builder);
+    }
+    ShaderModuleBase* Device::CreateShaderModule(ShaderModuleBuilder* builder) {
+        return new ShaderModule(this, builder);
+    }
+    TextureBase* Device::CreateTexture(TextureBuilder* builder) {
+        return new Texture(this, builder);
+    }
+    TextureViewBase* Device::CreateTextureView(TextureViewBuilder* builder) {
+        return new TextureView(this, builder);
+    }
+
+    void Device::Reference() {
+    }
+
+    void Device::Release() {
+    }
+
+    // Bind Group
+
+    BindGroup::BindGroup(Device* device, BindGroupBuilder* builder)
+        : BindGroupBase(builder), device(device) {
+    }
+
+    // Bind Group Layout
+
+    BindGroupLayout::BindGroupLayout(Device* device, BindGroupLayoutBuilder* builder)
+        : BindGroupLayoutBase(builder), device(device) {
+    }
+
+    // Buffer
+
+    Buffer::Buffer(Device* device, BufferBuilder* builder)
+        : BufferBase(builder), device(device) {
+        glGenBuffers(1, &buffer);
+        glBindBuffer(GL_ARRAY_BUFFER, buffer);
+        glBufferData(GL_ARRAY_BUFFER, GetSize(), nullptr, GL_STATIC_DRAW);
+    }
+
+    GLuint Buffer::GetHandle() const {
+        return buffer;
+    }
+
+    void Buffer::SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) {
+        glBindBuffer(GL_ARRAY_BUFFER, buffer);
+        glBufferSubData(GL_ARRAY_BUFFER, start * sizeof(uint32_t), count * sizeof(uint32_t), data);
+    }
+
+    // BufferView
+
+    BufferView::BufferView(Device* device, BufferViewBuilder* builder)
+        : BufferViewBase(builder), device(device) {
+    }
+
+    // InputState
+
+    InputState::InputState(Device* device, InputStateBuilder* builder)
+        : InputStateBase(builder), device(device) {
+        glGenVertexArrays(1, &vertexArrayObject);
+        glBindVertexArray(vertexArrayObject);
+        auto& attributesSetMask = GetAttributesSetMask();
+        for (uint32_t location = 0; location < attributesSetMask.size(); ++location) {
+            if (!attributesSetMask[location]) {
+                continue;
+            }
+            auto attribute = GetAttribute(location);
+            glEnableVertexAttribArray(location);
+
+            auto input = GetInput(attribute.bindingSlot);
+            if (input.stride == 0) {
+                // Emulate a stride of zero (constant vertex attribute) by
+                // setting the attribute instance divisor to a huge number.
+                glVertexAttribDivisor(location, 0xffffffff);
+            } else {
+                switch (input.stepMode) {
+                    case nxt::InputStepMode::Vertex:
+                        break;
+                    case nxt::InputStepMode::Instance:
+                        glVertexAttribDivisor(location, 1);
+                        break;
+                    default:
+                        ASSERT(false);
+                        break;
+                }
+            }
+        }
+    }
+
+    GLuint InputState::GetVAO() {
+        return vertexArrayObject;
+    }
+
+    // Queue
+
+    Queue::Queue(Device* device, QueueBuilder* builder) : device(device) {
+    }
+
+    void Queue::Submit(uint32_t numCommands, CommandBuffer* const * commands) {
+        for (uint32_t i = 0; i < numCommands; ++i) {
+            commands[i]->Execute();
+        }
+    }
+
+}
+}
diff --git a/src/backend/opengl/OpenGLBackend.h b/src/backend/opengl/OpenGLBackend.h
new file mode 100644
index 0000000..41742ea
--- /dev/null
+++ b/src/backend/opengl/OpenGLBackend.h
@@ -0,0 +1,151 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_OPENGLBACKEND_H_
+#define BACKEND_OPENGL_OPENGLBACKEND_H_
+
+#include "nxt/nxtcpp.h"
+
+#include "common/Buffer.h"
+#include "common/BindGroup.h"
+#include "common/BindGroupLayout.h"
+#include "common/Device.h"
+#include "common/InputState.h"
+#include "common/Queue.h"
+#include "common/ToBackend.h"
+
+#include "glad/glad.h"
+
+namespace backend {
+namespace opengl {
+
+    class BindGroup;
+    class BindGroupLayout;
+    class Buffer;
+    class BufferView;
+    class CommandBuffer;
+    class InputState;
+    class Pipeline;
+    class PipelineLayout;
+    class Queue;
+    class Sampler;
+    class ShaderModule;
+    class Texture;
+    class TextureView;
+
+    struct OpenGLBackendTraits {
+        using BindGroupType = BindGroup;
+        using BindGroupLayoutType = BindGroupLayout;
+        using BufferType = Buffer;
+        using BufferViewType = BufferView;
+        using CommandBufferType = CommandBuffer;
+        using InputStateType = InputState;
+        using PipelineType = Pipeline;
+        using PipelineLayoutType = PipelineLayout;
+        using QueueType = Queue;
+        using SamplerType = Sampler;
+        using ShaderModuleType = ShaderModule;
+        using TextureType = Texture;
+        using TextureViewType = TextureView;
+    };
+
+    template<typename T>
+    auto ToBackend(T&& common) -> decltype(ToBackendBase<OpenGLBackendTraits>(common)) {
+        return ToBackendBase<OpenGLBackendTraits>(common);
+    }
+
+    // Definition of backend types
+    class Device : public DeviceBase {
+        public:
+            BindGroupBase* CreateBindGroup(BindGroupBuilder* builder) override;
+            BindGroupLayoutBase* CreateBindGroupLayout(BindGroupLayoutBuilder* builder) override;
+            BufferBase* CreateBuffer(BufferBuilder* builder) override;
+            BufferViewBase* CreateBufferView(BufferViewBuilder* builder) override;
+            CommandBufferBase* CreateCommandBuffer(CommandBufferBuilder* builder) override;
+            InputStateBase* CreateInputState(InputStateBuilder* builder) override;
+            PipelineBase* CreatePipeline(PipelineBuilder* builder) override;
+            PipelineLayoutBase* CreatePipelineLayout(PipelineLayoutBuilder* builder) override;
+            QueueBase* CreateQueue(QueueBuilder* builder) override;
+            SamplerBase* CreateSampler(SamplerBuilder* builder) override;
+            ShaderModuleBase* CreateShaderModule(ShaderModuleBuilder* builder) override;
+            TextureBase* CreateTexture(TextureBuilder* builder) override;
+            TextureViewBase* CreateTextureView(TextureViewBuilder* builder) override;
+
+            // NXT API
+            void Reference();
+            void Release();
+    };
+
+    class BindGroup : public BindGroupBase {
+        public:
+            BindGroup(Device* device, BindGroupBuilder* builder);
+
+        private:
+            Device* device;
+    };
+
+    class BindGroupLayout : public BindGroupLayoutBase {
+        public:
+            BindGroupLayout(Device* device, BindGroupLayoutBuilder* builder);
+
+        private:
+            Device* device;
+    };
+
+    class Buffer : public BufferBase {
+        public:
+            Buffer(Device* device, BufferBuilder* builder);
+
+            GLuint GetHandle() const;
+
+        private:
+            void SetSubDataImpl(uint32_t start, uint32_t count, const uint32_t* data) override;
+
+            Device* device;
+            GLuint buffer = 0;
+    };
+
+    class BufferView : public BufferViewBase {
+        public:
+            BufferView(Device* device, BufferViewBuilder* builder);
+
+        private:
+            Device* device;
+    };
+
+    class InputState : public InputStateBase {
+        public:
+            InputState(Device* device, InputStateBuilder* builder);
+            GLuint GetVAO();
+
+        private:
+            Device* device;
+            GLuint vertexArrayObject;
+    };
+
+    class Queue : public QueueBase {
+        public:
+            Queue(Device* device, QueueBuilder* builder);
+
+            // NXT API
+            void Submit(uint32_t numCommands, CommandBuffer* const * commands);
+
+        private:
+            Device* device;
+    };
+
+}
+}
+
+#endif // BACKEND_OPENGL_OPENGLBACKEND_H_
diff --git a/src/backend/opengl/PipelineGL.cpp b/src/backend/opengl/PipelineGL.cpp
new file mode 100644
index 0000000..50b9f08
--- /dev/null
+++ b/src/backend/opengl/PipelineGL.cpp
@@ -0,0 +1,213 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "PipelineGL.h"
+
+#include "OpenGLBackend.h"
+#include "PipelineLayoutGL.h"
+#include "ShaderModuleGL.h"
+
+#include <iostream>
+#include <set>
+
+namespace backend {
+namespace opengl {
+
+    namespace {
+
+        GLenum GLShaderType(nxt::ShaderStage stage) {
+            switch (stage) {
+                case nxt::ShaderStage::Vertex:
+                    return GL_VERTEX_SHADER;
+                case nxt::ShaderStage::Fragment:
+                    return GL_FRAGMENT_SHADER;
+                case nxt::ShaderStage::Compute:
+                    return GL_COMPUTE_SHADER;
+            }
+        }
+
+    }
+
+    Pipeline::Pipeline(Device* device, PipelineBuilder* builder) : PipelineBase(builder), device(device) {
+        auto CreateShader = [](GLenum type, const char* source) -> GLuint {
+            GLuint shader = glCreateShader(type);
+            glShaderSource(shader, 1, &source, nullptr);
+            glCompileShader(shader);
+
+            GLint compileStatus = GL_FALSE;
+            glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
+            if (compileStatus == GL_FALSE) {
+                GLint infoLogLength = 0;
+                glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+                if (infoLogLength > 1) {
+                    std::vector<char> buffer(infoLogLength);
+                    glGetShaderInfoLog(shader, infoLogLength, nullptr, &buffer[0]);
+                    std::cout << source << std::endl;
+                    std::cout << "Program compilation failed:\n";
+                    std::cout << buffer.data() << std::endl;
+                }
+            }
+            return shader;
+        };
+
+        auto FillPushConstants = [](const ShaderModule* module, GLPushConstantInfo* info, GLuint program) {
+            const auto& moduleInfo = module->GetPushConstants();
+            for (uint32_t i = 0; i < moduleInfo.names.size(); i++) {
+                (*info)[i] = -1;
+
+                unsigned int size = moduleInfo.sizes[i];
+                if (size == 0) {
+                    continue;
+                }
+
+                GLint location = glGetUniformLocation(program, moduleInfo.names[i].c_str());
+                if (location == -1) {
+                    continue;
+                }
+
+                for (uint32_t offset = 0; offset < size; offset++) {
+                    (*info)[i + offset] = location + offset;
+                }
+                i += size - 1;
+            }
+        };
+
+        program = glCreateProgram();
+
+        for (auto stage : IterateStages(GetStageMask())) {
+            const ShaderModule* module = ToBackend(builder->GetStageInfo(stage).module.Get());
+
+            GLuint shader = CreateShader(GLShaderType(stage), module->GetSource());
+            glAttachShader(program, shader);
+        }
+
+        glLinkProgram(program);
+
+        GLint linkStatus = GL_FALSE;
+        glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+        if (linkStatus == GL_FALSE) {
+            GLint infoLogLength = 0;
+            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
+
+            if (infoLogLength > 1) {
+                std::vector<char> buffer(infoLogLength);
+                glGetProgramInfoLog(program, infoLogLength, nullptr, &buffer[0]);
+                std::cout << "Program link failed:\n";
+                std::cout << buffer.data() << std::endl;
+            }
+        }
+
+        for (auto stage : IterateStages(GetStageMask())) {
+            const ShaderModule* module = ToBackend(builder->GetStageInfo(stage).module.Get());
+            FillPushConstants(module, &glPushConstants[stage], program);
+        }
+
+        glUseProgram(program);
+
+        // The uniforms are part of the program state so we can pre-bind buffer units, texture units etc.
+        const auto& layout = ToBackend(GetLayout());
+        const auto& indices = layout->GetBindingIndexInfo();
+
+        for (uint32_t group = 0; group < kMaxBindGroups; ++group) {
+            const auto& groupInfo = layout->GetBindGroupLayout(group)->GetBindingInfo();
+
+            for (uint32_t binding = 0; binding < kMaxBindingsPerGroup; ++binding) {
+                if (!groupInfo.mask[binding]) {
+                    continue;
+                }
+
+                std::string name = GetBindingName(group, binding);
+                switch (groupInfo.types[binding]) {
+                    case nxt::BindingType::UniformBuffer:
+                        {
+                            GLint location = glGetUniformBlockIndex(program, name.c_str());
+                            glUniformBlockBinding(program, location, indices[group][binding]);
+                        }
+                        break;
+
+                    case nxt::BindingType::StorageBuffer:
+                        {
+                            GLuint location = glGetProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, name.c_str());
+                            glShaderStorageBlockBinding(program, location, indices[group][binding]);
+                        }
+                        break;
+
+                    case nxt::BindingType::Sampler:
+                    case nxt::BindingType::SampledTexture:
+                        // These binding types are handled in the separate sampler and texture emulation
+                        break;
+
+                }
+            }
+        }
+
+        // Compute links between stages for combined samplers, then bind them to texture units
+        {
+            std::set<CombinedSampler> combinedSamplersSet;
+            for (auto stage : IterateStages(GetStageMask())) {
+                const auto& module = ToBackend(builder->GetStageInfo(stage).module);
+
+                for (const auto& combined : module->GetCombinedSamplerInfo()) {
+                    combinedSamplersSet.insert(combined);
+                }
+            }
+
+            unitsForSamplers.resize(layout->GetNumSamplers());
+            unitsForTextures.resize(layout->GetNumSampledTextures());
+
+            GLuint textureUnit = layout->GetTextureUnitsUsed();
+            for (const auto& combined : combinedSamplersSet) {
+                std::string name = combined.GetName();
+                GLint location = glGetUniformLocation(program, name.c_str());
+                glUniform1i(location, textureUnit);
+
+                GLuint samplerIndex = indices[combined.samplerLocation.group][combined.samplerLocation.binding];
+                unitsForSamplers[samplerIndex].push_back(textureUnit);
+
+                GLuint textureIndex = indices[combined.textureLocation.group][combined.textureLocation.binding];
+                unitsForTextures[textureIndex].push_back(textureUnit);
+
+                textureUnit ++;
+            }
+        }
+    }
+
+    const Pipeline::GLPushConstantInfo& Pipeline::GetGLPushConstants(nxt::ShaderStage stage) const {
+        return glPushConstants[stage];
+    }
+
+    const std::vector<GLuint>& Pipeline::GetTextureUnitsForSampler(GLuint index) const {
+        ASSERT(index >= 0 && index < unitsForSamplers.size());
+        return unitsForSamplers[index];
+    }
+
+    const std::vector<GLuint>& Pipeline::GetTextureUnitsForTexture(GLuint index) const {
+        ASSERT(index >= 0 && index < unitsForSamplers.size());
+        return unitsForTextures[index];
+    }
+
+    GLuint Pipeline::GetProgramHandle() const {
+        return program;
+    }
+
+    void Pipeline::ApplyNow() {
+        glUseProgram(program);
+
+        auto inputState = ToBackend(GetInputState());
+        glBindVertexArray(inputState->GetVAO());
+    }
+
+}
+}
diff --git a/src/backend/opengl/PipelineGL.h b/src/backend/opengl/PipelineGL.h
new file mode 100644
index 0000000..ec0c0c5
--- /dev/null
+++ b/src/backend/opengl/PipelineGL.h
@@ -0,0 +1,55 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_PIPELINEGL_H_
+#define BACKEND_OPENGL_PIPELINEGL_H_
+
+#include "common/Pipeline.h"
+
+#include "glad/glad.h"
+
+#include <vector>
+
+namespace backend {
+namespace opengl {
+
+    class Device;
+    class ShaderModule;
+
+    class Pipeline : public PipelineBase {
+        public:
+            Pipeline(Device* device, PipelineBuilder* builder);
+
+            using GLPushConstantInfo = std::array<GLint, kMaxPushConstants>;
+            using BindingLocations = std::array<std::array<GLint, kMaxBindingsPerGroup>, kMaxBindGroups>;
+
+            const GLPushConstantInfo& GetGLPushConstants(nxt::ShaderStage stage) const;
+            const std::vector<GLuint>& GetTextureUnitsForSampler(GLuint index) const;
+            const std::vector<GLuint>& GetTextureUnitsForTexture(GLuint index) const;
+            GLuint GetProgramHandle() const;
+
+            void ApplyNow();
+
+        private:
+            GLuint program;
+            PerStage<GLPushConstantInfo> glPushConstants;
+            std::vector<std::vector<GLuint>> unitsForSamplers;
+            std::vector<std::vector<GLuint>> unitsForTextures;
+            Device* device;
+    };
+
+}
+}
+
+#endif // BACKEND_OPENGL_PIPELINEGL_H_
diff --git a/src/backend/opengl/PipelineLayoutGL.cpp b/src/backend/opengl/PipelineLayoutGL.cpp
new file mode 100644
index 0000000..1f3cb5e
--- /dev/null
+++ b/src/backend/opengl/PipelineLayoutGL.cpp
@@ -0,0 +1,80 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "PipelineLayoutGL.h"
+
+#include "OpenGLBackend.h"
+
+namespace backend {
+namespace opengl {
+
+    PipelineLayout::PipelineLayout(Device* device, PipelineLayoutBuilder* builder)
+        : PipelineLayoutBase(builder), device(device) {
+        GLuint uboIndex = 0;
+        GLuint samplerIndex = 0;
+        GLuint sampledTextureIndex = 0;
+        GLuint ssboIndex = 0;
+
+        for (size_t group = 0; group < kMaxBindGroups; ++group) {
+            const auto& groupInfo = GetBindGroupLayout(group)->GetBindingInfo();
+
+            for (size_t binding = 0; binding < kMaxBindingsPerGroup; ++binding) {
+                if (!groupInfo.mask[binding]) {
+                    continue;
+                }
+
+                switch (groupInfo.types[binding]) {
+                    case nxt::BindingType::UniformBuffer:
+                        indexInfo[group][binding] = uboIndex;
+                        uboIndex ++;
+                        break;
+                    case nxt::BindingType::Sampler:
+                        indexInfo[group][binding] = samplerIndex;
+                        samplerIndex ++;
+                        break;
+                    case nxt::BindingType::SampledTexture:
+                        indexInfo[group][binding] = sampledTextureIndex;
+                        sampledTextureIndex ++;
+                        break;
+
+                    case nxt::BindingType::StorageBuffer:
+                        indexInfo[group][binding] = ssboIndex;
+                        ssboIndex ++;
+                        break;
+                }
+            }
+        }
+
+        numSamplers = samplerIndex;
+        numSampledTextures = sampledTextureIndex;
+    }
+
+    const PipelineLayout::BindingIndexInfo& PipelineLayout::GetBindingIndexInfo() const {
+        return indexInfo;
+    }
+
+    GLuint PipelineLayout::GetTextureUnitsUsed() const {
+        return 0;
+    }
+
+    size_t PipelineLayout::GetNumSamplers() const {
+        return numSamplers;
+    }
+
+    size_t PipelineLayout::GetNumSampledTextures() const {
+        return numSampledTextures;
+    }
+
+}
+}
diff --git a/src/backend/opengl/PipelineLayoutGL.h b/src/backend/opengl/PipelineLayoutGL.h
new file mode 100644
index 0000000..fc35099
--- /dev/null
+++ b/src/backend/opengl/PipelineLayoutGL.h
@@ -0,0 +1,48 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_PIPELINELAYOUTGL_H_
+#define BACKEND_OPENGL_PIPELINELAYOUTGL_H_
+
+#include "common/PipelineLayout.h"
+
+#include "glad/glad.h"
+
+namespace backend {
+namespace opengl {
+
+    class Device;
+
+    class PipelineLayout : public PipelineLayoutBase {
+        public:
+            PipelineLayout(Device* device, PipelineLayoutBuilder* builder);
+
+            using BindingIndexInfo = std::array<std::array<GLuint, kMaxBindingsPerGroup>, kMaxBindGroups>;
+            const BindingIndexInfo& GetBindingIndexInfo() const;
+
+            GLuint GetTextureUnitsUsed() const;
+            size_t GetNumSamplers() const;
+            size_t GetNumSampledTextures() const;
+
+        private:
+            Device* device;
+            BindingIndexInfo indexInfo;
+            size_t numSamplers;
+            size_t numSampledTextures;
+    };
+
+}
+}
+
+#endif // BACKEND_OPENGL_PIPELINELAYOUTGL_H_
diff --git a/src/backend/opengl/SamplerGL.cpp b/src/backend/opengl/SamplerGL.cpp
new file mode 100644
index 0000000..73d418b
--- /dev/null
+++ b/src/backend/opengl/SamplerGL.cpp
@@ -0,0 +1,62 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "SamplerGL.h"
+
+namespace backend {
+namespace opengl {
+
+    namespace {
+        GLenum MagFilterMode(nxt::FilterMode filter) {
+            switch (filter) {
+                case nxt::FilterMode::Nearest:
+                    return GL_NEAREST;
+                case nxt::FilterMode::Linear:
+                    return GL_LINEAR;
+            }
+        }
+
+        GLenum MinFilterMode(nxt::FilterMode minFilter, nxt::FilterMode mipMapFilter) {
+            switch (minFilter) {
+                case nxt::FilterMode::Nearest:
+                    switch (mipMapFilter) {
+                        case nxt::FilterMode::Nearest:
+                            return GL_NEAREST_MIPMAP_NEAREST;
+                        case nxt::FilterMode::Linear:
+                            return GL_NEAREST_MIPMAP_LINEAR;
+                    }
+                case nxt::FilterMode::Linear:
+                    switch (mipMapFilter) {
+                        case nxt::FilterMode::Nearest:
+                            return GL_LINEAR_MIPMAP_NEAREST;
+                        case nxt::FilterMode::Linear:
+                            return GL_LINEAR_MIPMAP_LINEAR;
+                    }
+            }
+        }
+    }
+
+    Sampler::Sampler(Device* device, SamplerBuilder* builder)
+        : SamplerBase(builder), device(device) {
+        glGenSamplers(1, &handle);
+        glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, MagFilterMode(builder->GetMagFilter()));
+        glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, MinFilterMode(builder->GetMinFilter(), builder->GetMipMapFilter()));
+    }
+
+    GLuint Sampler::GetHandle() const {
+        return handle;
+    }
+
+}
+}
diff --git a/src/backend/opengl/SamplerGL.h b/src/backend/opengl/SamplerGL.h
new file mode 100644
index 0000000..c238e8a
--- /dev/null
+++ b/src/backend/opengl/SamplerGL.h
@@ -0,0 +1,41 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_SAMPLERGL_H_
+#define BACKEND_OPENGL_SAMPLERGL_H_
+
+#include "common/Sampler.h"
+
+#include "glad/glad.h"
+
+namespace backend {
+namespace opengl {
+
+    class Device;
+
+    class Sampler : public SamplerBase {
+        public:
+            Sampler(Device* device, SamplerBuilder* builder);
+
+            GLuint GetHandle() const;
+
+        private:
+            Device* device;
+            GLuint handle;
+    };
+
+}
+}
+
+#endif // BACKEND_OPENGL_SAMPLERGL_H_
diff --git a/src/backend/opengl/ShaderModuleGL.cpp b/src/backend/opengl/ShaderModuleGL.cpp
new file mode 100644
index 0000000..afb5706
--- /dev/null
+++ b/src/backend/opengl/ShaderModuleGL.cpp
@@ -0,0 +1,105 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "ShaderModuleGL.h"
+
+#include <spirv-cross/spirv_glsl.hpp>
+
+#include <sstream>
+
+namespace backend {
+namespace opengl {
+
+    std::string GetBindingName(uint32_t group, uint32_t binding) {
+        std::ostringstream o;
+        o << "nxt_binding_" << group << "_" << binding;
+        return o.str();
+    }
+
+    bool operator < (const BindingLocation& a, const BindingLocation& b) {
+        return std::tie(a.group, a.binding) < std::tie(b.group, b.binding);
+    }
+
+    bool operator < (const CombinedSampler& a, const CombinedSampler& b) {
+        return std::tie(a.samplerLocation, a.textureLocation) < std::tie(b.samplerLocation, b.textureLocation);
+    }
+
+    std::string CombinedSampler::GetName() const {
+        std::ostringstream o;
+        o << "nxt_combined";
+        o << "_" << samplerLocation.group << "_" << samplerLocation.binding;
+        o << "_with_" << textureLocation.group << "_" << textureLocation.binding;
+        return o.str();
+    }
+
+    ShaderModule::ShaderModule(Device* device, ShaderModuleBuilder* builder)
+        : ShaderModuleBase(builder), device(device) {
+        spirv_cross::CompilerGLSL compiler(builder->AcquireSpirv());
+        spirv_cross::CompilerGLSL::Options options;
+
+        // TODO(cwallez@chromium.org): discover the backing context version and use that.
+#if defined(__APPLE__)
+        options.version = 410;
+#else
+        options.version = 450;
+#endif
+        compiler.set_options(options);
+
+        ExtractSpirvInfo(compiler);
+
+        const auto& bindingInfo = GetBindingInfo();
+
+        // Extract bindings names so that it can be used to get its location in program.
+        // Now translate the separate sampler / textures into combined ones and store their info.
+        // We need to do this before removing the set and binding decorations.
+        compiler.build_combined_image_samplers();
+
+        for (const auto& combined : compiler.get_combined_image_samplers()) {
+            combinedInfo.emplace_back();
+
+            auto& info = combinedInfo.back();
+            info.samplerLocation.group = compiler.get_decoration(combined.sampler_id, spv::DecorationDescriptorSet);
+            info.samplerLocation.binding = compiler.get_decoration(combined.sampler_id, spv::DecorationBinding);
+            info.textureLocation.group = compiler.get_decoration(combined.image_id, spv::DecorationDescriptorSet);
+            info.textureLocation.binding = compiler.get_decoration(combined.image_id, spv::DecorationBinding);
+            compiler.set_name(combined.combined_id, info.GetName());
+        }
+
+        // Change binding names to be "nxt_binding_<group>_<binding>".
+        // Also unsets the SPIRV "Binding" decoration as it outputs "layout(binding=)" which
+        // isn't supported on OSX's OpenGL.
+        for (uint32_t group = 0; group < kMaxBindGroups; ++group) {
+            for (uint32_t binding = 0; binding < kMaxBindingsPerGroup; ++binding) {
+                const auto& info = bindingInfo[group][binding];
+                if (info.used) {
+                    compiler.set_name(info.base_type_id, GetBindingName(group, binding));
+                    compiler.unset_decoration(info.id, spv::DecorationBinding);
+                    compiler.unset_decoration(info.id, spv::DecorationDescriptorSet);
+                }
+            }
+        }
+
+        glslSource = compiler.compile();
+    }
+
+    const char* ShaderModule::GetSource() const {
+        return reinterpret_cast<const char*>(glslSource.data());
+    }
+
+    const ShaderModule::CombinedSamplerInfo& ShaderModule::GetCombinedSamplerInfo() const {
+        return combinedInfo;
+    }
+
+}
+}
diff --git a/src/backend/opengl/ShaderModuleGL.h b/src/backend/opengl/ShaderModuleGL.h
new file mode 100644
index 0000000..d985527
--- /dev/null
+++ b/src/backend/opengl/ShaderModuleGL.h
@@ -0,0 +1,60 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_SHADERMODULEGL_H_
+#define BACKEND_OPENGL_SHADERMODULEGL_H_
+
+#include "common/ShaderModule.h"
+
+#include "glad/glad.h"
+
+namespace backend {
+namespace opengl {
+
+    class Device;
+
+    std::string GetBindingName(uint32_t group, uint32_t binding);
+
+    struct BindingLocation {
+        uint32_t group;
+        uint32_t binding;
+    };
+    bool operator < (const BindingLocation& a, const BindingLocation& b);
+
+    struct CombinedSampler {
+        BindingLocation samplerLocation;
+        BindingLocation textureLocation;
+        std::string GetName() const;
+    };
+    bool operator < (const CombinedSampler& a, const CombinedSampler& b);
+
+    class ShaderModule : public ShaderModuleBase {
+        public:
+            ShaderModule(Device* device, ShaderModuleBuilder* builder);
+
+            using CombinedSamplerInfo = std::vector<CombinedSampler>;
+
+            const char* GetSource() const;
+            const CombinedSamplerInfo& GetCombinedSamplerInfo() const;
+
+        private:
+            Device* device;
+            CombinedSamplerInfo combinedInfo;
+            std::string glslSource;
+    };
+
+}
+}
+
+#endif // BACKEND_OPENGL_SHADERMODULEGL_H_
diff --git a/src/backend/opengl/TextureGL.cpp b/src/backend/opengl/TextureGL.cpp
new file mode 100644
index 0000000..fc1a733
--- /dev/null
+++ b/src/backend/opengl/TextureGL.cpp
@@ -0,0 +1,86 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "TextureGL.h"
+
+#include <algorithm>
+#include <vector>
+
+namespace backend {
+namespace opengl {
+
+    namespace {
+
+        GLenum TargetForDimension(nxt::TextureDimension dimension) {
+            switch (dimension) {
+                case nxt::TextureDimension::e2D:
+                    return GL_TEXTURE_2D;
+            }
+        }
+
+        TextureFormatInfo GetGLFormatInfo(nxt::TextureFormat format) {
+            switch (format) {
+                case nxt::TextureFormat::R8G8B8A8Unorm:
+                    return {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
+            }
+        }
+
+    }
+
+    // Texture
+
+    Texture::Texture(Device* device, TextureBuilder* builder)
+        : TextureBase(builder), device(device) {
+        target = TargetForDimension(GetDimension());
+
+        uint32_t width = GetWidth();
+        uint32_t height = GetHeight();
+        uint32_t levels = GetNumMipLevels();
+
+        auto formatInfo = GetGLFormatInfo(GetFormat());
+
+        glGenTextures(1, &handle);
+        glBindTexture(target, handle);
+
+        for (uint32_t i = 0; i < levels; ++i) {
+            glTexImage2D(target, i, formatInfo.internalFormat, width, height, 0, formatInfo.format, formatInfo.type, nullptr);
+            width = std::max(uint32_t(1), width / 2);
+            height = std::max(uint32_t(1), height / 2);
+        }
+
+        // The texture is not complete if it uses mipmapping and not all levels up to
+        // MAX_LEVEL have been defined.
+        glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, levels - 1);
+    }
+
+    GLuint Texture::GetHandle() const {
+        return handle;
+    }
+
+    GLenum Texture::GetGLTarget() const {
+        return target;
+    }
+
+    TextureFormatInfo Texture::GetGLFormat() const {
+        return GetGLFormatInfo(GetFormat());
+    }
+
+    // TextureView
+
+    TextureView::TextureView(Device* device, TextureViewBuilder* builder)
+        : TextureViewBase(builder), device(device) {
+    }
+
+}
+}
diff --git a/src/backend/opengl/TextureGL.h b/src/backend/opengl/TextureGL.h
new file mode 100644
index 0000000..6bdf534
--- /dev/null
+++ b/src/backend/opengl/TextureGL.h
@@ -0,0 +1,59 @@
+// Copyright 2017 The NXT Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef BACKEND_OPENGL_TEXTUREGL_H_
+#define BACKEND_OPENGL_TEXTUREGL_H_
+
+#include "common/Texture.h"
+
+#include "glad/glad.h"
+
+namespace backend {
+namespace opengl {
+
+    class Device;
+
+    struct TextureFormatInfo {
+        GLenum internalFormat;
+        GLenum format;
+        GLenum type;
+    };
+
+    class Texture : public TextureBase {
+        public:
+            Texture(Device* device, TextureBuilder* builder);
+
+            GLuint GetHandle() const;
+            GLenum GetGLTarget() const;
+            TextureFormatInfo GetGLFormat() const;
+
+        private:
+            Device* device;
+            GLuint handle;
+            GLenum target;
+    };
+
+    class TextureView : public TextureViewBase {
+        public:
+            TextureView(Device* device, TextureViewBuilder* builder);
+
+        private:
+            Device* device;
+    };
+
+
+}
+}
+
+#endif // BACKEND_OPENGL_TEXTUREGL_H_