Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for sdl_image and sdl_ttf (also other libs) #179

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
[submodule "SDL"]
path = External/SDL
url = https://github.com/libsdl-org/SDL
[submodule "SDL_image"]
path = External/SDL_image
url = https://github.com/libsdl-org/SDL_image.git
branch = main
[submodule "SDL_ttf"]
path = External/SDL_ttf
url = https://github.com/libsdl-org/SDL_ttf.git
branch = main
1 change: 1 addition & 0 deletions External/SDL_image
Submodule SDL_image added at 0d418a
1 change: 1 addition & 0 deletions External/SDL_ttf
Submodule SDL_ttf added at 582c69
44 changes: 44 additions & 0 deletions External/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,48 @@ elif [[ $RUNNER_OS == 'macOS' ]]; then
cp SDL/install_output/lib/libSDL3.dylib ../native/$NAME/libSDL3.dylib
fi

# Build SDL_image
pushd SDL_image >/dev/null
git reset --hard HEAD
# -DSDLIMAGE_AVIF=OFF is used because windows requires special setup to build avif support (nasm)
# TODO: Add support for avif on windows (VisualC script uses dynamic imports)
cmake -B build $FLAGS -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSDL_SHARED_ENABLED_BY_DEFAULT=ON -DSDL_STATIC_ENABLED_BY_DEFAULT=ON -DCMAKE_PREFIX_PATH="../SDL/install_output/cmake/" -DSDLIMAGE_AVIF=OFF
cmake --build build/ --config Release
$SUDO cmake --install build/ --prefix install_output --config Release
popd >/dev/null

# Move build lib into correct folders
if [[ $RUNNER_OS == 'Windows' ]]; then
cp SDL_image/install_output/bin/SDL3_image.dll ../native/$NAME/SDL3_image.dll
cp SDL_image/install_output/bin/libwebp.dll ../native/$NAME/libwebp.dll
cp SDL_image/install_output/bin/libwebpdemux.dll ../native/$NAME/libwebpdemux.dll
cp SDL_image/install_output/bin/tiff.dll ../native/$NAME/tiff.dll
elif [[ $RUNNER_OS == 'Linux' ]]; then
cp SDL_image/install_output/lib/libSDL3_image.so ../native/$NAME/libSDL3_image.so
# TODO: find out if webp, etc. are also needed on linux here
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With these TODOs, is it just that you haven't tested if these are output at all, or only if they're necessary at runtime? If they're output they I think we should include them.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Output at all. I know that the Linux build statically compiles a lot more dependencies than a windows build by default

I agree that they should be included, if they are built

elif [[ $RUNNER_OS == 'macOS' ]]; then
cp SDL_image/install_output/lib/libSDL3_image.dylib ../native/$NAME/libSDL3_image.dylib
# TODO: find out if webp, etc. are also needed on macOS here
fi


# Build SDL_ttf
pushd SDL_ttf >/dev/null
git reset --hard HEAD
cmake -B build $FLAGS -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSDL_SHARED_ENABLED_BY_DEFAULT=ON -DSDL_STATIC_ENABLED_BY_DEFAULT=ON -DCMAKE_PREFIX_PATH="../SDL/install_output/cmake/"
cmake --build build/ --config Release
$SUDO cmake --install build/ --prefix install_output --config Release
popd >/dev/null

# Move build lib into correct folders
if [[ $RUNNER_OS == 'Windows' ]]; then
cp SDL3_ttf/install_output/bin/SDL3_ttf.dll ../native/$NAME/SDL3_ttf.dll
elif [[ $RUNNER_OS == 'Linux' ]]; then
cp SDL3_ttf/install_output/lib/libSDL3_ttf.so ../native/$NAME/libSDL3_ttf.so
elif [[ $RUNNER_OS == 'macOS' ]]; then
cp SDL3_ttf/install_output/lib/libSDL3_ttf.dylib ../native/$NAME/libSDL3_ttf.dylib
fi


# pop External
popd >/dev/null
2 changes: 2 additions & 0 deletions SDL3-CS.Android.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"path": "SDL3-CS.sln",
"projects": [
"SDL3-CS\\SDL3-CS.csproj",
"SDL3_ttf-CS\\SDL3_ttf-CS.csproj",
"SDL3_image-CS\\SDL3_image-CS.csproj",
"SDL3-CS.SourceGeneration\\SDL3-CS.SourceGeneration.csproj",
"SDL3-CS.Android\\SDL3-CS.Android.csproj",
"SDL3-CS.Tests\\SDL3-CS.Tests.csproj",
Expand Down
2 changes: 2 additions & 0 deletions SDL3-CS.Desktop.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"path": "SDL3-CS.sln",
"projects": [
"SDL3-CS\\SDL3-CS.csproj",
"SDL3_ttf-CS\\SDL3_ttf-CS.csproj",
"SDL3_image-CS\\SDL3_image-CS.csproj",
"SDL3-CS.SourceGeneration\\SDL3-CS.SourceGeneration.csproj",
"SDL3-CS.Tests\\SDL3-CS.Tests.csproj",
"SDL3-CS.Tests.Desktop\\SDL3-CS.Tests.Desktop.csproj"
Expand Down
18 changes: 17 additions & 1 deletion SDL3-CS.SourceGeneration/FriendlyOverloadGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Linq;
using System.Text;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -32,17 +33,22 @@ public void Execute(GeneratorExecutionContext context)

foreach (var kvp in finder.Methods)
{
if (kvp.Value.Count == 0)
return;

string filename = kvp.Key;
var foundMethods = kvp.Value;

string className = ClassNameFromMethod(foundMethods.First().NativeMethod);

var result = new StringBuilder();
result.Append(file_header);
result.Append(
SyntaxFactory.NamespaceDeclaration(
SyntaxFactory.IdentifierName("SDL"))
.WithMembers(
SyntaxFactory.SingletonList<MemberDeclarationSyntax>(
SyntaxFactory.ClassDeclaration("SDL3")
SyntaxFactory.ClassDeclaration(className)
.WithModifiers(
SyntaxFactory.TokenList(
SyntaxFactory.Token(SyntaxKind.UnsafeKeyword),
Expand All @@ -54,6 +60,16 @@ public void Execute(GeneratorExecutionContext context)
}
}

private static string ClassNameFromMethod(MethodDeclarationSyntax methodNode)
{
if (methodNode.Parent is ClassDeclarationSyntax classDeclaration)
{
return classDeclaration.Identifier.Text;
}

return "SDL3"; // fallback!
}

private static MemberDeclarationSyntax makeFriendlyMethod(GeneratedMethod gm)
{
var returnType = gm.RequiredChanges.HasFlag(Changes.ChangeReturnTypeToString)
Expand Down
21 changes: 19 additions & 2 deletions SDL3-CS.SourceGeneration/UnfriendlyMethodFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,31 @@ public class UnfriendlyMethodFinder : ISyntaxReceiver
{
public readonly Dictionary<string, List<GeneratedMethod>> Methods = new Dictionary<string, List<GeneratedMethod>>();

private static readonly string[] sdlPrefixes = ["SDL_", "TTF_", "IMG_"];

/// <summary>
/// Checks whether the method is from any SDL library.
/// It identifies those by checking the SDL prefix in the method name.
/// </summary>
private static bool IsMethodFromSDL(MethodDeclarationSyntax methodNode)
{
foreach (string prefix in sdlPrefixes)
{
if (methodNode.Identifier.ValueText.StartsWith(prefix, StringComparison.Ordinal))
return true;
}

return false;
}

public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is MethodDeclarationSyntax method)
{
string name = method.Identifier.ValueText;
bool isUnsafe = name.StartsWith($"{Helper.UnsafePrefix}SDL_", StringComparison.Ordinal);
bool isUnsafe = name.StartsWith(Helper.UnsafePrefix, StringComparison.Ordinal);

if (!name.StartsWith("SDL_", StringComparison.Ordinal) && !isUnsafe)
if (!IsMethodFromSDL(method) && !isUnsafe)
return;

if (method.ParameterList.Parameters.Any(p => p.Identifier.IsKind(SyntaxKind.ArgListKeyword)))
Expand Down
2 changes: 2 additions & 0 deletions SDL3-CS.iOS.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"path": "SDL3-CS.sln",
"projects": [
"SDL3-CS\\SDL3-CS.csproj",
"SDL3_ttf-CS\\SDL3_ttf-CS.csproj",
"SDL3_image-CS\\SDL3_image-CS.csproj",
"SDL3-CS.SourceGeneration\\SDL3-CS.SourceGeneration.csproj",
"SDL3-CS.Tests\\SDL3-CS.Tests.csproj",
"SDL3-CS.Tests.iOS\\SDL3-CS.Tests.iOS.csproj"
Expand Down
12 changes: 12 additions & 0 deletions SDL3-CS.sln
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDL3-CS.Tests.iOS", "SDL3-C
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDL3-CS.Tests.Desktop", "SDL3-CS.Tests.Desktop\SDL3-CS.Tests.Desktop.csproj", "{7E8D719A-5B69-43B7-A9D5-385B6FE7F411}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL3_ttf-CS", "SDL3_ttf-CS\SDL3_ttf-CS.csproj", "{8E37EB82-ACC4-4656-A6E5-DB298AE72066}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SDL3_image-CS", "SDL3_image-CS\SDL3_image-CS.csproj", "{A0D6FC5F-BA26-4298-ABF0-234D2481E323}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -61,6 +65,14 @@ Global
{7E8D719A-5B69-43B7-A9D5-385B6FE7F411}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E8D719A-5B69-43B7-A9D5-385B6FE7F411}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E8D719A-5B69-43B7-A9D5-385B6FE7F411}.Release|Any CPU.Build.0 = Release|Any CPU
{8E37EB82-ACC4-4656-A6E5-DB298AE72066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8E37EB82-ACC4-4656-A6E5-DB298AE72066}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E37EB82-ACC4-4656-A6E5-DB298AE72066}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E37EB82-ACC4-4656-A6E5-DB298AE72066}.Release|Any CPU.Build.0 = Release|Any CPU
{A0D6FC5F-BA26-4298-ABF0-234D2481E323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A0D6FC5F-BA26-4298-ABF0-234D2481E323}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A0D6FC5F-BA26-4298-ABF0-234D2481E323}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A0D6FC5F-BA26-4298-ABF0-234D2481E323}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
4 changes: 4 additions & 0 deletions SDL3-CS/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("SDL3-CS.Tests")]

// Allow access to internal CodeGen members (e.g. NativeTypeNameAttribute, etc.) for SDL sister projects:
[assembly: InternalsVisibleTo("SDL3_ttf-CS")]
[assembly: InternalsVisibleTo("SDL3_image-CS")]
4 changes: 2 additions & 2 deletions SDL3-CS/SDL3-CS.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@
<PackagePath>runtimes/linux-arm/native</PackagePath>
<Pack>true</Pack>
</None>
<None Include="$(MSBuildThisFileDirectory)..\native\ios\**\*">
<PackagePath>runtimes/ios/native</PackagePath>
<None Include="$(MSBuildThisFileDirectory)..\native\ios\SDL3.xcframework\**\*">
<PackagePath>runtimes/ios/native/SDL3.xcframework</PackagePath>
<Pack>true</Pack>
</None>
<None Include="$(MSBuildThisFileDirectory)..\native\android\armeabi-v7a\libSDL3.so">
Expand Down
69 changes: 41 additions & 28 deletions SDL3-CS/generate_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,14 @@

repository_root = pathlib.Path(__file__).resolve().parents[1]

SDL_root = repository_root / "External" / "SDL"
SDL_include_root = SDL_root / "include"
SDL_lib_root = "External"
SDL_libs = ["SDL", "SDL_image", "SDL_ttf"]
SDL_lib_include_root = {
"SDL3": SDL_lib_root + "/SDL/include",
"SDL3_image": SDL_lib_root + "/SDL_image/include",
"SDL3_ttf": SDL_lib_root + "/SDL_ttf/include"
}

SDL3_header_base = "SDL3" # base folder of header files

csproj_root = repository_root / "SDL3-CS"
Expand All @@ -44,12 +50,13 @@
class Header:
"""Represents a SDL header file that is used in ClangSharp generation."""

def __init__(self, base: str, name: str, output_suffix=None):
assert base == SDL3_header_base
def __init__(self, base: str, name: str, folder: str, output_suffix=None):
assert base in SDL_lib_include_root
assert name.startswith("SDL")
assert not name.endswith(".h")
self.base = base
self.name = name
self.folder = folder
self.output_suffix = output_suffix

def __str__(self):
Expand All @@ -60,28 +67,28 @@ def sdl_api_name(self):
return f"{self.name}.h"

def input_file(self):
"""Input header file relative to SDL_include_root."""
return f"{self.base}/{self.name}.h"
"""Input header file relative to repository_root."""
return f"{self.folder}/{self.base}/{self.name}.h"

def output_file(self):
"""Location of generated C# file."""
if self.output_suffix is None:
return csproj_root / f"{self.base}/ClangSharp/{self.name}.g.cs"
return repository_root / f"{self.base}-CS" / f"{self.base}/ClangSharp/{self.name}.g.cs"
else:
return csproj_root / f"{self.base}/ClangSharp/{self.name}.{self.output_suffix}.g.cs"
return repository_root / f"{self.base}-CS" / f"{self.base}/ClangSharp/{self.name}.{self.output_suffix}.g.cs"

def rsp_files(self):
"""Location of ClangSharp response files."""
yield csproj_root / f"{self.base}/{self.name}.rsp"
yield repository_root / f"{self.base}-CS" / f"{self.base}/{self.name}.rsp"
if self.output_suffix is not None:
yield csproj_root / f"{self.base}/{self.name}.{self.output_suffix}.rsp"
yield repository_root / f"{self.base}-CS" / f"{self.base}/{self.name}.{self.output_suffix}.rsp"

def cs_file(self):
"""Location of the manually-written C# file that implements some parts of the header."""
if self.output_suffix is None:
return csproj_root / f"{self.base}/{self.name}.cs"
return repository_root / f"{self.base}-CS" / f"{self.base}/{self.name}.cs"
else:
return csproj_root / f"{self.base}/{self.name}.{self.output_suffix}.cs"
return repository_root / f"{self.base}-CS" / f"{self.base}/{self.name}.{self.output_suffix}.cs"


def make_header_fuzzy(s: str) -> Header:
Expand All @@ -99,14 +106,14 @@ def make_header_fuzzy(s: str) -> Header:
if name.endswith(".h"):
name = name.replace(".h", "")

return Header(base, name)
return Header(base, name, SDL_lib_include_root[base])


def add(s: str):
base, name = s.split("/")
assert s.endswith(".h")
name = name.replace(".h", "")
return Header(base, name)
return Header(base, name, SDL_lib_include_root[base])


headers = [
Expand Down Expand Up @@ -160,22 +167,26 @@ def add(s: str):
add("SDL3/SDL_version.h"),
add("SDL3/SDL_video.h"),
add("SDL3/SDL_vulkan.h"),
add("SDL3_image/SDL_image.h"),
add("SDL3_ttf/SDL_ttf.h"),
add("SDL3_ttf/SDL_textengine.h"),
]


def prepare_sdl_source():
subprocess.run([
"git",
"reset",
"--hard",
"HEAD"
], cwd=SDL_root)
for lib in SDL_libs:
subprocess.run([
"git",
"reset",
"--hard",
"HEAD"
], cwd=repository_root / SDL_lib_root / lib)


def get_sdl_api_dump():
subprocess.run([
sys.executable,
SDL_root / "src" / "dynapi" / "gendynapi.py",
repository_root / SDL_lib_root / "SDL" / "src" / "dynapi" / "gendynapi.py",
"--dump"
])

Expand Down Expand Up @@ -210,7 +221,7 @@ def check_generated_functions(sdl_api, header, generated_file_paths):
print(f"[⚠️ Warning] Function {name} not found in generated files:", *generated_file_paths)


defined_constant_regex = re.compile(r"\[Constant]\s*public (const|static readonly) \w+ (SDL_\w+) = ", re.MULTILINE)
defined_constant_regex = re.compile(r"\[Constant]\s*public (const|static readonly) \w+ (\w+_\w+) = ", re.MULTILINE)


def get_manually_written_symbols(header):
Expand All @@ -221,11 +232,10 @@ def get_manually_written_symbols(header):
text = f.read()
for match in defined_constant_regex.finditer(text):
m = match.group(2)
assert m.startswith("SDL_")
yield m


typedef_enum_regex = re.compile(r"\[Typedef]\s*public enum (SDL_\w+)", re.MULTILINE)
typedef_enum_regex = re.compile(r"\[Typedef]\s*public enum (\w+_\w+)", re.MULTILINE)


def get_typedefs():
Expand All @@ -250,10 +260,10 @@ def typedef(t):
"windows-types",
"generate-macro-bindings",

"--file-directory", SDL_include_root,
"--include-directory", SDL_include_root,
"--libraryPath", "SDL3",
"--methodClassName", "SDL3",
"--file-directory", repository_root,
"--include-directory", repository_root / SDL_lib_include_root["SDL3"],
"--include-directory", repository_root / SDL_lib_include_root["SDL3_image"],
"--include-directory", repository_root / SDL_lib_include_root["SDL3_ttf"],
"--namespace", "SDL",

"--remap",
Expand Down Expand Up @@ -295,6 +305,9 @@ def run_clangsharp(command, header: Header):
cmd = command + [
"--file", header.input_file(),
"--output", header.output_file(),
"--libraryPath", header.base,

"--methodClassName", header.base,
]

for rsp in header.rsp_files():
Expand Down
Loading