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

SCons: Apply generated_wrapper to shader builders #91840

Closed
Closed
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
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 @@ -164,9 +167,6 @@ gmon.out
# Kdevelop
*.kdev4

# Mypy
.mypy_cache

# Qt Creator
*.config
*.creator
Expand Down
686 changes: 314 additions & 372 deletions gles3_builders.py

Large diffs are not rendered by default.

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

import os.path
from typing import Iterable, Optional
from typing import Iterable, List, Optional

from methods import print_error
from methods import generated_wrapper, print_error


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 @@ -73,10 +72,10 @@ 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 included_file not in header_data.vertex_included_files and header_data.reading == "vertex":
header_data.vertex_included_files += [included_file]
Expand Down Expand Up @@ -119,58 +118,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 @@ -192,7 +180,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 @@ -201,25 +189,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))
106 changes: 86 additions & 20 deletions methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
from enum import Enum
from io import StringIO, TextIOWrapper
from pathlib import Path
from typing import Generator, Optional
from typing import TYPE_CHECKING, Generator, List, Optional, Tuple, Union

if TYPE_CHECKING:
from SCons.Node import Node


# Get the "Godot" folder name ahead of time
base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/"
Expand Down Expand Up @@ -1521,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 @@ -1554,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 @@ -1586,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 @@ -1622,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
Loading
Loading