Skip to content

Commit

Permalink
SCons: Apply generated_wrapper to shader builders
Browse files Browse the repository at this point in the history
  • Loading branch information
Repiteo committed May 11, 2024
1 parent 916ea00 commit af8d2b4
Show file tree
Hide file tree
Showing 9 changed files with 651 additions and 520 deletions.
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ venv
__pycache__/
*.pyc

# Python modules
.*_cache/

# Documentation
doc/_build/

Expand Down Expand Up @@ -163,9 +166,6 @@ gmon.out
# Kdevelop
*.kdev4

# Mypy
.mypy_cache

# Qt Creator
*.config
*.creator
Expand Down
696 changes: 319 additions & 377 deletions gles3_builders.py

Large diffs are not rendered by default.

107 changes: 44 additions & 63 deletions glsl_builders.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,40 @@
"""Functions used to generate source files during build time"""

import os.path
from methods import print_error
from typing import Optional, Iterable
from methods import generated_wrapper, print_error
from typing import Optional, Iterable, List


def generate_inline_code(input_lines: Iterable[str], insert_newline: bool = True):
def generate_inline_code(input_lines: Iterable[str], insert_newline: bool = True) -> str:
"""Take header data and generate inline code
:param: input_lines: values for shared inline code
:return: str - generated inline value
"""
output = []
for line in input_lines:
if line:
output.append(",".join(str(ord(c)) for c in line))
output += [str(ord(char)) for char in line]
if insert_newline:
output.append("%s" % ord("\n"))
output.append(str(ord("\n")))
output.append("0")
return ",".join(output)
return ", ".join(output)


class RDHeaderStruct:
def __init__(self):
self.vertex_lines = []
self.fragment_lines = []
self.compute_lines = []
def __init__(self) -> None:
self.vertex_lines: List[str] = []
self.fragment_lines: List[str] = []
self.compute_lines: List[str] = []

self.vertex_included_files = []
self.fragment_included_files = []
self.compute_included_files = []
self.vertex_included_files: List[str] = []
self.fragment_included_files: List[str] = []
self.compute_included_files: List[str] = []

self.reading = ""
self.line_offset = 0
self.vertex_offset = 0
self.fragment_offset = 0
self.compute_offset = 0
self.reading: str = ""
self.line_offset: int = 0
self.vertex_offset: int = 0
self.fragment_offset: int = 0
self.compute_offset: int = 0


def include_file_in_rd_header(filename: str, header_data: RDHeaderStruct, depth: int) -> RDHeaderStruct:
Expand Down Expand Up @@ -72,20 +71,20 @@ def include_file_in_rd_header(filename: str, header_data: RDHeaderStruct, depth:
includeline = line.replace("#include ", "").strip()[1:-1]

if includeline.startswith("thirdparty/"):
included_file = os.path.relpath(includeline)
included_file = os.path.relpath(includeline).replace("\\", "/")

else:
included_file = os.path.relpath(os.path.dirname(filename) + "/" + includeline)
included_file = os.path.relpath(os.path.dirname(filename) + "/" + includeline).replace("\\", "/")

if not included_file in header_data.vertex_included_files and header_data.reading == "vertex":
if included_file not in header_data.vertex_included_files and header_data.reading == "vertex":
header_data.vertex_included_files += [included_file]
if include_file_in_rd_header(included_file, header_data, depth + 1) is None:
print_error(f'In file "{filename}": #include "{includeline}" could not be found!"')
elif not included_file in header_data.fragment_included_files and header_data.reading == "fragment":
elif included_file not in header_data.fragment_included_files and header_data.reading == "fragment":
header_data.fragment_included_files += [included_file]
if include_file_in_rd_header(included_file, header_data, depth + 1) is None:
print_error(f'In file "{filename}": #include "{includeline}" could not be found!"')
elif not included_file in header_data.compute_included_files and header_data.reading == "compute":
elif included_file not in header_data.compute_included_files and header_data.reading == "compute":
header_data.compute_included_files += [included_file]
if include_file_in_rd_header(included_file, header_data, depth + 1) is None:
print_error(f'In file "{filename}": #include "{includeline}" could not be found!"')
Expand Down Expand Up @@ -118,58 +117,47 @@ def build_rd_header(
else:
out_file = optional_output_filename

out_file_base = out_file
out_file_base = out_file_base[out_file_base.rfind("/") + 1 :]
out_file_base = out_file_base[out_file_base.rfind("\\") + 1 :]
out_file_ifdef = out_file_base.replace(".", "_").upper()
out_file_class = out_file_base.replace(".glsl.gen.h", "").title().replace("_", "").replace(".", "") + "ShaderRD"
out_file_base = os.path.basename(out_file).split(".")[0]
out_file_class = out_file_base.title().replace("_", "") + "ShaderRD"

if header_data.compute_lines:
body_parts = [
"static const char _compute_code[] = {\n%s\n\t\t};" % generate_inline_code(header_data.compute_lines),
"static const char _compute_code[] = {\n\t\t\t%s\n\t\t};" % generate_inline_code(header_data.compute_lines),
f'setup(nullptr, nullptr, _compute_code, "{out_file_class}");',
]
else:
body_parts = [
"static const char _vertex_code[] = {\n%s\n\t\t};" % generate_inline_code(header_data.vertex_lines),
"static const char _fragment_code[] = {\n%s\n\t\t};" % generate_inline_code(header_data.fragment_lines),
"static const char _vertex_code[] = {\n\t\t\t%s\n\t\t};" % generate_inline_code(header_data.vertex_lines),
"static const char _fragment_code[] = {\n\t\t\t%s\n\t\t};"
% generate_inline_code(header_data.fragment_lines),
f'setup(_vertex_code, _fragment_code, nullptr, "{out_file_class}");',
]

body_content = "\n\t\t".join(body_parts)

# Intended curly brackets are doubled so f-string doesn't eat them up.
shader_template = f"""/* WARNING, THIS FILE WAS GENERATED, DO NOT EDIT */
#ifndef {out_file_ifdef}_RD
#define {out_file_ifdef}_RD
with generated_wrapper(out_file, suffix="rd") as file:
file.write(
f"""\
#include "servers/rendering/renderer_rd/shader_rd.h"
class {out_file_class} : public ShaderRD {{
public:
{out_file_class}() {{
{body_content}
}}
}};
#endif
"""
)

with open(out_file, "w", encoding="utf-8", newline="\n") as fd:
fd.write(shader_template)


def build_rd_headers(target, source, env):
def build_rd_headers(target, source, env) -> None:
for x in source:
build_rd_header(filename=str(x))


class RAWHeaderStruct:
def __init__(self):
self.code = ""
def __init__(self) -> None:
self.code: str = ""


def include_file_in_raw_header(filename: str, header_data: RAWHeaderStruct, depth: int) -> None:
Expand All @@ -191,7 +179,7 @@ def include_file_in_raw_header(filename: str, header_data: RAWHeaderStruct, dept

def build_raw_header(
filename: str, optional_output_filename: Optional[str] = None, header_data: Optional[RAWHeaderStruct] = None
):
) -> None:
header_data = header_data or RAWHeaderStruct()
include_file_in_raw_header(filename, header_data, 0)

Expand All @@ -200,25 +188,18 @@ def build_raw_header(
else:
out_file = optional_output_filename

out_file_base = out_file.replace(".glsl.gen.h", "_shader_glsl")
out_file_base = out_file_base[out_file_base.rfind("/") + 1 :]
out_file_base = out_file_base[out_file_base.rfind("\\") + 1 :]
out_file_ifdef = out_file_base.replace(".", "_").upper()

shader_template = f"""/* WARNING, THIS FILE WAS GENERATED, DO NOT EDIT */
#ifndef {out_file_ifdef}_RAW_H
#define {out_file_ifdef}_RAW_H
out_file_base = os.path.basename(out_file).split(".")[0] + "_shader_glsl"

with generated_wrapper(out_file, suffix="raw") as file:
file.write(
f"""\
static const char {out_file_base}[] = {{
{generate_inline_code(header_data.code, insert_newline=False)}
{generate_inline_code(header_data.code, insert_newline=False)}
}};
#endif
"""

with open(out_file, "w", encoding="utf-8", newline="\n") as f:
f.write(shader_template)
)


def build_raw_headers(target, source, env):
def build_raw_headers(target, source, env) -> None:
for x in source:
build_raw_header(filename=str(x))
107 changes: 85 additions & 22 deletions methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
import subprocess
import contextlib
from collections import OrderedDict
from collections.abc import Mapping
from enum import Enum
from typing import Generator, Optional
from typing import TYPE_CHECKING, Generator, Optional, Union, Tuple, List
from io import TextIOWrapper, StringIO
from pathlib import Path
from os.path import normpath, basename

if TYPE_CHECKING:
from SCons.Node import Node


# Get the "Godot" folder name ahead of time
Expand Down Expand Up @@ -1524,7 +1525,51 @@ def format_key_value(v):
sys.exit()


def format_defines(text: str, tab_width: int = 4) -> str:
"""
Returns the formatted text contents of C/CPP code, ensuring that any defines had
their ending `\\` wrappers properly right-justified.
- `text`: A string representing the C/CPP script contents.
- `tab_width`: An int specifying how many spaces a Character Tabulation (`\\t`)
should represent. Defaults to 4.
"""

lines = text.splitlines()
defines: List[Tuple[int, int]] = []

start_index = -1
for index, line in enumerate(lines):
if line.endswith("\\"):
if start_index == -1:
start_index = index
elif start_index != -1:
defines.append((start_index, index))
start_index = -1

for start, end in defines:
lengths: List[int] = []
max_length = -1
for line in lines[start:end]:
length = len(line.expandtabs(tab_width))
max_length = max(length, max_length)
lengths.append(length)
max_length += 2 # one to reach actual line offset, two to append a space
for index, line in enumerate(lines[start:end]):
lines[index + start] = line[:-1] + "\\".rjust(max_length - lengths[index])

return "\n".join(lines) + "\n"


def generate_copyright_header(filename: str) -> str:
"""
Outputs a fully-formatted copyright header for C/CPP code.
- `filename`: The name of the file to receive the header guard. Can be either the
basename of the file, or a path leading to the file. Should not be just the file
stem, as it makes use of the full name.
"""

MARGIN = 70
TEMPLATE = """\
/**************************************************************************/
Expand Down Expand Up @@ -1557,15 +1602,41 @@ def generate_copyright_header(filename: str) -> str:
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
"""
filename = filename.split("/")[-1].ljust(MARGIN)
if len(filename) > MARGIN:
print(f'WARNING: Filename "{filename}" too large for copyright header.')
return TEMPLATE % filename

basename = os.path.basename(filename).strip()
if not basename:
raise ValueError("Copyright header filename cannot be empty.")
elif len(basename) > MARGIN:
print_warning(f'Filename "{filename}" too large for copyright header.')
return TEMPLATE % basename.ljust(MARGIN)


def generate_header_guard(filename: str, prefix: str = "", suffix: str = "") -> str:
"""
Outputs a fully-formatted header guard for C/CPP header files.
- `filename`: The name of the file to receive the header guard. Can be either the
basename of the file, or a path leading to the file. Should not be just the file
stem, as it makes use of the full name.
- `prefix`: Optional custom prefix to prepend to the header guard.
- `suffix`: Optional custom suffix to append to the header guard.
"""

split = os.path.basename(filename).split(".")
if len(split) < 2:
if len(split) == 1:
print_warning("Generating header guard with no file extension; make sure you didn't pass a file stem.")
else:
raise ValueError("Header guard filename cannot be empty.")
split.insert(0, prefix) # Prefix comes before everything.
split.insert(2, suffix) # Suffix comes after stem, but before any file extension.
guard = "_".join([x for x in split if x.strip()])
return guard.upper().replace(".", "_").replace("-", "_").replace(" ", "_")


@contextlib.contextmanager
def generated_wrapper(
path, # FIXME: type with `Union[str, Node, List[Node]]` when pytest conflicts are resolved
path: Union[str, "Node", List["Node"]],
guard: Optional[bool] = None,
prefix: str = "",
suffix: str = "",
Expand All @@ -1589,31 +1660,23 @@ def generated_wrapper(
# Handle unfiltered SCons target[s] passed as path.
if not isinstance(path, str):
if isinstance(path, list):
if len(path) == 0:
raise ValueError("Node list cannot be empty.")
if len(path) > 1:
print_warning(
"Attempting to use generated wrapper with multiple targets; "
f"will only use first entry: {path[0]}"
)
path = path[0]
if not hasattr(path, "get_abspath"):
if not hasattr(path, "srcnode"):
raise TypeError(f'Expected type "str", "Node" or "List[Node]"; was passed {type(path)}.')
path = path.get_abspath()
path = str(path.srcnode().abspath)

path = str(path).replace("\\", "/")
if guard is None:
guard = path.endswith((".h", ".hh", ".hpp", ".inc"))
if not guard and (prefix or suffix):
print_warning(f'Trying to assign header guard prefix/suffix while `guard` is disabled: "{path}".')

header_guard = ""
if guard:
if prefix:
prefix += "_"
if suffix:
suffix = f"_{suffix}"
split = path.split("/")[-1].split(".")
header_guard = (f"{prefix}{split[0]}{suffix}.{'.'.join(split[1:])}".upper()
.replace(".", "_").replace("-", "_").replace(" ", "_").replace("__", "_")) # fmt: skip
header_guard = generate_header_guard(path, prefix, suffix) if guard else ""

with open(path, "wt", encoding="utf-8", newline="\n") as file:
file.write(generate_copyright_header(path))
Expand All @@ -1625,7 +1688,7 @@ def generated_wrapper(

with StringIO(newline="\n") as str_io:
yield str_io
file.write(str_io.getvalue().strip() or "/* NO CONTENT */")
file.write(format_defines(str_io.getvalue()).strip() or "/* NO CONTENT */")

if guard:
file.write(f"\n\n#endif // {header_guard}")
Expand Down

0 comments on commit af8d2b4

Please sign in to comment.