Initial commit of Amber for open source

Amber is a multi-API shader test framework.

Amber lets you capture and communicate shader bugs with the fluidity and ease of a scripting flow:

* No graphics API programming is required.
  * WIP: Supports Vulkan and [Dawn][Dawn] graphics APIs.
* A single text string (or file) maps to a single graphics API pipeline test case.  The text includes:
  * Input data, including buffers and images.
  * Shaders.
  * Expectations for the result of running the pipeline.
* Shaders can be expressed in binary form (as hex), in SPIR-V assembly, or in a higher level shader language.
* After executing the pipeline, result buffers and images can be saved to output files.

This is not an officially supported Google product.
diff --git a/src/vulkan/CMakeLists.txt b/src/vulkan/CMakeLists.txt
new file mode 100644
index 0000000..8df5759
--- /dev/null
+++ b/src/vulkan/CMakeLists.txt
@@ -0,0 +1,43 @@
+# Copyright 2018 The Amber Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(VULKAN_ENGINE_SOURCES
+    bit_copy.cc
+    buffer.cc
+    command.cc
+    descriptor.cc
+    device.cc
+    engine_vulkan.cc
+    format_data.cc
+    frame_buffer.cc
+    resource.cc
+    image.cc
+    pipeline.cc
+    graphics_pipeline.cc
+    vertex_buffer.cc
+)
+
+add_library(libamberenginevulkan ${VULKAN_ENGINE_SOURCES})
+amber_default_compile_options(libamberenginevulkan)
+set_target_properties(libamberenginevulkan PROPERTIES
+    OUTPUT_NAME "amberenginevulkan"
+)
+target_link_libraries(libamberenginevulkan ${VULKAN_LIB})
+
+if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
+  # vulkan/vulkan.h defines VK_NULL_HANDLE as 0u and that also serves as a null pointer.
+  # Disable Clang's warning that will alwaays fire on that.  This is required to build
+  # with XCode 10.
+  target_compile_options(libamberenginevulkan PRIVATE -Wno-zero-as-null-pointer-constant)
+endif()
diff --git a/src/vulkan/bit_copy.cc b/src/vulkan/bit_copy.cc
new file mode 100644
index 0000000..3be826f
--- /dev/null
+++ b/src/vulkan/bit_copy.cc
@@ -0,0 +1,166 @@
+// Copyright 2018 The Amber 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 "src/vulkan/bit_copy.h"
+
+#include <cassert>
+#include <cstring>
+
+namespace amber {
+namespace vulkan {
+
+// static
+void BitCopy::ShiftBufferBits(uint8_t* buffer,
+                              uint8_t length_bytes,
+                              uint8_t shift_bits) {
+  if (shift_bits == 0)
+    return;
+
+  assert(shift_bits < 8);
+
+  uint8_t carry = 0;
+  for (uint32_t i = 0; i < length_bytes; ++i) {
+    uint8_t new_value = static_cast<uint8_t>(buffer[i] << shift_bits) | carry;
+    carry = buffer[i] >> (8 - shift_bits);
+    buffer[i] = new_value;
+  }
+}
+
+// static
+void BitCopy::CopyValueToBuffer(uint8_t* dst,
+                                const Value& src,
+                                uint8_t bit_offset,
+                                uint8_t bits) {
+  uint8_t data[9] = {};
+  if (src.IsInteger()) {
+    if (bits <= 8) {
+      uint8_t data_uint8 = src.AsUint8();
+      data[0] = data_uint8;
+    } else if (bits <= 16) {
+      uint16_t data_uint16 = src.AsUint16();
+      std::memcpy(data, &data_uint16, sizeof(uint16_t));
+    } else if (bits <= 32) {
+      uint32_t data_uint32 = src.AsUint32();
+      std::memcpy(data, &data_uint32, sizeof(uint32_t));
+    } else if (bits <= 64) {
+      uint64_t data_uint64 = src.AsUint64();
+      std::memcpy(data, &data_uint64, sizeof(uint64_t));
+    } else {
+      assert(false && "Invalid int bits for CopyBits");
+    }
+  } else {
+    if (bits == 64) {
+      double data_double = src.AsDouble();
+      std::memcpy(data, &data_double, sizeof(double));
+    } else {
+      float data_float = src.AsFloat();
+      uint16_t hex_float = 0;
+      switch (bits) {
+        case 32:
+          std::memcpy(data, &data_float, sizeof(float));
+          break;
+        case 16:
+        case 11:
+        case 10:
+          hex_float = FloatToHexFloat(data_float, bits);
+          std::memcpy(data, &hex_float, sizeof(uint16_t));
+          break;
+        default:
+          assert(false && "Invalid float bits for CopyBits");
+          break;
+      }
+    }
+  }
+
+  while (bit_offset > 7) {
+    ++dst;
+    bit_offset -= 8;
+  }
+
+  ShiftBufferBits(data, ((bit_offset + bits - 1) / 8) + 1, bit_offset);
+  CopyBits(dst, data, bit_offset, bits);
+}
+
+// static
+void BitCopy::CopyBits(uint8_t* dst,
+                       const uint8_t* src,
+                       uint8_t bit_offset,
+                       uint8_t bits) {
+  while (bit_offset + bits > 0) {
+    uint8_t target_bits = bits;
+    if (bit_offset + target_bits > 8)
+      target_bits = 8 - bit_offset;
+
+    uint8_t bit_mask =
+        static_cast<uint8_t>(((1U << target_bits) - 1U) << bit_offset);
+    *dst = (*src & bit_mask) | (*dst & ~bit_mask);
+
+    bit_offset -= bit_offset;
+    bits -= target_bits;
+    ++dst;
+    ++src;
+  }
+}
+
+// static
+uint16_t BitCopy::FloatExponent(const uint32_t hex_float) {
+  uint32_t exponent = ((hex_float >> 23U) & ((1U << 8U) - 1U)) - 127U + 15U;
+  const uint32_t half_exponent_mask = (1U << 5U) - 1U;
+  assert((exponent & ~half_exponent_mask) == 0U);
+  return static_cast<uint16_t>(exponent & half_exponent_mask);
+}
+
+// static
+uint16_t BitCopy::FloatToHexFloat(float value, uint8_t bits) {
+  switch (bits) {
+    case 10:
+      return FloatToHexFloat10(value);
+    case 11:
+      return FloatToHexFloat11(value);
+    case 16:
+      return FloatToHexFloat16(value);
+  }
+
+  assert(false && "Invalid bits");
+  return 0;
+}
+
+// static
+uint16_t BitCopy::FloatToHexFloat16(const float value) {
+  uint32_t hex;
+  memcpy(&hex, &value, sizeof(float));
+  return static_cast<uint16_t>(FloatSign(hex) << 15U) |
+         static_cast<uint16_t>(FloatExponent(hex) << 10U) | FloatMantissa(hex);
+}
+
+// static
+uint16_t BitCopy::FloatToHexFloat11(const float value) {
+  uint32_t hex;
+  memcpy(&hex, &value, sizeof(float));
+  assert(FloatSign(hex) == 0);
+  return static_cast<uint16_t>(FloatExponent(hex) << 6U) |
+         static_cast<uint16_t>(FloatMantissa(hex) >> 4U);
+}
+
+// static
+uint16_t BitCopy::FloatToHexFloat10(const float value) {
+  uint32_t hex;
+  memcpy(&hex, &value, sizeof(float));
+  assert(FloatSign(hex) == 0);
+  return static_cast<uint16_t>(FloatExponent(hex) << 5U) |
+         static_cast<uint16_t>(FloatMantissa(hex) >> 5U);
+}
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/bit_copy.h b/src/vulkan/bit_copy.h
new file mode 100644
index 0000000..31b19bc
--- /dev/null
+++ b/src/vulkan/bit_copy.h
@@ -0,0 +1,82 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_BIT_COPY_H_
+#define SRC_VULKAN_BIT_COPY_H_
+
+#include "src/value.h"
+
+namespace amber {
+namespace vulkan {
+
+class BitCopy {
+ public:
+  // Copy [0, bits) bits of src to [bit_offset, bit_offset + bits) of dst.
+  static void CopyValueToBuffer(uint8_t* dst,
+                                const Value& src,
+                                uint8_t bit_offset,
+                                uint8_t bits);
+
+ private:
+  BitCopy() = delete;
+
+  ~BitCopy() = delete;
+
+  static void ShiftBufferBits(uint8_t* buffer,
+                              uint8_t length_bytes,
+                              uint8_t shift_bits);
+
+  static void CopyBits(uint8_t* dst,
+                       const uint8_t* src,
+                       uint8_t bit_offset,
+                       uint8_t bits);
+
+  // Convert float to small float format.
+  // See https://www.khronos.org/opengl/wiki/Small_Float_Formats
+  // and https://en.wikipedia.org/wiki/IEEE_754.
+  //
+  //    Sign Exponent Mantissa Exponent-Bias
+  // 16    1        5       10            15
+  // 11    0        5        6            15
+  // 10    0        5        5            15
+  // 32    1        8       23           127
+  // 64    1       11       52          1023
+  //
+  // 11 and 10 bits floats are always positive.
+  // 14 bits float is used only RGB9_E5 format but it does not exist in Vulkan.
+  //
+  // For example, 1234 in 32 bits float = 1.0011010010 * 2^10 with base 2.
+  //
+  // 1.0011010010 * 2^10 --> 0 (sign) / 10 + 127 (exp) / 0011010010 (Mantissa)
+  //                     --> 0x449a4000
+  static uint16_t FloatToHexFloat(float value, uint8_t bits);
+  static uint16_t FloatToHexFloat16(const float value);
+  static uint16_t FloatToHexFloat11(const float value);
+  static uint16_t FloatToHexFloat10(const float value);
+
+  static uint16_t FloatSign(const uint32_t hex_float) {
+    return static_cast<uint16_t>(hex_float >> 31U);
+  }
+
+  static uint16_t FloatExponent(const uint32_t hex_float);
+
+  static uint16_t FloatMantissa(const uint32_t hex_float) {
+    return static_cast<uint16_t>((hex_float & ((1U << 23U) - 1U)) >> 13U);
+  }
+};
+
+}  // namespace vulkan
+}  // namespace amber
+
+#endif  // SRC_VULKAN_BIT_COPY_H_
diff --git a/src/vulkan/bit_copy_test.cc b/src/vulkan/bit_copy_test.cc
new file mode 100644
index 0000000..b9367c5
--- /dev/null
+++ b/src/vulkan/bit_copy_test.cc
@@ -0,0 +1,382 @@
+// Copyright 2018 The Amber 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 "src/vulkan/bit_copy.h"
+
+#include <cstring>
+#include <type_traits>
+
+#include "gtest/gtest.h"
+#include "src/value.h"
+
+namespace amber {
+namespace vulkan {
+namespace {
+
+template <typename T>
+void ExpectBitsEQ(const uint8_t* actual, T expected) {
+  const T* ptr = reinterpret_cast<const T*>(actual);
+  EXPECT_EQ(*ptr, expected);
+}
+
+}  // namespace
+
+using BitCopyTest = testing::Test;
+
+TEST_F(BitCopyTest, CopyInt8) {
+  Value value;
+  uint8_t data = 0;
+
+  // 7      0          0      7
+  // 00000000      --> 11011100 (220)
+  // 110111   (55)
+  value.SetIntValue(55);
+  BitCopy::CopyValueToBuffer(&data, value, 2, 6);
+  EXPECT_EQ(data, 220);
+
+  // 7      0          0      7
+  // 11011100      --> 11011111 (223)
+  //       11 (3)
+  value.SetIntValue(3);
+  BitCopy::CopyValueToBuffer(&data, value, 0, 2);
+  EXPECT_EQ(data, 223);
+
+  // 7      0          0      7
+  // 11011111      --> 10110111 (183)
+  //  011011  (27)
+  value.SetIntValue(27);
+  BitCopy::CopyValueToBuffer(&data, value, 1, 6);
+  EXPECT_EQ(data, 183);
+
+  // 7      0          0      7
+  // 10110111      --> 11010111 (215)
+  //  1010    (10)
+  value.SetIntValue(10);
+  BitCopy::CopyValueToBuffer(&data, value, 3, 4);
+  EXPECT_EQ(data, 215);
+}
+
+TEST_F(BitCopyTest, CopyInt16) {
+  Value value;
+  uint8_t data[2] = {};
+
+  // 15              0          15              0
+  //  0000000000000000      -->  1100000011100100 (49380)
+  //  11000000111001   (12345)
+  value.SetIntValue(12345);
+  BitCopy::CopyValueToBuffer(data, value, 2, 14);
+  ExpectBitsEQ<uint16_t>(data, 49380);
+
+  // 15              0          15              0
+  //  1100000011100100      -->  1110100000100100 (59428)
+  //    101000001      (321)
+  value.SetIntValue(321);
+  BitCopy::CopyValueToBuffer(data, value, 5, 9);
+  ExpectBitsEQ<uint16_t>(data, 59428);
+
+  // 15              0          15              0
+  //  1110100000100100      -->  1111000111010111 (61911)
+  //     1000111010111 (4567)
+  value.SetIntValue(4567);
+  BitCopy::CopyValueToBuffer(data, value, 0, 13);
+  ExpectBitsEQ<uint16_t>(data, 61911);
+
+  // 15              0          15              0
+  //  1111000111010111      -->  1001101111011111 (39903)
+  //   001101111011    (891)
+  value.SetIntValue(891);
+  BitCopy::CopyValueToBuffer(data, value, 3, 12);
+  ExpectBitsEQ<uint16_t>(data, 39903);
+}
+
+TEST_F(BitCopyTest, CopyInt32) {
+  Value value;
+  uint8_t data[4] = {};
+
+  // 31                         31
+  //  0000000000000000      -->  0001011110001100
+  //  0000000000000000           0010100111000000 (395061696)
+  //                 0                          0
+  //
+  //     1011110001100
+  //  00101001110      (12345678)
+  value.SetIntValue(12345678);
+  BitCopy::CopyValueToBuffer(data, value, 5, 24);
+  ExpectBitsEQ<uint32_t>(data, 395061696);
+
+  // 31                         31
+  //  0001011110001100      -->  0001011110000001
+  //  0010100111000000           1100110111000000 (394382784)
+  //                 0                          0
+  //
+  //         110000001
+  //  11001101         (98765)
+  value.SetIntValue(98765);
+  BitCopy::CopyValueToBuffer(data, value, 8, 17);
+  ExpectBitsEQ<uint32_t>(data, 394382784);
+
+  // 31                         31
+  //  0001011110000001      -->  0001011110000001
+  //  1100110111000000           1100111010001110 (394382990)
+  //                 0                          0
+  //
+  //        1010001110 (654)
+  value.SetIntValue(654);
+  BitCopy::CopyValueToBuffer(data, value, 0, 10);
+  ExpectBitsEQ<uint32_t>(data, 394382990);
+
+  // 31                         31
+  //  0001011110000001      -->  1101001011111100
+  //  1100111010001110           0101011010001110 (3539752590)
+  //                 0                          0
+  //
+  //  1101001011111100
+  //  010101           (654)
+  value.SetIntValue(3456789);
+  BitCopy::CopyValueToBuffer(data, value, 10, 22);
+  ExpectBitsEQ<uint32_t>(data, 3539752590);
+}
+
+TEST_F(BitCopyTest, CopyInt64) {
+  Value value;
+  uint8_t data[8] = {};
+
+  // 63                         63
+  //  0000000000000000      -->  0010001111101110
+  //  0000000000000000           0011111101100110
+  //  0000000000000000           0001011110101100
+  //  0000000000000000           0000000000000000 (2589076543500976128)
+  //                 0                          0
+  //
+  //    10001111101110
+  //  0011111101100110
+  //  00010111101011
+  //                   (9876543210987)
+  value.SetIntValue(9876543210987UL);
+  BitCopy::CopyValueToBuffer(data, value, 18, 44);
+  ExpectBitsEQ<uint64_t>(data, 2589076543500976128UL);
+
+  // 63                         63
+  //  0010001111101110      -->  0011110001001110
+  //  0011111101100110           1111110000011110
+  //  0001011110101100           1111010011010001
+  //  0000000000000000           0101111101011000 (4345687900345687896)
+  //                 0                          0
+  //
+  //    11110001001110
+  //  1111110000011110
+  //  1111010011010001
+  //  0101111101011    (543210987543210987)
+  value.SetIntValue(543210987543210987UL);
+  BitCopy::CopyValueToBuffer(data, value, 3, 59);
+  ExpectBitsEQ<uint64_t>(data, 4345687900345687896UL);
+
+  // 63                         63
+  //  0011110001001110      -->  0011110001001110
+  //  1111110000011110           1001011111100010
+  //  1111010011010001           1011010011010001
+  //  0101111101011000           0101111101011000 (4345577690411130712)
+  //                 0                          0
+  //
+  //               110
+  //  1001011111100010
+  //  101
+  //                   (3456789)
+  value.SetIntValue(3456789UL);
+  BitCopy::CopyValueToBuffer(data, value, 29, 22);
+  ExpectBitsEQ<uint64_t>(data, 4345577690411130712UL);
+}
+
+TEST_F(BitCopyTest, CopyIntMultiple) {
+  uint8_t data[32] = {};
+  Value value;
+
+  // Fill [0, 32) bits of data with
+  // 11(3) / 0010001111(143) / 0001000011(67) / 1000010001(529)
+  value.SetIntValue(529);
+  BitCopy::CopyValueToBuffer(data, value, 0, 10);
+  value.SetIntValue(67);
+  BitCopy::CopyValueToBuffer(data, value, 10, 10);
+  value.SetIntValue(143);
+  BitCopy::CopyValueToBuffer(data, value, 20, 10);
+  value.SetIntValue(3);
+  BitCopy::CopyValueToBuffer(data, value, 30, 2);
+
+  // Fill [32, 96) bits of data with
+  // 00000111010110111100110100010101(123456789) /
+  // 00000000100101101011010000111111(9876543)
+  value.SetIntValue(9876543);
+  BitCopy::CopyValueToBuffer(data, value, 32, 32);
+  value.SetIntValue(123456789);
+  BitCopy::CopyValueToBuffer(data, value, 64, 32);
+
+  // Fill [96, 120) bits of data with
+  // 00011111(31) / 00001001(9) / 01001101(77)
+  value.SetIntValue(77);
+  BitCopy::CopyValueToBuffer(data, value, 96, 8);
+  value.SetIntValue(9);
+  BitCopy::CopyValueToBuffer(data, value, 104, 8);
+  value.SetIntValue(31);
+  BitCopy::CopyValueToBuffer(data, value, 112, 8);
+
+  // Fill [120, 184) bits of data with
+  // 00000001101101101001101101001011
+  // 10100110001100001111001101001110(123456789012345678)
+  value.SetIntValue(123456789012345678UL);
+  BitCopy::CopyValueToBuffer(data, value, 120, 64);
+
+  // Fill [184, 216) bits of data with
+  // 10000011110111011011010010000000(34567890)
+  value.SetIntValue(34567890);
+  BitCopy::CopyValueToBuffer(data, value, 184, 32);
+
+  // Fill [216, 256) bits of data with
+  // 01100011(99) / 1000001000110101(33333) / 11011110(222) / 01101111(111)
+  value.SetIntValue(111);
+  BitCopy::CopyValueToBuffer(data, value, 216, 8);
+  value.SetIntValue(222);
+  BitCopy::CopyValueToBuffer(data, value, 224, 8);
+  value.SetIntValue(33333);
+  BitCopy::CopyValueToBuffer(data, value, 232, 16);
+  value.SetIntValue(99);
+  BitCopy::CopyValueToBuffer(data, value, 248, 8);
+
+  // [0, 32) bits of data
+  ExpectBitsEQ<uint32_t>(data, 3371240977);
+
+  // [32, 96) bits of data
+  ExpectBitsEQ<uint64_t>(&data[4], 530242871234049087UL);
+
+  // [96, 120) bits of data
+  ExpectBitsEQ<uint16_t>(&data[12], 2381);
+  EXPECT_EQ(data[14], 31);
+
+  // [120, 184) bits of data
+  ExpectBitsEQ<uint64_t>(&data[15], 123456789012345678UL);
+
+  // [184, 216) bits of data
+  ExpectBitsEQ<uint32_t>(&data[23], 34567890);
+
+  // [216, 256) bits of data
+  ExpectBitsEQ<uint32_t>(&data[27], 2184568431);
+  EXPECT_EQ(data[31], 99);
+}
+
+TEST_F(BitCopyTest, CopyFloat16) {
+  uint8_t data[2] = {};
+  Value value;
+
+  // 16 bits
+  //         Sig / Exp / Mantissa     Sig / Exp / Mantissa
+  // 12.34 =   0 / 130 /  4550820 -->   0 /  18 /      555
+  value.SetDoubleValue(12.34);
+  data[0] = 0;
+  data[1] = 0;
+  BitCopy::CopyValueToBuffer(data, value, 0, 16);
+  ExpectBitsEQ<uint16_t>(data, 18987);
+
+  // 11 bits
+  //         Sig / Exp / Mantissa     Sig / Exp / Mantissa
+  // 5.67 =    0 / 129 /  3502244 -->        17 /       26
+  value.SetDoubleValue(5.67);
+  data[0] = 0;
+  data[1] = 0;
+  BitCopy::CopyValueToBuffer(data, value, 3, 11);
+  ExpectBitsEQ<uint16_t>(data, 8912);
+
+  // 10 bits
+  //         Sig / Exp / Mantissa     Sig / Exp / Mantissa
+  // 0.89 =    0 / 126 /  6543114 -->        14 /       24
+  value.SetDoubleValue(0.89);
+  data[0] = 0;
+  data[1] = 0;
+  BitCopy::CopyValueToBuffer(data, value, 2, 10);
+  ExpectBitsEQ<uint16_t>(data, 1888);
+}
+
+TEST_F(BitCopyTest, CopyFloat) {
+  uint8_t data[4] = {};
+  Value value;
+
+  // 16 bits
+  //         Sig / Exp / Mantissa
+  // 12.34 =   0 / 130 /  4550820
+  value.SetDoubleValue(12.34);
+  BitCopy::CopyValueToBuffer(data, value, 0, 32);
+  ExpectBitsEQ<uint32_t>(data, 1095069860);
+
+  // 11 bits
+  //         Sig / Exp / Mantissa
+  // 5.67 =    0 / 129 /  3502244
+  value.SetDoubleValue(5.67);
+  data[0] = 0;
+  data[1] = 0;
+  data[2] = 0;
+  data[3] = 0;
+  BitCopy::CopyValueToBuffer(data, value, 0, 32);
+  ExpectBitsEQ<uint32_t>(data, 1085632676);
+
+  // 10 bits
+  //         Sig / Exp / Mantissa
+  // 0.89 =    0 / 126 /  6543114
+  value.SetDoubleValue(0.89);
+  data[0] = 0;
+  data[1] = 0;
+  data[2] = 0;
+  data[3] = 0;
+  BitCopy::CopyValueToBuffer(data, value, 0, 32);
+  ExpectBitsEQ<uint32_t>(data, 1063507722);
+}
+
+TEST_F(BitCopyTest, CopyDouble) {
+  uint8_t data[8] = {};
+  Value value;
+
+  //         Sig /  Exp /         Mantissa
+  // 12.34 =   0 / 1026 / 2443202797848494
+  value.SetDoubleValue(12.34);
+  BitCopy::CopyValueToBuffer(data, value, 0, 64);
+  ExpectBitsEQ<uint64_t>(data, 4623136420479977390);
+
+  //         Sig /  Exp /         Mantissa
+  // 5.67 =    0 / 1025 / 1880252844427182
+  value.SetDoubleValue(5.67);
+  data[0] = 0;
+  data[1] = 0;
+  data[2] = 0;
+  data[3] = 0;
+  data[4] = 0;
+  data[5] = 0;
+  data[6] = 0;
+  data[7] = 0;
+  BitCopy::CopyValueToBuffer(data, value, 0, 64);
+  ExpectBitsEQ<uint64_t>(data, 4618069870899185582);
+
+  //         Sig /  Exp /         Mantissa
+  // 0.89 =    0 / 1022 / 3512807709348987
+  value.SetDoubleValue(0.89);
+  data[0] = 0;
+  data[1] = 0;
+  data[2] = 0;
+  data[3] = 0;
+  data[4] = 0;
+  data[5] = 0;
+  data[6] = 0;
+  data[7] = 0;
+  BitCopy::CopyValueToBuffer(data, value, 0, 64);
+  ExpectBitsEQ<uint64_t>(data, 4606191626881995899);
+}
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/buffer.cc b/src/vulkan/buffer.cc
new file mode 100644
index 0000000..4c7d2fe
--- /dev/null
+++ b/src/vulkan/buffer.cc
@@ -0,0 +1,93 @@
+// Copyright 2018 The Amber 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 "src/vulkan/buffer.h"
+
+namespace amber {
+namespace vulkan {
+
+Buffer::Buffer(VkDevice device,
+               size_t size,
+               const VkPhysicalDeviceMemoryProperties& properties)
+    : Resource(device, size, properties) {}
+
+Buffer::~Buffer() = default;
+
+Result Buffer::Initialize(const VkBufferUsageFlags usage) {
+  Result r = CreateVkBuffer(&buffer_, usage);
+  if (!r.IsSuccess())
+    return r;
+
+  AllocateResult allocate_result = AllocateAndBindMemoryToVkBuffer(
+      buffer_, &memory_, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, false);
+  if (!allocate_result.r.IsSuccess())
+    return allocate_result.r;
+
+  if (CheckMemoryHostAccessible(allocate_result.memory_type_index)) {
+    is_buffer_host_accessible_ = true;
+    return MapMemory(memory_);
+  }
+
+  is_buffer_host_accessible_ = false;
+  return Resource::Initialize();
+}
+
+Result Buffer::CreateVkBufferView(VkFormat format) {
+  VkBufferViewCreateInfo buffer_view_info = {};
+  buffer_view_info.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
+  buffer_view_info.buffer = buffer_;
+  buffer_view_info.format = format;
+  buffer_view_info.offset = 0;
+  buffer_view_info.range = VK_WHOLE_SIZE;
+  if (vkCreateBufferView(GetDevice(), &buffer_view_info, nullptr, &view_) !=
+      VK_SUCCESS) {
+    return Result("Vulkan::Calling vkCreateBufferView Fail");
+  }
+
+  return {};
+}
+
+void Buffer::CopyToDevice(VkCommandBuffer command) {
+  if (is_buffer_host_accessible_)
+    return;
+
+  VkBufferCopy region = {};
+  region.srcOffset = 0;
+  region.dstOffset = 0;
+  region.size = GetSize();
+
+  vkCmdCopyBuffer(command, GetHostAccessibleBuffer(), buffer_, 1, &region);
+}
+
+void Buffer::Shutdown() {
+  // TODO(jaebaek): Doublecheck what happens if |view_| is VK_NULL_HANDLE on
+  //                Android and Windows.
+  if (view_ != VK_NULL_HANDLE) {
+    vkDestroyBufferView(GetDevice(), view_, nullptr);
+    view_ = VK_NULL_HANDLE;
+  }
+
+  if (buffer_ != VK_NULL_HANDLE) {
+    vkDestroyBuffer(GetDevice(), buffer_, nullptr);
+    buffer_ = VK_NULL_HANDLE;
+  }
+
+  if (memory_ != VK_NULL_HANDLE) {
+    vkFreeMemory(GetDevice(), memory_, nullptr);
+    memory_ = VK_NULL_HANDLE;
+  }
+}
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/buffer.h b/src/vulkan/buffer.h
new file mode 100644
index 0000000..9ec1dd2
--- /dev/null
+++ b/src/vulkan/buffer.h
@@ -0,0 +1,60 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_BUFFER_H_
+#define SRC_VULKAN_BUFFER_H_
+
+#include "amber/result.h"
+#include "src/vulkan/resource.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class Buffer : public Resource {
+ public:
+  Buffer(VkDevice device,
+         size_t size,
+         const VkPhysicalDeviceMemoryProperties& properties);
+  ~Buffer() override;
+
+  Result Initialize(const VkBufferUsageFlags usage);
+  VkBuffer GetVkBuffer() const { return buffer_; }
+  Result CreateVkBufferView(VkFormat format);
+  VkBufferView GetVkBufferView() const { return view_; }
+
+  // TODO(jaebaek): Determine copy all or partial data
+  void CopyToDevice(VkCommandBuffer command);
+
+  // Resource
+  VkDeviceMemory GetHostAccessMemory() const override {
+    if (is_buffer_host_accessible_)
+      return memory_;
+
+    return Resource::GetHostAccessMemory();
+  }
+
+  void Shutdown() override;
+
+ private:
+  VkBuffer buffer_ = VK_NULL_HANDLE;
+  VkBufferView view_ = VK_NULL_HANDLE;
+  VkDeviceMemory memory_ = VK_NULL_HANDLE;
+  bool is_buffer_host_accessible_ = false;
+};
+
+}  // namespace vulkan
+}  // namespace amber
+
+#endif  // SRC_VULKAN_BUFFER_H_
diff --git a/src/vulkan/command.cc b/src/vulkan/command.cc
new file mode 100644
index 0000000..9ff205f
--- /dev/null
+++ b/src/vulkan/command.cc
@@ -0,0 +1,131 @@
+// Copyright 2018 The Amber 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 "src/vulkan/command.h"
+
+#include <memory>
+
+#include "src/make_unique.h"
+
+namespace amber {
+
+namespace vulkan {
+
+CommandPool::CommandPool(VkDevice device) : device_(device) {}
+
+CommandPool::~CommandPool() = default;
+
+Result CommandPool::Initialize(uint32_t queue_family_index) {
+  VkCommandPoolCreateInfo pool_info = {};
+  pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+  pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+  pool_info.queueFamilyIndex = queue_family_index;
+
+  if (vkCreateCommandPool(device_, &pool_info, nullptr, &pool_) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkCreateCommandPool Fail");
+
+  return {};
+}
+
+void CommandPool::Shutdown() {
+  vkDestroyCommandPool(device_, pool_, nullptr);
+}
+
+CommandBuffer::CommandBuffer(VkDevice device, VkCommandPool pool, VkQueue queue)
+    : device_(device), pool_(pool), queue_(queue) {}
+
+CommandBuffer::~CommandBuffer() = default;
+
+Result CommandBuffer::Initialize() {
+  VkCommandBufferAllocateInfo command_info = {};
+  command_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+  command_info.commandPool = pool_;
+  command_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+  command_info.commandBufferCount = 1;
+
+  if (vkAllocateCommandBuffers(device_, &command_info, &command_) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkAllocateCommandBuffers Fail");
+
+  VkFenceCreateInfo fence_info = {};
+  fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+  if (vkCreateFence(device_, &fence_info, nullptr, &fence_) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkCreateFence Fail");
+
+  return {};
+}
+
+Result CommandBuffer::BeginIfNotInRecording() {
+  if (state_ == CommandBufferState::kRecording)
+    return {};
+
+  if (state_ != CommandBufferState::kInitial)
+    return Result("Vulkan::Begin CommandBuffer from Not Valid State");
+
+  VkCommandBufferBeginInfo command_begin_info = {};
+  command_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+  command_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+  if (vkBeginCommandBuffer(command_, &command_begin_info) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkBeginCommandBuffer Fail");
+
+  state_ = CommandBufferState::kRecording;
+  return {};
+}
+
+Result CommandBuffer::End() {
+  if (state_ != CommandBufferState::kRecording)
+    return Result("Vulkan::End CommandBuffer from Not Valid State");
+
+  if (vkEndCommandBuffer(command_) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkEndCommandBuffer Fail");
+
+  state_ = CommandBufferState::kExecutable;
+  return {};
+}
+
+Result CommandBuffer::SubmitAndReset() {
+  if (state_ != CommandBufferState::kExecutable)
+    return Result("Vulkan::Submit CommandBuffer from Not Valid State");
+
+  if (vkResetFences(device_, 1, &fence_) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkResetFences Fail");
+
+  VkSubmitInfo submit_info = {};
+  submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+  submit_info.commandBufferCount = 1;
+  submit_info.pCommandBuffers = &command_;
+  if (vkQueueSubmit(queue_, 1, &submit_info, fence_) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkQueueSubmit Fail");
+
+  VkResult r =
+      vkWaitForFences(device_, 1, &fence_, VK_TRUE, 100000000 /* nanosecond */);
+  if (r == VK_TIMEOUT)
+    return Result("Vulkan::Calling vkWaitForFences Timeout");
+  if (r != VK_SUCCESS)
+    return Result("Vulkan::Calling vkWaitForFences Fail");
+
+  if (vkResetCommandBuffer(command_, 0) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkResetCommandBuffer Fail");
+
+  state_ = CommandBufferState::kInitial;
+  return {};
+}
+
+void CommandBuffer::Shutdown() {
+  vkDestroyFence(device_, fence_, nullptr);
+  vkFreeCommandBuffers(device_, pool_, 1, &command_);
+}
+
+}  // namespace vulkan
+
+}  // namespace amber
diff --git a/src/vulkan/command.h b/src/vulkan/command.h
new file mode 100644
index 0000000..97bd521
--- /dev/null
+++ b/src/vulkan/command.h
@@ -0,0 +1,79 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_COMMAND_H_
+#define SRC_VULKAN_COMMAND_H_
+
+#include "amber/result.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+
+namespace vulkan {
+
+// Command buffer states based on "5.1. Command Buffer Lifecycle" of Vulkan
+// spec
+enum class CommandBufferState : uint8_t {
+  kInitial = 0,
+  kRecording,
+  kExecutable,
+  kPending,
+  kInvalid,
+};
+
+class CommandPool {
+ public:
+  explicit CommandPool(VkDevice device);
+  ~CommandPool();
+
+  Result Initialize(uint32_t queue_family_index);
+  VkCommandPool GetCommandPool() const { return pool_; }
+  void Shutdown();
+
+ private:
+  VkDevice device_ = VK_NULL_HANDLE;
+  VkCommandPool pool_ = VK_NULL_HANDLE;
+};
+
+class CommandBuffer {
+ public:
+  CommandBuffer(VkDevice device, VkCommandPool pool, VkQueue queue);
+  ~CommandBuffer();
+
+  Result Initialize();
+  VkCommandBuffer GetCommandBuffer() const { return command_; }
+  void Shutdown();
+
+  // Do nothing and return if it is already ready to record. If it is in
+  // initial state, call command begin API and make it ready to record.
+  // Otherwise, report error.
+  Result BeginIfNotInRecording();
+
+  Result End();
+  Result SubmitAndReset();
+
+ private:
+  VkDevice device_ = VK_NULL_HANDLE;
+  VkCommandPool pool_ = VK_NULL_HANDLE;
+  VkQueue queue_ = VK_NULL_HANDLE;
+  VkCommandBuffer command_ = VK_NULL_HANDLE;
+  VkFence fence_ = VK_NULL_HANDLE;
+  CommandBufferState state_ = CommandBufferState::kInitial;
+};
+
+}  // namespace vulkan
+
+}  // namespace amber
+
+#endif  // SRC_VULKAN_COMMAND_H_
diff --git a/src/vulkan/descriptor.cc b/src/vulkan/descriptor.cc
new file mode 100644
index 0000000..294e3b6
--- /dev/null
+++ b/src/vulkan/descriptor.cc
@@ -0,0 +1,58 @@
+// Copyright 2018 The Amber 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 "src/vulkan/descriptor.h"
+
+#include <cassert>
+
+namespace amber {
+namespace vulkan {
+
+VkDescriptorType ToVkDescriptorType(DescriptorType type) {
+  switch (type) {
+    case DescriptorType::kStorageImage:
+      return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+    case DescriptorType::kSampler:
+      return VK_DESCRIPTOR_TYPE_SAMPLER;
+    case DescriptorType::kSampledImage:
+      return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+    case DescriptorType::kCombinedImageSampler:
+      return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+    case DescriptorType::kUniformTexelBuffer:
+      return VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
+    case DescriptorType::kStorageTexelBuffer:
+      return VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
+    case DescriptorType::kStorageBuffer:
+      return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+    case DescriptorType::kUniformBuffer:
+      return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    case DescriptorType::kDynamicUniformBuffer:
+      return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
+    case DescriptorType::kDynamicStorageBuffer:
+      return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
+    case DescriptorType::kInputAttachment:
+      return VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
+  }
+
+  assert(false && "Unknown resource type");
+  return VK_DESCRIPTOR_TYPE_SAMPLER;
+}
+
+Descriptor::Descriptor(DescriptorType type, uint32_t desc_set, uint32_t binding)
+    : type_(type), descriptor_set_(desc_set), binding_(binding) {}
+
+Descriptor::~Descriptor() = default;
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/descriptor.h b/src/vulkan/descriptor.h
new file mode 100644
index 0000000..ea24733
--- /dev/null
+++ b/src/vulkan/descriptor.h
@@ -0,0 +1,92 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_DESCRIPTOR_H_
+#define SRC_VULKAN_DESCRIPTOR_H_
+
+#include <memory>
+
+#include "amber/result.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+enum class DescriptorType : uint8_t {
+  kStorageImage = 0,
+  kSampler,
+  kSampledImage,
+  kCombinedImageSampler,
+  kUniformTexelBuffer,
+  kStorageTexelBuffer,
+  kStorageBuffer,
+  kUniformBuffer,
+  kDynamicUniformBuffer,
+  kDynamicStorageBuffer,
+  kInputAttachment,
+};
+
+VkDescriptorType ToVkDescriptorType(DescriptorType type);
+
+class Descriptor {
+ public:
+  Descriptor(DescriptorType type, uint32_t desc_set, uint32_t binding);
+
+  ~Descriptor();
+
+  uint32_t GetDescriptorSet() const { return descriptor_set_; }
+  uint32_t GetBinding() const { return binding_; }
+  bool operator<(const Descriptor& r) {
+    if (descriptor_set_ == r.descriptor_set_)
+      return binding_ < r.binding_;
+    return descriptor_set_ < r.descriptor_set_;
+  }
+
+  DescriptorType GetType() const { return type_; }
+
+  bool IsStorageImage() const { return type_ == DescriptorType::kStorageImage; }
+  bool IsSampler() const { return type_ == DescriptorType::kSampler; }
+  bool IsSampledImage() const { return type_ == DescriptorType::kSampledImage; }
+  bool IsCombinedImageSampler() const {
+    return type_ == DescriptorType::kCombinedImageSampler;
+  }
+  bool IsUniformTexelBuffer() const {
+    return type_ == DescriptorType::kUniformTexelBuffer;
+  }
+  bool IsStorageTexelBuffer() const {
+    return type_ == DescriptorType::kStorageTexelBuffer;
+  }
+  bool IsStorageBuffer() const {
+    return type_ == DescriptorType::kStorageBuffer;
+  }
+  bool IsUniformBuffer() const {
+    return type_ == DescriptorType::kUniformBuffer;
+  }
+  bool IsDynamicUniformBuffer() const {
+    return type_ == DescriptorType::kDynamicUniformBuffer;
+  }
+  bool IsDynamicStorageBuffer() const {
+    return type_ == DescriptorType::kDynamicStorageBuffer;
+  }
+
+ private:
+  DescriptorType type_ = DescriptorType::kSampledImage;
+  uint32_t descriptor_set_ = 0;
+  uint32_t binding_ = 0;
+};
+
+}  // namespace vulkan
+}  // namespace amber
+
+#endif  // SRC_VULKAN_DESCRIPTOR_H_
diff --git a/src/vulkan/device.cc b/src/vulkan/device.cc
new file mode 100644
index 0000000..c5dc430
--- /dev/null
+++ b/src/vulkan/device.cc
@@ -0,0 +1,155 @@
+// Copyright 2018 The Amber 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 "src/vulkan/device.h"
+
+#include <memory>
+#include <vector>
+
+#include "src/make_unique.h"
+
+namespace amber {
+
+namespace vulkan {
+
+Device::Device() = default;
+Device::Device(VkDevice device) : device_(device), destroy_device_(false) {}
+Device::~Device() = default;
+
+void Device::Shutdown() {
+  if (destroy_device_) {
+    vkDestroyDevice(device_, nullptr);
+    vkDestroyInstance(instance_, nullptr);
+  }
+}
+
+Result Device::Initialize() {
+  if (device_ == VK_NULL_HANDLE) {
+    Result r = CreateInstance();
+    if (!r.IsSuccess())
+      return r;
+
+    r = ChoosePhysicalDevice();
+    if (!r.IsSuccess())
+      return r;
+
+    r = CreateDevice();
+    if (!r.IsSuccess())
+      return r;
+  }
+
+  if (queue_ == VK_NULL_HANDLE) {
+    vkGetDeviceQueue(device_, queue_family_index_, 0, &queue_);
+    if (queue_ == VK_NULL_HANDLE)
+      return Result("Vulkan::Calling vkGetDeviceQueue Fail");
+  }
+  return {};
+}
+
+bool Device::ChooseQueueFamilyIndex(const VkPhysicalDevice& physical_device) {
+  uint32_t count;
+  std::vector<VkQueueFamilyProperties> properties;
+
+  vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &count, nullptr);
+  properties.resize(count);
+  vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &count,
+                                           properties.data());
+
+  for (uint32_t i = 0; i < count; ++i) {
+    if (properties[i].queueFlags &
+        (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT)) {
+      queue_family_flags_ = properties[i].queueFlags;
+      queue_family_index_ = i;
+      return true;
+    }
+  }
+
+  for (uint32_t i = 0; i < count; ++i) {
+    if (properties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
+      queue_family_flags_ = properties[i].queueFlags;
+      queue_family_index_ = i;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+Result Device::CreateInstance() {
+  VkApplicationInfo appInfo = {};
+  appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+  appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0);
+
+  VkInstanceCreateInfo instInfo = {};
+  instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+  instInfo.pApplicationInfo = &appInfo;
+  // TODO(jaebaek): Enable layers, extensions
+
+  if (vkCreateInstance(&instInfo, nullptr, &instance_) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkCreateInstance Fail");
+
+  return {};
+}
+
+Result Device::ChoosePhysicalDevice() {
+  uint32_t count;
+  std::vector<VkPhysicalDevice> physical_devices;
+
+  if (vkEnumeratePhysicalDevices(instance_, &count, nullptr) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkEnumeratePhysicalDevices Fail");
+  physical_devices.resize(count);
+  if (vkEnumeratePhysicalDevices(instance_, &count, physical_devices.data()) !=
+      VK_SUCCESS)
+    return Result("Vulkan::Calling vkEnumeratePhysicalDevices Fail");
+
+  for (uint32_t i = 0; i < count; ++i) {
+    VkPhysicalDeviceProperties property;
+    vkGetPhysicalDeviceProperties(physical_devices[i], &property);
+    // TODO(jaebaek): Check physical device property
+
+    if (ChooseQueueFamilyIndex(physical_devices[i])) {
+      physical_device_ = physical_devices[i];
+      vkGetPhysicalDeviceMemoryProperties(physical_device_,
+                                          &physical_memory_properties_);
+      return {};
+    }
+  }
+
+  return Result("Vulkan::No physical device supports Vulkan");
+}
+
+Result Device::CreateDevice() {
+  VkDeviceQueueCreateInfo queue_info;
+  const float priorities[] = {1.0f};
+
+  queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+  queue_info.queueFamilyIndex = queue_family_index_;
+  queue_info.queueCount = 1;
+  queue_info.pQueuePriorities = priorities;
+
+  VkDeviceCreateInfo info = {};
+  info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+  info.pQueueCreateInfos = &queue_info;
+  info.queueCreateInfoCount = 1;
+  // TODO(jaebaek): Enable layers, extensions, features
+
+  if (vkCreateDevice(physical_device_, &info, nullptr, &device_) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkCreateDevice Fail");
+
+  return {};
+}
+
+}  // namespace vulkan
+
+}  // namespace amber
diff --git a/src/vulkan/device.h b/src/vulkan/device.h
new file mode 100644
index 0000000..634e07d
--- /dev/null
+++ b/src/vulkan/device.h
@@ -0,0 +1,76 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_DEVICE_H_
+#define SRC_VULKAN_DEVICE_H_
+
+#include <memory>
+
+#include "amber/result.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+
+namespace vulkan {
+
+class Device {
+ public:
+  Device();
+  explicit Device(VkDevice device);
+  ~Device();
+
+  Result Initialize();
+  void Shutdown();
+
+  VkDevice GetDevice() const { return device_; }
+  uint32_t GetQueueFamilyIndex() const { return queue_family_index_; }
+  VkQueue GetQueue() const { return queue_; }
+  const VkPhysicalDeviceMemoryProperties& GetPhysicalMemoryProperties() const {
+    return physical_memory_properties_;
+  }
+
+ private:
+  Result CreateInstance();
+
+  // Get a physical device by checking if the physical device has a proper
+  // queue family.
+  Result ChoosePhysicalDevice();
+
+  // Return true if |physical_device| has a queue family that supports both
+  // graphics and compute or only a compute pipeline. If the proper queue
+  // family exists, |queue_family_index_| and |queue_family_flags_| will have
+  // the queue family index and flags, respectively. Return false if the proper
+  // queue family does not exist.
+  bool ChooseQueueFamilyIndex(const VkPhysicalDevice& physical_device);
+
+  // Create a logical device.
+  Result CreateDevice();
+
+  VkInstance instance_ = VK_NULL_HANDLE;
+  VkPhysicalDevice physical_device_ = VK_NULL_HANDLE;
+  VkPhysicalDeviceMemoryProperties physical_memory_properties_ = {};
+  VkDevice device_ = VK_NULL_HANDLE;
+  VkQueueFlags queue_family_flags_ = 0;
+  uint32_t queue_family_index_ = 0;
+
+  VkQueue queue_ = VK_NULL_HANDLE;
+
+  bool destroy_device_ = true;
+};
+
+}  // namespace vulkan
+
+}  // namespace amber
+
+#endif  // SRC_VULKAN_DEVICE_H_
diff --git a/src/vulkan/engine_vulkan.cc b/src/vulkan/engine_vulkan.cc
new file mode 100644
index 0000000..4618296
--- /dev/null
+++ b/src/vulkan/engine_vulkan.cc
@@ -0,0 +1,278 @@
+// Copyright 2018 The Amber 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 "src/vulkan/engine_vulkan.h"
+
+#include <algorithm>
+
+#include "src/make_unique.h"
+#include "src/vulkan/format_data.h"
+#include "src/vulkan/graphics_pipeline.h"
+
+namespace amber {
+namespace vulkan {
+namespace {
+
+const uint32_t kFramebufferWidth = 250;
+const uint32_t kFramebufferHeight = 250;
+const VkFormat kDefaultColorFormat = VK_FORMAT_R8G8B8A8_UNORM;
+
+VkShaderStageFlagBits ToVkShaderStage(ShaderType type) {
+  switch (type) {
+    case ShaderType::kGeometry:
+      return VK_SHADER_STAGE_GEOMETRY_BIT;
+    case ShaderType::kFragment:
+      return VK_SHADER_STAGE_FRAGMENT_BIT;
+    case ShaderType::kVertex:
+      return VK_SHADER_STAGE_VERTEX_BIT;
+    case ShaderType::kTessellationControl:
+      return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
+    case ShaderType::kTessellationEvaluation:
+      return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
+    case ShaderType::kCompute:
+      return VK_SHADER_STAGE_COMPUTE_BIT;
+  }
+
+  // Unreachable
+  return VK_SHADER_STAGE_FRAGMENT_BIT;
+}
+
+}  // namespace
+
+EngineVulkan::EngineVulkan() : Engine() {}
+
+EngineVulkan::~EngineVulkan() = default;
+
+Result EngineVulkan::InitDeviceAndCreateCommand() {
+  Result r = device_->Initialize();
+  if (!r.IsSuccess())
+    return r;
+
+  if (!pool_) {
+    pool_ = MakeUnique<CommandPool>(device_->GetDevice());
+    r = pool_->Initialize(device_->GetQueueFamilyIndex());
+    if (!r.IsSuccess())
+      return r;
+  }
+
+  return {};
+}
+
+Result EngineVulkan::Initialize() {
+  if (device_)
+    return Result("Vulkan::Set device_ already exists");
+
+  device_ = MakeUnique<Device>();
+  return InitDeviceAndCreateCommand();
+}
+
+Result EngineVulkan::InitializeWithDevice(void* default_device) {
+  if (device_)
+    return Result("Vulkan::Set device_ already exists");
+
+  VkDevice device = static_cast<VkDevice>(default_device);
+  if (device == VK_NULL_HANDLE)
+    return Result("Vulkan::Set VK_NULL_HANDLE is given");
+
+  device_ = MakeUnique<Device>(device);
+  return InitDeviceAndCreateCommand();
+}
+
+Result EngineVulkan::Shutdown() {
+  for (auto it = modules_.begin(); it != modules_.end(); ++it)
+    vkDestroyShaderModule(device_->GetDevice(), it->second, nullptr);
+
+  pipeline_->Shutdown();
+  pool_->Shutdown();
+  device_->Shutdown();
+  return {};
+}
+
+Result EngineVulkan::AddRequirement(Feature feature, const Format* fmt) {
+  auto it = std::find_if(requirements_.begin(), requirements_.end(),
+                         [&feature](const EngineVulkan::Requirement& req) {
+                           return req.feature == feature;
+                         });
+  if (it != requirements_.end())
+    return Result("Vulkan::Feature Already Exists");
+
+  requirements_.push_back({feature, fmt});
+  return {};
+}
+
+Result EngineVulkan::CreatePipeline(PipelineType type) {
+  if (type == PipelineType::kCompute)
+    return Result("Vulkan::Compute Pipeline Not Implemented");
+
+  VkFormat frame_buffer_format = kDefaultColorFormat;
+  auto it_frame_buffer =
+      std::find_if(requirements_.begin(), requirements_.end(),
+                   [](const EngineVulkan::Requirement& req) {
+                     return req.feature == Feature::kFramebuffer;
+                   });
+  if (it_frame_buffer != requirements_.end()) {
+    frame_buffer_format = ToVkFormat(it_frame_buffer->format->GetFormatType());
+  }
+
+  VkFormat depth_stencil_format = VK_FORMAT_UNDEFINED;
+  auto it_depth_stencil =
+      std::find_if(requirements_.begin(), requirements_.end(),
+                   [](const EngineVulkan::Requirement& req) {
+                     return req.feature == Feature::kDepthStencil;
+                   });
+  if (it_depth_stencil != requirements_.end()) {
+    depth_stencil_format =
+        ToVkFormat(it_depth_stencil->format->GetFormatType());
+  }
+
+  pipeline_ = MakeUnique<GraphicsPipeline>(
+      type, device_->GetDevice(), device_->GetPhysicalMemoryProperties(),
+      frame_buffer_format, depth_stencil_format, GetShaderStageInfo());
+
+  return pipeline_->AsGraphics()->Initialize(
+      kFramebufferWidth, kFramebufferHeight, pool_->GetCommandPool(),
+      device_->GetQueue());
+}
+
+Result EngineVulkan::SetShader(ShaderType type,
+                               const std::vector<uint32_t>& data) {
+  if (type == ShaderType::kCompute)
+    return Result("Vulkan::Compute Pipeline Not Implemented");
+
+  VkShaderModuleCreateInfo info = {};
+  info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+  info.codeSize = data.size() * sizeof(uint32_t);
+  info.pCode = data.data();
+
+  auto it = modules_.find(type);
+  if (it != modules_.end())
+    return Result("Vulkan::Setting Duplicated Shader Types Fail");
+
+  VkShaderModule shader;
+  if (vkCreateShaderModule(device_->GetDevice(), &info, nullptr, &shader) !=
+      VK_SUCCESS) {
+    return Result("Vulkan::Calling vkCreateShaderModule Fail");
+  }
+
+  modules_[type] = shader;
+  return {};
+}
+
+std::vector<VkPipelineShaderStageCreateInfo>
+EngineVulkan::GetShaderStageInfo() {
+  std::vector<VkPipelineShaderStageCreateInfo> stage_info(modules_.size());
+  uint32_t stage_count = 0;
+  for (auto it : modules_) {
+    stage_info[stage_count] = {};
+    stage_info[stage_count].sType =
+        VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+    stage_info[stage_count].stage = ToVkShaderStage(it.first);
+    stage_info[stage_count].module = it.second;
+    // TODO(jaebaek): Handle entry point command
+    stage_info[stage_count].pName = "main";
+    ++stage_count;
+  }
+  return stage_info;
+}
+
+Result EngineVulkan::SetBuffer(BufferType type,
+                               uint8_t location,
+                               const Format& format,
+                               const std::vector<Value>& values) {
+  if (!pipeline_)
+    return Result("Vulkan::SetBuffer no Pipeline exists");
+
+  // TODO(jaebaek): Doublecheck those buffers are only for the graphics
+  //                pipeline.
+  if (!pipeline_->IsGraphics())
+    return Result("Vulkan::SetBuffer for Non-Graphics Pipeline");
+
+  pipeline_->AsGraphics()->SetBuffer(type, location, format, values);
+  return {};
+}
+
+Result EngineVulkan::ExecuteClearColor(const ClearColorCommand* command) {
+  if (!pipeline_->IsGraphics())
+    return Result("Vulkan::Clear Color Command for Non-Graphics Pipeline");
+
+  return pipeline_->AsGraphics()->SetClearColor(
+      command->GetR(), command->GetG(), command->GetB(), command->GetA());
+}
+
+Result EngineVulkan::ExecuteClearStencil(const ClearStencilCommand* command) {
+  if (!pipeline_->IsGraphics())
+    return Result("Vulkan::Clear Stencil Command for Non-Graphics Pipeline");
+
+  return pipeline_->AsGraphics()->SetClearStencil(command->GetValue());
+}
+
+Result EngineVulkan::ExecuteClearDepth(const ClearDepthCommand* command) {
+  if (!pipeline_->IsGraphics())
+    return Result("Vulkan::Clear Depth Command for Non-Graphics Pipeline");
+
+  return pipeline_->AsGraphics()->SetClearDepth(command->GetValue());
+}
+
+Result EngineVulkan::ExecuteClear(const ClearCommand*) {
+  if (!pipeline_->IsGraphics())
+    return Result("Vulkan::Clear Command for Non-Graphics Pipeline");
+
+  return pipeline_->AsGraphics()->Clear();
+}
+
+Result EngineVulkan::ExecuteDrawRect(const DrawRectCommand*) {
+  return Result("Vulkan::ExecuteDrawRect Not Implemented");
+}
+
+Result EngineVulkan::ExecuteDrawArrays(const DrawArraysCommand*) {
+  if (!pipeline_->IsGraphics())
+    return Result("Vulkan::DrawArrays for Non-Graphics Pipeline");
+
+  return pipeline_->AsGraphics()->Draw();
+}
+
+Result EngineVulkan::ExecuteCompute(const ComputeCommand*) {
+  return Result("Vulkan::ExecuteCompute Not Implemented");
+}
+
+Result EngineVulkan::ExecuteEntryPoint(const EntryPointCommand*) {
+  return Result("Vulkan::ExecuteEntryPoint Not Implemented");
+}
+
+Result EngineVulkan::ExecutePatchParameterVertices(
+    const PatchParameterVerticesCommand*) {
+  return Result("Vulkan::ExecutePatch Not Implemented");
+}
+
+Result EngineVulkan::ExecuteProbe(const ProbeCommand* command) {
+  if (!pipeline_->IsGraphics())
+    return Result("Vulkan::Probe FrameBuffer for Non-Graphics Pipeline");
+
+  return pipeline_->AsGraphics()->Probe(command);
+}
+
+Result EngineVulkan::ExecuteProbeSSBO(const ProbeSSBOCommand*) {
+  return Result("Vulkan::ExecuteProbeSSBO Not Implemented");
+}
+
+Result EngineVulkan::ExecuteBuffer(const BufferCommand*) {
+  return Result("Vulkan::ExecuteBuffer Not Implemented");
+}
+
+Result EngineVulkan::ExecuteTolerance(const ToleranceCommand*) {
+  return Result("Vulkan::ExecuteTolerance Not Implemented");
+}
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/engine_vulkan.h b/src/vulkan/engine_vulkan.h
new file mode 100644
index 0000000..c8c2a00
--- /dev/null
+++ b/src/vulkan/engine_vulkan.h
@@ -0,0 +1,85 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_ENGINE_VULKAN_H_
+#define SRC_VULKAN_ENGINE_VULKAN_H_
+
+#include <memory>
+#include <unordered_map>
+
+#include "src/cast_hash.h"
+#include "src/engine.h"
+#include "src/vulkan/command.h"
+#include "src/vulkan/device.h"
+#include "src/vulkan/pipeline.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class EngineVulkan : public Engine {
+ public:
+  EngineVulkan();
+  ~EngineVulkan() override;
+
+  // Engine
+  Result Initialize() override;
+  Result InitializeWithDevice(void* default_device) override;
+  Result Shutdown() override;
+  Result AddRequirement(Feature feature, const Format*) override;
+  Result CreatePipeline(PipelineType type) override;
+  Result SetShader(ShaderType type, const std::vector<uint32_t>& data) override;
+  Result SetBuffer(BufferType type,
+                   uint8_t location,
+                   const Format& format,
+                   const std::vector<Value>& data) override;
+  Result ExecuteClearColor(const ClearColorCommand* cmd) override;
+  Result ExecuteClearStencil(const ClearStencilCommand* cmd) override;
+  Result ExecuteClearDepth(const ClearDepthCommand* cmd) override;
+  Result ExecuteClear(const ClearCommand* cmd) override;
+  Result ExecuteDrawRect(const DrawRectCommand* cmd) override;
+  Result ExecuteDrawArrays(const DrawArraysCommand* cmd) override;
+  Result ExecuteCompute(const ComputeCommand* cmd) override;
+  Result ExecuteEntryPoint(const EntryPointCommand* cmd) override;
+  Result ExecutePatchParameterVertices(
+      const PatchParameterVerticesCommand* cmd) override;
+  Result ExecuteProbe(const ProbeCommand* cmd) override;
+  Result ExecuteProbeSSBO(const ProbeSSBOCommand* cmd) override;
+  Result ExecuteBuffer(const BufferCommand* cmd) override;
+  Result ExecuteTolerance(const ToleranceCommand* cmd) override;
+
+ private:
+  Result InitDeviceAndCreateCommand();
+
+  std::vector<VkPipelineShaderStageCreateInfo> GetShaderStageInfo();
+
+  std::unique_ptr<Device> device_;
+  std::unique_ptr<CommandPool> pool_;
+  std::unique_ptr<Pipeline> pipeline_;
+
+  std::unordered_map<ShaderType, VkShaderModule, CastHash<ShaderType>> modules_;
+
+  struct Requirement {
+    Feature feature;
+    const Format* format;
+  };
+
+  std::vector<Requirement> requirements_;
+  std::vector<Requirement>::iterator FindFeature(Feature feature);
+};
+
+}  // namespace vulkan
+}  // namespace amber
+
+#endif  // SRC_VULKAN_ENGINE_VULKAN_H_
diff --git a/src/vulkan/find_vulkan.cmake b/src/vulkan/find_vulkan.cmake
new file mode 100644
index 0000000..630346a
--- /dev/null
+++ b/src/vulkan/find_vulkan.cmake
@@ -0,0 +1,77 @@
+# Copyright 2018 The Amber 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 this file to find Vulkan and and set up compilation and linking.
+
+
+# Export these settings to the includer.
+set(Vulkan_FOUND FALSE)
+set(VULKAN_LIB "")
+
+
+# Our first choice is to pick up the Vulkan headers from an enclosing project.
+# And if that's the case, then use Vulkan libraries as specified by
+# Vulkan_LIBRARIES, with a default library of "vulkan".
+set(X "${Vulkan-Headers_SOURCE_DIR}/include")
+if (IS_DIRECTORY "${X}")
+  message(STATUS "Amber: Using Vulkan header dir ${X}")
+  list(APPEND CMAKE_REQUIRED_INCLUDES "${X}")
+  # Add the directory to the list of include paths, before any others.
+  include_directories(BEFORE "${X}")
+  CHECK_INCLUDE_FILE(vulkan/vulkan.h HAVE_VULKAN_HEADER)
+
+  if (${HAVE_VULKAN_HEADER})
+    if ("${Vulkan_LIBRARIES}" STREQUAL "")
+      message(STATUS "Amber: Defaulting to Vulkan library: vulkan")
+      set(VULKAN_LIB vulkan)
+    else()
+      message(STATUS "Amber: Using specified Vulkan libraries: ${Vulkan_LIBRARIES}")
+      set(VULKAN_LIB "${Vulkan_LIBRARIES}")
+    endif()
+    # For now assume we have Vulkan.  We have its header, but we haven't checked
+    # for the library.
+    # TODO(dneto): Actually check for the libraries.
+    set(Vulkan_FOUND TRUE)
+  endif()
+endif()
+unset(X)
+
+if (NOT ${Vulkan_FOUND})
+  # If we aren't already building a Vulkan library, then use CMake to find it.
+  if(NOT ${CMAKE_VERSION} VERSION_LESS "3.7")
+    # LunarG added FindVulkan support to CMake 3.7.  If you have the Vulkan SDK
+    # published by LunarG, then set environment variables:
+    #  VULKAN_SDK should point to the platform-specific SDK directory containing
+    #    the include and lib directories.
+    #  VK_ICD_FILENAMES should point to ICD JSON file.
+
+    # Example, with the LunarG SDK macOS edition with MoltenVK:
+    #  export VULKAN_SDK="$HOME/vulkan-macos-1.1.85.0/macOS"
+    #  export VK_ICD_FILENAMES="$VULKAN_SDK/etc/vulkan/icd/MoltenVK_icd.json"
+    # See https://cmake.org/cmake/help/v3.7/module/FindVulkan.html
+    find_package(Vulkan)
+    if(${Vulkan_FOUND})
+      message(STATUS "Amber: Using Vulkan from Vulkan SDK at $ENV{VULKAN_SDK}")
+      # Use the imported library target set up by find_package.
+      set(VULKAN_LIB Vulkan::Vulkan)
+      # Add the Vulkan include directory to the list of include paths.
+      include_directories("${Vulkan_INCLUDE_DIRS}")
+    endif()
+  endif()
+endif()
+
+if (NOT ${Vulkan_FOUND})
+  message(STATUS "Amber: Did not find Vulkan")
+endif()
diff --git a/src/vulkan/format_data.cc b/src/vulkan/format_data.cc
new file mode 100644
index 0000000..a78aad5
--- /dev/null
+++ b/src/vulkan/format_data.cc
@@ -0,0 +1,299 @@
+// Copyright 2018 The Amber 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 "src/vulkan/format_data.h"
+
+namespace amber {
+namespace vulkan {
+
+VkFormat ToVkFormat(FormatType type) {
+  switch (type) {
+    case FormatType::kUnknown:
+      return VK_FORMAT_UNDEFINED;
+    case FormatType::kA1R5G5B5_UNORM_PACK16:
+      return VK_FORMAT_A1R5G5B5_UNORM_PACK16;
+    case FormatType::kA2B10G10R10_SINT_PACK32:
+      return VK_FORMAT_A2B10G10R10_SINT_PACK32;
+    case FormatType::kA2B10G10R10_SNORM_PACK32:
+      return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
+    case FormatType::kA2B10G10R10_SSCALED_PACK32:
+      return VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
+    case FormatType::kA2B10G10R10_UINT_PACK32:
+      return VK_FORMAT_A2B10G10R10_UINT_PACK32;
+    case FormatType::kA2B10G10R10_UNORM_PACK32:
+      return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
+    case FormatType::kA2B10G10R10_USCALED_PACK32:
+      return VK_FORMAT_A2B10G10R10_USCALED_PACK32;
+    case FormatType::kA2R10G10B10_SINT_PACK32:
+      return VK_FORMAT_A2R10G10B10_SINT_PACK32;
+    case FormatType::kA2R10G10B10_SNORM_PACK32:
+      return VK_FORMAT_A2R10G10B10_SNORM_PACK32;
+    case FormatType::kA2R10G10B10_SSCALED_PACK32:
+      return VK_FORMAT_A2R10G10B10_SSCALED_PACK32;
+    case FormatType::kA2R10G10B10_UINT_PACK32:
+      return VK_FORMAT_A2R10G10B10_UINT_PACK32;
+    case FormatType::kA2R10G10B10_UNORM_PACK32:
+      return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
+    case FormatType::kA2R10G10B10_USCALED_PACK32:
+      return VK_FORMAT_A2R10G10B10_USCALED_PACK32;
+    case FormatType::kA8B8G8R8_SINT_PACK32:
+      return VK_FORMAT_A8B8G8R8_SINT_PACK32;
+    case FormatType::kA8B8G8R8_SNORM_PACK32:
+      return VK_FORMAT_A8B8G8R8_SNORM_PACK32;
+    case FormatType::kA8B8G8R8_SRGB_PACK32:
+      return VK_FORMAT_A8B8G8R8_SRGB_PACK32;
+    case FormatType::kA8B8G8R8_SSCALED_PACK32:
+      return VK_FORMAT_A8B8G8R8_SSCALED_PACK32;
+    case FormatType::kA8B8G8R8_UINT_PACK32:
+      return VK_FORMAT_A8B8G8R8_UINT_PACK32;
+    case FormatType::kA8B8G8R8_UNORM_PACK32:
+      return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
+    case FormatType::kA8B8G8R8_USCALED_PACK32:
+      return VK_FORMAT_A8B8G8R8_USCALED_PACK32;
+    case FormatType::kB10G11R11_UFLOAT_PACK32:
+      return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
+    case FormatType::kB4G4R4A4_UNORM_PACK16:
+      return VK_FORMAT_B4G4R4A4_UNORM_PACK16;
+    case FormatType::kB5G5R5A1_UNORM_PACK16:
+      return VK_FORMAT_B5G5R5A1_UNORM_PACK16;
+    case FormatType::kB5G6R5_UNORM_PACK16:
+      return VK_FORMAT_B5G6R5_UNORM_PACK16;
+    case FormatType::kB8G8R8A8_SINT:
+      return VK_FORMAT_B8G8R8A8_SINT;
+    case FormatType::kB8G8R8A8_SNORM:
+      return VK_FORMAT_B8G8R8A8_SNORM;
+    case FormatType::kB8G8R8A8_SRGB:
+      return VK_FORMAT_B8G8R8A8_SRGB;
+    case FormatType::kB8G8R8A8_SSCALED:
+      return VK_FORMAT_B8G8R8A8_SSCALED;
+    case FormatType::kB8G8R8A8_UINT:
+      return VK_FORMAT_B8G8R8A8_UINT;
+    case FormatType::kB8G8R8A8_UNORM:
+      return VK_FORMAT_B8G8R8A8_UNORM;
+    case FormatType::kB8G8R8A8_USCALED:
+      return VK_FORMAT_B8G8R8A8_USCALED;
+    case FormatType::kB8G8R8_SINT:
+      return VK_FORMAT_B8G8R8_SINT;
+    case FormatType::kB8G8R8_SNORM:
+      return VK_FORMAT_B8G8R8_SNORM;
+    case FormatType::kB8G8R8_SRGB:
+      return VK_FORMAT_B8G8R8_SRGB;
+    case FormatType::kB8G8R8_SSCALED:
+      return VK_FORMAT_B8G8R8_SSCALED;
+    case FormatType::kB8G8R8_UINT:
+      return VK_FORMAT_B8G8R8_UINT;
+    case FormatType::kB8G8R8_UNORM:
+      return VK_FORMAT_B8G8R8_UNORM;
+    case FormatType::kB8G8R8_USCALED:
+      return VK_FORMAT_B8G8R8_USCALED;
+    case FormatType::kD16_UNORM:
+      return VK_FORMAT_D16_UNORM;
+    case FormatType::kD16_UNORM_S8_UINT:
+      return VK_FORMAT_D16_UNORM_S8_UINT;
+    case FormatType::kD24_UNORM_S8_UINT:
+      return VK_FORMAT_D24_UNORM_S8_UINT;
+    case FormatType::kD32_SFLOAT:
+      return VK_FORMAT_D32_SFLOAT;
+    case FormatType::kD32_SFLOAT_S8_UINT:
+      return VK_FORMAT_D32_SFLOAT_S8_UINT;
+    case FormatType::kR16G16B16A16_SFLOAT:
+      return VK_FORMAT_R16G16B16A16_SFLOAT;
+    case FormatType::kR16G16B16A16_SINT:
+      return VK_FORMAT_R16G16B16A16_SINT;
+    case FormatType::kR16G16B16A16_SNORM:
+      return VK_FORMAT_R16G16B16A16_SNORM;
+    case FormatType::kR16G16B16A16_SSCALED:
+      return VK_FORMAT_R16G16B16A16_SSCALED;
+    case FormatType::kR16G16B16A16_UINT:
+      return VK_FORMAT_R16G16B16A16_UINT;
+    case FormatType::kR16G16B16A16_UNORM:
+      return VK_FORMAT_R16G16B16A16_UNORM;
+    case FormatType::kR16G16B16A16_USCALED:
+      return VK_FORMAT_R16G16B16A16_USCALED;
+    case FormatType::kR16G16B16_SFLOAT:
+      return VK_FORMAT_R16G16B16_SFLOAT;
+    case FormatType::kR16G16B16_SINT:
+      return VK_FORMAT_R16G16B16_SINT;
+    case FormatType::kR16G16B16_SNORM:
+      return VK_FORMAT_R16G16B16_SNORM;
+    case FormatType::kR16G16B16_SSCALED:
+      return VK_FORMAT_R16G16B16_SSCALED;
+    case FormatType::kR16G16B16_UINT:
+      return VK_FORMAT_R16G16B16_UINT;
+    case FormatType::kR16G16B16_UNORM:
+      return VK_FORMAT_R16G16B16_UNORM;
+    case FormatType::kR16G16B16_USCALED:
+      return VK_FORMAT_R16G16B16_USCALED;
+    case FormatType::kR16G16_SFLOAT:
+      return VK_FORMAT_R16G16_SFLOAT;
+    case FormatType::kR16G16_SINT:
+      return VK_FORMAT_R16G16_SINT;
+    case FormatType::kR16G16_SNORM:
+      return VK_FORMAT_R16G16_SNORM;
+    case FormatType::kR16G16_SSCALED:
+      return VK_FORMAT_R16G16_SSCALED;
+    case FormatType::kR16G16_UINT:
+      return VK_FORMAT_R16G16_UINT;
+    case FormatType::kR16G16_UNORM:
+      return VK_FORMAT_R16G16_UNORM;
+    case FormatType::kR16G16_USCALED:
+      return VK_FORMAT_R16G16_USCALED;
+    case FormatType::kR16_SFLOAT:
+      return VK_FORMAT_R16_SFLOAT;
+    case FormatType::kR16_SINT:
+      return VK_FORMAT_R16_SINT;
+    case FormatType::kR16_SNORM:
+      return VK_FORMAT_R16_SNORM;
+    case FormatType::kR16_SSCALED:
+      return VK_FORMAT_R16_SSCALED;
+    case FormatType::kR16_UINT:
+      return VK_FORMAT_R16_UINT;
+    case FormatType::kR16_UNORM:
+      return VK_FORMAT_R16_UNORM;
+    case FormatType::kR16_USCALED:
+      return VK_FORMAT_R16_USCALED;
+    case FormatType::kR32G32B32A32_SFLOAT:
+      return VK_FORMAT_R32G32B32A32_SFLOAT;
+    case FormatType::kR32G32B32A32_SINT:
+      return VK_FORMAT_R32G32B32A32_SINT;
+    case FormatType::kR32G32B32A32_UINT:
+      return VK_FORMAT_R32G32B32A32_UINT;
+    case FormatType::kR32G32B32_SFLOAT:
+      return VK_FORMAT_R32G32B32_SFLOAT;
+    case FormatType::kR32G32B32_SINT:
+      return VK_FORMAT_R32G32B32_SINT;
+    case FormatType::kR32G32B32_UINT:
+      return VK_FORMAT_R32G32B32_UINT;
+    case FormatType::kR32G32_SFLOAT:
+      return VK_FORMAT_R32G32_SFLOAT;
+    case FormatType::kR32G32_SINT:
+      return VK_FORMAT_R32G32_SINT;
+    case FormatType::kR32G32_UINT:
+      return VK_FORMAT_R32G32_UINT;
+    case FormatType::kR32_SFLOAT:
+      return VK_FORMAT_R32_SFLOAT;
+    case FormatType::kR32_SINT:
+      return VK_FORMAT_R32_SINT;
+    case FormatType::kR32_UINT:
+      return VK_FORMAT_R32_UINT;
+    case FormatType::kR4G4B4A4_UNORM_PACK16:
+      return VK_FORMAT_R4G4B4A4_UNORM_PACK16;
+    case FormatType::kR4G4_UNORM_PACK8:
+      return VK_FORMAT_R4G4_UNORM_PACK8;
+    case FormatType::kR5G5B5A1_UNORM_PACK16:
+      return VK_FORMAT_R5G5B5A1_UNORM_PACK16;
+    case FormatType::kR5G6B5_UNORM_PACK16:
+      return VK_FORMAT_R5G6B5_UNORM_PACK16;
+    case FormatType::kR64G64B64A64_SFLOAT:
+      return VK_FORMAT_R64G64B64A64_SFLOAT;
+    case FormatType::kR64G64B64A64_SINT:
+      return VK_FORMAT_R64G64B64A64_SINT;
+    case FormatType::kR64G64B64A64_UINT:
+      return VK_FORMAT_R64G64B64A64_UINT;
+    case FormatType::kR64G64B64_SFLOAT:
+      return VK_FORMAT_R64G64B64_SFLOAT;
+    case FormatType::kR64G64B64_SINT:
+      return VK_FORMAT_R64G64B64_SINT;
+    case FormatType::kR64G64B64_UINT:
+      return VK_FORMAT_R64G64B64_UINT;
+    case FormatType::kR64G64_SFLOAT:
+      return VK_FORMAT_R64G64_SFLOAT;
+    case FormatType::kR64G64_SINT:
+      return VK_FORMAT_R64G64_SINT;
+    case FormatType::kR64G64_UINT:
+      return VK_FORMAT_R64G64_UINT;
+    case FormatType::kR64_SFLOAT:
+      return VK_FORMAT_R64_SFLOAT;
+    case FormatType::kR64_SINT:
+      return VK_FORMAT_R64_SINT;
+    case FormatType::kR64_UINT:
+      return VK_FORMAT_R64_UINT;
+    case FormatType::kR8G8B8A8_SINT:
+      return VK_FORMAT_R8G8B8A8_SINT;
+    case FormatType::kR8G8B8A8_SNORM:
+      return VK_FORMAT_R8G8B8A8_SNORM;
+    case FormatType::kR8G8B8A8_SRGB:
+      return VK_FORMAT_R8G8B8A8_SRGB;
+    case FormatType::kR8G8B8A8_SSCALED:
+      return VK_FORMAT_R8G8B8A8_SSCALED;
+    case FormatType::kR8G8B8A8_UINT:
+      return VK_FORMAT_R8G8B8A8_UINT;
+    case FormatType::kR8G8B8A8_UNORM:
+      return VK_FORMAT_R8G8B8A8_UNORM;
+    case FormatType::kR8G8B8A8_USCALED:
+      return VK_FORMAT_R8G8B8A8_USCALED;
+    case FormatType::kR8G8B8_SINT:
+      return VK_FORMAT_R8G8B8_SINT;
+    case FormatType::kR8G8B8_SNORM:
+      return VK_FORMAT_R8G8B8_SNORM;
+    case FormatType::kR8G8B8_SRGB:
+      return VK_FORMAT_R8G8B8_SRGB;
+    case FormatType::kR8G8B8_SSCALED:
+      return VK_FORMAT_R8G8B8_SSCALED;
+    case FormatType::kR8G8B8_UINT:
+      return VK_FORMAT_R8G8B8_UINT;
+    case FormatType::kR8G8B8_UNORM:
+      return VK_FORMAT_R8G8B8_UNORM;
+    case FormatType::kR8G8B8_USCALED:
+      return VK_FORMAT_R8G8B8_USCALED;
+    case FormatType::kR8G8_SINT:
+      return VK_FORMAT_R8G8_SINT;
+    case FormatType::kR8G8_SNORM:
+      return VK_FORMAT_R8G8_SNORM;
+    case FormatType::kR8G8_SRGB:
+      return VK_FORMAT_R8G8_SRGB;
+    case FormatType::kR8G8_SSCALED:
+      return VK_FORMAT_R8G8_SSCALED;
+    case FormatType::kR8G8_UINT:
+      return VK_FORMAT_R8G8_UINT;
+    case FormatType::kR8G8_UNORM:
+      return VK_FORMAT_R8G8_UNORM;
+    case FormatType::kR8G8_USCALED:
+      return VK_FORMAT_R8G8_USCALED;
+    case FormatType::kR8_SINT:
+      return VK_FORMAT_R8_SINT;
+    case FormatType::kR8_SNORM:
+      return VK_FORMAT_R8_SNORM;
+    case FormatType::kR8_SRGB:
+      return VK_FORMAT_R8_SRGB;
+    case FormatType::kR8_SSCALED:
+      return VK_FORMAT_R8_SSCALED;
+    case FormatType::kR8_UINT:
+      return VK_FORMAT_R8_UINT;
+    case FormatType::kR8_UNORM:
+      return VK_FORMAT_R8_UNORM;
+    case FormatType::kR8_USCALED:
+      return VK_FORMAT_R8_USCALED;
+    case FormatType::kS8_UINT:
+      return VK_FORMAT_S8_UINT;
+    case FormatType::kX8_D24_UNORM_PACK32:
+      return VK_FORMAT_X8_D24_UNORM_PACK32;
+  }
+  return VK_FORMAT_UNDEFINED;
+}
+
+uint32_t VkFormatToByteSize(VkFormat format) {
+  switch (format) {
+    case VK_FORMAT_R8G8B8A8_UNORM:
+      return 4;
+
+    // TODO(jaebaek): Handle all cases.
+    default:
+      break;
+  }
+  return 0;
+}
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/format_data.h b/src/vulkan/format_data.h
new file mode 100644
index 0000000..c9dc44a
--- /dev/null
+++ b/src/vulkan/format_data.h
@@ -0,0 +1,30 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_FORMAT_DATA_H_
+#define SRC_VULKAN_FORMAT_DATA_H_
+
+#include "src/format_data.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+VkFormat ToVkFormat(FormatType type);
+uint32_t VkFormatToByteSize(VkFormat format);
+
+}  // namespace vulkan
+}  // namespace amber
+
+#endif  // SRC_VULKAN_FORMAT_DATA_H_
diff --git a/src/vulkan/frame_buffer.cc b/src/vulkan/frame_buffer.cc
new file mode 100644
index 0000000..ab36bbc
--- /dev/null
+++ b/src/vulkan/frame_buffer.cc
@@ -0,0 +1,83 @@
+// Copyright 2018 The Amber 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 "src/vulkan/frame_buffer.h"
+
+#include <limits>
+
+#include "src/make_unique.h"
+
+namespace amber {
+namespace vulkan {
+
+FrameBuffer::FrameBuffer(VkDevice device, uint32_t width, uint32_t height)
+    : device_(device), width_(width), height_(height) {}
+
+FrameBuffer::~FrameBuffer() = default;
+
+Result FrameBuffer::Initialize(
+    VkRenderPass render_pass,
+    VkFormat color_format,
+    VkFormat depth_format,
+    const VkPhysicalDeviceMemoryProperties& properties) {
+  std::vector<VkImageView> attachments;
+
+  if (color_format != VK_FORMAT_UNDEFINED) {
+    color_image_ = MakeUnique<Image>(device_, color_format, width_, height_,
+                                     depth_, properties);
+    Result r = color_image_->Initialize(VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+                                        VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
+    if (!r.IsSuccess())
+      return r;
+    attachments.push_back(color_image_->GetVkImageView());
+  }
+
+  if (depth_format != VK_FORMAT_UNDEFINED) {
+    depth_image_ = MakeUnique<Image>(device_, depth_format, width_, height_,
+                                     depth_, properties);
+    Result r =
+        depth_image_->Initialize(VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+                                 VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
+    if (!r.IsSuccess())
+      return r;
+    attachments.push_back(depth_image_->GetVkImageView());
+  }
+
+  VkFramebufferCreateInfo frame_buffer_info = {};
+  frame_buffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+  frame_buffer_info.renderPass = render_pass;
+  frame_buffer_info.attachmentCount = static_cast<uint32_t>(attachments.size());
+  frame_buffer_info.pAttachments = attachments.data();
+  frame_buffer_info.width = width_;
+  frame_buffer_info.height = height_;
+  frame_buffer_info.layers = 1;
+
+  if (vkCreateFramebuffer(device_, &frame_buffer_info, nullptr, &frame_) !=
+      VK_SUCCESS) {
+    return Result("Vulkan::Calling vkCreateFramebuffer Fail");
+  }
+
+  return {};
+}
+
+void FrameBuffer::Shutdown() {
+  vkDestroyFramebuffer(device_, frame_, nullptr);
+  if (color_image_)
+    color_image_->Shutdown();
+  if (depth_image_)
+    depth_image_->Shutdown();
+}
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/frame_buffer.h b/src/vulkan/frame_buffer.h
new file mode 100644
index 0000000..0adf7ca
--- /dev/null
+++ b/src/vulkan/frame_buffer.h
@@ -0,0 +1,59 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_FRAME_BUFFER_H_
+#define SRC_VULKAN_FRAME_BUFFER_H_
+
+#include "src/vulkan/image.h"
+
+namespace amber {
+namespace vulkan {
+
+class FrameBuffer {
+ public:
+  FrameBuffer(VkDevice device, uint32_t width, uint32_t height);
+  ~FrameBuffer();
+
+  Result Initialize(VkRenderPass render_pass,
+                    VkFormat color_format,
+                    VkFormat depth_format,
+                    const VkPhysicalDeviceMemoryProperties& properties);
+  void Shutdown();
+
+  VkFramebuffer GetFrameBuffer() const { return frame_; }
+  const void* GetColorBufferPtr() const {
+    return color_image_->HostAccessibleMemoryPtr();
+  }
+  VkImage GetColorImage() const { return color_image_->GetVkImage(); }
+  Result CopyColorImageToHost(VkCommandBuffer command) {
+    return color_image_->CopyToHost(command);
+  }
+
+  uint32_t GetWidth() const { return width_; }
+  uint32_t GetHeight() const { return height_; }
+
+ private:
+  VkDevice device_ = VK_NULL_HANDLE;
+  VkFramebuffer frame_ = VK_NULL_HANDLE;
+  std::unique_ptr<Image> color_image_;
+  std::unique_ptr<Image> depth_image_;
+  uint32_t width_ = 0;
+  uint32_t height_ = 0;
+  uint32_t depth_ = 1;
+};
+
+}  // namespace vulkan
+}  // namespace amber
+
+#endif  // SRC_VULKAN_FRAME_BUFFER_H_
diff --git a/src/vulkan/graphics_pipeline.cc b/src/vulkan/graphics_pipeline.cc
new file mode 100644
index 0000000..500d423
--- /dev/null
+++ b/src/vulkan/graphics_pipeline.cc
@@ -0,0 +1,590 @@
+// Copyright 2018 The Amber 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 "src/vulkan/graphics_pipeline.h"
+
+#include <cmath>
+
+#include "src/command.h"
+#include "src/make_unique.h"
+#include "src/vulkan/format_data.h"
+
+namespace amber {
+namespace vulkan {
+namespace {
+
+const VkAttachmentDescription kDefaultAttachmentDesc = {
+    0,                     /* flags */
+    VK_FORMAT_UNDEFINED,   /* format */
+    VK_SAMPLE_COUNT_1_BIT, /* samples */
+    // TODO(jaebaek): Set up proper loadOp, StoreOp.
+    VK_ATTACHMENT_LOAD_OP_LOAD,           /* loadOp */
+    VK_ATTACHMENT_STORE_OP_STORE,         /* storeOp */
+    VK_ATTACHMENT_LOAD_OP_LOAD,           /* stencilLoadOp */
+    VK_ATTACHMENT_STORE_OP_STORE,         /* stencilStoreOp */
+    VK_IMAGE_LAYOUT_UNDEFINED,            /* initialLayout */
+    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, /* finalLayout */
+};
+
+const VkPipelineRasterizationStateCreateInfo kDefaultRasterizationInfo = {
+    VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, /* sType */
+    nullptr,                                                    /* pNext */
+    0,                                                          /* flags */
+    VK_FALSE,                /* depthClampEnable */
+    VK_FALSE,                /* rasterizerDiscardEnable */
+    VK_POLYGON_MODE_FILL,    /* polygonMode */
+    VK_CULL_MODE_NONE,       /* cullMode */
+    VK_FRONT_FACE_CLOCKWISE, /* frontFace */
+    VK_FALSE,                /* depthBiasEnable */
+    0,                       /* depthBiasConstantFactor */
+    0,                       /* depthBiasClamp */
+    0,                       /* depthBiasSlopeFactor */
+    0,                       /* lineWidth */
+};
+
+const VkSampleMask kSampleMask = ~0U;
+
+const VkPipelineMultisampleStateCreateInfo kMultisampleInfo = {
+    VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, /* sType */
+    nullptr,                                                  /* pNext */
+    0,                                                        /* flags */
+    kDefaultAttachmentDesc.samples, /* rasterizationSamples */
+    VK_FALSE,                       /* sampleShadingEnable */
+    0,                              /* minSampleShading */
+    &kSampleMask,                   /* pSampleMask */
+    VK_FALSE,                       /* alphaToCoverageEnable */
+    VK_FALSE,                       /* alphaToOneEnable */
+};
+
+const float kEpsilon = 0.002f;
+
+bool IsFloatPixelEqualInt(float pixel, uint8_t expected) {
+  // TODO(jaebaek): Change kEpsilon to tolerance.
+  return std::fabs(pixel - static_cast<float>(expected) / 255.0f) < kEpsilon;
+}
+
+}  // namespace
+
+GraphicsPipeline::GraphicsPipeline(
+    PipelineType type,
+    VkDevice device,
+    const VkPhysicalDeviceMemoryProperties& properties,
+    VkFormat color_format,
+    VkFormat depth_stencil_format,
+    std::vector<VkPipelineShaderStageCreateInfo> shader_stage_info)
+    : Pipeline(type, device, properties),
+      color_format_(color_format),
+      depth_stencil_format_(depth_stencil_format),
+      shader_stage_info_(shader_stage_info) {}
+
+GraphicsPipeline::~GraphicsPipeline() = default;
+
+Result GraphicsPipeline::CreateRenderPass() {
+  VkSubpassDescription subpass_desc = {};
+  subpass_desc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+
+  std::vector<VkAttachmentDescription> attachment_desc;
+
+  VkAttachmentReference color_refer = {};
+  VkAttachmentReference depth_refer = {};
+
+  if (color_format_ != VK_FORMAT_UNDEFINED) {
+    attachment_desc.push_back(kDefaultAttachmentDesc);
+    attachment_desc.back().format = color_format_;
+
+    color_refer.attachment = static_cast<uint32_t>(attachment_desc.size() - 1);
+    color_refer.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+    subpass_desc.colorAttachmentCount = 1;
+    subpass_desc.pColorAttachments = &color_refer;
+  }
+
+  if (depth_stencil_format_ != VK_FORMAT_UNDEFINED) {
+    attachment_desc.push_back(kDefaultAttachmentDesc);
+    attachment_desc.back().format = depth_stencil_format_;
+
+    depth_refer.attachment = static_cast<uint32_t>(attachment_desc.size() - 1);
+    depth_refer.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+    subpass_desc.pDepthStencilAttachment = &depth_refer;
+  }
+
+  VkRenderPassCreateInfo render_pass_info = {};
+  render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+  render_pass_info.attachmentCount =
+      static_cast<uint32_t>(attachment_desc.size());
+  render_pass_info.pAttachments = attachment_desc.data();
+  render_pass_info.subpassCount = 1;
+  render_pass_info.pSubpasses = &subpass_desc;
+
+  if (vkCreateRenderPass(device_, &render_pass_info, nullptr, &render_pass_) !=
+      VK_SUCCESS) {
+    return Result("Vulkan::Calling vkCreateRenderPass Fail");
+  }
+
+  return {};
+}
+
+VkPipelineDepthStencilStateCreateInfo
+GraphicsPipeline::GetPipelineDepthStencilInfo() {
+  VkPipelineDepthStencilStateCreateInfo depthstencil_info = {};
+  // TODO(jaebaek): Depth/stencil test setup should be come from the
+  // PipelineData.
+  depthstencil_info.depthTestEnable = VK_TRUE;
+  depthstencil_info.depthWriteEnable = VK_TRUE;
+  depthstencil_info.depthCompareOp = VK_COMPARE_OP_LESS;
+  depthstencil_info.depthBoundsTestEnable = VK_FALSE;
+  depthstencil_info.stencilTestEnable = VK_FALSE;
+  return depthstencil_info;
+}
+
+VkPipelineColorBlendAttachmentState
+GraphicsPipeline::GetPipelineColorBlendAttachmentState() {
+  VkPipelineColorBlendAttachmentState colorblend_attachment = {};
+  // TODO(jaebaek): Update blend state should be come from the PipelineData.
+  colorblend_attachment.blendEnable = VK_FALSE;
+  colorblend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ZERO;
+  colorblend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
+  colorblend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
+  colorblend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+  colorblend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+  colorblend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
+  colorblend_attachment.colorWriteMask =
+      VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+      VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+  return colorblend_attachment;
+}
+
+Result GraphicsPipeline::CreateVkGraphicsPipeline() {
+  if (pipeline_ != VK_NULL_HANDLE)
+    return Result("Vulkan::Pipeline already created");
+
+  Result r = CreatePipelineLayout();
+  if (!r.IsSuccess())
+    return r;
+
+  VkPipelineVertexInputStateCreateInfo vertex_input_info = {};
+  vertex_input_info.sType =
+      VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+  vertex_input_info.vertexBindingDescriptionCount = 1;
+
+  VkVertexInputBindingDescription vertex_binding_desc = {};
+  if (vertex_buffer_) {
+    vertex_binding_desc = vertex_buffer_->GetVertexInputBinding();
+    auto vertex_attr_desc = vertex_buffer_->GetVertexInputAttr();
+
+    vertex_input_info.pVertexBindingDescriptions = &vertex_binding_desc;
+    vertex_input_info.vertexAttributeDescriptionCount =
+        static_cast<uint32_t>(vertex_attr_desc.size());
+    vertex_input_info.pVertexAttributeDescriptions = vertex_attr_desc.data();
+  } else {
+    vertex_binding_desc.binding = 0;
+    vertex_binding_desc.stride = 0;
+    vertex_binding_desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+    vertex_input_info.pVertexBindingDescriptions = &vertex_binding_desc;
+    vertex_input_info.vertexAttributeDescriptionCount = 0;
+    vertex_input_info.pVertexAttributeDescriptions = nullptr;
+  }
+
+  VkPipelineInputAssemblyStateCreateInfo input_assembly_info = {};
+  input_assembly_info.sType =
+      VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+  // TODO(jaebaek): Handle the given index if exists.
+  input_assembly_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+  input_assembly_info.primitiveRestartEnable = VK_FALSE;
+
+  VkViewport viewport = {0,
+                         0,
+                         static_cast<float>(frame_->GetWidth()),
+                         static_cast<float>(frame_->GetHeight()),
+                         0,
+                         1};
+
+  VkRect2D scissor = {{0, 0}, {frame_->GetWidth(), frame_->GetHeight()}};
+
+  VkPipelineViewportStateCreateInfo viewport_info = {};
+  viewport_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+  viewport_info.viewportCount = 1;
+  viewport_info.pViewports = &viewport;
+  viewport_info.scissorCount = 1;
+  viewport_info.pScissors = &scissor;
+
+  VkGraphicsPipelineCreateInfo pipeline_info = {};
+  pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+  pipeline_info.stageCount = static_cast<uint32_t>(shader_stage_info_.size());
+  pipeline_info.pStages = shader_stage_info_.data();
+  pipeline_info.pVertexInputState = &vertex_input_info;
+  pipeline_info.pInputAssemblyState = &input_assembly_info;
+  pipeline_info.pViewportState = &viewport_info;
+  pipeline_info.pRasterizationState = &kDefaultRasterizationInfo;
+  pipeline_info.pMultisampleState = &kMultisampleInfo;
+
+  VkPipelineDepthStencilStateCreateInfo depthstencil_info;
+  if (depth_stencil_format_ != VK_FORMAT_UNDEFINED) {
+    depthstencil_info = GetPipelineDepthStencilInfo();
+    pipeline_info.pDepthStencilState = &depthstencil_info;
+  }
+
+  VkPipelineColorBlendStateCreateInfo colorblend_info = {};
+  VkPipelineColorBlendAttachmentState colorblend_attachment;
+  if (color_format_ != VK_FORMAT_UNDEFINED) {
+    colorblend_attachment = GetPipelineColorBlendAttachmentState();
+
+    colorblend_info.sType =
+        VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+    colorblend_info.logicOpEnable = VK_FALSE;
+    colorblend_info.logicOp = VK_LOGIC_OP_COPY;
+    colorblend_info.attachmentCount = 1;
+    colorblend_info.pAttachments = &colorblend_attachment;
+    pipeline_info.pColorBlendState = &colorblend_info;
+  }
+
+  pipeline_info.layout = pipeline_layout_;
+  pipeline_info.renderPass = render_pass_;
+  pipeline_info.subpass = 0;
+
+  if (vkCreateGraphicsPipelines(device_, VK_NULL_HANDLE, 1, &pipeline_info,
+                                nullptr, &pipeline_) != VK_SUCCESS) {
+    return Result("Vulkan::Calling vkCreateGraphicsPipelines Fail");
+  }
+
+  return {};
+}
+
+Result GraphicsPipeline::Initialize(uint32_t width,
+                                    uint32_t height,
+                                    VkCommandPool pool,
+                                    VkQueue queue) {
+  Result r = Pipeline::InitializeCommandBuffer(pool, queue);
+  if (!r.IsSuccess())
+    return r;
+
+  r = CreateRenderPass();
+  if (!r.IsSuccess())
+    return r;
+
+  frame_ = MakeUnique<FrameBuffer>(device_, width, height);
+  r = frame_->Initialize(render_pass_, color_format_, depth_stencil_format_,
+                         memory_properties_);
+  if (!r.IsSuccess())
+    return r;
+
+  frame_width_ = width;
+  frame_height_ = height;
+
+  return {};
+}
+
+void GraphicsPipeline::SetBuffer(BufferType type,
+                                 uint8_t location,
+                                 const Format& format,
+                                 const std::vector<Value>& values) {
+  // TODO(jaebaek): Handle indices data.
+  if (type != BufferType::kVertexData)
+    return;
+
+  if (!vertex_buffer_)
+    vertex_buffer_ = MakeUnique<VertexBuffer>(device_);
+
+  vertex_buffer_->SetData(location, format, values);
+}
+
+Result GraphicsPipeline::SendBufferDataIfNeeded() {
+  if (!vertex_buffer_)
+    return {};
+
+  if (vertex_buffer_->VertexDataSent())
+    return {};
+
+  Result r = command_->BeginIfNotInRecording();
+  if (!r.IsSuccess())
+    return r;
+
+  DeactivateRenderPassIfNeeded();
+
+  // TODO(jaebaek): Send indices data too.
+  return vertex_buffer_->SendVertexData(command_->GetCommandBuffer(),
+                                        memory_properties_);
+}
+
+void GraphicsPipeline::ActivateRenderPassIfNeeded() {
+  if (render_pass_state_ == RenderPassState::kActive)
+    return;
+
+  VkRenderPassBeginInfo render_begin_info = {};
+  render_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+  render_begin_info.renderPass = render_pass_;
+  render_begin_info.framebuffer = frame_->GetFrameBuffer();
+  render_begin_info.renderArea = {{0, 0}, {frame_width_, frame_height_}};
+  vkCmdBeginRenderPass(command_->GetCommandBuffer(), &render_begin_info,
+                       VK_SUBPASS_CONTENTS_INLINE);
+  render_pass_state_ = RenderPassState::kActive;
+}
+
+void GraphicsPipeline::DeactivateRenderPassIfNeeded() {
+  if (render_pass_state_ == RenderPassState::kInactive)
+    return;
+
+  vkCmdEndRenderPass(command_->GetCommandBuffer());
+  render_pass_state_ = RenderPassState::kInactive;
+}
+
+Result GraphicsPipeline::SetClearColor(float r, float g, float b, float a) {
+  if (color_format_ == VK_FORMAT_UNDEFINED) {
+    return Result(
+        "Vulkan::ClearColorCommand No Color Buffer for FrameBuffer Exists");
+  }
+
+  clear_color_r_ = r;
+  clear_color_g_ = g;
+  clear_color_b_ = b;
+  clear_color_a_ = a;
+  return {};
+}
+
+Result GraphicsPipeline::SetClearStencil(uint32_t stencil) {
+  if (depth_stencil_format_ == VK_FORMAT_UNDEFINED) {
+    return Result(
+        "Vulkan::ClearStencilCommand No DepthStencil Buffer for FrameBuffer "
+        "Exists");
+  }
+
+  clear_stencil_ = stencil;
+  return {};
+}
+
+Result GraphicsPipeline::SetClearDepth(float depth) {
+  if (depth_stencil_format_ == VK_FORMAT_UNDEFINED) {
+    return Result(
+        "Vulkan::ClearStencilCommand No DepthStencil Buffer for FrameBuffer "
+        "Exists");
+  }
+
+  clear_depth_ = depth;
+  return {};
+}
+
+Result GraphicsPipeline::Clear() {
+  if (color_format_ == VK_FORMAT_UNDEFINED &&
+      depth_stencil_format_ == VK_FORMAT_UNDEFINED) {
+    return Result(
+        "Vulkan::ClearColorCommand No Color nor DepthStencil Buffer for "
+        "FrameBuffer Exists");
+  }
+
+  if (color_format_ != VK_FORMAT_UNDEFINED) {
+    VkClearValue clear_value;
+    clear_value.color = {
+        {clear_color_r_, clear_color_g_, clear_color_b_, clear_color_a_}};
+    Result r = ClearBuffer(clear_value, VK_IMAGE_ASPECT_COLOR_BIT);
+    if (!r.IsSuccess())
+      return r;
+  }
+
+  if (depth_stencil_format_ == VK_FORMAT_UNDEFINED)
+    return {};
+
+  VkClearValue clear_value;
+  clear_value.depthStencil = {clear_depth_, clear_stencil_};
+  return ClearBuffer(clear_value,
+                     VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
+}
+
+Result GraphicsPipeline::ClearBuffer(const VkClearValue& clear_value,
+                                     VkImageAspectFlags aspect) {
+  Result r = command_->BeginIfNotInRecording();
+  if (!r.IsSuccess())
+    return r;
+
+  // TODO(jaebaek): When multiple clear and draw commands exist, handle
+  //                begin/end render pass properly.
+  ActivateRenderPassIfNeeded();
+
+  VkClearAttachment clear_attachment = {};
+  clear_attachment.aspectMask = aspect;
+  clear_attachment.colorAttachment = 0;
+  clear_attachment.clearValue = clear_value;
+
+  VkClearRect clear_rect;
+  clear_rect.rect = {{0, 0}, {frame_width_, frame_height_}};
+  clear_rect.baseArrayLayer = 0;
+  clear_rect.layerCount = 1;
+
+  vkCmdClearAttachments(command_->GetCommandBuffer(), 1, &clear_attachment, 1,
+                        &clear_rect);
+
+  return {};
+}
+
+Result GraphicsPipeline::Draw() {
+  // TODO(jaebaek): Handle primitive topology.
+  if (pipeline_ == VK_NULL_HANDLE) {
+    Result r = CreateVkGraphicsPipeline();
+    if (!r.IsSuccess())
+      return r;
+  }
+
+  Result r = SendBufferDataIfNeeded();
+  if (!r.IsSuccess())
+    return r;
+
+  r = command_->BeginIfNotInRecording();
+  if (!r.IsSuccess())
+    return r;
+
+  ActivateRenderPassIfNeeded();
+
+  vkCmdBindPipeline(command_->GetCommandBuffer(),
+                    VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_);
+
+  uint32_t vertex_count = 0;
+  uint32_t instance_count = 0;
+  if (vertex_buffer_) {
+    vertex_buffer_->BindToCommandBuffer(command_->GetCommandBuffer());
+    vertex_count = static_cast<uint32_t>(vertex_buffer_->GetVertexCount());
+    instance_count = 1;
+  }
+
+  vkCmdDraw(command_->GetCommandBuffer(), vertex_count, instance_count, 0, 0);
+
+  return {};
+}
+
+Result GraphicsPipeline::SubmitProbeCommand() {
+  Result r = command_->BeginIfNotInRecording();
+  if (!r.IsSuccess())
+    return r;
+
+  ActivateRenderPassIfNeeded();
+  DeactivateRenderPassIfNeeded();
+
+  r = frame_->CopyColorImageToHost(command_->GetCommandBuffer());
+  if (!r.IsSuccess())
+    return r;
+
+  r = command_->End();
+  if (!r.IsSuccess())
+    return r;
+
+  return command_->SubmitAndReset();
+}
+
+Result GraphicsPipeline::VerifyPixels(const uint32_t x,
+                                      const uint32_t y,
+                                      const uint32_t width,
+                                      const uint32_t height,
+                                      const ProbeCommand* command) {
+  const uint32_t stride = VkFormatToByteSize(color_format_);
+
+  // TODO(jaebaek): Support all VkFormat
+  const uint8_t* ptr = static_cast<const uint8_t*>(frame_->GetColorBufferPtr());
+  uint32_t count_of_invalid_pixels = 0;
+  uint32_t first_invalid_i = 0;
+  uint32_t first_invalid_j = 0;
+  for (uint32_t j = 0; j < height; ++j) {
+    const uint8_t* p = ptr + stride * frame_->GetWidth() * (j + y) + stride * x;
+    for (uint32_t i = 0; i < width; ++i) {
+      // TODO(jaebaek): Get actual pixel values based on frame buffer formats.
+      if (!IsFloatPixelEqualInt(command->GetR(), p[stride * i]) ||
+          !IsFloatPixelEqualInt(command->GetG(), p[stride * i + 1]) ||
+          !IsFloatPixelEqualInt(command->GetB(), p[stride * i + 2]) ||
+          (command->IsRGBA() &&
+           !IsFloatPixelEqualInt(command->GetA(), p[stride * i + 3]))) {
+        if (!count_of_invalid_pixels) {
+          first_invalid_i = i;
+          first_invalid_j = j;
+        }
+        ++count_of_invalid_pixels;
+      }
+    }
+  }
+
+  if (count_of_invalid_pixels) {
+    const uint8_t* p =
+        ptr + stride * frame_->GetWidth() * (first_invalid_j + y) + stride * x;
+    return Result(
+        "Probe failed at: " + std::to_string(first_invalid_i + x) + ", " +
+        std::to_string(first_invalid_j + y) + "\n" +
+        "  Expected RGBA: " + std::to_string(command->GetR() * 255) + ", " +
+        std::to_string(command->GetG() * 255) + ", " +
+        std::to_string(command->GetB() * 255) +
+        (command->IsRGBA() ? ", " + std::to_string(command->GetA() * 255) +
+                                 "\n  Actual RGBA: "
+                           : "\n  Actual RGB: ") +
+        std::to_string(static_cast<int>(p[stride * first_invalid_i])) + ", " +
+        std::to_string(static_cast<int>(p[stride * first_invalid_i + 1])) +
+        ", " +
+        std::to_string(static_cast<int>(p[stride * first_invalid_i + 2])) +
+        (command->IsRGBA() ? ", " + std::to_string(static_cast<int>(
+                                        p[stride * first_invalid_i + 3]))
+                           : "") +
+        "\n" + "Probe failed in " + std::to_string(count_of_invalid_pixels) +
+        " pixels");
+  }
+
+  return {};
+}
+
+Result GraphicsPipeline::Probe(const ProbeCommand* command) {
+  uint32_t x = 0;
+  uint32_t y = 0;
+  uint32_t width = 0;
+  uint32_t height = 0;
+  const uint32_t frame_width = frame_->GetWidth();
+  const uint32_t frame_height = frame_->GetHeight();
+
+  if (command->IsWholeWindow()) {
+    width = frame_width;
+    height = frame_height;
+  } else if (command->IsRelative()) {
+    x = static_cast<uint32_t>(frame_width * command->GetX());
+    y = static_cast<uint32_t>(frame_height * command->GetY());
+    width = static_cast<uint32_t>(frame_width * command->GetWidth());
+    height = static_cast<uint32_t>(frame_height * command->GetHeight());
+  } else {
+    x = static_cast<uint32_t>(command->GetX());
+    y = static_cast<uint32_t>(command->GetY());
+    width = static_cast<uint32_t>(command->GetWidth());
+    height = static_cast<uint32_t>(command->GetHeight());
+  }
+
+  if (x + width > frame_width || y + height > frame_height) {
+    return Result(
+        "Vulkan::Probe Position(" + std::to_string(x + width - 1) + ", " +
+        std::to_string(y + height - 1) + ") is out of framebuffer scope (" +
+        std::to_string(frame_width) + "," + std::to_string(frame_height) + ")");
+  }
+
+  Result r = SubmitProbeCommand();
+  if (!r.IsSuccess())
+    return r;
+
+  return VerifyPixels(x, y, width, height, command);
+}
+
+void GraphicsPipeline::Shutdown() {
+  DeactivateRenderPassIfNeeded();
+
+  Result r = command_->End();
+  if (r.IsSuccess())
+    command_->SubmitAndReset();
+
+  Pipeline::Shutdown();
+  frame_->Shutdown();
+  vkDestroyRenderPass(device_, render_pass_, nullptr);
+}
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/graphics_pipeline.h b/src/vulkan/graphics_pipeline.h
new file mode 100644
index 0000000..9608dc9
--- /dev/null
+++ b/src/vulkan/graphics_pipeline.h
@@ -0,0 +1,121 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_GRAPHICS_PIPELINE_H_
+#define SRC_VULKAN_GRAPHICS_PIPELINE_H_
+
+#include <memory>
+#include <vector>
+
+#include "amber/result.h"
+#include "src/buffer_data.h"
+#include "src/format.h"
+#include "src/value.h"
+#include "src/vulkan/frame_buffer.h"
+#include "src/vulkan/pipeline.h"
+#include "src/vulkan/vertex_buffer.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+
+class ProbeCommand;
+
+namespace vulkan {
+
+class GraphicsPipeline : public Pipeline {
+ public:
+  GraphicsPipeline(PipelineType type,
+                   VkDevice device,
+                   const VkPhysicalDeviceMemoryProperties& properties,
+                   VkFormat color_format,
+                   VkFormat depth_stencil_format,
+                   std::vector<VkPipelineShaderStageCreateInfo>);
+  ~GraphicsPipeline() override;
+
+  Result Initialize(uint32_t width,
+                    uint32_t height,
+                    VkCommandPool pool,
+                    VkQueue queue);
+  void Shutdown() override;
+
+  void SetBuffer(BufferType type,
+                 uint8_t location,
+                 const Format& format,
+                 const std::vector<Value>& values);
+
+  Result Clear();
+  Result ClearBuffer(const VkClearValue& clear_value,
+                     VkImageAspectFlags aspect);
+  Result Probe(const ProbeCommand*);
+
+  VkFormat GetColorFormat() const { return color_format_; }
+  VkFormat GetDepthStencilFormat() const { return depth_stencil_format_; }
+
+  Result SetClearColor(float r, float g, float b, float a);
+  Result SetClearStencil(uint32_t stencil);
+  Result SetClearDepth(float depth);
+
+  Result Draw();
+
+ private:
+  enum class RenderPassState : uint8_t {
+    kActive = 0,
+    kInactive,
+  };
+
+  Result CreateVkGraphicsPipeline();
+
+  Result CreateRenderPass();
+  void ActivateRenderPassIfNeeded();
+  void DeactivateRenderPassIfNeeded();
+
+  Result SendBufferDataIfNeeded();
+
+  // TODO(jaebaek): Implement image/ssbo probe.
+  Result SubmitProbeCommand();
+  Result VerifyPixels(const uint32_t x,
+                      const uint32_t y,
+                      const uint32_t width,
+                      const uint32_t height,
+                      const ProbeCommand* command);
+
+  VkPipelineDepthStencilStateCreateInfo GetPipelineDepthStencilInfo();
+  VkPipelineColorBlendAttachmentState GetPipelineColorBlendAttachmentState();
+
+  VkRenderPass render_pass_ = VK_NULL_HANDLE;
+  RenderPassState render_pass_state_ = RenderPassState::kInactive;
+
+  std::unique_ptr<FrameBuffer> frame_;
+  VkFormat color_format_;
+  VkFormat depth_stencil_format_;
+
+  std::vector<VkPipelineShaderStageCreateInfo> shader_stage_info_;
+
+  uint32_t frame_width_ = 0;
+  uint32_t frame_height_ = 0;
+
+  float clear_color_r_ = 0;
+  float clear_color_g_ = 0;
+  float clear_color_b_ = 0;
+  float clear_color_a_ = 0;
+  uint32_t clear_stencil_ = 0;
+  float clear_depth_ = 1.0f;
+
+  std::unique_ptr<VertexBuffer> vertex_buffer_;
+};
+
+}  // namespace vulkan
+}  // namespace amber
+
+#endif  // SRC_VULKAN_GRAPHICS_PIPELINE_H_
diff --git a/src/vulkan/image.cc b/src/vulkan/image.cc
new file mode 100644
index 0000000..4722187
--- /dev/null
+++ b/src/vulkan/image.cc
@@ -0,0 +1,151 @@
+// Copyright 2018 The Amber 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 "src/vulkan/image.h"
+
+#include <limits>
+
+#include "src/vulkan/format_data.h"
+
+namespace amber {
+namespace vulkan {
+namespace {
+
+const VkImageCreateInfo kDefaultImageInfo = {
+    VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, /* sType */
+    nullptr,                             /* pNext */
+    0,                                   /* flags */
+    VK_IMAGE_TYPE_2D,                    /* imageType */
+    VK_FORMAT_R8G8B8A8_UNORM,            /* format */
+    {250, 250, 1},                       /* extent */
+    1,                                   /* mipLevels */
+    1,                                   /* arrayLayers */
+    VK_SAMPLE_COUNT_1_BIT,               /* samples */
+    VK_IMAGE_TILING_OPTIMAL,             /* tiling */
+    VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+        VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, /* usage */
+    VK_SHARING_MODE_EXCLUSIVE,               /* sharingMode */
+    0,                                       /* queueFamilyIndexCount */
+    nullptr,                                 /* pQueueFamilyIndices */
+    VK_IMAGE_LAYOUT_UNDEFINED,               /* initialLayout */
+};
+
+}  // namespace
+
+Image::Image(VkDevice device,
+             VkFormat format,
+             uint32_t x,
+             uint32_t y,
+             uint32_t z,
+             const VkPhysicalDeviceMemoryProperties& properties)
+    : Resource(device, x * y * z * VkFormatToByteSize(format), properties),
+      image_info_(kDefaultImageInfo) {
+  image_info_.format = format;
+  image_info_.extent = {x, y, z};
+}
+
+Image::~Image() = default;
+
+Result Image::Initialize(VkImageUsageFlags usage) {
+  if (image_ != VK_NULL_HANDLE)
+    return Result("Vulkan::Image was already initalized");
+
+  image_info_.usage = usage;
+
+  if (vkCreateImage(GetDevice(), &image_info_, nullptr, &image_) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkCreateImage Fail");
+
+  AllocateResult allocate_result = AllocateAndBindMemoryToVkImage(
+      image_, &memory_, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, false);
+  if (!allocate_result.r.IsSuccess())
+    return allocate_result.r;
+
+  Result r = CreateVkImageView();
+  if (!r.IsSuccess())
+    return r;
+
+  if (CheckMemoryHostAccessible(allocate_result.memory_type_index)) {
+    is_image_host_accessible_ = true;
+    return MapMemory(memory_);
+  }
+
+  is_image_host_accessible_ = false;
+  return Resource::Initialize();
+}
+
+Result Image::CreateVkImageView() {
+  VkImageViewCreateInfo image_view_info = {};
+  image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+  image_view_info.image = image_;
+  // TODO(jaebaek): Set .viewType correctly
+  image_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+  image_view_info.format = image_info_.format;
+  image_view_info.components = {
+      VK_COMPONENT_SWIZZLE_R,
+      VK_COMPONENT_SWIZZLE_G,
+      VK_COMPONENT_SWIZZLE_B,
+      VK_COMPONENT_SWIZZLE_A,
+  };
+  image_view_info.subresourceRange = {
+      VK_IMAGE_ASPECT_COLOR_BIT, /* aspectMask */
+      0,                         /* baseMipLevel */
+      1,                         /* levelCount */
+      0,                         /* baseArrayLayer */
+      1,                         /* layerCount */
+  };
+
+  if (vkCreateImageView(GetDevice(), &image_view_info, nullptr, &view_) !=
+      VK_SUCCESS) {
+    return Result("Vulkan::Calling vkCreateImageView Fail");
+  }
+
+  return {};
+}
+
+void Image::Shutdown() {
+  vkDestroyImageView(GetDevice(), view_, nullptr);
+  vkDestroyImage(GetDevice(), image_, nullptr);
+  vkFreeMemory(GetDevice(), memory_, nullptr);
+
+  view_ = VK_NULL_HANDLE;
+  image_ = VK_NULL_HANDLE;
+  memory_ = VK_NULL_HANDLE;
+}
+
+Result Image::CopyToHost(VkCommandBuffer command) {
+  if (is_image_host_accessible_)
+    return {};
+
+  VkBufferImageCopy copy_region = {};
+  copy_region.bufferOffset = 0;
+  copy_region.bufferRowLength = 0;
+  copy_region.bufferImageHeight = 0;
+  copy_region.imageSubresource = {
+      VK_IMAGE_ASPECT_COLOR_BIT, /* aspectMask */
+      0,                         /* mipLevel */
+      0,                         /* baseArrayLayer */
+      1,                         /* layerCount */
+  };
+  copy_region.imageOffset = {0, 0, 0};
+  copy_region.imageExtent = {image_info_.extent.width,
+                             image_info_.extent.height, 1};
+
+  vkCmdCopyImageToBuffer(command, image_, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+                         GetHostAccessibleBuffer(), 1, &copy_region);
+
+  return {};
+}
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/image.h b/src/vulkan/image.h
new file mode 100644
index 0000000..c946af4
--- /dev/null
+++ b/src/vulkan/image.h
@@ -0,0 +1,68 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_IMAGE_H_
+#define SRC_VULKAN_IMAGE_H_
+
+#include "amber/result.h"
+#include "src/vulkan/resource.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class Image : public Resource {
+ public:
+  Image(VkDevice device,
+        VkFormat format,
+        uint32_t x,
+        uint32_t y,
+        uint32_t z,
+        const VkPhysicalDeviceMemoryProperties& properties);
+  ~Image() override;
+
+  Result Initialize(VkImageUsageFlags usage);
+  VkImage GetVkImage() const { return image_; }
+  VkImageView GetVkImageView() const { return view_; }
+
+  // TODO(jaebaek): Determine copy all or partial data
+  Result CopyToHost(VkCommandBuffer command);
+
+  // TODO(jaebaek): Implement CopyToDevice
+
+  // Resource
+  VkDeviceMemory GetHostAccessMemory() const override {
+    if (is_image_host_accessible_)
+      return memory_;
+
+    return Resource::GetHostAccessMemory();
+  }
+
+  void Shutdown() override;
+
+ private:
+  Result CreateVkImageView();
+
+  VkImageCreateInfo image_info_;
+
+  VkImage image_ = VK_NULL_HANDLE;
+  VkImageView view_ = VK_NULL_HANDLE;
+  VkDeviceMemory memory_ = VK_NULL_HANDLE;
+  bool is_image_host_accessible_ = false;
+};
+
+}  // namespace vulkan
+}  // namespace amber
+
+#endif  // SRC_VULKAN_IMAGE_H_
diff --git a/src/vulkan/pipeline.cc b/src/vulkan/pipeline.cc
new file mode 100644
index 0000000..e16c277
--- /dev/null
+++ b/src/vulkan/pipeline.cc
@@ -0,0 +1,68 @@
+// Copyright 2018 The Amber 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 "src/vulkan/pipeline.h"
+
+#include <limits>
+
+#include "src/command.h"
+#include "src/make_unique.h"
+#include "src/vulkan/graphics_pipeline.h"
+
+namespace amber {
+namespace vulkan {
+
+Pipeline::Pipeline(PipelineType type,
+                   VkDevice device,
+                   const VkPhysicalDeviceMemoryProperties& properties)
+    : device_(device), memory_properties_(properties), pipeline_type_(type) {}
+
+Pipeline::~Pipeline() = default;
+
+GraphicsPipeline* Pipeline::AsGraphics() {
+  return static_cast<GraphicsPipeline*>(this);
+}
+
+Result Pipeline::InitializeCommandBuffer(VkCommandPool pool, VkQueue queue) {
+  command_ = MakeUnique<CommandBuffer>(device_, pool, queue);
+  Result r = command_->Initialize();
+  if (!r.IsSuccess())
+    return r;
+
+  return {};
+}
+
+void Pipeline::Shutdown() {
+  // TODO(jaebaek): destroy pipeline_cache_ and pipeline_
+  command_->Shutdown();
+  vkDestroyPipelineLayout(device_, pipeline_layout_, nullptr);
+}
+
+Result Pipeline::CreatePipelineLayout() {
+  VkPipelineLayoutCreateInfo pipeline_layout_info = {};
+  pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+  pipeline_layout_info.setLayoutCount = 0;
+  pipeline_layout_info.pSetLayouts = nullptr;
+  // TODO(jaebaek): Push constant for pipeline_layout_info.
+
+  if (vkCreatePipelineLayout(device_, &pipeline_layout_info, nullptr,
+                             &pipeline_layout_) != VK_SUCCESS) {
+    return Result("Vulkan::Calling vkCreatePipelineLayout Fail");
+  }
+
+  return {};
+}
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/pipeline.h b/src/vulkan/pipeline.h
new file mode 100644
index 0000000..38a234a
--- /dev/null
+++ b/src/vulkan/pipeline.h
@@ -0,0 +1,67 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_PIPELINE_H_
+#define SRC_VULKAN_PIPELINE_H_
+
+#include <memory>
+#include <vector>
+
+#include "amber/result.h"
+#include "src/engine.h"
+#include "src/vulkan/command.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class GraphicsPipeline;
+
+class Pipeline {
+ public:
+  virtual ~Pipeline();
+
+  bool IsGraphics() const { return pipeline_type_ == PipelineType::kGraphics; }
+  bool IsCompute() const { return pipeline_type_ == PipelineType::kCompute; }
+
+  GraphicsPipeline* AsGraphics();
+
+  virtual void Shutdown();
+
+ protected:
+  Pipeline(PipelineType type,
+           VkDevice device,
+           const VkPhysicalDeviceMemoryProperties& properties);
+  Result InitializeCommandBuffer(VkCommandPool pool, VkQueue queue);
+
+  Result CreatePipelineLayout();
+
+  VkPipelineCache pipeline_cache_ = VK_NULL_HANDLE;
+  VkPipeline pipeline_ = VK_NULL_HANDLE;
+  VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
+
+  std::vector<VkDescriptorSetLayout> descriptor_set_layout_;
+
+  VkDevice device_ = VK_NULL_HANDLE;
+  VkPhysicalDeviceMemoryProperties memory_properties_;
+  std::unique_ptr<CommandBuffer> command_;
+
+ private:
+  PipelineType pipeline_type_;
+};
+
+}  // namespace vulkan
+}  // namespace amber
+
+#endif  // SRC_VULKAN_PIPELINE_H_
diff --git a/src/vulkan/resource.cc b/src/vulkan/resource.cc
new file mode 100644
index 0000000..69a21a5
--- /dev/null
+++ b/src/vulkan/resource.cc
@@ -0,0 +1,209 @@
+// Copyright 2018 The Amber 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 "src/vulkan/resource.h"
+
+#include <limits>
+
+#include "src/make_unique.h"
+#include "src/vulkan/format_data.h"
+
+namespace amber {
+namespace vulkan {
+
+Resource::Resource(VkDevice device,
+                   size_t size,
+                   const VkPhysicalDeviceMemoryProperties& properties)
+    : device_(device), size_(size), physical_memory_properties_(properties) {}
+
+Resource::~Resource() = default;
+
+void Resource::Shutdown() {
+  UnMapMemory(host_accessible_memory_);
+  vkDestroyBuffer(device_, host_accessible_buffer_, nullptr);
+  vkFreeMemory(device_, host_accessible_memory_, nullptr);
+}
+
+Result Resource::Initialize() {
+  Result r = CreateVkBuffer(
+      &host_accessible_buffer_,
+      VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
+  if (!r.IsSuccess())
+    return r;
+
+  AllocateResult allocate_result = AllocateAndBindMemoryToVkBuffer(
+      host_accessible_buffer_, &host_accessible_memory_,
+      VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+          VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+      true);
+  if (!allocate_result.r.IsSuccess())
+    return allocate_result.r;
+
+  return MapMemory(host_accessible_memory_);
+}
+
+Result Resource::CreateVkBuffer(VkBuffer* buffer, VkBufferUsageFlags usage) {
+  if (buffer == nullptr)
+    return Result("Vulkan::Given VkBuffer pointer is nullptr");
+
+  VkBufferCreateInfo buffer_info = {};
+  buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+  buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+  buffer_info.size = size_;
+  buffer_info.usage = usage;
+
+  if (vkCreateBuffer(device_, &buffer_info, nullptr, buffer) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkCreateBuffer Fail");
+
+  return {};
+}
+
+uint32_t Resource::ChooseMemory(uint32_t memory_type_bits,
+                                VkMemoryPropertyFlags flags,
+                                bool force_flags) {
+  // Based on Vulkan spec about VkMemoryRequirements, N th bit of
+  // |memory_type_bits| is 1 where N can be the proper memory type index.
+  // This code is looking for the first non-zero bit whose memory type
+  // VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT property. If not exists,
+  // it returns the first non-zero bit.
+  uint32_t first_non_zero = std::numeric_limits<uint32_t>::max();
+  uint32_t memory_type_index = 0;
+  while (memory_type_bits) {
+    if (memory_type_bits % 2) {
+      if (first_non_zero == std::numeric_limits<uint32_t>::max())
+        first_non_zero = memory_type_index;
+
+      if ((physical_memory_properties_.memoryTypes[memory_type_index]
+               .propertyFlags &
+           flags) == flags) {
+        return memory_type_index;
+      }
+    }
+
+    ++memory_type_index;
+    memory_type_bits >>= 1;
+  }
+
+  if (force_flags)
+    return std::numeric_limits<uint32_t>::max();
+
+  return first_non_zero;
+}
+
+const VkMemoryRequirements Resource::GetVkBufferMemoryRequirements(
+    VkBuffer buffer) const {
+  VkMemoryRequirements requirement;
+  vkGetBufferMemoryRequirements(device_, buffer, &requirement);
+  return requirement;
+}
+
+const VkMemoryRequirements Resource::GetVkImageMemoryRequirements(
+    VkImage image) const {
+  VkMemoryRequirements requirement;
+  vkGetImageMemoryRequirements(device_, image, &requirement);
+  return requirement;
+}
+
+Resource::AllocateResult Resource::AllocateAndBindMemoryToVkBuffer(
+    VkBuffer buffer,
+    VkDeviceMemory* memory,
+    VkMemoryPropertyFlags flags,
+    bool force_flags) {
+  if (buffer == VK_NULL_HANDLE)
+    return {Result("Vulkan::Given VkBuffer is VK_NULL_HANDLE"), 0};
+
+  if (memory == nullptr)
+    return {Result("Vulkan::Given VkDeviceMemory pointer is nullptr"), 0};
+
+  auto requirement = GetVkBufferMemoryRequirements(buffer);
+
+  uint32_t memory_type_index =
+      ChooseMemory(requirement.memoryTypeBits, flags, force_flags);
+  if (memory_type_index == std::numeric_limits<uint32_t>::max())
+    return {Result("Vulkan::Find Proper Memory Fail"), 0};
+
+  Result r = AllocateMemory(memory, requirement.size, memory_type_index);
+  if (!r.IsSuccess())
+    return {r, 0};
+
+  return {BindMemoryToVkBuffer(buffer, *memory), memory_type_index};
+}
+
+Result Resource::AllocateMemory(VkDeviceMemory* memory,
+                                VkDeviceSize size,
+                                uint32_t memory_type_index) {
+  VkMemoryAllocateInfo alloc_info = {};
+  alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+  alloc_info.allocationSize = size;
+  alloc_info.memoryTypeIndex = memory_type_index;
+  if (vkAllocateMemory(device_, &alloc_info, nullptr, memory) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkAllocateMemory Fail");
+
+  return {};
+}
+
+Result Resource::BindMemoryToVkBuffer(VkBuffer buffer, VkDeviceMemory memory) {
+  if (vkBindBufferMemory(device_, buffer, memory, 0) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkBindBufferMemory Fail");
+
+  return {};
+}
+
+Resource::AllocateResult Resource::AllocateAndBindMemoryToVkImage(
+    VkImage image,
+    VkDeviceMemory* memory,
+    VkMemoryPropertyFlags flags,
+    bool force_flags) {
+  if (image == nullptr)
+    return {Result("Vulkan::Given VkImage pointer is nullptr"), 0};
+
+  if (memory == nullptr)
+    return {Result("Vulkan::Given VkDeviceMemory pointer is nullptr"), 0};
+
+  auto requirement = GetVkImageMemoryRequirements(image);
+
+  uint32_t memory_type_index =
+      ChooseMemory(requirement.memoryTypeBits, flags, force_flags);
+  if (memory_type_index == std::numeric_limits<uint32_t>::max())
+    return {Result("Vulkan::Find Proper Memory Fail"), 0};
+
+  Result r = AllocateMemory(memory, requirement.size, memory_type_index);
+  if (!r.IsSuccess())
+    return {r, 0};
+
+  return {BindMemoryToVkImage(image, *memory), memory_type_index};
+}
+
+Result Resource::BindMemoryToVkImage(VkImage image, VkDeviceMemory memory) {
+  if (vkBindImageMemory(device_, image, memory, 0) != VK_SUCCESS)
+    return Result("Vulkan::Calling vkBindImageMemory Fail");
+
+  return {};
+}
+
+Result Resource::MapMemory(VkDeviceMemory memory) {
+  if (vkMapMemory(device_, memory, 0, VK_WHOLE_SIZE, 0, &memory_ptr_) !=
+      VK_SUCCESS) {
+    return Result("Vulkan::Calling vkMapMemory Fail");
+  }
+
+  return {};
+}
+
+void Resource::UnMapMemory(VkDeviceMemory memory) {
+  vkUnmapMemory(device_, memory);
+}
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/resource.h b/src/vulkan/resource.h
new file mode 100644
index 0000000..c2530ae
--- /dev/null
+++ b/src/vulkan/resource.h
@@ -0,0 +1,101 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_RESOURCE_H_
+#define SRC_VULKAN_RESOURCE_H_
+
+#include <memory>
+
+#include "amber/result.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class Resource {
+ public:
+  virtual ~Resource();
+
+  virtual VkDeviceMemory GetHostAccessMemory() const {
+    return host_accessible_memory_;
+  }
+
+  virtual void Shutdown();
+
+  void* HostAccessibleMemoryPtr() const { return memory_ptr_; }
+
+ protected:
+  Resource(VkDevice device,
+           size_t size,
+           const VkPhysicalDeviceMemoryProperties& properties);
+  Result Initialize();
+  Result CreateVkBuffer(VkBuffer* buffer, VkBufferUsageFlags usage);
+
+  VkDevice GetDevice() const { return device_; }
+  VkBuffer GetHostAccessibleBuffer() const { return host_accessible_buffer_; }
+
+  size_t GetSize() const { return size_; }
+
+  struct AllocateResult {
+    Result r;
+    uint32_t memory_type_index;
+  };
+
+  AllocateResult AllocateAndBindMemoryToVkBuffer(VkBuffer buffer,
+                                                 VkDeviceMemory* memory,
+                                                 VkMemoryPropertyFlags flags,
+                                                 bool force_flags);
+  AllocateResult AllocateAndBindMemoryToVkImage(VkImage image,
+                                                VkDeviceMemory* memory,
+                                                VkMemoryPropertyFlags flags,
+                                                bool force_flags);
+
+  bool CheckMemoryHostAccessible(uint32_t memory_type_index) {
+    return (physical_memory_properties_.memoryTypes[memory_type_index]
+                .propertyFlags &
+            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) ==
+           VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
+  }
+
+  Result MapMemory(VkDeviceMemory memory);
+  void UnMapMemory(VkDeviceMemory memory);
+
+ private:
+  uint32_t ChooseMemory(uint32_t memory_type_bits,
+                        VkMemoryPropertyFlags flags,
+                        bool force_flags);
+  Result AllocateMemory(VkDeviceMemory* memory,
+                        VkDeviceSize size,
+                        uint32_t memory_type_index);
+
+  Result BindMemoryToVkBuffer(VkBuffer buffer, VkDeviceMemory memory);
+  const VkMemoryRequirements GetVkBufferMemoryRequirements(
+      VkBuffer buffer) const;
+
+  Result BindMemoryToVkImage(VkImage image, VkDeviceMemory memory);
+  const VkMemoryRequirements GetVkImageMemoryRequirements(VkImage image) const;
+
+  VkDevice device_ = VK_NULL_HANDLE;
+  size_t size_ = 0;
+  VkPhysicalDeviceMemoryProperties physical_memory_properties_;
+
+  VkBuffer host_accessible_buffer_ = VK_NULL_HANDLE;
+  VkDeviceMemory host_accessible_memory_ = VK_NULL_HANDLE;
+  void* memory_ptr_ = nullptr;
+};
+
+}  // namespace vulkan
+}  // namespace amber
+
+#endif  // SRC_VULKAN_RESOURCE_H_
diff --git a/src/vulkan/vertex_buffer.cc b/src/vulkan/vertex_buffer.cc
new file mode 100644
index 0000000..943cbb1
--- /dev/null
+++ b/src/vulkan/vertex_buffer.cc
@@ -0,0 +1,118 @@
+// Copyright 2018 The Amber 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 "src/vulkan/vertex_buffer.h"
+
+#include <cassert>
+#include <cstring>
+
+#include "src/make_unique.h"
+#include "src/vulkan/bit_copy.h"
+#include "src/vulkan/format_data.h"
+
+namespace amber {
+namespace vulkan {
+
+VertexBuffer::VertexBuffer(VkDevice device) : device_(device) {}
+
+VertexBuffer::~VertexBuffer() = default;
+
+void VertexBuffer::SetData(uint8_t location,
+                           const Format& format,
+                           const std::vector<Value>& values) {
+  vertex_attr_desc_.emplace_back();
+  // TODO(jaebaek): Support multiple binding
+  vertex_attr_desc_.back().binding = 0;
+  vertex_attr_desc_.back().location = location;
+  vertex_attr_desc_.back().format = ToVkFormat(format.GetFormatType());
+  vertex_attr_desc_.back().offset = stride_in_bytes_;
+
+  stride_in_bytes_ += format.GetByteSize();
+
+  formats_.push_back(format);
+  data_.push_back(values);
+}
+
+void VertexBuffer::FillVertexBufferWithData(VkCommandBuffer command) {
+  // Send vertex data from host to device.
+  uint8_t* ptr = static_cast<uint8_t*>(buffer_->HostAccessibleMemoryPtr());
+  for (uint32_t i = 0; i < GetVertexCount(); ++i) {
+    for (uint32_t j = 0; j < formats_.size(); ++j) {
+      const auto pack_size = formats_[j].GetPackSize();
+      if (pack_size) {
+        BitCopy::CopyValueToBuffer(ptr, data_[j][i], 0, pack_size);
+        ptr += pack_size / 8;
+        continue;
+      }
+
+      const auto& components = formats_[j].GetComponents();
+      uint8_t bit_offset = 0;
+
+      for (uint32_t k = 0; k < components.size(); ++k) {
+        uint8_t bits = components[k].num_bits;
+        BitCopy::CopyValueToBuffer(ptr, data_[j][i * components.size() + k],
+                                   bit_offset, bits);
+
+        assert(k == components.size() - 1 ||
+               static_cast<uint32_t>(bit_offset) + static_cast<uint32_t>(bits) <
+                   256);
+        bit_offset += bits;
+      }
+
+      ptr += formats_[j].GetByteSize();
+    }
+  }
+
+  ptr = static_cast<uint8_t*>(buffer_->HostAccessibleMemoryPtr());
+  buffer_->CopyToDevice(command);
+}
+
+void VertexBuffer::BindToCommandBuffer(VkCommandBuffer command) {
+  const VkDeviceSize offset = 0;
+  const VkBuffer buffer = buffer_->GetVkBuffer();
+  // TODO(jaebaek): Support multiple binding
+  vkCmdBindVertexBuffers(command, 0, 1, &buffer, &offset);
+}
+
+Result VertexBuffer::SendVertexData(
+    VkCommandBuffer command,
+    const VkPhysicalDeviceMemoryProperties& properties) {
+  if (!is_vertex_data_pending_)
+    return Result("Vulkan::Vertices data was already sent");
+
+  const size_t n_vertices = GetVertexCount();
+  if (n_vertices == 0)
+    return Result("Vulkan::Data for VertexBuffer is empty");
+
+  size_t bytes = stride_in_bytes_ * n_vertices;
+
+  if (!buffer_) {
+    buffer_ = MakeUnique<Buffer>(device_, bytes, properties);
+    Result r = buffer_->Initialize(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
+                                   VK_BUFFER_USAGE_TRANSFER_DST_BIT);
+    if (!r.IsSuccess())
+      return r;
+  }
+
+  if (formats_.empty() || formats_[0].GetComponents().empty())
+    return Result("Vulkan::Formats for VertexBuffer is empty");
+
+  FillVertexBufferWithData(command);
+
+  is_vertex_data_pending_ = false;
+  return {};
+}
+
+}  // namespace vulkan
+}  // namespace amber
diff --git a/src/vulkan/vertex_buffer.h b/src/vulkan/vertex_buffer.h
new file mode 100644
index 0000000..ef0c242
--- /dev/null
+++ b/src/vulkan/vertex_buffer.h
@@ -0,0 +1,84 @@
+// Copyright 2018 The Amber 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 SRC_VULKAN_VERTEX_BUFFER_H_
+#define SRC_VULKAN_VERTEX_BUFFER_H_
+
+#include <memory>
+#include <vector>
+
+#include "amber/result.h"
+#include "src/format.h"
+#include "src/value.h"
+#include "src/vulkan/buffer.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class VertexBuffer {
+ public:
+  explicit VertexBuffer(VkDevice device);
+  ~VertexBuffer();
+
+  Result SendVertexData(VkCommandBuffer command,
+                        const VkPhysicalDeviceMemoryProperties& properties);
+  bool VertexDataSent() { return !is_vertex_data_pending_; }
+
+  void SetData(uint8_t location,
+               const Format& format,
+               const std::vector<Value>& values);
+
+  const std::vector<VkVertexInputAttributeDescription>& GetVertexInputAttr()
+      const {
+    return vertex_attr_desc_;
+  }
+
+  VkVertexInputBindingDescription GetVertexInputBinding() const {
+    VkVertexInputBindingDescription vertex_binding_desc = {};
+    vertex_binding_desc.binding = 0;
+    vertex_binding_desc.stride = stride_in_bytes_;
+    vertex_binding_desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+    return vertex_binding_desc;
+  }
+
+  size_t GetVertexCount() const {
+    if (data_.empty())
+      return 0;
+
+    return data_[0].size() / formats_[0].GetComponents().size();
+  }
+
+  void BindToCommandBuffer(VkCommandBuffer command);
+
+ private:
+  void FillVertexBufferWithData(VkCommandBuffer command);
+
+  VkDevice device_ = VK_NULL_HANDLE;
+
+  bool is_vertex_data_pending_ = true;
+
+  std::unique_ptr<Buffer> buffer_;
+  uint32_t stride_in_bytes_ = 0;
+
+  std::vector<Format> formats_;
+  std::vector<std::vector<Value>> data_;
+
+  std::vector<VkVertexInputAttributeDescription> vertex_attr_desc_;
+};
+
+}  // namespace vulkan
+}  // namespace amber
+
+#endif  // SRC_VULKAN_VERTEX_BUFFER_H_