summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSahan Fernando <sahan.h.fernando@gmail.com>2022-02-18 13:27:19 +1100
committerAli Mohammad Pur <Ali.mpfard@gmail.com>2022-03-09 14:58:48 +0330
commitfd5eaf6494a920ba76bc9629b553bae6fd12ee19 (patch)
tree2c2e0acf05931de52f6c67f54414b49be2805540
parent2939f65753bf4cfaaf5605dc545898018ea99785 (diff)
downloadserenity-fd5eaf6494a920ba76bc9629b553bae6fd12ee19.zip
Demos: Create demo for VirGL gpu device
This is a simple demo for VirGL, that does the bare minimum required to create and render a spinning cube (with one color per face).
-rw-r--r--Userland/Demos/CMakeLists.txt1
-rw-r--r--Userland/Demos/VirGLDemo/CMakeLists.txt13
-rw-r--r--Userland/Demos/VirGLDemo/CommandBufferBuilder.cpp291
-rw-r--r--Userland/Demos/VirGLDemo/CommandBufferBuilder.h41
-rw-r--r--Userland/Demos/VirGLDemo/VirGLDemo.cpp323
-rw-r--r--Userland/Demos/VirGLDemo/VirGLProtocol.h210
-rw-r--r--Userland/Demos/VirGLDemo/Widget.cpp37
-rw-r--r--Userland/Demos/VirGLDemo/Widget.h41
8 files changed, 957 insertions, 0 deletions
diff --git a/Userland/Demos/CMakeLists.txt b/Userland/Demos/CMakeLists.txt
index f75aa4adc7..cb9b1e2f00 100644
--- a/Userland/Demos/CMakeLists.txt
+++ b/Userland/Demos/CMakeLists.txt
@@ -9,4 +9,5 @@ add_subdirectory(ModelGallery)
add_subdirectory(Mouse)
add_subdirectory(Screensaver)
add_subdirectory(Starfield)
+add_subdirectory(VirGLDemo)
add_subdirectory(WidgetGallery)
diff --git a/Userland/Demos/VirGLDemo/CMakeLists.txt b/Userland/Demos/VirGLDemo/CMakeLists.txt
new file mode 100644
index 0000000000..8b11dec1a8
--- /dev/null
+++ b/Userland/Demos/VirGLDemo/CMakeLists.txt
@@ -0,0 +1,13 @@
+serenity_component(
+ VirGLDemo
+ TARGETS VirGLDemo
+)
+
+set(SOURCES
+ CommandBufferBuilder.cpp
+ VirGLDemo.cpp
+ Widget.cpp
+)
+
+serenity_app(VirGLDemo ICON app-cube)
+target_link_libraries(VirGLDemo LibMain LibGUI)
diff --git a/Userland/Demos/VirGLDemo/CommandBufferBuilder.cpp b/Userland/Demos/VirGLDemo/CommandBufferBuilder.cpp
new file mode 100644
index 0000000000..8d7b8627db
--- /dev/null
+++ b/Userland/Demos/VirGLDemo/CommandBufferBuilder.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2022, Sahan Fernando <sahan.h.fernando@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <Kernel/API/VirGL.h>
+#include <sys/ioctl_numbers.h>
+
+#include "CommandBufferBuilder.h"
+#include "VirGLProtocol.h"
+#include "Widget.h"
+
+static u32 encode_command(u32 length, u32 mid, Protocol::VirGLCommand command)
+{
+ u32 command_value = to_underlying(command);
+ return (length << 16) | ((mid & 0xff) << 8) | (command_value & 0xff);
+};
+
+class CommandBuilder {
+public:
+ CommandBuilder() = delete;
+ CommandBuilder(Vector<u32>& buffer, Protocol::VirGLCommand command, u32 mid)
+ : m_buffer(buffer)
+ , m_start_offset(buffer.size())
+ , m_command(command)
+ , m_command_mid(mid)
+ {
+ m_buffer.append(0);
+ }
+ void appendu32(u32 value)
+ {
+ VERIFY(!m_finalized);
+ m_buffer.append(value);
+ }
+ void appendf32(float value)
+ {
+ VERIFY(!m_finalized);
+ m_buffer.append(bit_cast<u32>(value));
+ }
+ void appendf64(double value)
+ {
+ VERIFY(!m_finalized);
+ m_buffer.append(0);
+ m_buffer.append(0);
+ auto* depth = (u64*)(&m_buffer[m_buffer.size() - 2]);
+ *depth = bit_cast<u64>(value);
+ }
+ void append_string_null_padded(StringView string)
+ {
+ VERIFY(!m_finalized);
+ // Remember to have at least one null terminator byte
+ auto length = string.length() + 1;
+ auto num_required_words = (length + sizeof(u32) - 1) / sizeof(u32);
+ m_buffer.resize(m_buffer.size() + num_required_words);
+ char* dest = (char*)&m_buffer[m_buffer.size() - num_required_words];
+ memcpy(dest, string.characters_without_null_termination(), string.length());
+ // Pad end with null bytes
+ memset(&dest[string.length()], 0, 4 * num_required_words - string.length());
+ }
+ void finalize()
+ {
+ if (!m_finalized) {
+ m_finalized = true;
+ size_t num_elems = m_buffer.size() - m_start_offset - 1;
+ m_buffer[m_start_offset] = encode_command(num_elems, m_command_mid, m_command);
+ }
+ }
+ ~CommandBuilder()
+ {
+ if (!m_finalized)
+ finalize();
+ }
+
+private:
+ Vector<u32>& m_buffer;
+ size_t m_start_offset;
+ Protocol::VirGLCommand m_command;
+ u32 m_command_mid;
+ bool m_finalized { false };
+};
+
+void CommandBufferBuilder::append_set_tweaks(u32 id, u32 value)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::SET_TWEAKS, 0);
+ builder.appendu32(id);
+ builder.appendu32(value);
+}
+
+void CommandBufferBuilder::append_transfer3d(ResourceID resource, size_t width, size_t height, size_t depth, size_t direction)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::TRANSFER3D, 0);
+ builder.appendu32(resource.value()); // res_handle
+ builder.appendu32(0); // level
+ builder.appendu32(242); // usage
+ builder.appendu32(0); // stride
+ builder.appendu32(0); // layer_stride
+ builder.appendu32(0); // x
+ builder.appendu32(0); // y
+ builder.appendu32(0); // z
+ builder.appendu32(width); // width
+ builder.appendu32(height); // height
+ builder.appendu32(depth); // depth
+ builder.appendu32(0); // data_offset
+ builder.appendu32(direction); // direction
+}
+
+void CommandBufferBuilder::append_end_transfers_3d()
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::END_TRANSFERS, 0);
+}
+
+void CommandBufferBuilder::append_draw_vbo(u32 count)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::DRAW_VBO, 0);
+ builder.appendu32(0); // start
+ builder.appendu32(count); // count
+ builder.appendu32(to_underlying(Protocol::PipePrimitiveTypes::TRIANGLES)); // mode
+ builder.appendu32(0); // indexed
+ builder.appendu32(1); // instance_count
+ builder.appendu32(0); // index_bias
+ builder.appendu32(0); // start_instance
+ builder.appendu32(0); // primitive_restart
+ builder.appendu32(0); // restart_index
+ builder.appendu32(0); // min_index
+ builder.appendu32(0xffffffff); // max_index
+ builder.appendu32(0); // cso
+}
+
+void CommandBufferBuilder::append_gl_clear(float r, float g, float b)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::CLEAR, 0);
+ Protocol::ClearType clear_flags {};
+ clear_flags.flags.depth = 1;
+ clear_flags.flags.color0 = 1;
+ builder.appendu32(clear_flags.value);
+ builder.appendf32(r);
+ builder.appendf32(g);
+ builder.appendf32(b);
+ builder.appendf32(1.0f); // Alpha
+ builder.appendf64(1.0); // Depth
+ builder.appendu32(0); // Stencil
+}
+
+void CommandBufferBuilder::append_set_vertex_buffers(u32 stride, u32 offset, ResourceID resource)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::SET_VERTEX_BUFFERS, 0);
+ builder.appendu32(stride);
+ builder.appendu32(offset);
+ builder.appendu32(resource.value());
+}
+
+void CommandBufferBuilder::append_create_blend(ObjectHandle handle)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::CREATE_OBJECT, to_underlying(Protocol::ObjectType::BLEND));
+ builder.appendu32(handle.value());
+ builder.appendu32(4); // Enable dither flag, and nothing else
+ builder.appendu32(0);
+ builder.appendu32(0x78000000); // Enable all bits of color mask for color buffer 0, and nothing else
+ for (size_t i = 1; i < 8; ++i) {
+ builder.appendu32(0); // Explicitly disable all flags for other color buffers
+ }
+}
+
+void CommandBufferBuilder::append_bind_blend(ObjectHandle handle)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::BIND_OBJECT, to_underlying(Protocol::ObjectType::BLEND));
+ builder.appendu32(handle.value()); // VIRGL_OBJ_BIND_HANDLE
+}
+
+void CommandBufferBuilder::append_create_vertex_elements(ObjectHandle handle)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::CREATE_OBJECT, to_underlying(Protocol::ObjectType::VERTEX_ELEMENTS));
+ builder.appendu32(handle.value());
+ builder.appendu32(12); // src_offset_0
+ builder.appendu32(0); // instance_divisor_0
+ builder.appendu32(0); // vertex_buffer_index_0
+ builder.appendu32(30); // src_format_0 (PIPE_FORMAT_R32G32B32_FLOAT = 30)
+ builder.appendu32(0); // src_offset_1
+ builder.appendu32(0); // instance_divisor_1
+ builder.appendu32(0); // vertex_buffer_index_1
+ builder.appendu32(30); // src_format_1 (PIPE_FORMAT_R32G32B32_FLOAT = 30)
+}
+
+void CommandBufferBuilder::append_bind_vertex_elements(ObjectHandle handle)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::BIND_OBJECT, to_underlying(Protocol::ObjectType::VERTEX_ELEMENTS));
+ builder.appendu32(handle.value()); // VIRGL_OBJ_BIND_HANDLE
+}
+
+void CommandBufferBuilder::append_create_surface(ResourceID drawtarget_resource, ObjectHandle drawtarget_handle, Protocol::TextureFormat format)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::CREATE_OBJECT, to_underlying(Protocol::ObjectType::SURFACE));
+ builder.appendu32(drawtarget_handle.value());
+ builder.appendu32(drawtarget_resource.value());
+ builder.appendu32(to_underlying(format));
+ builder.appendu32(0); // First element / Texture Level
+ builder.appendu32(0); // Last element / Texture Element
+}
+
+void CommandBufferBuilder::append_set_framebuffer_state(ObjectHandle drawtarget, ObjectHandle depthbuffer)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::SET_FRAMEBUFFER_STATE, 0);
+ builder.appendu32(1); // nr_cbufs
+ builder.appendu32(depthbuffer.value()); // zsurf_handle
+ builder.appendu32(drawtarget.value()); // surf_handle
+}
+
+void CommandBufferBuilder::append_gl_viewport()
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::SET_VIEWPORT_STATE, 0);
+ builder.appendu32(0);
+ builder.appendf32(DRAWTARGET_WIDTH / 2); // scale_x
+ builder.appendf32((DRAWTARGET_HEIGHT / 2)); // scale_y (flipped, due to VirGL being different from our coordinate space)
+ builder.appendf32(0.5f); // scale_z
+ builder.appendf32(DRAWTARGET_WIDTH / 2); // translate_x
+ builder.appendf32(DRAWTARGET_HEIGHT / 2); // translate_y
+ builder.appendf32(0.5f); // translate_z
+}
+
+void CommandBufferBuilder::append_set_framebuffer_state_no_attach()
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::SET_FRAMEBUFFER_STATE_NO_ATTACH, 0);
+ builder.appendu32((DRAWTARGET_HEIGHT << 16) | DRAWTARGET_WIDTH); // (height << 16) | width
+ builder.appendu32(0); // (samples << 16) | layers
+}
+
+void CommandBufferBuilder::append_set_constant_buffer(Vector<float> const& constant_buffer)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::SET_CONSTANT_BUFFER, 0);
+ builder.appendu32(to_underlying(Gallium::ShaderType::SHADER_VERTEX));
+ builder.appendu32(0); // index (currently unused according to virglrenderer source code)
+ for (auto v : constant_buffer) {
+ builder.appendf32(v);
+ }
+}
+
+void CommandBufferBuilder::append_create_shader(ObjectHandle handle, Gallium::ShaderType shader_type, const char* shader_data)
+{
+ size_t shader_len = strlen(shader_data) + 1; // Need to remember to copy null terminator as well if needed
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::CREATE_OBJECT, to_underlying(Protocol::ObjectType::SHADER));
+ builder.appendu32(handle.value()); // VIRGL_OBJ_CREATE_HANDLE
+ builder.appendu32(to_underlying(shader_type));
+ builder.appendu32(0); // VIRGL_OBJ_SHADER_OFFSET
+ builder.appendu32(shader_len);
+ builder.appendu32(0); // VIRGL_OBJ_SHADER_NUM_TOKENS
+ builder.append_string_null_padded(shader_data);
+}
+
+void CommandBufferBuilder::append_bind_shader(ObjectHandle handle, Gallium::ShaderType shader_type)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::BIND_SHADER, 0);
+ builder.appendu32(handle.value()); // VIRGL_OBJ_BIND_HANDLE
+ builder.appendu32(to_underlying(shader_type));
+}
+
+void CommandBufferBuilder::append_create_rasterizer(ObjectHandle handle)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::CREATE_OBJECT, to_underlying(Protocol::ObjectType::RASTERIZER));
+ builder.appendu32(handle.value()); // Handle
+ builder.appendu32(0x00000002); // S0 (bitfield of state bits)
+ builder.appendf32(1.0); // Point size
+ builder.appendu32(0); // Sprite coord enable
+ builder.appendu32(0x00000000); // S3 (bitfield of state bits)
+ builder.appendf32(0.1); // Line width
+ builder.appendf32(0.0); // Offset units
+ builder.appendf32(0.0); // offset scale
+ builder.appendf32(0.0); // Offset clamp
+}
+
+void CommandBufferBuilder::append_bind_rasterizer(ObjectHandle handle)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::BIND_OBJECT, to_underlying(Protocol::ObjectType::RASTERIZER));
+ builder.appendu32(handle.value()); // VIRGL_OBJ_BIND_HANDLE
+}
+
+void CommandBufferBuilder::append_create_dsa(ObjectHandle handle)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::CREATE_OBJECT, to_underlying(Protocol::ObjectType::DSA));
+ builder.appendu32(handle.value()); // Handle
+ builder.appendu32(0x00000007); // S0 (bitset: (v >> 0) & 1 = depth.enabled, (v >> 1) & 1 = depth.writemask, (v >> 2) & 7 = depth.func)
+ builder.appendu32(0x00000000); // S1 (bitset for 1st stencil buffer)
+ builder.appendu32(0x00000000); // S2 (bitset for 2nd stencil buffer)
+ builder.appendf32(1.0); // Alpha Ref
+}
+
+void CommandBufferBuilder::append_bind_dsa(ObjectHandle handle)
+{
+ CommandBuilder builder(m_buffer, Protocol::VirGLCommand::BIND_OBJECT, to_underlying(Protocol::ObjectType::DSA));
+ builder.appendu32(handle.value()); // VIRGL_OBJ_BIND_HANDLE
+}
diff --git a/Userland/Demos/VirGLDemo/CommandBufferBuilder.h b/Userland/Demos/VirGLDemo/CommandBufferBuilder.h
new file mode 100644
index 0000000000..8be91f9f35
--- /dev/null
+++ b/Userland/Demos/VirGLDemo/CommandBufferBuilder.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022, Sahan Fernando <sahan.h.fernando@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Vector.h>
+#include <sys/ioctl_numbers.h>
+
+#include "VirGLProtocol.h"
+
+class CommandBufferBuilder {
+public:
+ void append_set_tweaks(u32 id, u32 value);
+ void append_transfer3d(ResourceID resource, size_t width, size_t height = 1, size_t depth = 1, size_t direction = VIRGL_DATA_DIR_GUEST_TO_HOST);
+ void append_end_transfers_3d();
+ void append_draw_vbo(u32 count);
+ void append_gl_clear(float r, float g, float b);
+ void append_set_vertex_buffers(u32 stride, u32 offset, ResourceID resource);
+ void append_create_blend(ObjectHandle handle);
+ void append_bind_blend(ObjectHandle handle);
+ void append_create_surface(ResourceID drawtarget_resource, ObjectHandle drawtarget_handle, Protocol::TextureFormat format);
+ void append_set_framebuffer_state(ObjectHandle drawtarget, ObjectHandle depthbuffer = 0);
+ void append_create_vertex_elements(ObjectHandle handle);
+ void append_bind_vertex_elements(ObjectHandle handle);
+ void append_gl_viewport();
+ void append_set_framebuffer_state_no_attach();
+ void append_set_constant_buffer(Vector<float> const& constant_buffer);
+ void append_create_shader(ObjectHandle handle, Gallium::ShaderType shader_type, const char* shader_data);
+ void append_bind_shader(ObjectHandle handle, Gallium::ShaderType shader_type);
+ void append_create_rasterizer(ObjectHandle handle);
+ void append_bind_rasterizer(ObjectHandle handle);
+ void append_create_dsa(ObjectHandle handle);
+ void append_bind_dsa(ObjectHandle handle);
+ Vector<u32> const& build() { return m_buffer; }
+
+private:
+ Vector<u32> m_buffer;
+};
diff --git a/Userland/Demos/VirGLDemo/VirGLDemo.cpp b/Userland/Demos/VirGLDemo/VirGLDemo.cpp
new file mode 100644
index 0000000000..f3763a54e1
--- /dev/null
+++ b/Userland/Demos/VirGLDemo/VirGLDemo.cpp
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2022, Sahan Fernando <sahan.h.fernando@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/String.h>
+#include <AK/Vector.h>
+#include <Kernel/API/VirGL.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Icon.h>
+#include <LibGUI/Window.h>
+#include <LibGfx/Matrix4x4.h>
+#include <LibMain/Main.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/ioctl_numbers.h>
+#include <unistd.h>
+
+#include "CommandBufferBuilder.h"
+#include "VirGLProtocol.h"
+#include "Widget.h"
+
+static const char* frag_shader = "FRAG\n"
+ "PROPERTY FS_COLOR0_WRITES_ALL_CBUFS 1\n"
+ "DCL IN[0], COLOR, COLOR\n"
+ "DCL OUT[0], COLOR\n"
+ " 0: MOV OUT[0], IN[0]\n"
+ " 1: END\n";
+
+static const char* vert_shader = "VERT\n"
+ "DCL IN[0]\n"
+ "DCL IN[1]\n"
+ "DCL OUT[0], POSITION\n"
+ "DCL OUT[1], COLOR\n"
+ "DCL CONST[0..3]\n"
+ "DCL TEMP[0..1]\n"
+ " 0: MUL TEMP[0], IN[0].xxxx, CONST[0]\n"
+ " 1: MAD TEMP[1], IN[0].yyyy, CONST[1], TEMP[0]\n"
+ " 2: MAD TEMP[0], IN[0].zzzz, CONST[2], TEMP[1]\n"
+ " 3: MAD OUT[0], IN[0].wwww, CONST[3], TEMP[0]\n"
+ " 4: MOV_SAT OUT[1], IN[1]\n"
+ " 5: END\n";
+
+struct VertexData {
+ float r;
+ float g;
+ float b;
+ float x;
+ float y;
+ float z;
+};
+
+int gpu_fd;
+ResourceID vbo_resource_id;
+ResourceID drawtarget;
+ResourceID depthbuffer_surface;
+ObjectHandle blend_handle;
+ObjectHandle drawtarget_surface_handle;
+ObjectHandle depthbuffer_surface_handle;
+ObjectHandle ve_handle;
+ObjectHandle frag_shader_handle;
+ObjectHandle vert_shader_handle;
+ObjectHandle rasterizer_handle;
+ObjectHandle dsa_handle;
+Vector<VertexData> g_vertices;
+
+static ObjectHandle allocate_handle()
+{
+ static u32 last_allocated_handle = 0;
+ return { ++last_allocated_handle };
+}
+
+static void upload_command_buffer(Vector<u32> const& command_buffer)
+{
+ VERIFY(command_buffer.size() <= NumericLimits<u32>::max());
+ VirGLCommandBuffer command_buffer_descriptor {
+ .data = command_buffer.data(),
+ .num_elems = (u32)command_buffer.size(),
+ };
+ VERIFY(ioctl(gpu_fd, VIRGL_IOCTL_SUBMIT_CMD, &command_buffer_descriptor) >= 0);
+}
+
+static ResourceID create_virgl_resource(VirGL3DResourceSpec& spec)
+{
+ VERIFY(ioctl(gpu_fd, VIRGL_IOCTL_CREATE_RESOURCE, &spec) >= 0);
+ return spec.created_resource_id;
+}
+
+static Vector<VertexData> gen_vertex_data()
+{
+ Vector<VertexData> data;
+ static const VertexData vertices[8] = {
+ VertexData { .r = 0, .g = 0, .b = 0, .x = -0.5, .y = -0.5, .z = -0.5 },
+ VertexData { .r = 0, .g = 0, .b = 0, .x = 0.5, .y = -0.5, .z = -0.5 },
+ VertexData { .r = 0, .g = 0, .b = 0, .x = -0.5, .y = 0.5, .z = -0.5 },
+ VertexData { .r = 0, .g = 0, .b = 0, .x = 0.5, .y = 0.5, .z = -0.5 },
+ VertexData { .r = 0, .g = 0, .b = 0, .x = -0.5, .y = -0.5, .z = 0.5 },
+ VertexData { .r = 0, .g = 0, .b = 0, .x = 0.5, .y = -0.5, .z = 0.5 },
+ VertexData { .r = 0, .g = 0, .b = 0, .x = -0.5, .y = 0.5, .z = 0.5 },
+ VertexData { .r = 0, .g = 0, .b = 0, .x = 0.5, .y = 0.5, .z = 0.5 },
+ };
+ size_t tris[36] = {
+ 0, 1, 2, 1, 3, 2, // Top
+ 4, 0, 6, 0, 2, 6, // Left
+ 4, 5, 0, 5, 1, 0, // Up
+ 1, 5, 3, 5, 7, 3, // Right
+ 2, 3, 6, 3, 7, 6, // Down
+ 5, 4, 7, 4, 6, 7, // Bottom
+ };
+ for (auto index : tris) {
+ data.append(vertices[index]);
+ }
+ // Choose random colors for each face of the cube
+ for (auto i = 0; i < 6; ++i) {
+ float red = (rand() % 256) / 255.f;
+ float green = (rand() % 256) / 255.f;
+ float blue = (rand() % 256) / 255.f;
+ for (auto j = 0; j < 6; ++j) {
+ auto& vertex = data[i * 6 + j];
+ vertex.r = red;
+ vertex.g = green;
+ vertex.b = blue;
+ }
+ }
+ return data;
+}
+
+static void init()
+{
+ // Open the device
+ gpu_fd = open("/dev/gpu0", O_RDWR);
+ VERIFY(gpu_fd >= 0);
+ // Create a VertexElements resource
+ VirGL3DResourceSpec vbo_spec {
+ .target = to_underlying(Gallium::PipeTextureTarget::BUFFER), // pipe_texture_target
+ .format = 45, // pipe_to_virgl_format
+ .bind = VIRGL_BIND_VERTEX_BUFFER,
+ .width = PAGE_SIZE,
+ .height = 1,
+ .depth = 1,
+ .array_size = 1,
+ .last_level = 0,
+ .nr_samples = 0,
+ .flags = 0,
+ .created_resource_id = 0,
+ };
+ vbo_resource_id = create_virgl_resource(vbo_spec);
+ // Create a texture to draw to
+ VirGL3DResourceSpec drawtarget_spec {
+ .target = to_underlying(Gallium::PipeTextureTarget::TEXTURE_RECT), // pipe_texture_target
+ .format = to_underlying(Protocol::TextureFormat::VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM), // pipe_to_virgl_format
+ .bind = VIRGL_BIND_RENDER_TARGET,
+ .width = DRAWTARGET_WIDTH,
+ .height = DRAWTARGET_HEIGHT,
+ .depth = 1,
+ .array_size = 1,
+ .last_level = 0,
+ .nr_samples = 0,
+ .flags = 0,
+ .created_resource_id = 0,
+ };
+ drawtarget = create_virgl_resource(drawtarget_spec);
+ // Create a depthbuffer surface
+ VirGL3DResourceSpec depthbuffer_surface_spec {
+ .target = to_underlying(Gallium::PipeTextureTarget::TEXTURE_RECT), // pipe_texture_target
+ .format = to_underlying(Protocol::TextureFormat::VIRTIO_GPU_FORMAT_Z32_FLOAT), // pipe_to_virgl_format
+ .bind = VIRGL_BIND_RENDER_TARGET | VIRGL_BIND_DEPTH_STENCIL,
+ .width = DRAWTARGET_WIDTH,
+ .height = DRAWTARGET_HEIGHT,
+ .depth = 1,
+ .array_size = 1,
+ .last_level = 0,
+ .nr_samples = 0,
+ .flags = 0,
+ .created_resource_id = 0,
+ };
+ depthbuffer_surface = create_virgl_resource(depthbuffer_surface_spec);
+
+ // Initialize all required state
+ CommandBufferBuilder builder;
+ // Create and set the blend, to control the color mask
+ blend_handle = allocate_handle();
+ builder.append_create_blend(blend_handle);
+ builder.append_bind_blend(blend_handle);
+ // Create drawtarget surface
+ drawtarget_surface_handle = allocate_handle();
+ builder.append_create_surface(drawtarget, drawtarget_surface_handle, Protocol::TextureFormat::VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM);
+ // Create depthbuffer surface
+ depthbuffer_surface_handle = allocate_handle();
+ builder.append_create_surface(depthbuffer_surface, depthbuffer_surface_handle, Protocol::TextureFormat::VIRTIO_GPU_FORMAT_Z32_FLOAT);
+ // Set some framebuffer state (attached handle, framebuffer size, etc)
+ builder.append_set_framebuffer_state(drawtarget_surface_handle, depthbuffer_surface_handle);
+ builder.append_set_framebuffer_state_no_attach();
+ // Set the vertex buffer
+ builder.append_set_vertex_buffers(sizeof(VertexData), 0, vbo_resource_id);
+ // Create and bind fragment shader
+ frag_shader_handle = allocate_handle();
+ builder.append_create_shader(frag_shader_handle, Gallium::ShaderType::SHADER_FRAGMENT, frag_shader);
+ builder.append_bind_shader(frag_shader_handle, Gallium::ShaderType::SHADER_FRAGMENT);
+ // Create and bind vertex shader
+ vert_shader_handle = allocate_handle();
+ builder.append_create_shader(vert_shader_handle, Gallium::ShaderType::SHADER_VERTEX, vert_shader);
+ builder.append_bind_shader(vert_shader_handle, Gallium::ShaderType::SHADER_VERTEX);
+ // Create a VertexElements object (used to specify layout of vertex data)
+ ve_handle = allocate_handle();
+ builder.append_create_vertex_elements(ve_handle);
+ builder.append_bind_vertex_elements(ve_handle);
+ // Create a DepthStencilAlpha (DSA) object
+ dsa_handle = allocate_handle();
+ builder.append_create_dsa(dsa_handle);
+ builder.append_bind_dsa(dsa_handle);
+ // Create a Rasterizer object
+ rasterizer_handle = allocate_handle();
+ builder.append_create_rasterizer(rasterizer_handle);
+ builder.append_bind_rasterizer(rasterizer_handle);
+ // Set the Viewport
+ builder.append_gl_viewport();
+ // Upload buffer
+ upload_command_buffer(builder.build());
+
+ // Setup the vertex data
+ g_vertices = gen_vertex_data();
+}
+
+static Gfx::FloatMatrix4x4 get_transform_matrix(unsigned step_num)
+{
+ auto mat = Gfx::FloatMatrix4x4::identity();
+ float angle = step_num * 0.02;
+ mat = mat * Gfx::rotation_matrix(FloatVector3(1, 0, 0), angle * 1.17356641f);
+ mat = mat * Gfx::rotation_matrix(FloatVector3(0, 1, 0), angle * 0.90533273f);
+ mat = mat * Gfx::rotation_matrix(FloatVector3(0, 0, 1), angle);
+ return mat;
+}
+
+static Vector<float> encode_constant_buffer(Gfx::FloatMatrix4x4 const& mat)
+{
+ // Flip the y axis. This is done because OpenGLs coordinate space has a Y-axis of
+ // Opposite direction to that of LibGfx
+ Gfx::FloatMatrix4x4 flip_y = Gfx::FloatMatrix4x4::identity();
+ flip_y.elements()[1][1] = -1;
+ auto real_mat = mat * flip_y;
+ Vector<float> values;
+ for (int i = 0; i < 4; ++i) {
+ for (int j = 0; j < 4; ++j) {
+ values.append(real_mat.elements()[i][j]);
+ }
+ }
+ return values;
+}
+
+static void draw_frame(unsigned step_num)
+{
+ // Get model matrix
+ auto model_matrix = get_transform_matrix(step_num);
+ VirGLTransferDescriptor descriptor {
+ .data = (void*)g_vertices.data(),
+ .offset_in_region = 0,
+ .num_bytes = sizeof(VertexData) * g_vertices.size(),
+ .direction = VIRGL_DATA_DIR_GUEST_TO_HOST,
+ };
+ // Transfer data from vertices array to kernel virgl transfer region
+ VERIFY(ioctl(gpu_fd, VIRGL_IOCTL_TRANSFER_DATA, &descriptor) >= 0);
+ // Create command buffer
+ CommandBufferBuilder builder;
+ // Transfer data from kernel virgl transfer region to host resource
+ builder.append_transfer3d(vbo_resource_id, sizeof(VertexData) * g_vertices.size(), 1, 1, VIRGL_DATA_DIR_GUEST_TO_HOST);
+ builder.append_end_transfers_3d();
+ // Set the constant buffer to the identity matrix
+ builder.append_set_constant_buffer(encode_constant_buffer(model_matrix));
+ // Clear the framebuffer
+ builder.append_gl_clear(0, 0, 0);
+ // Draw the vbo
+ builder.append_draw_vbo(g_vertices.size());
+ // Upload the buffer
+ upload_command_buffer(builder.build());
+}
+
+void update_frame(RefPtr<Gfx::Bitmap> target, unsigned num_cycles)
+{
+ VERIFY(target->width() == DRAWTARGET_WIDTH);
+ VERIFY(target->height() == DRAWTARGET_HEIGHT);
+ // Run logic to draw the frame
+ draw_frame(num_cycles);
+ // Transfer data back from hypervisor to kernel transfer region
+ CommandBufferBuilder builder;
+ builder.append_transfer3d(drawtarget, DRAWTARGET_WIDTH, DRAWTARGET_HEIGHT, 1, VIRGL_DATA_DIR_HOST_TO_GUEST);
+ builder.append_end_transfers_3d();
+ upload_command_buffer(builder.build());
+ // Copy from kernel transfer region to userspace
+ VirGLTransferDescriptor descriptor {
+ .data = (void*)target->scanline_u8(0),
+ .offset_in_region = 0,
+ .num_bytes = DRAWTARGET_WIDTH * DRAWTARGET_HEIGHT * sizeof(u32),
+ .direction = VIRGL_DATA_DIR_HOST_TO_GUEST,
+ };
+ VERIFY(ioctl(gpu_fd, VIRGL_IOCTL_TRANSFER_DATA, &descriptor) >= 0);
+}
+
+ErrorOr<int> serenity_main(Main::Arguments arguments)
+{
+ auto app = TRY(GUI::Application::try_create(arguments));
+
+ auto window = TRY(GUI::Window::try_create());
+ window->set_double_buffering_enabled(true);
+ window->set_title("VirGLDemo");
+ window->set_resizable(false);
+ window->resize(DRAWTARGET_WIDTH, DRAWTARGET_HEIGHT);
+ window->set_has_alpha_channel(false);
+ window->set_alpha_hit_threshold(1);
+
+ auto demo = TRY(window->try_set_main_widget<Demo>());
+
+ auto app_icon = GUI::Icon::default_icon("app-cube");
+ window->set_icon(app_icon.bitmap_for_size(16));
+
+ init();
+ window->show();
+
+ return app->exec();
+}
diff --git a/Userland/Demos/VirGLDemo/VirGLProtocol.h b/Userland/Demos/VirGLDemo/VirGLProtocol.h
new file mode 100644
index 0000000000..21d9da96bc
--- /dev/null
+++ b/Userland/Demos/VirGLDemo/VirGLProtocol.h
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2022, Sahan Fernando <sahan.h.fernando@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/DistinctNumeric.h>
+
+TYPEDEF_DISTINCT_ORDERED_ID(u32, ObjectHandle);
+TYPEDEF_DISTINCT_ORDERED_ID(u32, ResourceID);
+
+#define VIRGL_BIND_DEPTH_STENCIL (1 << 0)
+#define VIRGL_BIND_RENDER_TARGET (1 << 1)
+#define VIRGL_BIND_SAMPLER_VIEW (1 << 3)
+#define VIRGL_BIND_VERTEX_BUFFER (1 << 4)
+#define VIRGL_BIND_INDEX_BUFFER (1 << 5)
+#define VIRGL_BIND_CONSTANT_BUFFER (1 << 6)
+#define VIRGL_BIND_DISPLAY_TARGET (1 << 7)
+#define VIRGL_BIND_COMMAND_ARGS (1 << 8)
+#define VIRGL_BIND_STREAM_OUTPUT (1 << 11)
+#define VIRGL_BIND_SHADER_BUFFER (1 << 14)
+#define VIRGL_BIND_QUERY_BUFFER (1 << 15)
+#define VIRGL_BIND_CURSOR (1 << 16)
+#define VIRGL_BIND_CUSTOM (1 << 17)
+#define VIRGL_BIND_SCANOUT (1 << 18)
+#define VIRGL_BIND_STAGING (1 << 19)
+#define VIRGL_BIND_SHARED (1 << 20)
+
+namespace Protocol {
+
+enum class TextureFormat : u32 {
+ // RGBA Formats
+ VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM = 1,
+ VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM = 2,
+ VIRTIO_GPU_FORMAT_A8R8G8B8_UNORM = 3,
+ VIRTIO_GPU_FORMAT_X8R8G8B8_UNORM = 4,
+
+ VIRTIO_GPU_FORMAT_R8G8B8A8_UNORM = 67,
+ VIRTIO_GPU_FORMAT_X8B8G8R8_UNORM = 68,
+
+ VIRTIO_GPU_FORMAT_A8B8G8R8_UNORM = 121,
+ VIRTIO_GPU_FORMAT_R8G8B8X8_UNORM = 134,
+
+ // Stencil-Depth Formats
+ VIRTIO_GPU_FORMAT_Z16_UNORM = 16,
+ VIRTIO_GPU_FORMAT_Z32_UNORM = 17,
+ VIRTIO_GPU_FORMAT_Z32_FLOAT = 18,
+ VIRTIO_GPU_FORMAT_Z24_UNORM_S8_UINT = 19,
+ VIRTIO_GPU_FORMAT_S8_UINT_Z24_UNORM = 20,
+ VIRTIO_GPU_FORMAT_Z24X8_UNORM = 21,
+ VIRTIO_GPU_FORMAT_X8Z24_UNORM = 22,
+ VIRTIO_GPU_FORMAT_S8_UINT = 23,
+ VIRTIO_GPU_FORMAT_Z32_FLOAT_S8X24_UINT = 128,
+ VIRTIO_GPU_FORMAT_X24S8_UINT = 136,
+ VIRTIO_GPU_FORMAT_S8X24_UINT = 137,
+ VIRTIO_GPU_FORMAT_X32_S8X24_UINT = 138,
+
+};
+
+enum class VirGLCommand : u32 {
+ NOP = 0,
+ CREATE_OBJECT = 1,
+ BIND_OBJECT,
+ DESTROY_OBJECT,
+ SET_VIEWPORT_STATE,
+ SET_FRAMEBUFFER_STATE,
+ SET_VERTEX_BUFFERS,
+ CLEAR,
+ DRAW_VBO,
+ RESOURCE_INLINE_WRITE,
+ SET_SAMPLER_VIEWS,
+ SET_INDEX_BUFFER,
+ SET_CONSTANT_BUFFER,
+ SET_STENCIL_REF,
+ SET_BLEND_COLOR,
+ SET_SCISSOR_STATE,
+ BLIT,
+ RESOURCE_COPY_REGION,
+ BIND_SAMPLER_STATES,
+ BEGIN_QUERY,
+ END_QUERY,
+ GET_QUERY_RESULT,
+ SET_POLYGON_STIPPLE,
+ SET_CLIP_STATE,
+ SET_SAMPLE_MASK,
+ SET_STREAMOUT_TARGETS,
+ SET_RENDER_CONDITION,
+ SET_UNIFORM_BUFFER,
+
+ SET_SUB_CTX,
+ CREATE_SUB_CTX,
+ DESTROY_SUB_CTX,
+ BIND_SHADER,
+ SET_TESS_STATE,
+ SET_MIN_SAMPLES,
+ SET_SHADER_BUFFERS,
+ SET_SHADER_IMAGES,
+ MEMORY_BARRIER,
+ LAUNCH_GRID,
+ SET_FRAMEBUFFER_STATE_NO_ATTACH,
+ TEXTURE_BARRIER,
+ SET_ATOMIC_BUFFERS,
+ SET_DBG_FLAGS,
+ GET_QUERY_RESULT_QBO,
+ TRANSFER3D,
+ END_TRANSFERS,
+ COPY_TRANSFER3D,
+ SET_TWEAKS,
+ CLEAR_TEXTURE,
+ PIPE_RESOURCE_CREATE,
+ PIPE_RESOURCE_SET_TYPE,
+ GET_MEMORY_INFO,
+ SEND_STRING_MARKER,
+ MAX_COMMANDS
+};
+
+union ClearType {
+ struct {
+ u32 depth : 1;
+ u32 stencil : 1;
+ u32 color0 : 1;
+ u32 color1 : 1;
+ u32 color2 : 1;
+ u32 color3 : 1;
+ u32 color4 : 1;
+ u32 color5 : 1;
+ u32 color6 : 1;
+ u32 color7 : 1;
+ } flags;
+ u32 value;
+};
+
+enum class ObjectType : u32 {
+ NONE,
+ BLEND,
+ RASTERIZER,
+ DSA,
+ SHADER,
+ VERTEX_ELEMENTS,
+ SAMPLER_VIEW,
+ SAMPLER_STATE,
+ SURFACE,
+ QUERY,
+ STREAMOUT_TARGET,
+ MSAA_SURFACE,
+ MAX_OBJECTS,
+};
+
+enum class PipeTextureTarget : u32 {
+ BUFFER = 0,
+ TEXTURE_1D,
+ TEXTURE_2D,
+ TEXTURE_3D,
+ TEXTURE_CUBE,
+ TEXTURE_RECT,
+ TEXTURE_1D_ARRAY,
+ TEXTURE_2D_ARRAY,
+ TEXTURE_CUBE_ARRAY,
+ MAX
+};
+
+enum class PipePrimitiveTypes : u32 {
+ POINTS = 0,
+ LINES,
+ LINE_LOOP,
+ LINE_STRIP,
+ TRIANGLES,
+ TRIANGLE_STRIP,
+ TRIANGLE_FAN,
+ QUADS,
+ QUAD_STRIP,
+ POLYGON,
+ LINES_ADJACENCY,
+ LINE_STRIP_ADJACENCY,
+ TRIANGLES_ADJACENCY,
+ TRIANGLE_STRIP_ADJACENCY,
+ PATCHES,
+ MAX
+};
+
+}
+
+namespace Gallium {
+
+enum class PipeTextureTarget : u32 {
+ BUFFER,
+ TEXTURE_1D,
+ TEXTURE_2D,
+ TEXTURE_3D,
+ TEXTURE_CUBE,
+ TEXTURE_RECT,
+ TEXTURE_1D_ARRAY,
+ TEXTURE_2D_ARRAY,
+ TEXTURE_CUBE_ARRAY,
+ MAX_TEXTURE_TYPES,
+};
+
+enum class ShaderType : u32 {
+ SHADER_VERTEX = 0,
+ SHADER_FRAGMENT,
+ SHADER_GEOMETRY,
+ SHADER_TESS_CTRL,
+ SHADER_TESS_EVAL,
+ SHADER_COMPUTE,
+ SHADER_TYPES
+};
+
+}
diff --git a/Userland/Demos/VirGLDemo/Widget.cpp b/Userland/Demos/VirGLDemo/Widget.cpp
new file mode 100644
index 0000000000..41f640961f
--- /dev/null
+++ b/Userland/Demos/VirGLDemo/Widget.cpp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022, Sahan Fernando <sahan.h.fernando@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibGUI/Label.h>
+#include <LibGUI/Painter.h>
+
+#include "Widget.h"
+
+Demo::Demo()
+{
+ m_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, { DRAWTARGET_WIDTH, DRAWTARGET_HEIGHT }).release_value_but_fixme_should_propagate_errors();
+ m_bitmap->fill(Gfx::Color::Black);
+
+ m_cycles = 0;
+
+ stop_timer();
+ start_timer(16);
+}
+
+Demo::~Demo() { }
+
+void Demo::paint_event(GUI::PaintEvent& event)
+{
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ painter.draw_scaled_bitmap(rect(), *m_bitmap, m_bitmap->rect());
+}
+
+void Demo::timer_event(Core::TimerEvent&)
+{
+ m_cycles += 1;
+ update_frame(m_bitmap, m_cycles);
+ update();
+}
diff --git a/Userland/Demos/VirGLDemo/Widget.h b/Userland/Demos/VirGLDemo/Widget.h
new file mode 100644
index 0000000000..bf51ce7e2c
--- /dev/null
+++ b/Userland/Demos/VirGLDemo/Widget.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022, Sahan Fernando <sahan.h.fernando@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+void update_frame(RefPtr<Gfx::Bitmap>, unsigned num_cycles);
+
+constexpr size_t DRAWTARGET_WIDTH = 500;
+constexpr size_t DRAWTARGET_HEIGHT = 500;
+
+class Demo final : public GUI::Widget {
+ C_OBJECT(Demo)
+public:
+ virtual ~Demo() override;
+ bool show_window_frame() const { return m_show_window_frame; }
+
+ Function<void(GUI::ContextMenuEvent&)> on_context_menu_request;
+
+protected:
+ virtual void context_menu_event(GUI::ContextMenuEvent& event) override
+ {
+ if (on_context_menu_request)
+ on_context_menu_request(event);
+ }
+
+private:
+ Demo();
+
+ RefPtr<Gfx::Bitmap> m_bitmap;
+
+ virtual void paint_event(GUI::PaintEvent&) override;
+ virtual void timer_event(Core::TimerEvent&) override;
+
+ int m_cycles;
+ bool m_show_window_frame { true };
+};