1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 17:04:59 +00:00
serenity/Meta/Lagom/Tools/CodeGenerators/LibGL/GenerateGLAPIWrapper.cpp
Jelle Raaijmakers 2da1c1c10e LibGL: Generate the API wrappers
We now generate all LibGL API wrappers from a single API method
definition list stored in `GLAPI.json`. Since a significant portion of
the OpenGL API methods are relatively consistent variants, we take
advantage of this to generate a lot of these variants at once.

The autogenerated methods check for the non-nullness of the current
`GLContext`, and only perform an action if a `GLContext` is present.
This prevents a crash in ports like GLTron, who assume you can still
call the OpenGL API without an active context.

This increases our API wrapper method count from 211 to 356.

Fixes #15814.
2022-12-20 10:42:31 +01:00

570 lines
23 KiB
C++

/*
* Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Array.h>
#include <AK/DeprecatedString.h>
#include <AK/JsonObject.h>
#include <AK/NumericLimits.h>
#include <AK/Optional.h>
#include <AK/SourceGenerator.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/Stream.h>
#include <LibMain/Main.h>
struct ArgumentDefinition {
Optional<DeprecatedString> name;
Optional<DeprecatedString> cpp_type;
DeprecatedString expression;
Optional<DeprecatedString> cast_to;
};
struct FunctionDefinition {
DeprecatedString name;
DeprecatedString return_type;
Vector<ArgumentDefinition> arguments;
DeprecatedString implementation;
bool unimplemented;
DeprecatedString variant_gl_type;
};
struct VariantType {
DeprecatedString encoded_type;
Optional<DeprecatedString> implementation;
bool unimplemented;
};
struct Variants {
Vector<DeprecatedString> api_suffixes { "" };
Vector<u32> argument_counts { NumericLimits<u32>::max() };
Vector<DeprecatedString> argument_defaults { "" };
bool convert_range { false };
Vector<VariantType> types {
{
.encoded_type = "",
.implementation = Optional<DeprecatedString> {},
.unimplemented = false,
}
};
DeprecatedString pointer_argument { "" };
};
struct EncodedTypeEntry {
StringView encoded_type;
StringView cpp_type;
StringView gl_type;
};
// clang-format off
constexpr static Array<EncodedTypeEntry, 9> type_definitions = {
EncodedTypeEntry { "b"sv, "GLbyte"sv, "GL_BYTE"sv },
EncodedTypeEntry { "d"sv, "GLdouble"sv, "GL_DOUBLE"sv },
EncodedTypeEntry { "f"sv, "GLfloat"sv, "GL_FLOAT"sv },
EncodedTypeEntry { "i"sv, "GLint"sv, "GL_INT"sv },
EncodedTypeEntry { "s"sv, "GLshort"sv, "GL_SHORT"sv },
EncodedTypeEntry { "ub"sv, "GLubyte"sv, "GL_UNSIGNED_BYTE"sv },
EncodedTypeEntry { "ui"sv, "GLuint"sv, "GL_UNSIGNED_INT"sv },
EncodedTypeEntry { "us"sv, "GLushort"sv, "GL_UNSIGNED_SHORT"sv },
EncodedTypeEntry { "x"sv, "GLfixed"sv, "GL_INT"sv },
};
// clang-format on
struct EncodedType {
EncodedTypeEntry type_entry;
DeprecatedString cpp_type;
DeprecatedString function_name_suffix;
bool is_pointer;
bool is_const_pointer;
};
Vector<DeprecatedString> get_name_list(JsonValue const& name_definition)
{
if (name_definition.is_null())
return {};
Vector<DeprecatedString, 1> names;
if (name_definition.is_string()) {
names.append(name_definition.as_string());
} else if (name_definition.is_array()) {
name_definition.as_array().for_each([&names](auto& value) {
VERIFY(value.is_string());
names.append(value.as_string());
});
} else {
VERIFY_NOT_REACHED();
}
return names;
}
Optional<EncodedType> get_encoded_type(DeprecatedString encoded_type)
{
bool is_const_pointer = !encoded_type.ends_with('!');
if (!is_const_pointer)
encoded_type = encoded_type.substring_view(0, encoded_type.length() - 1);
DeprecatedString function_name_suffix = encoded_type;
bool is_pointer = encoded_type.ends_with('v');
if (is_pointer)
encoded_type = encoded_type.substring_view(0, encoded_type.length() - 1);
VERIFY(is_const_pointer || is_pointer);
Optional<EncodedTypeEntry> type_definition;
for (size_t i = 0; i < type_definitions.size(); ++i) {
if (type_definitions[i].encoded_type == encoded_type) {
type_definition = type_definitions[i];
break;
}
}
if (!type_definition.has_value())
return {};
return EncodedType {
.type_entry = type_definition.value(),
.cpp_type = DeprecatedString::formatted(
"{}{}{}",
type_definition->cpp_type,
is_pointer && is_const_pointer ? " const" : "",
is_pointer ? "*" : ""),
.function_name_suffix = function_name_suffix,
.is_pointer = is_pointer,
.is_const_pointer = is_const_pointer,
};
}
DeprecatedString wrap_expression_in_range_conversion(DeprecatedString source_type, DeprecatedString target_type, DeprecatedString expression)
{
VERIFY(target_type == "GLfloat" || target_type == "GLdouble");
// No range conversion required
if (source_type == target_type || source_type == "GLdouble")
return expression;
if (source_type == "GLbyte")
return DeprecatedString::formatted("({} + 128.) / 127.5 - 1.", expression);
else if (source_type == "GLfloat")
return DeprecatedString::formatted("static_cast<GLdouble>({})", expression);
else if (source_type == "GLint")
return DeprecatedString::formatted("({} + 2147483648.) / 2147483647.5 - 1.", expression);
else if (source_type == "GLshort")
return DeprecatedString::formatted("({} + 32768.) / 32767.5 - 1.", expression);
else if (source_type == "GLubyte")
return DeprecatedString::formatted("{} / 255.", expression);
else if (source_type == "GLuint")
return DeprecatedString::formatted("{} / 4294967296.", expression);
else if (source_type == "GLushort")
return DeprecatedString::formatted("{} / 65536.", expression);
VERIFY_NOT_REACHED();
}
Variants read_variants_settings(JsonObject const& variants_obj)
{
Variants variants;
if (variants_obj.has("argument_counts"sv)) {
variants.argument_counts.clear_with_capacity();
variants_obj.get("argument_counts"sv).as_array().for_each([&](auto const& argument_count_value) {
variants.argument_counts.append(argument_count_value.to_u32());
});
}
if (variants_obj.has("argument_defaults"sv)) {
variants.argument_defaults.clear_with_capacity();
variants_obj.get("argument_defaults"sv).as_array().for_each([&](auto const& argument_default_value) {
variants.argument_defaults.append(argument_default_value.as_string());
});
}
if (variants_obj.has("convert_range"sv)) {
variants.convert_range = variants_obj.get("convert_range"sv).to_bool();
}
if (variants_obj.has("api_suffixes"sv)) {
variants.api_suffixes.clear_with_capacity();
variants_obj.get("api_suffixes"sv).as_array().for_each([&](auto const& suffix_value) {
variants.api_suffixes.append(suffix_value.as_string());
});
}
if (variants_obj.has("pointer_argument"sv)) {
variants.pointer_argument = variants_obj.get("pointer_argument"sv).as_string();
}
if (variants_obj.has("types"sv)) {
variants.types.clear_with_capacity();
variants_obj.get("types"sv).as_object().for_each_member([&](auto const& key, auto const& type_value) {
auto const& type = type_value.as_object();
variants.types.append(VariantType {
.encoded_type = key,
.implementation = type.has("implementation"sv) ? type.get("implementation"sv).as_string() : Optional<DeprecatedString> {},
.unimplemented = type.get("unimplemented"sv).to_bool(false),
});
});
}
return variants;
}
Vector<ArgumentDefinition> copy_arguments_for_variant(Vector<ArgumentDefinition> arguments, Variants variants,
u32 argument_count, EncodedType encoded_type)
{
Vector<ArgumentDefinition> variant_arguments = arguments;
auto base_cpp_type = encoded_type.type_entry.cpp_type;
size_t variadic_index = 0;
for (size_t i = 0; i < variant_arguments.size(); ++i) {
// Skip arguments with a fixed type
if (variant_arguments[i].cpp_type.has_value())
continue;
variant_arguments[i].cpp_type = encoded_type.cpp_type;
auto cast_to = variant_arguments[i].cast_to;
// Pointer argument
if (encoded_type.is_pointer) {
variant_arguments[i].name = (variadic_index == 0) ? variants.pointer_argument : Optional<DeprecatedString> {};
if (variadic_index >= argument_count) {
// If this variable argument is past the argument count, fall back to the defaults
variant_arguments[i].expression = variants.argument_defaults[variadic_index];
variant_arguments[i].cast_to = Optional<DeprecatedString> {};
} else if (argument_count == 1 && variants.argument_counts.size() == 1) {
// Otherwise, if the pointer is the only variadic argument, pass it through unchanged
variant_arguments[i].cast_to = Optional<DeprecatedString> {};
} else {
// Otherwise, index into the pointer argument
auto indexed_expression = DeprecatedString::formatted("{}[{}]", variants.pointer_argument, variadic_index);
if (variants.convert_range && cast_to.has_value())
indexed_expression = wrap_expression_in_range_conversion(base_cpp_type, cast_to.value(), indexed_expression);
variant_arguments[i].expression = indexed_expression;
}
} else {
// Regular argument
if (variadic_index >= argument_count) {
// If the variable argument is past the argument count, fall back to the defaults
variant_arguments[i].name = Optional<DeprecatedString> {};
variant_arguments[i].expression = variants.argument_defaults[variadic_index];
variant_arguments[i].cast_to = Optional<DeprecatedString> {};
} else if (variants.convert_range && cast_to.has_value()) {
// Otherwise, if we need to convert the input values, wrap the expression in a range conversion
variant_arguments[i].expression = wrap_expression_in_range_conversion(
base_cpp_type,
cast_to.value(),
variant_arguments[i].expression);
}
}
// Determine if we can skip casting to the target type
if (cast_to == base_cpp_type || (variants.convert_range && cast_to == "GLdouble"))
variant_arguments[i].cast_to = Optional<DeprecatedString> {};
variadic_index++;
}
return variant_arguments;
}
Vector<FunctionDefinition> create_function_definitions(DeprecatedString function_name, JsonObject const& function_definition)
{
// A single function definition can expand to multiple generated functions by way of:
// - differing API suffices (ARB, EXT, etc.);
// - differing argument counts;
// - differing argument types.
// These can all be combined.
// Parse base argument definitions first; these may later be modified by variants
Vector<ArgumentDefinition> argument_definitions;
JsonArray const& arguments = function_definition.has("arguments"sv)
? function_definition.get("arguments"sv).as_array()
: JsonArray {};
arguments.for_each([&argument_definitions](auto const& argument_value) {
VERIFY(argument_value.is_object());
auto const& argument = argument_value.as_object();
auto type = argument.has("type"sv) ? argument.get("type"sv).as_string() : Optional<DeprecatedString> {};
auto argument_names = get_name_list(argument.get("name"sv));
auto expression = argument.get("expression"sv).as_string_or("@argument_name@");
auto cast_to = argument.has("cast_to"sv) ? argument.get("cast_to"sv).as_string() : Optional<DeprecatedString> {};
// Add an empty dummy name when all we have is an expression
if (argument_names.is_empty() && !expression.is_empty())
argument_names.append("");
for (auto const& argument_name : argument_names) {
argument_definitions.append({ .name = argument_name.is_empty() ? Optional<DeprecatedString> {} : argument_name,
.cpp_type = type,
.expression = expression,
.cast_to = cast_to });
}
});
// Create functions for each name and/or variant
Vector<FunctionDefinition> functions;
auto return_type = function_definition.get("return_type"sv).as_string_or("void"sv);
auto function_implementation = function_definition.get("implementation"sv).as_string_or(function_name.to_snakecase());
auto function_unimplemented = function_definition.get("unimplemented"sv).to_bool(false);
if (!function_definition.has("variants"sv)) {
functions.append({
.name = function_name,
.return_type = return_type,
.arguments = argument_definitions,
.implementation = function_implementation,
.unimplemented = function_unimplemented,
.variant_gl_type = "",
});
return functions;
}
// Read variants settings for this function
auto variants_obj = function_definition.get("variants"sv).as_object();
auto variants = read_variants_settings(variants_obj);
for (auto argument_count : variants.argument_counts) {
for (auto const& variant_type : variants.types) {
auto encoded_type = get_encoded_type(variant_type.encoded_type);
auto variant_arguments = encoded_type.has_value()
? copy_arguments_for_variant(argument_definitions, variants, argument_count, encoded_type.value())
: argument_definitions;
auto variant_type_implementation = variant_type.implementation.has_value()
? variant_type.implementation.value()
: function_implementation;
for (auto const& api_suffix : variants.api_suffixes) {
functions.append({
.name = DeprecatedString::formatted(
"{}{}{}{}",
function_name,
variants.argument_counts.size() > 1 ? DeprecatedString::formatted("{}", argument_count) : "",
encoded_type.has_value() && variants.types.size() > 1 ? encoded_type->function_name_suffix : "",
api_suffix),
.return_type = return_type,
.arguments = variant_arguments,
.implementation = variant_type_implementation,
.unimplemented = variant_type.unimplemented || function_unimplemented,
.variant_gl_type = encoded_type.has_value() ? encoded_type->type_entry.gl_type : ""sv,
});
}
}
}
return functions;
}
ErrorOr<void> generate_header_file(JsonObject& api_data, Core::Stream::File& file)
{
StringBuilder builder;
SourceGenerator generator { builder };
generator.appendln("#pragma once");
generator.append("\n");
generator.appendln("#include <LibGL/GL/glplatform.h>");
generator.append("\n");
generator.appendln("#ifdef __cplusplus");
generator.appendln("extern \"C\" {");
generator.appendln("#endif");
generator.append("\n");
api_data.for_each_member([&](auto& function_name, auto& value) {
VERIFY(value.is_object());
auto const& function = value.as_object();
auto function_definitions = create_function_definitions(function_name, function);
for (auto const& function_definition : function_definitions) {
auto function_generator = generator.fork();
function_generator.set("name", function_definition.name);
function_generator.set("return_type", function_definition.return_type);
function_generator.append("GLAPI @return_type@ gl@name@(");
bool first = true;
for (auto const& argument_definition : function_definition.arguments) {
if (!argument_definition.name.has_value() || !argument_definition.cpp_type.has_value())
continue;
auto argument_generator = function_generator.fork();
argument_generator.set("argument_type", argument_definition.cpp_type.value());
argument_generator.set("argument_name", argument_definition.name.value());
if (!first)
argument_generator.append(", ");
first = false;
argument_generator.append("@argument_type@ @argument_name@");
}
function_generator.appendln(");");
}
});
generator.appendln("#ifdef __cplusplus");
generator.appendln("}");
generator.appendln("#endif");
TRY(file.write(generator.as_string_view().bytes()));
return {};
}
ErrorOr<void> generate_implementation_file(JsonObject& api_data, Core::Stream::File& file)
{
StringBuilder builder;
SourceGenerator generator { builder };
generator.appendln("#include <LibGL/GL/glapi.h>");
generator.appendln("#include <LibGL/GLContext.h>");
generator.append("\n");
generator.appendln("extern GL::GLContext* g_gl_context;");
generator.append("\n");
api_data.for_each_member([&](auto& function_name, auto& value) {
VERIFY(value.is_object());
JsonObject const& function = value.as_object();
auto function_definitions = create_function_definitions(function_name, function);
for (auto const& function_definition : function_definitions) {
auto function_generator = generator.fork();
auto return_type = function_definition.return_type;
function_generator.set("name"sv, function_definition.name);
function_generator.set("return_type"sv, return_type);
function_generator.set("implementation"sv, function_definition.implementation);
function_generator.set("variant_gl_type"sv, function_definition.variant_gl_type);
function_generator.append("@return_type@ gl@name@(");
bool first = true;
for (auto const& argument_definition : function_definition.arguments) {
if (!argument_definition.name.has_value() || !argument_definition.cpp_type.has_value())
continue;
auto argument_generator = function_generator.fork();
argument_generator.set("argument_type", argument_definition.cpp_type.value());
argument_generator.set("argument_name", argument_definition.name.value());
if (!first)
argument_generator.append(", ");
first = false;
argument_generator.append("@argument_type@ @argument_name@");
}
function_generator.appendln(")");
function_generator.appendln("{");
if (function_definition.unimplemented) {
function_generator.append(" dbgln(\"gl@name@(");
first = true;
for (auto const& argument_definition : function_definition.arguments) {
if (!argument_definition.name.has_value())
continue;
if (!first)
function_generator.append(", ");
first = false;
if (argument_definition.cpp_type.value().ends_with('*'))
function_generator.append("{:p}");
else if (argument_definition.cpp_type.value() == "GLenum")
function_generator.append("{:#x}");
else
function_generator.append("{}");
}
function_generator.append("): unimplemented\"");
for (auto const& argument_definition : function_definition.arguments) {
if (!argument_definition.name.has_value())
continue;
function_generator.append(", ");
function_generator.append(argument_definition.name.value());
}
function_generator.appendln(");");
function_generator.appendln(" TODO();");
} else {
function_generator.appendln(" if (!g_gl_context)");
if (return_type.ends_with('*'))
function_generator.appendln(" return nullptr;");
else if (return_type == "GLboolean"sv)
function_generator.appendln(" return GL_FALSE;");
else if (return_type == "GLenum"sv)
function_generator.appendln(" return GL_INVALID_OPERATION;");
else if (return_type == "GLuint"sv)
function_generator.appendln(" return 0;");
else if (return_type == "void"sv)
function_generator.appendln(" return;");
else
VERIFY_NOT_REACHED();
function_generator.append(" ");
if (return_type != "void"sv)
function_generator.append("return ");
function_generator.append("g_gl_context->gl_@implementation@(");
first = true;
for (auto const& argument_definition : function_definition.arguments) {
auto argument_generator = function_generator.fork();
auto cast_to = argument_definition.cast_to;
argument_generator.set("argument_name", argument_definition.name.value_or(""));
argument_generator.set("cast_to", cast_to.value_or(""));
if (!first)
argument_generator.append(", ");
first = false;
if (cast_to.has_value())
argument_generator.append("static_cast<@cast_to@>(");
argument_generator.append(argument_definition.expression);
if (cast_to.has_value())
argument_generator.append(")");
}
function_generator.appendln(");");
}
function_generator.appendln("}");
function_generator.append("\n");
}
});
TRY(file.write(generator.as_string_view().bytes()));
return {};
}
ErrorOr<JsonValue> read_entire_file_as_json(StringView filename)
{
auto file = TRY(Core::Stream::File::open(filename, Core::Stream::OpenMode::Read));
auto json_size = TRY(file->size());
auto json_data = TRY(ByteBuffer::create_uninitialized(json_size));
TRY(file->read_entire_buffer(json_data.bytes()));
return JsonValue::from_string(json_data);
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
StringView generated_header_path;
StringView generated_implementation_path;
StringView api_json_path;
Core::ArgsParser args_parser;
args_parser.add_option(generated_header_path, "Path to the OpenGL API header file to generate", "generated-header-path", 'h', "generated-header-path");
args_parser.add_option(generated_implementation_path, "Path to the OpenGL API implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
args_parser.add_option(api_json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path");
args_parser.parse(arguments);
auto json = TRY(read_entire_file_as_json(api_json_path));
VERIFY(json.is_object());
auto api_data = json.as_object();
auto generated_header_file = TRY(Core::Stream::File::open(generated_header_path, Core::Stream::OpenMode::Write));
auto generated_implementation_file = TRY(Core::Stream::File::open(generated_implementation_path, Core::Stream::OpenMode::Write));
TRY(generate_header_file(api_data, *generated_header_file));
TRY(generate_implementation_file(api_data, *generated_implementation_file));
return 0;
}