mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
3f3f45580a
Each of these strings would previously rely on StringView's char const* constructor overload, which would call __builtin_strlen on the string. Since we now have operator ""sv, we can replace these with much simpler versions. This opens the door to being able to remove StringView(char const*). No functional changes.
328 lines
12 KiB
C++
328 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2022, Sahan Fernando <sahan.h.fernando@gmail.com>
|
|
* Copyright (c) 2022, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Array.h>
|
|
#include <AK/String.h>
|
|
#include <AK/StringView.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 constexpr auto 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"sv;
|
|
|
|
static constexpr auto 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"sv;
|
|
|
|
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 constexpr Array<VertexData, 8> vertices = {
|
|
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 },
|
|
};
|
|
static constexpr Array<size_t, 36> tris = {
|
|
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/gpu/render0", O_RDWR);
|
|
VERIFY(gpu_fd >= 0);
|
|
// Create a virgl context for this file descriptor
|
|
VERIFY(ioctl(gpu_fd, VIRGL_IOCTL_CREATE_CONTEXT) >= 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"sv);
|
|
window->set_icon(app_icon.bitmap_for_size(16));
|
|
|
|
init();
|
|
window->show();
|
|
|
|
return app->exec();
|
|
}
|