Skip to content

Commit

Permalink
moved script back, edited info, edited readme
Browse files Browse the repository at this point in the history
  • Loading branch information
m0dB authored and m0dB committed Jan 18, 2025
1 parent efae93e commit 0d99bd6
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 3 deletions.
11 changes: 8 additions & 3 deletions src/rendergraph/shaders/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@ The GLSL shaders are extracted programmatically with `QShader` and then used wit
### Qt < 6.6

The GLSL shader have to extracted from the qsb shader bundles to be used by `QOpenGLShader`.
This can be done using the script `generate_shaders_gl.py` in the ../tools directory. To
use this script, make sure that the qsb and spirv commands are in your path. qsb is part of
Qt. spirv is part of the Vulkan SDK and can be downloaded from <https://vulkan.org>
This can be done using the script `rg_generate_shaders_gl.py` in the mixxx/tools directory:

```console
$ ../../../tools/rg_generate_shaders_gl.py --cmake generated_shaders_gl.cmake *.vert *.frag
```

To use this script, make sure that the qsb and spirv commands are in your path. qsb is part
of Qt. spirv is part of the Vulkan SDK and can be downloaded from <https://vulkan.org>

The script also generates the file `generated_shaders_gl.cmake` which sets a cmake
variable containing a list of all GLSL shaders, used by the CMakeLists.txt in this folder.
159 changes: 159 additions & 0 deletions tools/rg_generate_shaders_gl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""
Converts a fragment or vertex shader file into a GL shader file.
You can use it like this:
$ ./rg_generate_shaders_gl.py --cmake-output \\
../src/rendergraph/shaders/generated_shaders_gl.cmake \\
../src/rendergraph/shaders/*.{vert,frag}
"""
import argparse
import logging
import os
import pathlib
import re
import shutil
import subprocess
import tempfile
import typing


def find_executable(
executable_name: str, additional_paths: typing.Optional[list[str]] = None
) -> pathlib.Path:
"""Find an executable by name in $PATH and in the additional paths."""
if executable_path := shutil.which(executable_name):
return pathlib.Path(executable_path)

if additional_paths:
if executable_path := shutil.which(
executable_name, path=os.pathsep.join(additional_paths)
):
return pathlib.Path(executable_path)

raise OSError(f"Executable {executable_name!r} not found!")


QSB_EXECUTABLE = find_executable(
"qsb",
additional_paths=[
"/usr/lib/qt6/bin",
"/lib/qt6/bin",
"/usr/local/lib/qt6/bin",
],
)


def parse_shader(input_filepath: pathlib.Path) -> typing.Iterator[str]:
"""Parse a Fragment/Vertex shader file and yield lines for a GL file."""
with tempfile.NamedTemporaryFile() as fp:
subprocess.check_call(
[
QSB_EXECUTABLE,
"--glsl",
"120",
"--output",
fp.name,
input_filepath,
]
)
output = subprocess.check_output(
[QSB_EXECUTABLE, "--dump", fp.name],
encoding="utf-8",
universal_newlines=True,
)

comment_added = False
ok = False
in_shader_block = 0
buffered_blank_line = False
for line in output.splitlines():
if in_shader_block == 2:
if re.match(r"^\*\*", line):
ok = True
else:
if not comment_added and not re.match(r"^#", line):
yield "//// GENERATED - EDITS WILL BE OVERWRITTEN"
comment_added = True
if line:
if buffered_blank_line:
yield ""
buffered_blank_line = False
yield line
else:
buffered_blank_line = True
elif in_shader_block == 1:
if line.rstrip() == "Contents:":
in_shader_block = 2
else:
if line.rstrip() == "Shader 1: GLSL 120 [Standard]":
in_shader_block = 1
if not ok:
raise EOFError("end of file reached before end marker reached")


def get_paths(paths: list[pathlib.Path]) -> typing.Iterator[pathlib.Path]:
for path in paths:
if path.is_dir():
yield from path.glob("*.vert")
yield from path.glob("*.frag")
else:
yield path


def main(argv: typing.Optional[list[str]] = None) -> int:
logging.basicConfig(level=logging.DEBUG, format="%(message)s")

logger = logging.getLogger(__name__)

description, _, epilog = __doc__.strip().partition("\n\n")
parser = argparse.ArgumentParser(
description=description,
epilog=epilog,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"file",
nargs="+",
type=pathlib.Path,
help="Input files (.vert, .frag) or directory",
)
parser.add_argument(
"--cmake-output",
type=argparse.FileType("w"),
required=True,
help="CMake Output files (.cmake)",
)
args = parser.parse_args(argv)

generated_shaders: list[pathlib.Path] = []

for file in sorted(get_paths(args.file)):
logger.info("Reading file: %s", file)
try:
lines = list(parse_shader(file))
except EOFError as err:
logger.error("Failed to parse %s: %s", file, err)
continue

output_file = file.with_suffix(f"{file.suffix}.gl")
logger.info("Writing file: %s", output_file)
with output_file.open("w") as fp:
for line in lines:
fp.write(f"{line}\n")

generated_shaders.append(output_file)

args.cmake_output.write("set(\n")
args.cmake_output.write(" generated_shaders_gl\n")
for generated_file in generated_shaders:
args.cmake_output.write(f" {generated_file.name}\n")
args.cmake_output.write(")\n")
logger.info("Generated %d shader files.", len(generated_shaders))

return 0


if __name__ == "__main__":
main()

0 comments on commit 0d99bd6

Please sign in to comment.