Add offscreen render targets and rename AssetRegistry to ResourceRegistry #8
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ""
|
||||||
|
labels: ""
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
- Browser [e.g. chrome, safari]
|
||||||
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
|
||||||
|
1. Something
|
||||||
|
2. ...
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ""
|
||||||
|
labels: ""
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
Relates to:
|
||||||
|
|
||||||
|
- TODO
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Additional resources
|
||||||
|
|
||||||
|
TODO
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
resolves TODO
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Testing Instructions
|
||||||
|
|
||||||
|
1. Build engine via `bin/dev build && cd build/ && make -j4`
|
||||||
|
2. Run testbed via `./testbed`
|
||||||
@@ -24,7 +24,6 @@ CMakeUserPresets.json
|
|||||||
*.bin
|
*.bin
|
||||||
*.out
|
*.out
|
||||||
*.spv
|
*.spv
|
||||||
*.spv.h
|
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
pids
|
pids
|
||||||
|
|||||||
Vendored
+2
-2
@@ -41,8 +41,8 @@
|
|||||||
"LD_LIBRARY_PATH": "/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64/lib/VulkanLoader/lib:/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64/lib",
|
"LD_LIBRARY_PATH": "/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64/lib/VulkanLoader/lib:/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64/lib",
|
||||||
"VK_ADD_LAYER_PATH": "/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64/share/vulkan/explicit_layer.d"
|
"VK_ADD_LAYER_PATH": "/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64/share/vulkan/explicit_layer.d"
|
||||||
},
|
},
|
||||||
"cmake.buildDirectory": "${workspaceFolder}/build",
|
"cmake.useCMakePresets": "always",
|
||||||
"cmake.generator": "Ninja",
|
"cmake.copyCompileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||||
"cmake.configureOnOpen": true,
|
"cmake.configureOnOpen": true,
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
".clang-format": "yaml",
|
".clang-format": "yaml",
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Sophia
|
||||||
|
|
||||||
|
## Dev helper (`bin/dev`)
|
||||||
|
|
||||||
|
`bin/dev` wraps common project tasks. Run `bin/dev bash-completions` to list commands.
|
||||||
|
|
||||||
|
### Creating issues
|
||||||
|
|
||||||
|
Use `bin/dev issue` to create Gitea issues instead of calling the API or `tea` directly:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bin/dev issue "<title>" [-b "<body>"] [-l <label>]... [-a <user>]... [--open]
|
||||||
|
```
|
||||||
|
|
||||||
|
- Labels are repo label names (`bug`, `enhancement`, `question`, ...).
|
||||||
|
- The Gitea API cannot add an issue to a project. After creating an issue, tell the user its URL so they can add it to the "sophia" project (issue sidebar > Projects), or pass `--open`.
|
||||||
|
- Only create issues when the user asks; issues are visible on the remote.
|
||||||
+1
-3
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.22.2)
|
cmake_minimum_required(VERSION 3.25.2)
|
||||||
|
|
||||||
set(NAME sophia)
|
set(NAME sophia)
|
||||||
|
|
||||||
@@ -15,8 +15,6 @@ endif()
|
|||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
message(STATUS "Debug build: enabling debug logging")
|
message(STATUS "Debug build: enabling debug logging")
|
||||||
else()
|
|
||||||
add_compile_definitions(NDEBUG)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"version": 6,
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "base",
|
||||||
|
"hidden": true,
|
||||||
|
"generator": "Ninja",
|
||||||
|
"binaryDir": "${sourceDir}/build/${presetName}",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "debug",
|
||||||
|
"displayName": "Debug",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Debug"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "release",
|
||||||
|
"displayName": "Release (with debug info)",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asan",
|
||||||
|
"displayName": "Debug + AddressSanitizer/UBSan",
|
||||||
|
"inherits": "debug",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_CXX_FLAGS": "-fsanitize=address,undefined -fno-omit-frame-pointer",
|
||||||
|
"CMAKE_C_FLAGS": "-fsanitize=address,undefined -fno-omit-frame-pointer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tsan",
|
||||||
|
"displayName": "Debug + ThreadSanitizer",
|
||||||
|
"inherits": "debug",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_CXX_FLAGS": "-fsanitize=thread",
|
||||||
|
"CMAKE_C_FLAGS": "-fsanitize=thread"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "debug",
|
||||||
|
"configurePreset": "debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "release",
|
||||||
|
"configurePreset": "release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "asan",
|
||||||
|
"configurePreset": "asan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tsan",
|
||||||
|
"configurePreset": "tsan"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -8,17 +8,21 @@ COMING SOON
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|
||||||
- [cmake](https://cmake.org/)
|
- [cmake](https://cmake.org/) 3.25+
|
||||||
|
- [Ninja](https://ninja-build.org/)
|
||||||
- [Vulkan SDK](https://vulkan.lunarg.com/) - With validation layers
|
- [Vulkan SDK](https://vulkan.lunarg.com/) - With validation layers
|
||||||
|
|
||||||
### Build instructions
|
### Build instructions
|
||||||
|
|
||||||
|
Builds use the presets in `CMakePresets.json` (`debug`, `release`), each in its own `build/<preset>` directory:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
|
cmake --preset debug
|
||||||
cd build
|
cmake --build --preset debug
|
||||||
make -j
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or with the dev helper: `dev build [preset] [--fresh]`.
|
||||||
|
|
||||||
## Third Party libraries used
|
## Third Party libraries used
|
||||||
|
|
||||||
- [GLFW](https://github.com/glfw/glfw) - Window and input handling
|
- [GLFW](https://github.com/glfw/glfw) - Window and input handling
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ WAIT_FOR_PROCESS = "wait_for_process"
|
|||||||
|
|
||||||
class DevHelper:
|
class DevHelper:
|
||||||
SHADER_EXTENSIONS = (".vert", ".frag", ".comp", ".geom", ".tesc", ".tese")
|
SHADER_EXTENSIONS = (".vert", ".frag", ".comp", ".geom", ".tesc", ".tese")
|
||||||
|
PROJECT_NAME = "sophia"
|
||||||
|
DEFAULT_PRESET = "debug"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.last_exit_status = 0
|
self.last_exit_status = 0
|
||||||
@@ -26,6 +28,9 @@ class DevHelper:
|
|||||||
# Core logic
|
# Core logic
|
||||||
def call(self, *args, **kwargs):
|
def call(self, *args, **kwargs):
|
||||||
if not args:
|
if not args:
|
||||||
|
print("Usage: dev <command> [args...]\n\nCommands:")
|
||||||
|
for command in self._commands():
|
||||||
|
print(f" {command}")
|
||||||
return
|
return
|
||||||
|
|
||||||
command, rest = args[0], args[1:]
|
command, rest = args[0], args[1:]
|
||||||
@@ -35,9 +40,29 @@ class DevHelper:
|
|||||||
return method(*rest, **kwargs)
|
return method(*rest, **kwargs)
|
||||||
|
|
||||||
# Primary command wrappers
|
# Primary command wrappers
|
||||||
|
|
||||||
|
##
|
||||||
|
# Configures and builds a CMake preset (see CMakePresets.json).
|
||||||
|
# dev build [preset] [--fresh]
|
||||||
|
# The preset defaults to debug and builds into build/<preset>. --fresh
|
||||||
|
# discards the preset's CMake cache before configuring.
|
||||||
def build(self, *args, **kwargs):
|
def build(self, *args, **kwargs):
|
||||||
command = f"rm -rf build && cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug"
|
preset, fresh = self._parse_preset_args(args, "build")
|
||||||
self._exec_in_root(command, replace=True)
|
self._build_preset(preset, fresh)
|
||||||
|
|
||||||
|
##
|
||||||
|
# Builds the debug preset (incrementally), then runs the testbed from
|
||||||
|
# build/debug. Any arguments are passed through to the testbed.
|
||||||
|
# dev testbed [args...]
|
||||||
|
def testbed(self, *args, **kwargs):
|
||||||
|
preset = self.DEFAULT_PRESET
|
||||||
|
self._build_preset(preset, fresh=False)
|
||||||
|
|
||||||
|
preset_dir = os.path.join(self._project_root(), "build", preset)
|
||||||
|
binary = os.path.join(preset_dir, "bin", "testbed")
|
||||||
|
print(f"Running: {shlex.join([binary, *args])}")
|
||||||
|
os.chdir(preset_dir)
|
||||||
|
os.execv(binary, [binary, *args])
|
||||||
|
|
||||||
##
|
##
|
||||||
# Checks out a branch named after a Gitea issue URL.
|
# Checks out a branch named after a Gitea issue URL.
|
||||||
@@ -61,6 +86,40 @@ class DevHelper:
|
|||||||
flag = "" if exists else "-b "
|
flag = "" if exists else "-b "
|
||||||
self._exec_in_root(f"git checkout {flag}{shlex.quote(branch_name)}", replace=True)
|
self._exec_in_root(f"git checkout {flag}{shlex.quote(branch_name)}", replace=True)
|
||||||
|
|
||||||
|
##
|
||||||
|
# Creates an issue on this project's Gitea repo.
|
||||||
|
# dev issue <title> [-b|--body <text>] [-l|--label <name>]... [-a|--assignee <user>]... [-o|--open]
|
||||||
|
# The body is read from stdin if --body is not given and stdin is piped.
|
||||||
|
# Gitea's API cannot add issues to a project (only the web UI can), so the
|
||||||
|
# issue URL is printed and --open opens it in the browser to do so.
|
||||||
|
def issue(self, *args, **kwargs):
|
||||||
|
title, body, labels, assignees, open_in_browser = self._parse_issue_args(args)
|
||||||
|
|
||||||
|
repo = self._origin_repo()
|
||||||
|
if repo is None:
|
||||||
|
raise RuntimeError("could not determine the Gitea repo from the origin remote")
|
||||||
|
|
||||||
|
payload = {"title": title, "body": body}
|
||||||
|
if labels:
|
||||||
|
label_ids = self._fetch_label_ids(repo)
|
||||||
|
unknown = [label for label in labels if label not in label_ids]
|
||||||
|
if unknown:
|
||||||
|
raise ValueError(f"Unknown label(s): {', '.join(unknown)} (available: {', '.join(sorted(label_ids))})")
|
||||||
|
payload["labels"] = [label_ids[label] for label in labels]
|
||||||
|
if assignees:
|
||||||
|
payload["assignees"] = assignees
|
||||||
|
|
||||||
|
issue = self._tea_api(f"repos/{repo}/issues", payload)
|
||||||
|
print(f"Created issue #{issue['number']}: {issue['title']}")
|
||||||
|
print(issue["html_url"])
|
||||||
|
print(f"Add it to the project from the issue sidebar: Projects > {self.PROJECT_NAME}")
|
||||||
|
|
||||||
|
if open_in_browser:
|
||||||
|
opener = shutil.which("xdg-open") or shutil.which("open")
|
||||||
|
if opener is None:
|
||||||
|
raise RuntimeError("xdg-open not found on PATH")
|
||||||
|
subprocess.Popen([opener, issue["html_url"]], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
def renderdoc(self, *args, **kwargs):
|
def renderdoc(self, *args, **kwargs):
|
||||||
qrenderdoc = self._find_qrenderdoc()
|
qrenderdoc = self._find_qrenderdoc()
|
||||||
self._exec_in_root(qrenderdoc, replace=True)
|
self._exec_in_root(qrenderdoc, replace=True)
|
||||||
@@ -101,14 +160,62 @@ class DevHelper:
|
|||||||
raise NotImplementedError(f"Not implemented for platform {sys.platform}")
|
raise NotImplementedError(f"Not implemented for platform {sys.platform}")
|
||||||
|
|
||||||
def bash_completions(self, *args, **kwargs):
|
def bash_completions(self, *args, **kwargs):
|
||||||
completions = sorted(
|
print(" ".join(self._commands()))
|
||||||
|
|
||||||
|
# Private helpers
|
||||||
|
def _commands(self):
|
||||||
|
return sorted(
|
||||||
METHOD_TO_COMMAND.get(name, name)
|
METHOD_TO_COMMAND.get(name, name)
|
||||||
for name in vars(DevHelper)
|
for name in vars(DevHelper)
|
||||||
if not name.startswith("_") and name != "call" and callable(getattr(DevHelper, name))
|
if not name.startswith("_") and name != "call" and callable(getattr(DevHelper, name))
|
||||||
)
|
)
|
||||||
print(" ".join(completions))
|
|
||||||
|
|
||||||
# Private helpers
|
def _parse_preset_args(self, args, command):
|
||||||
|
preset = None
|
||||||
|
fresh = False
|
||||||
|
for arg in args:
|
||||||
|
if arg == "--fresh":
|
||||||
|
fresh = True
|
||||||
|
elif arg.startswith("-"):
|
||||||
|
raise ValueError(f"Unknown option: {arg}")
|
||||||
|
elif preset is None:
|
||||||
|
preset = arg
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Usage: dev {command} [preset] [--fresh]")
|
||||||
|
|
||||||
|
preset = preset or self.DEFAULT_PRESET
|
||||||
|
presets = self._configure_presets()
|
||||||
|
if preset not in presets:
|
||||||
|
raise ValueError(f"Unknown preset: {preset} (available: {', '.join(presets)})")
|
||||||
|
return preset, fresh
|
||||||
|
|
||||||
|
def _configure_presets(self):
|
||||||
|
with open(os.path.join(self._project_root(), "CMakePresets.json")) as f:
|
||||||
|
presets = json.load(f)["configurePresets"]
|
||||||
|
return [preset["name"] for preset in presets if not preset.get("hidden")]
|
||||||
|
|
||||||
|
def _build_preset(self, preset, fresh):
|
||||||
|
configure = ["cmake", "--preset", preset]
|
||||||
|
if fresh:
|
||||||
|
configure.append("--fresh")
|
||||||
|
self._run_in_root(configure)
|
||||||
|
self._copy_compile_commands(preset)
|
||||||
|
self._run_in_root(["cmake", "--build", "--preset", preset])
|
||||||
|
|
||||||
|
# Editor tooling reads build/compile_commands.json, so point it at the
|
||||||
|
# most recently configured preset.
|
||||||
|
def _copy_compile_commands(self, preset):
|
||||||
|
build_dir = os.path.join(self._project_root(), "build")
|
||||||
|
source = os.path.join(build_dir, preset, "compile_commands.json")
|
||||||
|
if os.path.isfile(source):
|
||||||
|
shutil.copyfile(source, os.path.join(build_dir, "compile_commands.json"))
|
||||||
|
|
||||||
|
def _run_in_root(self, command):
|
||||||
|
print(f"Running: {shlex.join(command)}")
|
||||||
|
result = subprocess.run(command, cwd=self._project_root())
|
||||||
|
if result.returncode != 0:
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
def _parse_issue_url(self, url):
|
def _parse_issue_url(self, url):
|
||||||
match = re.match(r"^(https?://[^/]+)/([^/]+)/([^/]+)/issues/(\d+)", url)
|
match = re.match(r"^(https?://[^/]+)/([^/]+)/([^/]+)/issues/(\d+)", url)
|
||||||
if match is None:
|
if match is None:
|
||||||
@@ -126,6 +233,63 @@ class DevHelper:
|
|||||||
raise RuntimeError(f"failed to fetch issue title from {endpoint}")
|
raise RuntimeError(f"failed to fetch issue title from {endpoint}")
|
||||||
return json.loads(result.stdout)["title"]
|
return json.loads(result.stdout)["title"]
|
||||||
|
|
||||||
|
def _parse_issue_args(self, args):
|
||||||
|
title = None
|
||||||
|
body = None
|
||||||
|
labels = []
|
||||||
|
assignees = []
|
||||||
|
open_in_browser = False
|
||||||
|
|
||||||
|
args = list(args)
|
||||||
|
while args:
|
||||||
|
arg = args.pop(0)
|
||||||
|
if arg in ("-b", "--body", "-l", "--label", "-a", "--assignee"):
|
||||||
|
if not args:
|
||||||
|
raise ValueError(f"{arg} requires a value")
|
||||||
|
value = args.pop(0)
|
||||||
|
if arg in ("-b", "--body"):
|
||||||
|
body = value
|
||||||
|
elif arg in ("-l", "--label"):
|
||||||
|
labels.append(value)
|
||||||
|
else:
|
||||||
|
assignees.append(value)
|
||||||
|
elif arg in ("-o", "--open"):
|
||||||
|
open_in_browser = True
|
||||||
|
elif arg.startswith("-"):
|
||||||
|
raise ValueError(f"Unknown option: {arg}")
|
||||||
|
elif title is None:
|
||||||
|
title = arg
|
||||||
|
else:
|
||||||
|
title = f"{title} {arg}"
|
||||||
|
|
||||||
|
if not title:
|
||||||
|
raise ValueError("Must provide an issue title. Usage: dev issue <title> [-b <body>] [-l <label>]... [-a <user>]... [-o]")
|
||||||
|
if body is None:
|
||||||
|
body = "" if sys.stdin.isatty() else sys.stdin.read()
|
||||||
|
return title, body, labels, assignees, open_in_browser
|
||||||
|
|
||||||
|
def _tea_api(self, endpoint, payload=None):
|
||||||
|
if shutil.which("tea") is None:
|
||||||
|
raise RuntimeError("tea not found on PATH (install gitea tea and run `tea login add`)")
|
||||||
|
|
||||||
|
command = ["tea", "api", endpoint]
|
||||||
|
if payload is not None:
|
||||||
|
command += ["-d", "@-"]
|
||||||
|
print(f"Running: {' '.join(command)}")
|
||||||
|
result = subprocess.run(
|
||||||
|
command,
|
||||||
|
input=json.dumps(payload) if payload is not None else None,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(f"tea api request failed: {endpoint}")
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
def _fetch_label_ids(self, repo):
|
||||||
|
labels = self._tea_api(f"repos/{repo}/labels?limit=100")
|
||||||
|
return {label["name"]: label["id"] for label in labels}
|
||||||
|
|
||||||
def _format_branch_name(self, issue_repo, issue_number, issue_title):
|
def _format_branch_name(self, issue_repo, issue_number, issue_title):
|
||||||
formatted_title = re.sub(r"-+", "-", re.sub(r"[^a-z0-9\-]", "", re.sub(r"\s+", "-", issue_title.lower().strip())))
|
formatted_title = re.sub(r"-+", "-", re.sub(r"[^a-z0-9\-]", "", re.sub(r"\s+", "-", issue_title.lower().strip())))
|
||||||
if issue_repo == self._origin_repo():
|
if issue_repo == self._origin_repo():
|
||||||
|
|||||||
@@ -1,36 +1,56 @@
|
|||||||
# Glossary
|
# Glossary
|
||||||
|
|
||||||
## AssetRegistry
|
## ResourceRegistry
|
||||||
|
|
||||||
An AssetRegistry owns GPU-backed assets on the caller's behalf and hands out
|
A ResourceRegistry owns GPU-backed resources on the caller's behalf and hands out
|
||||||
lightweight Handles instead of pointers or ownership.
|
lightweight Handles instead of pointers or ownership. It is engine-internal:
|
||||||
|
frontend code never sees it, and reaches it through a ResourceRegistryRef.
|
||||||
|
|
||||||
Internally it's a collection of per-type pools, each responsible for one
|
Internally it's a collection of per-type pools, each a `Pool<T, Handle>`
|
||||||
asset type:
|
(a generation-checked slot pool). A type that needs extra behaviour wraps its
|
||||||
|
pool in a thin class instead:
|
||||||
|
|
||||||
```
|
```
|
||||||
AssetRegistry
|
ResourceRegistry
|
||||||
├── MeshPool → MeshHandle
|
├── meshPool Pool<Mesh, MeshHandle>
|
||||||
├── TexturePool → TextureHandle (planned)
|
├── renderTargetPool RenderTargetPool → RenderTargetHandle
|
||||||
└── MaterialPool → MaterialHandle (planned)
|
│ (wraps Pool; waits for the GPU on release, applies resizes)
|
||||||
|
├── texturePool (planned)
|
||||||
|
└── materialPool (planned)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Engine code (`Application`, `ImGuiRenderer`, `SceneRenderer`) uses the pools
|
||||||
|
directly, e.g. `resources.renderTargetPool.get(handle)`.
|
||||||
|
|
||||||
A Handle is a small (index, generation) pair. The index locates the slot in
|
A Handle is a small (index, generation) pair. The index locates the slot in
|
||||||
the pool; the generation is bumped when that slot is released, so a Handle
|
the pool; the generation is bumped when that slot is released, so a Handle
|
||||||
kept around after release() is detected as stale instead of resolving to
|
kept around after release() is detected as stale instead of resolving to
|
||||||
someone else's asset (or freed memory).
|
someone else's resource (or freed memory).
|
||||||
|
|
||||||
|
Think: "Where does the engine keep every GPU resource, and how does it look one
|
||||||
|
up by handle?"
|
||||||
|
|
||||||
|
## ResourceRegistryRef
|
||||||
|
|
||||||
|
A ResourceRegistryRef is the frontend's non-owning, cheap-to-copy view of the
|
||||||
|
ResourceRegistry, returned by `Application::resources()`. It exposes only what
|
||||||
|
frontend code needs and hands out no pools, so no Vulkan-facing types leak
|
||||||
|
through the public API:
|
||||||
|
|
||||||
|
```
|
||||||
|
create<T>(...) load<T>(path) release<T>(handle)
|
||||||
|
requestResize(target, w, h) getExtent(target) isAlive(handle)
|
||||||
|
```
|
||||||
|
|
||||||
Typical lifecycle:
|
Typical lifecycle:
|
||||||
|
|
||||||
```
|
```
|
||||||
load<Mesh>(path) / loadMesh(...) → MeshHandle
|
load<Mesh>(path) / create<Mesh>(...) → MeshHandle
|
||||||
│
|
|
||||||
get<Mesh>(handle) → const Mesh&
|
|
||||||
│
|
│
|
||||||
release<Mesh>(handle)
|
release<Mesh>(handle)
|
||||||
```
|
```
|
||||||
|
|
||||||
Think: "Where do I get or release a GPU asset by handle, without owning its
|
Think: "How do I create or release a GPU resource by handle, without owning its
|
||||||
lifetime directly?"
|
lifetime directly?"
|
||||||
|
|
||||||
## Buffer
|
## Buffer
|
||||||
@@ -256,11 +276,13 @@ This is primarily a UI concept, rather than a GPU concept.
|
|||||||
A PanelRegistry owns Panels on the caller's behalf and hands out lightweight
|
A PanelRegistry owns Panels on the caller's behalf and hands out lightweight
|
||||||
PanelHandles instead of pointers or ownership.
|
PanelHandles instead of pointers or ownership.
|
||||||
|
|
||||||
Internally it's a generation-checked slot pool, the same shape AssetRegistry's
|
Internally it wraps a `Pool<Panel, PanelHandle>` (the same generation-checked
|
||||||
per-type pools use:
|
slot pool ResourceRegistry uses, in `sophia/util/pool.hpp`) and adds the
|
||||||
|
Panel-specific parts: lookup by type or name, and the default dock layout:
|
||||||
|
|
||||||
```
|
```
|
||||||
PanelRegistry
|
PanelRegistry
|
||||||
|
└── Pool<Panel, PanelHandle>
|
||||||
└── Slot { Panel, generation } → PanelHandle
|
└── Slot { Panel, generation } → PanelHandle
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -279,6 +301,34 @@ Each frame, update(frameInfo) calls onUpdate on every visible Panel it holds.
|
|||||||
Think: "Where do I register or look up a UI panel by handle, without owning
|
Think: "Where do I register or look up a UI panel by handle, without owning
|
||||||
its lifetime directly?"
|
its lifetime directly?"
|
||||||
|
|
||||||
|
## SceneRegistry
|
||||||
|
|
||||||
|
A SceneRegistry owns Scenes on the caller's behalf and hands out lightweight
|
||||||
|
SceneHandles instead of pointers or ownership. Like PanelRegistry, it is owned
|
||||||
|
by Application and reached from frontend code directly (`scenes()`), rather than
|
||||||
|
through ResourceRegistryRef: scenes are CPU-side entity data, not GPU resources.
|
||||||
|
|
||||||
|
Internally it wraps a `Pool<Scene, SceneHandle>`:
|
||||||
|
|
||||||
|
```
|
||||||
|
SceneRegistry
|
||||||
|
└── Pool<Scene, SceneHandle>
|
||||||
|
└── Slot { Scene, generation } → SceneHandle
|
||||||
|
```
|
||||||
|
|
||||||
|
Typical lifecycle:
|
||||||
|
|
||||||
|
```
|
||||||
|
create() → SceneHandle
|
||||||
|
│
|
||||||
|
get(handle) → Scene&
|
||||||
|
│
|
||||||
|
release(handle)
|
||||||
|
```
|
||||||
|
|
||||||
|
Think: "Where do I create or look up a Scene by handle, without owning its
|
||||||
|
lifetime directly?"
|
||||||
|
|
||||||
## Sprite
|
## Sprite
|
||||||
|
|
||||||
A Sprite is a 2D renderable representation of an image, usually a rectangular region of a texture.
|
A Sprite is a 2D renderable representation of an image, usually a rectangular region of a texture.
|
||||||
+4
-1
@@ -18,7 +18,10 @@ class Editor : public sophia::Application {
|
|||||||
panels().emplace<SceneHierarchyPanel>();
|
panels().emplace<SceneHierarchyPanel>();
|
||||||
panels().emplace<InspectorPanel>();
|
panels().emplace<InspectorPanel>();
|
||||||
panels().emplace<AssetBrowserPanel>();
|
panels().emplace<AssetBrowserPanel>();
|
||||||
panels().emplace<ViewportPanel>();
|
sophia::RenderTargetHandle sceneTarget =
|
||||||
|
resources().create<sophia::RenderTarget>();
|
||||||
|
views().create({.scene = scenes().create(), .target = sceneTarget});
|
||||||
|
panels().emplace<ViewportPanel>(ui(), sceneTarget);
|
||||||
|
|
||||||
panels().setDefaultLayout(
|
panels().setDefaultLayout(
|
||||||
{.left = {"Scene Hierarchy"},
|
{.left = {"Scene Hierarchy"},
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ void ViewportPanel::onUpdate(const sophia::FrameInfo&) {
|
|||||||
const ImVec2 size = ImGui::GetContentRegionAvail();
|
const ImVec2 size = ImGui::GetContentRegionAvail();
|
||||||
const ImVec2 end(origin.x + size.x, origin.y + size.y);
|
const ImVec2 end(origin.x + size.x, origin.y + size.y);
|
||||||
|
|
||||||
|
ui.image(target);
|
||||||
|
|
||||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||||
drawList->AddRectFilled(origin, end, IM_COL32(24, 26, 32, 255));
|
|
||||||
|
|
||||||
if (showGrid) {
|
if (showGrid) {
|
||||||
constexpr float GRID_STEP = 40.0f;
|
constexpr float GRID_STEP = 40.0f;
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sophia/frame_info.hpp>
|
#include <sophia/frame_info.hpp>
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
#include <sophia/ui/panel.hpp>
|
#include <sophia/ui/panel.hpp>
|
||||||
|
#include <sophia/ui/ui.hpp>
|
||||||
|
|
||||||
class ViewportPanel : public sophia::Panel {
|
class ViewportPanel : public sophia::Panel {
|
||||||
public:
|
public:
|
||||||
ViewportPanel() : Panel{{.name = "Viewport"}} {}
|
ViewportPanel(sophia::UI& ui, sophia::RenderTargetHandle target)
|
||||||
|
: Panel{{.name = "Viewport",
|
||||||
|
.windowFlags = ImGuiWindowFlags_NoScrollbar |
|
||||||
|
ImGuiWindowFlags_NoScrollWithMouse}},
|
||||||
|
ui{ui},
|
||||||
|
target{target} {}
|
||||||
|
|
||||||
void onUpdate(const sophia::FrameInfo& frameInfo) override;
|
void onUpdate(const sophia::FrameInfo& frameInfo) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
sophia::UI& ui;
|
||||||
|
sophia::RenderTargetHandle target;
|
||||||
|
|
||||||
enum class GizmoMode { Move, Rotate, Scale };
|
enum class GizmoMode { Move, Rotate, Scale };
|
||||||
|
|
||||||
GizmoMode gizmoMode = GizmoMode::Move;
|
GizmoMode gizmoMode = GizmoMode::Move;
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ target_include_directories(${ENGINE_NAME} PUBLIC
|
|||||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(${ENGINE_NAME} PUBLIC
|
||||||
|
GLM_FORCE_RADIANS
|
||||||
|
GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(${ENGINE_NAME} PUBLIC
|
target_link_libraries(${ENGINE_NAME} PUBLIC
|
||||||
vulkan
|
vulkan
|
||||||
shaderc
|
shaderc
|
||||||
|
|||||||
@@ -3,21 +3,21 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sophia/asset_registry.hpp>
|
|
||||||
#include <sophia/camera_2d.hpp>
|
#include <sophia/camera_2d.hpp>
|
||||||
#include <sophia/canvas.hpp>
|
#include <sophia/canvas.hpp>
|
||||||
#include <sophia/console/console_command_registry.hpp>
|
#include <sophia/console/console_command_registry.hpp>
|
||||||
#include <sophia/frame_info.hpp>
|
#include <sophia/frame_info.hpp>
|
||||||
|
#include <sophia/math.hpp>
|
||||||
|
#include <sophia/resource_registry_ref.hpp>
|
||||||
|
#include <sophia/scene_registry.hpp>
|
||||||
|
#include <sophia/scene_view_registry.hpp>
|
||||||
#include <sophia/types.hpp>
|
#include <sophia/types.hpp>
|
||||||
#include <sophia/ui/debug_ui.hpp>
|
#include <sophia/ui/debug_ui.hpp>
|
||||||
#include <sophia/ui/panel_registry.hpp>
|
#include <sophia/ui/panel_registry.hpp>
|
||||||
|
#include <sophia/ui/ui.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define GLM_FORCE_RADIANS
|
|
||||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
class Application {
|
class Application {
|
||||||
@@ -44,8 +44,11 @@ class Application {
|
|||||||
virtual void onRender() {}
|
virtual void onRender() {}
|
||||||
virtual void onDestroy() {}
|
virtual void onDestroy() {}
|
||||||
|
|
||||||
AssetRegistry& assets() { return this->assetRegistry; }
|
ResourceRegistryRef resources();
|
||||||
PanelRegistry& panels() { return this->panelRegistry; }
|
PanelRegistry& panels() { return this->panelRegistry; }
|
||||||
|
SceneRegistry& scenes() { return this->sceneRegistry; }
|
||||||
|
SceneViewRegistry& views() { return this->sceneViewRegistry; }
|
||||||
|
UI& ui() { return this->_ui; }
|
||||||
|
|
||||||
DebugUI& debugUI() { return this->_debugUI; }
|
DebugUI& debugUI() { return this->_debugUI; }
|
||||||
|
|
||||||
@@ -66,8 +69,10 @@ class Application {
|
|||||||
class Impl;
|
class Impl;
|
||||||
std::unique_ptr<Impl> impl;
|
std::unique_ptr<Impl> impl;
|
||||||
|
|
||||||
AssetRegistry assetRegistry;
|
|
||||||
PanelRegistry panelRegistry;
|
PanelRegistry panelRegistry;
|
||||||
|
SceneRegistry sceneRegistry;
|
||||||
|
SceneViewRegistry sceneViewRegistry;
|
||||||
|
UI _ui;
|
||||||
|
|
||||||
DebugUI _debugUI;
|
DebugUI _debugUI;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <sophia/handles.hpp>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "sophia/canvas.hpp"
|
|
||||||
#include "sophia/types.hpp"
|
|
||||||
|
|
||||||
namespace sophia {
|
|
||||||
|
|
||||||
class Device;
|
|
||||||
class Mesh;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct AssetTraits;
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct AssetTraits<Mesh> {
|
|
||||||
using Handle = MeshHandle;
|
|
||||||
};
|
|
||||||
|
|
||||||
class AssetRegistry {
|
|
||||||
public:
|
|
||||||
~AssetRegistry();
|
|
||||||
|
|
||||||
AssetRegistry(const AssetRegistry&) = delete;
|
|
||||||
AssetRegistry& operator=(const AssetRegistry&) = delete;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
typename AssetTraits<T>::Handle load(const std::string& path);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TEMPORARY: Remove once load<Mesh> can read real model files from disk.
|
|
||||||
*/
|
|
||||||
MeshHandle loadMesh(std::vector<CanvasVertex> vertices,
|
|
||||||
std::vector<u32> indices);
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
void release(typename AssetTraits<T>::Handle handle);
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class Application;
|
|
||||||
|
|
||||||
explicit AssetRegistry(Device& device);
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
const T& get(typename AssetTraits<T>::Handle handle) const;
|
|
||||||
|
|
||||||
class Impl;
|
|
||||||
std::unique_ptr<Impl> impl;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
MeshHandle AssetRegistry::load<Mesh>(const std::string& path);
|
|
||||||
|
|
||||||
template <>
|
|
||||||
void AssetRegistry::release<Mesh>(MeshHandle handle);
|
|
||||||
|
|
||||||
template <>
|
|
||||||
const Mesh& AssetRegistry::get<Mesh>(MeshHandle handle) const;
|
|
||||||
|
|
||||||
} // namespace sophia
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define GLM_FORCE_RADIANS
|
|
||||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <sophia/math.hpp>
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,9 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sophia/frame_info.hpp>
|
#include <sophia/frame_info.hpp>
|
||||||
|
#include <sophia/math.hpp>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define GLM_FORCE_RADIANS
|
|
||||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
class Application;
|
class Application;
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sophia/asset_registry.hpp>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#define GLM_FORCE_RADIANS
|
|
||||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <iostream>
|
||||||
|
#include <ostream>
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
|
#include <sophia/math.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
struct TagComponent {
|
struct TagComponent {
|
||||||
std::string tag; // entt::hashed_string ?
|
std::string tag; // entt::hashed_string ?
|
||||||
|
uint32_t mask = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TransformComponent {
|
struct TransformComponent {
|
||||||
@@ -28,10 +27,56 @@ struct TransformComponent {
|
|||||||
glm::mat3 normalMatrix() const {
|
glm::mat3 normalMatrix() const {
|
||||||
return glm::mat3{glm::transpose(glm::inverse(mat4()))};
|
return glm::mat3{glm::transpose(glm::inverse(mat4()))};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
friend std::ostream& operator<<(std::ostream& os,
|
||||||
|
const TransformComponent& transform) {
|
||||||
|
os << "TransformComponent {\n"
|
||||||
|
<< " Position: (" << transform.position.x << ", "
|
||||||
|
<< transform.position.y << ", " << transform.position.z << ")\n"
|
||||||
|
<< " Rotation (w, x, y, z): (" << transform.rotation.w << ", "
|
||||||
|
<< transform.rotation.x << ", " << transform.rotation.y << ", "
|
||||||
|
<< transform.rotation.z << ")\n"
|
||||||
|
<< " Scale: (" << transform.scale.x << ", " << transform.scale.y << ", "
|
||||||
|
<< transform.scale.z << ")\n"
|
||||||
|
<< "}";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MeshComponent {
|
struct MeshComponent {
|
||||||
MeshHandle handle;
|
MeshHandle handle;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CameraComponent {
|
||||||
|
struct Orthographic {
|
||||||
|
float height = 10.f;
|
||||||
|
float nearPlane = -1.f;
|
||||||
|
float farPlane = 1.f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Perspective {
|
||||||
|
float fov = glm::radians(60.f);
|
||||||
|
float nearPlane = 0.1f;
|
||||||
|
float farPlane = 100.f;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Projection { Orthographic, Perspective };
|
||||||
|
|
||||||
|
Projection projection = Projection::Orthographic;
|
||||||
|
Orthographic ortho;
|
||||||
|
Perspective perspective;
|
||||||
|
|
||||||
|
glm::mat4 getProjection(float aspect) const {
|
||||||
|
if (projection == Projection::Perspective) {
|
||||||
|
return glm::perspective(perspective.fov, aspect, perspective.nearPlane,
|
||||||
|
perspective.farPlane);
|
||||||
|
}
|
||||||
|
|
||||||
|
float halfHeight = ortho.height / 2.f;
|
||||||
|
float halfWidth = halfHeight * aspect;
|
||||||
|
return glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight,
|
||||||
|
ortho.nearPlane, ortho.farPlane);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace sophia
|
} // namespace sophia
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <entt/entt.hpp>
|
||||||
|
#include <sophia/scene.hpp>
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
#include <sophia/util/assert.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class Entity {
|
||||||
|
public:
|
||||||
|
Entity(const Entity&) = default;
|
||||||
|
Entity& operator=(const Entity&) = default;
|
||||||
|
|
||||||
|
Entity() = default;
|
||||||
|
Entity(entt::entity handle, Scene* scene);
|
||||||
|
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
T& addComponent(Args&&... args) {
|
||||||
|
SOPHIA_ASSERT(!hasComponent<T>(), "Entity already has component!");
|
||||||
|
T& component =
|
||||||
|
scene->registry.emplace<T>(handle, std::forward<Args>(args)...);
|
||||||
|
// scene->OnComponentAdded<T>(*this, component);
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T& getComponent() {
|
||||||
|
SOPHIA_ASSERT(hasComponent<T>(), "Entity does not have component!");
|
||||||
|
return scene->registry.get<T>(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool hasComponent() {
|
||||||
|
return scene->registry.all_of<T>(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void removeComponent() {
|
||||||
|
SOPHIA_ASSERT(hasComponent<T>(), "Entity does not have component!");
|
||||||
|
scene->registry.remove<T>(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const { return handle != entt::null; }
|
||||||
|
operator entt::entity() const { return handle; }
|
||||||
|
operator u32() const { return (u32)handle; }
|
||||||
|
|
||||||
|
// UUID GetUUID() { return GetComponent<IDComponent>().ID; }
|
||||||
|
// const std::string& GetName() { return GetComponent<TagComponent>().Tag; }
|
||||||
|
|
||||||
|
bool operator==(const Entity& other) const {
|
||||||
|
return handle == other.handle && scene == other.scene;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const Entity& other) const { return !(*this == other); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
entt::entity handle{entt::null};
|
||||||
|
Scene* scene = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <sophia/camera_2d.hpp>
|
#include <sophia/camera_2d.hpp>
|
||||||
|
#include <sophia/math.hpp>
|
||||||
#include <sophia/types.hpp>
|
#include <sophia/types.hpp>
|
||||||
|
|
||||||
#define GLM_FORCE_RADIANS
|
|
||||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
struct FrameInfo {
|
struct FrameInfo {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
template <typename Tag>
|
template <typename T>
|
||||||
struct Handle {
|
struct Handle {
|
||||||
static constexpr u32 INVALID_INDEX = (1u << 20) - 1;
|
static constexpr u32 INVALID_INDEX = (1u << 20) - 1;
|
||||||
|
|
||||||
@@ -24,10 +24,25 @@ struct Handle {
|
|||||||
[[nodiscard]] auto operator<=>(const Handle&) const = default;
|
[[nodiscard]] auto operator<=>(const Handle&) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MeshTag {};
|
struct InvalidHandle {
|
||||||
struct PanelTag {};
|
template <typename T>
|
||||||
|
[[nodiscard]] constexpr operator Handle<T>() const {
|
||||||
|
return Handle<T>::invalid();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
using MeshHandle = Handle<MeshTag>;
|
inline constexpr InvalidHandle INVALID_HANDLE{};
|
||||||
using PanelHandle = Handle<PanelTag>;
|
|
||||||
|
class Mesh;
|
||||||
|
class Panel;
|
||||||
|
class RenderTarget;
|
||||||
|
class Scene;
|
||||||
|
struct SceneView;
|
||||||
|
|
||||||
|
using MeshHandle = Handle<Mesh>;
|
||||||
|
using PanelHandle = Handle<Panel>;
|
||||||
|
using RenderTargetHandle = Handle<RenderTarget>;
|
||||||
|
using SceneHandle = Handle<Scene>;
|
||||||
|
using SceneViewHandle = Handle<SceneView>;
|
||||||
|
|
||||||
} // namespace sophia
|
} // namespace sophia
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
using vec2 = glm::vec2;
|
||||||
|
using vec3 = glm::vec3;
|
||||||
|
using vec4 = glm::vec4;
|
||||||
|
using ivec2 = glm::ivec2;
|
||||||
|
using uvec2 = glm::uvec2;
|
||||||
|
using mat3 = glm::mat3;
|
||||||
|
using mat4 = glm::mat4;
|
||||||
|
using quat = glm::quat;
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sophia/canvas.hpp>
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
|
#include <sophia/math.hpp>
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class ResourceRegistry;
|
||||||
|
|
||||||
|
class ResourceRegistryRef {
|
||||||
|
public:
|
||||||
|
template <typename T>
|
||||||
|
Handle<T> load(const std::string& path);
|
||||||
|
|
||||||
|
template <typename T, typename... Args>
|
||||||
|
Handle<T> create(Args&&... args) {
|
||||||
|
return createResource(std::type_identity<T>{}, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void requestResize(RenderTargetHandle handle, u32 width, u32 height);
|
||||||
|
|
||||||
|
glm::vec2 getExtent(RenderTargetHandle handle) const;
|
||||||
|
|
||||||
|
bool isAlive(RenderTargetHandle handle) const;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void release(Handle<T> handle);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Application;
|
||||||
|
|
||||||
|
explicit ResourceRegistryRef(ResourceRegistry& registry)
|
||||||
|
: registry{®istry} {}
|
||||||
|
|
||||||
|
MeshHandle createResource(std::type_identity<Mesh>,
|
||||||
|
std::vector<CanvasVertex> vertices,
|
||||||
|
std::vector<u32> indices = {});
|
||||||
|
|
||||||
|
RenderTargetHandle createResource(std::type_identity<RenderTarget>,
|
||||||
|
u32 width = 1, u32 height = 1);
|
||||||
|
|
||||||
|
ResourceRegistry* registry;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
MeshHandle ResourceRegistryRef::load<Mesh>(const std::string& path);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void ResourceRegistryRef::release<Mesh>(MeshHandle handle);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void ResourceRegistryRef::release<RenderTarget>(RenderTargetHandle handle);
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <entt/entt.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class Entity;
|
||||||
|
class SceneRenderer; // !TEMP remove this
|
||||||
|
|
||||||
|
class Scene {
|
||||||
|
public:
|
||||||
|
Scene();
|
||||||
|
~Scene();
|
||||||
|
|
||||||
|
void createEntity(const std::string& name = {});
|
||||||
|
void destroyEntity(Entity entity);
|
||||||
|
|
||||||
|
// The camera a render request uses when it doesn't name one. Null until
|
||||||
|
// set.
|
||||||
|
entt::entity getActiveCamera() const { return activeCamera; }
|
||||||
|
void setActiveCamera(entt::entity camera) { activeCamera = camera; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Entity;
|
||||||
|
friend class SceneRenderer; // !TEMP remove this
|
||||||
|
|
||||||
|
entt::registry registry;
|
||||||
|
entt::entity activeCamera = entt::null;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
|
#include <sophia/scene.hpp>
|
||||||
|
#include <sophia/util/pool.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class SceneRegistry {
|
||||||
|
public:
|
||||||
|
SceneRegistry(const SceneRegistry&) = delete;
|
||||||
|
SceneRegistry& operator=(const SceneRegistry&) = delete;
|
||||||
|
|
||||||
|
SceneRegistry() = default;
|
||||||
|
~SceneRegistry() = default;
|
||||||
|
|
||||||
|
SceneHandle create() { return pool.create(); }
|
||||||
|
|
||||||
|
const Scene& get(SceneHandle handle) const { return pool.get(handle); }
|
||||||
|
Scene& get(SceneHandle handle) { return pool.get(handle); }
|
||||||
|
|
||||||
|
void release(SceneHandle handle) { pool.release(handle); }
|
||||||
|
bool isAlive(SceneHandle handle) const { return pool.isAlive(handle); }
|
||||||
|
|
||||||
|
u32 size() const { return pool.size(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Pool<Scene> pool{"Scene"};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sophia/entity.hpp>
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
|
#include <sophia/math.hpp>
|
||||||
|
#include <sophia/scene.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
enum class ClearFlags : u8 {
|
||||||
|
None = 0,
|
||||||
|
Color = 1 << 0,
|
||||||
|
Depth = 1 << 1,
|
||||||
|
Stencil = 1 << 2,
|
||||||
|
All = Color | Depth | Stencil
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SceneView {
|
||||||
|
SceneHandle scene = INVALID_HANDLE;
|
||||||
|
RenderTargetHandle target = INVALID_HANDLE;
|
||||||
|
Entity camera;
|
||||||
|
|
||||||
|
vec4 viewport{0.0f, 0.0f, 1.0f, 1.0f};
|
||||||
|
|
||||||
|
vec4 clearColor{0.f, 0.f, 0.f, 1.f};
|
||||||
|
ClearFlags clearFlags = ClearFlags::All;
|
||||||
|
|
||||||
|
u32 priority = 0; // Lower priority renders first
|
||||||
|
u32 layerMask = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
bool enabled = true; // Disabled views are kept but not rendered
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
|
#include <sophia/scene_view.hpp>
|
||||||
|
#include <sophia/util/pool.hpp>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every live SceneView. The application renders all enabled views each frame,
|
||||||
|
* in ascending priority order, before the UI.
|
||||||
|
*/
|
||||||
|
class SceneViewRegistry {
|
||||||
|
public:
|
||||||
|
SceneViewRegistry(const SceneViewRegistry&) = delete;
|
||||||
|
SceneViewRegistry& operator=(const SceneViewRegistry&) = delete;
|
||||||
|
|
||||||
|
SceneViewRegistry() = default;
|
||||||
|
~SceneViewRegistry() = default;
|
||||||
|
|
||||||
|
SceneViewHandle create(const SceneView& view = {}) {
|
||||||
|
isRenderQueueDirty = true;
|
||||||
|
return pool.create(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SceneView& get(SceneViewHandle handle) const {
|
||||||
|
return pool.get(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneView& get(SceneViewHandle handle) {
|
||||||
|
isRenderQueueDirty = true;
|
||||||
|
return pool.get(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void release(SceneViewHandle handle) {
|
||||||
|
isRenderQueueDirty = true;
|
||||||
|
pool.release(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAlive(SceneViewHandle handle) const { return pool.isAlive(handle); }
|
||||||
|
|
||||||
|
u32 size() const { return pool.size(); }
|
||||||
|
|
||||||
|
template <typename Fn>
|
||||||
|
void forEach(Fn&& fn) {
|
||||||
|
isRenderQueueDirty = true;
|
||||||
|
pool.forEach(std::forward<Fn>(fn));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Fn>
|
||||||
|
void forEach(Fn&& fn) const {
|
||||||
|
pool.forEach(std::forward<Fn>(fn));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<SceneViewHandle>& GetRenderQueue() const {
|
||||||
|
if (isRenderQueueDirty) {
|
||||||
|
rebuildRenderQueue();
|
||||||
|
}
|
||||||
|
return renderQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void rebuildRenderQueue() const {
|
||||||
|
renderQueue.clear();
|
||||||
|
pool.forEachWithHandle([&](SceneViewHandle handle, const SceneView& view) {
|
||||||
|
if (view.enabled) {
|
||||||
|
renderQueue.push_back(handle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::ranges::stable_sort(renderQueue, {}, [this](SceneViewHandle handle) {
|
||||||
|
return pool.get(handle).priority;
|
||||||
|
});
|
||||||
|
isRenderQueueDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pool<SceneView> pool{"SceneView"};
|
||||||
|
|
||||||
|
mutable std::vector<SceneViewHandle> renderQueue;
|
||||||
|
mutable bool isRenderQueueDirty = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -17,9 +17,9 @@ class DebugUI {
|
|||||||
void show();
|
void show();
|
||||||
void hide();
|
void hide();
|
||||||
|
|
||||||
void showPanelRegistry(bool show = true);
|
void showPanelRegistry(bool show = false);
|
||||||
void showFrameStats(bool show = true);
|
void showFrameStats(bool show = false);
|
||||||
void showConsole(bool show = true);
|
void showConsole(bool show = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PanelRegistry& registry;
|
PanelRegistry& registry;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <sophia/ui/dock_layout.hpp>
|
#include <sophia/ui/dock_layout.hpp>
|
||||||
#include <sophia/ui/panel.hpp>
|
#include <sophia/ui/panel.hpp>
|
||||||
#include <sophia/util/assert.hpp>
|
#include <sophia/util/assert.hpp>
|
||||||
|
#include <sophia/util/pool.hpp>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -21,17 +22,17 @@ class PanelRegistry {
|
|||||||
PanelRegistry() = default;
|
PanelRegistry() = default;
|
||||||
~PanelRegistry() = default;
|
~PanelRegistry() = default;
|
||||||
|
|
||||||
PanelHandle add(std::unique_ptr<Panel> panel);
|
PanelHandle add(std::unique_ptr<Panel> panel) {
|
||||||
|
return pool.add(std::move(panel));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, typename... Args>
|
template <typename T, typename... Args>
|
||||||
PanelHandle emplace(Args&&... args) {
|
PanelHandle emplace(Args&&... args) {
|
||||||
return add(std::make_unique<T>(std::forward<Args>(args)...));
|
return add(std::make_unique<T>(std::forward<Args>(args)...));
|
||||||
}
|
}
|
||||||
|
|
||||||
const Panel& get(PanelHandle handle) const;
|
const Panel& get(PanelHandle handle) const { return pool.get(handle); }
|
||||||
Panel& get(PanelHandle handle) {
|
Panel& get(PanelHandle handle) { return pool.get(handle); }
|
||||||
return const_cast<Panel&>(std::as_const(*this).get(handle));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
const T& get(PanelHandle handle) const {
|
const T& get(PanelHandle handle) const {
|
||||||
@@ -46,19 +47,17 @@ class PanelRegistry {
|
|||||||
return const_cast<T&>(std::as_const(*this).template get<T>(handle));
|
return const_cast<T&>(std::as_const(*this).template get<T>(handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(PanelHandle handle);
|
void remove(PanelHandle handle) { pool.release(handle); }
|
||||||
bool isValid(PanelHandle handle) const;
|
bool isValid(PanelHandle handle) const { return pool.isAlive(handle); }
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
const T* find() const {
|
const T* find() const {
|
||||||
for (const Slot& slot : slots) {
|
const T* result = nullptr;
|
||||||
if (slot.panel) {
|
pool.findIf([&result](const Panel& panel) {
|
||||||
if (const T* panel = dynamic_cast<const T*>(slot.panel.get())) {
|
result = dynamic_cast<const T*>(&panel);
|
||||||
return panel;
|
return result != nullptr;
|
||||||
}
|
});
|
||||||
}
|
return result;
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -67,29 +66,22 @@ class PanelRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Panel* find(std::string_view name) const {
|
const Panel* find(std::string_view name) const {
|
||||||
for (const Slot& slot : slots) {
|
return pool.findIf(
|
||||||
if (slot.panel && slot.panel->getName() == name) {
|
[name](const Panel& panel) { return panel.getName() == name; });
|
||||||
return slot.panel.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Panel* find(std::string_view name) {
|
Panel* find(std::string_view name) {
|
||||||
return const_cast<Panel*>(std::as_const(*this).find(name));
|
return const_cast<Panel*>(std::as_const(*this).find(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 size() const;
|
u32 size() const { return pool.size(); }
|
||||||
u32 visibleCount() const;
|
u32 visibleCount() const;
|
||||||
|
|
||||||
std::vector<std::string_view> names() const {
|
std::vector<std::string_view> names() const {
|
||||||
std::vector<std::string_view> result;
|
std::vector<std::string_view> result;
|
||||||
result.reserve(slots.size());
|
result.reserve(pool.size());
|
||||||
for (const Slot& slot : slots) {
|
pool.forEach(
|
||||||
if (slot.panel) {
|
[&result](const Panel& panel) { result.push_back(panel.getName()); });
|
||||||
result.push_back(slot.panel->getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,13 +93,7 @@ class PanelRegistry {
|
|||||||
const DockLayout& getDefaultLayout() const { return defaultLayout; }
|
const DockLayout& getDefaultLayout() const { return defaultLayout; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Slot {
|
Pool<Panel> pool{"Panel"};
|
||||||
std::unique_ptr<Panel> panel;
|
|
||||||
u32 generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Slot> slots;
|
|
||||||
std::vector<u32> freeList;
|
|
||||||
DockLayout defaultLayout;
|
DockLayout defaultLayout;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
|
#include <sophia/resource_registry_ref.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class ImGuiRenderer;
|
||||||
|
|
||||||
|
class UI {
|
||||||
|
public:
|
||||||
|
UI(const UI&) = delete;
|
||||||
|
UI& operator=(const UI&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the target filling the space left in the current window, and keeps
|
||||||
|
* the target sized to match. The target catches up on the next frame, so the
|
||||||
|
* image may be a frame behind while the window is being resized.
|
||||||
|
*/
|
||||||
|
void image(RenderTargetHandle handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Texture to hand to ImGui::Image() that shows the target's contents.
|
||||||
|
* Changes when the target is resized, so fetch it each frame rather than
|
||||||
|
* caching it.
|
||||||
|
*/
|
||||||
|
ImTextureID getTextureId(RenderTargetHandle handle);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Application;
|
||||||
|
|
||||||
|
explicit UI(ResourceRegistryRef resources) : resources{resources} {}
|
||||||
|
|
||||||
|
ResourceRegistryRef resources;
|
||||||
|
ImGuiRenderer* renderer = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
#include <sophia/util/assert.hpp>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Pool {
|
||||||
|
public:
|
||||||
|
explicit Pool(const char* name) : name{name} {}
|
||||||
|
|
||||||
|
Pool(const Pool&) = delete;
|
||||||
|
Pool& operator=(const Pool&) = delete;
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
Handle<T> create(Args&&... args) {
|
||||||
|
return add(std::make_unique<T>(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<T> add(std::unique_ptr<T> item) {
|
||||||
|
if (!freeList.empty()) {
|
||||||
|
u32 index = freeList.back();
|
||||||
|
freeList.pop_back();
|
||||||
|
slots[index].item = std::move(item);
|
||||||
|
return Handle<T>{index, slots[index].generation};
|
||||||
|
}
|
||||||
|
|
||||||
|
slots.push_back(Slot{std::move(item), 0});
|
||||||
|
return Handle<T>{static_cast<u32>(slots.size() - 1), 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
T& get(Handle<T> handle) {
|
||||||
|
return const_cast<T&>(std::as_const(*this).get(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
const T& get(Handle<T> handle) const { return *validate(handle).item; }
|
||||||
|
|
||||||
|
void release(Handle<T> handle) {
|
||||||
|
validate(handle);
|
||||||
|
|
||||||
|
Slot& slot = slots[handle.index];
|
||||||
|
slot.item.reset();
|
||||||
|
slot.generation = (slot.generation + 1) & GENERATION_MASK;
|
||||||
|
freeList.push_back(handle.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAlive(Handle<T> handle) const {
|
||||||
|
return handle.isValid() && handle.index < slots.size() &&
|
||||||
|
slots[handle.index].generation == handle.generation &&
|
||||||
|
slots[handle.index].item != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 size() const { return static_cast<u32>(slots.size() - freeList.size()); }
|
||||||
|
|
||||||
|
template <typename Fn>
|
||||||
|
void forEach(Fn&& fn) {
|
||||||
|
for (Slot& slot : slots) {
|
||||||
|
if (slot.item) {
|
||||||
|
fn(*slot.item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Fn>
|
||||||
|
void forEach(Fn&& fn) const {
|
||||||
|
for (const Slot& slot : slots) {
|
||||||
|
if (slot.item) {
|
||||||
|
fn(*slot.item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calls `fn(handle, item)` for every live item. */
|
||||||
|
template <typename Fn>
|
||||||
|
void forEachWithHandle(Fn&& fn) const {
|
||||||
|
for (u32 index = 0; index < slots.size(); index++) {
|
||||||
|
const Slot& slot = slots[index];
|
||||||
|
if (slot.item) {
|
||||||
|
fn(Handle<T>{index, slot.generation}, *slot.item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Predicate>
|
||||||
|
T* findIf(Predicate&& predicate) {
|
||||||
|
return const_cast<T*>(
|
||||||
|
std::as_const(*this).findIf(std::forward<Predicate>(predicate)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Predicate>
|
||||||
|
const T* findIf(Predicate&& predicate) const {
|
||||||
|
for (const Slot& slot : slots) {
|
||||||
|
if (slot.item && predicate(*slot.item)) {
|
||||||
|
return slot.item.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr u32 GENERATION_MASK = (1u << 12) - 1;
|
||||||
|
|
||||||
|
struct Slot {
|
||||||
|
std::unique_ptr<T> item;
|
||||||
|
u32 generation = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Slot& validate(Handle<T> handle) const {
|
||||||
|
SOPHIA_ASSERT(handle.isValid(), "invalid handle:", name);
|
||||||
|
SOPHIA_ASSERT(handle.index < slots.size(), name,
|
||||||
|
"handle index out of range:", handle.index);
|
||||||
|
|
||||||
|
const Slot& slot = slots[handle.index];
|
||||||
|
SOPHIA_ASSERT(slot.generation == handle.generation, "stale", name,
|
||||||
|
"handle (generation mismatch):", handle.index);
|
||||||
|
SOPHIA_ASSERT(slot.item != nullptr, name,
|
||||||
|
"handle refers to a released item:", handle.index);
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* name;
|
||||||
|
std::vector<Slot> slots;
|
||||||
|
std::vector<u32> freeList;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
+107
-60
@@ -1,6 +1,11 @@
|
|||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <map>
|
||||||
#include <sophia/application.hpp>
|
#include <sophia/application.hpp>
|
||||||
#include <sophia/util/logger.hpp>
|
#include <sophia/util/logger.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -8,20 +13,18 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
#define GLM_FORCE_RADIANS
|
#include "rendering/imgui_renderer.hpp"
|
||||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
#include "rendering/model_render_system.hpp"
|
||||||
#include <imgui.h>
|
#include "rendering/render_context.hpp"
|
||||||
|
#include "rendering/render_graph.hpp"
|
||||||
#include <glm/glm.hpp>
|
#include "rendering/scene_renderer.hpp"
|
||||||
|
#include "resources/mesh.hpp"
|
||||||
#include "buffer.hpp"
|
#include "resources/render_target.hpp"
|
||||||
#include "device.hpp"
|
#include "resources/resource_registry.hpp"
|
||||||
#include "imgui_renderer.hpp"
|
#include "vulkan/buffer.hpp"
|
||||||
#include "mesh.hpp"
|
#include "vulkan/device.hpp"
|
||||||
#include "pipelines/graphics_pipeline.hpp"
|
#include "vulkan/pipelines/graphics_pipeline.hpp"
|
||||||
#include "render_context.hpp"
|
#include "vulkan/shader.hpp"
|
||||||
#include "shader.hpp"
|
|
||||||
#include "systems/model_render_system.hpp"
|
|
||||||
#include "window.hpp"
|
#include "window.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
@@ -41,6 +44,12 @@ struct PushConstants {
|
|||||||
|
|
||||||
std::vector<std::string> commandLineArgs;
|
std::vector<std::string> commandLineArgs;
|
||||||
|
|
||||||
|
constexpr ClearColor CLEAR_COLOR = {0.01f, 0.01f, 0.02f, 1.0f};
|
||||||
|
|
||||||
|
bool hasFlag(ClearFlags flags, ClearFlags flag) {
|
||||||
|
return (static_cast<u8>(flags) & static_cast<u8>(flag)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
class Application::Impl {
|
class Application::Impl {
|
||||||
@@ -50,12 +59,17 @@ class Application::Impl {
|
|||||||
config{config},
|
config{config},
|
||||||
window{config.width, config.height, config.name},
|
window{config.width, config.height, config.name},
|
||||||
device{&window},
|
device{&window},
|
||||||
renderContext{window, device} {}
|
renderContext{window, device},
|
||||||
|
resourceRegistry{device, renderContext.getImageFormat()} {}
|
||||||
|
|
||||||
void run() {
|
void run() {
|
||||||
ModelRenderSystem modelRenderSystem{device, renderContext};
|
ModelRenderSystem modelRenderSystem{device, renderContext};
|
||||||
|
SceneRenderer sceneRenderer{this->application.sceneRegistry,
|
||||||
|
this->resourceRegistry};
|
||||||
ImGuiRenderer imguiRenderer{window, device, renderContext.getImageFormat(),
|
ImGuiRenderer imguiRenderer{window, device, renderContext.getImageFormat(),
|
||||||
renderContext.getImageCount()};
|
renderContext.getImageCount(),
|
||||||
|
this->resourceRegistry};
|
||||||
|
this->application._ui.renderer = &imguiRenderer;
|
||||||
|
|
||||||
this->application.consoleCommandRegistry.registerCommand(
|
this->application.consoleCommandRegistry.registerCommand(
|
||||||
"reset_layout", "Restore the default panel layout",
|
"reset_layout", "Restore the default panel layout",
|
||||||
@@ -65,8 +79,17 @@ class Application::Impl {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this->application.onCreate();
|
this->application.onCreate();
|
||||||
imguiRenderer.setDefaultLayout(
|
|
||||||
this->application.panelRegistry.getDefaultLayout());
|
auto& meshPool = this->resourceRegistry.meshPool;
|
||||||
|
auto& renderTargetPool = this->resourceRegistry.renderTargetPool;
|
||||||
|
|
||||||
|
PanelRegistry& panelRegistry = this->application.panelRegistry;
|
||||||
|
SceneRegistry& sceneRegistry = this->application.sceneRegistry;
|
||||||
|
SceneViewRegistry& sceneViewRegistry = this->application.sceneViewRegistry;
|
||||||
|
const SceneViewRegistry& sceneViewRegistryConst =
|
||||||
|
this->application.sceneViewRegistry;
|
||||||
|
|
||||||
|
imguiRenderer.setDefaultLayout(panelRegistry.getDefaultLayout());
|
||||||
|
|
||||||
auto startTime = std::chrono::high_resolution_clock::now();
|
auto startTime = std::chrono::high_resolution_clock::now();
|
||||||
auto currentTime = startTime;
|
auto currentTime = startTime;
|
||||||
@@ -83,6 +106,10 @@ class Application::Impl {
|
|||||||
double elapsedTime =
|
double elapsedTime =
|
||||||
std::chrono::duration<double>(currentTime - startTime).count();
|
std::chrono::duration<double>(currentTime - startTime).count();
|
||||||
|
|
||||||
|
// Resizing waits for the GPU, so it has to happen before any of this
|
||||||
|
// frame's work (or ImGui's references to the targets) is recorded.
|
||||||
|
renderTargetPool.applyPendingResizes();
|
||||||
|
|
||||||
vk::Extent2D extent = renderContext.getExtent();
|
vk::Extent2D extent = renderContext.getExtent();
|
||||||
FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime,
|
FrameInfo frameInfo{frameIndex, elapsedTime, deltaTime,
|
||||||
glm::vec2(static_cast<float>(extent.width),
|
glm::vec2(static_cast<float>(extent.width),
|
||||||
@@ -102,41 +129,9 @@ class Application::Impl {
|
|||||||
|
|
||||||
imguiRenderer.beginFrame();
|
imguiRenderer.beginFrame();
|
||||||
|
|
||||||
this->application.panelRegistry.update(frameInfo);
|
panelRegistry.update(frameInfo);
|
||||||
this->application.onRender();
|
this->application.onRender();
|
||||||
|
|
||||||
renderContext.transitionCurrentImage(
|
|
||||||
commandBuffer, vk::ImageLayout::eColorAttachmentOptimal);
|
|
||||||
|
|
||||||
vk::ClearValue clearValue{};
|
|
||||||
clearValue.color =
|
|
||||||
vk::ClearColorValue{std::array<float, 4>{0.01f, 0.01f, 0.02f, 1.0f}};
|
|
||||||
|
|
||||||
vk::RenderingAttachmentInfo colorAttachment{};
|
|
||||||
colorAttachment.imageView = renderContext.getCurrentImageView();
|
|
||||||
colorAttachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
|
||||||
colorAttachment.loadOp = vk::AttachmentLoadOp::eClear;
|
|
||||||
colorAttachment.storeOp = vk::AttachmentStoreOp::eStore;
|
|
||||||
colorAttachment.clearValue = clearValue;
|
|
||||||
|
|
||||||
vk::RenderingInfo renderingInfo{};
|
|
||||||
renderingInfo.renderArea = vk::Rect2D{{0, 0}, extent};
|
|
||||||
renderingInfo.layerCount = 1;
|
|
||||||
renderingInfo.colorAttachmentCount = 1;
|
|
||||||
renderingInfo.pColorAttachments = &colorAttachment;
|
|
||||||
|
|
||||||
commandBuffer.beginRendering(renderingInfo);
|
|
||||||
|
|
||||||
vk::Viewport viewport{0.0f,
|
|
||||||
0.0f,
|
|
||||||
static_cast<float>(extent.width),
|
|
||||||
static_cast<float>(extent.height),
|
|
||||||
0.0f,
|
|
||||||
1.0f};
|
|
||||||
vk::Rect2D scissor{{0, 0}, extent};
|
|
||||||
commandBuffer.setViewport(0, viewport);
|
|
||||||
commandBuffer.setScissor(0, scissor);
|
|
||||||
|
|
||||||
std::vector<ModelRenderSystem::MeshInstance> instances;
|
std::vector<ModelRenderSystem::MeshInstance> instances;
|
||||||
instances.reserve(this->meshes.size() + this->drawCalls.size());
|
instances.reserve(this->meshes.size() + this->drawCalls.size());
|
||||||
for (const std::unique_ptr<Mesh>& mesh : this->meshes) {
|
for (const std::unique_ptr<Mesh>& mesh : this->meshes) {
|
||||||
@@ -144,16 +139,61 @@ class Application::Impl {
|
|||||||
}
|
}
|
||||||
instances.insert(instances.end(), this->drawCalls.begin(),
|
instances.insert(instances.end(), this->drawCalls.begin(),
|
||||||
this->drawCalls.end());
|
this->drawCalls.end());
|
||||||
|
|
||||||
modelRenderSystem.render(commandBuffer, instances, frameInfo);
|
|
||||||
this->drawCalls.clear();
|
this->drawCalls.clear();
|
||||||
|
|
||||||
|
RenderGraph graph;
|
||||||
|
ImageRef backbuffer =
|
||||||
|
graph.importImage(renderContext.getCurrentImage(), Access::Present);
|
||||||
|
|
||||||
|
std::map<RenderTargetHandle, ImageRef> viewTargets;
|
||||||
|
for (SceneViewHandle handle : sceneViewRegistryConst.GetRenderQueue()) {
|
||||||
|
const SceneView& view = sceneViewRegistryConst.get(handle);
|
||||||
|
if (!sceneRegistry.isAlive(view.scene) ||
|
||||||
|
!renderTargetPool.isAlive(view.target)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [it, inserted] = viewTargets.try_emplace(view.target);
|
||||||
|
if (inserted) {
|
||||||
|
it->second =
|
||||||
|
graph.importImage(renderTargetPool.get(view.target).getImage(),
|
||||||
|
Access::SampledRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
const vec4& color = view.clearColor;
|
||||||
|
graph.addPass(
|
||||||
|
"scene view",
|
||||||
|
[&](RenderGraph::PassBuilder& pass) {
|
||||||
|
pass.writeColor(it->second,
|
||||||
|
hasFlag(view.clearFlags, ClearFlags::Color)
|
||||||
|
? vk::AttachmentLoadOp::eClear
|
||||||
|
: vk::AttachmentLoadOp::eLoad,
|
||||||
|
{color.r, color.g, color.b, color.a});
|
||||||
|
},
|
||||||
|
[&sceneRenderer, &sceneViewRegistry, handle](vk::CommandBuffer) {
|
||||||
|
const SceneView& view = sceneViewRegistry.get(handle);
|
||||||
|
if (view.camera) {
|
||||||
|
sceneRenderer.render(view);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
graph.addPass(
|
||||||
|
"main",
|
||||||
|
[&](RenderGraph::PassBuilder& pass) {
|
||||||
|
pass.writeColor(backbuffer, vk::AttachmentLoadOp::eClear,
|
||||||
|
CLEAR_COLOR);
|
||||||
|
for (const auto& [handle, target] : viewTargets) {
|
||||||
|
pass.sample(target);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&](vk::CommandBuffer commandBuffer) {
|
||||||
|
modelRenderSystem.render(commandBuffer, instances, frameInfo);
|
||||||
|
|
||||||
imguiRenderer.endFrame(commandBuffer);
|
imguiRenderer.endFrame(commandBuffer);
|
||||||
|
});
|
||||||
|
|
||||||
commandBuffer.endRendering();
|
graph.execute(commandBuffer);
|
||||||
|
|
||||||
renderContext.transitionCurrentImage(commandBuffer,
|
|
||||||
vk::ImageLayout::ePresentSrcKHR);
|
|
||||||
|
|
||||||
renderContext.endFrame();
|
renderContext.endFrame();
|
||||||
|
|
||||||
@@ -164,6 +204,8 @@ class Application::Impl {
|
|||||||
|
|
||||||
device.waitIdle();
|
device.waitIdle();
|
||||||
|
|
||||||
|
this->application._ui.renderer = nullptr;
|
||||||
|
|
||||||
auto endTime = std::chrono::high_resolution_clock::now();
|
auto endTime = std::chrono::high_resolution_clock::now();
|
||||||
double totalRuntime =
|
double totalRuntime =
|
||||||
std::chrono::duration<double>(endTime - startTime).count();
|
std::chrono::duration<double>(endTime - startTime).count();
|
||||||
@@ -193,7 +235,7 @@ class Application::Impl {
|
|||||||
this->drawCalls.push_back({&mesh, transform});
|
this->drawCalls.push_back({&mesh, transform});
|
||||||
}
|
}
|
||||||
|
|
||||||
Device& getDevice() { return device; }
|
ResourceRegistry& getResourceRegistry() { return resourceRegistry; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Application& application;
|
Application& application;
|
||||||
@@ -201,6 +243,7 @@ class Application::Impl {
|
|||||||
Window window;
|
Window window;
|
||||||
Device device;
|
Device device;
|
||||||
RenderContext renderContext;
|
RenderContext renderContext;
|
||||||
|
ResourceRegistry resourceRegistry;
|
||||||
|
|
||||||
std::vector<std::unique_ptr<Mesh>> meshes;
|
std::vector<std::unique_ptr<Mesh>> meshes;
|
||||||
std::vector<ModelRenderSystem::MeshInstance> drawCalls;
|
std::vector<ModelRenderSystem::MeshInstance> drawCalls;
|
||||||
@@ -214,13 +257,17 @@ Application::Application(const Config& config)
|
|||||||
: impl{std::make_unique<Impl>(*this, config)},
|
: impl{std::make_unique<Impl>(*this, config)},
|
||||||
canvas{this},
|
canvas{this},
|
||||||
args{std::move(commandLineArgs)},
|
args{std::move(commandLineArgs)},
|
||||||
assetRegistry{impl->getDevice()},
|
_ui{ResourceRegistryRef{impl->getResourceRegistry()}},
|
||||||
_debugUI{this->panelRegistry, this->consoleCommandRegistry} {}
|
_debugUI{this->panelRegistry, this->consoleCommandRegistry} {}
|
||||||
|
|
||||||
Application::~Application() = default;
|
Application::~Application() = default;
|
||||||
|
|
||||||
void Application::run() { impl->run(); }
|
void Application::run() { impl->run(); }
|
||||||
|
|
||||||
|
ResourceRegistryRef Application::resources() {
|
||||||
|
return ResourceRegistryRef{impl->getResourceRegistry()};
|
||||||
|
}
|
||||||
|
|
||||||
void Application::pushMesh(std::vector<CanvasVertex> vertices,
|
void Application::pushMesh(std::vector<CanvasVertex> vertices,
|
||||||
std::vector<u32> indices) {
|
std::vector<u32> indices) {
|
||||||
impl->pushMesh(std::move(vertices), std::move(indices));
|
impl->pushMesh(std::move(vertices), std::move(indices));
|
||||||
@@ -229,7 +276,7 @@ void Application::pushMesh(std::vector<CanvasVertex> vertices,
|
|||||||
void Application::clearMeshes() { impl->clearMeshes(); }
|
void Application::clearMeshes() { impl->clearMeshes(); }
|
||||||
|
|
||||||
void Application::drawMesh(MeshHandle handle, const glm::mat4& transform) {
|
void Application::drawMesh(MeshHandle handle, const glm::mat4& transform) {
|
||||||
impl->drawMesh(assetRegistry.get<Mesh>(handle), transform);
|
impl->drawMesh(impl->getResourceRegistry().meshPool.get(handle), transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sophia
|
} // namespace sophia
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
#include <sophia/asset_registry.hpp>
|
|
||||||
#include <sophia/util/assert.hpp>
|
|
||||||
|
|
||||||
#include "mesh.hpp"
|
|
||||||
#include "pools/mesh_pool.hpp"
|
|
||||||
|
|
||||||
namespace sophia {
|
|
||||||
|
|
||||||
class AssetRegistry::Impl {
|
|
||||||
public:
|
|
||||||
explicit Impl(Device& device) : meshPool{device} {}
|
|
||||||
|
|
||||||
MeshPool meshPool;
|
|
||||||
// TexturePool texturePool;
|
|
||||||
// MaterialPool materialPool;
|
|
||||||
};
|
|
||||||
|
|
||||||
AssetRegistry::AssetRegistry(Device& device)
|
|
||||||
: impl{std::make_unique<Impl>(device)} {}
|
|
||||||
|
|
||||||
AssetRegistry::~AssetRegistry() = default;
|
|
||||||
|
|
||||||
template <>
|
|
||||||
MeshHandle AssetRegistry::load<Mesh>(const std::string& path) {
|
|
||||||
(void)path;
|
|
||||||
SOPHIA_ASSERT(false,
|
|
||||||
"Mesh::load(path) is not implemented yet - no model file "
|
|
||||||
"format is supported; build one via Mesh::Builder for now");
|
|
||||||
return MeshHandle{};
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
typename AssetTraits<T>::Handle load(const std::string&) {
|
|
||||||
static_assert(sizeof(T) == 0,
|
|
||||||
"load<T> is not implemented for this asset type");
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
void AssetRegistry::release<Mesh>(MeshHandle handle) {
|
|
||||||
impl->meshPool.release(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
const Mesh& AssetRegistry::get<Mesh>(MeshHandle handle) const {
|
|
||||||
return impl->meshPool.get(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
MeshHandle AssetRegistry::loadMesh(std::vector<CanvasVertex> vertices,
|
|
||||||
std::vector<u32> indices) {
|
|
||||||
Mesh::Builder builder{};
|
|
||||||
builder.vertices.reserve(vertices.size());
|
|
||||||
for (const CanvasVertex& vertex : vertices) {
|
|
||||||
builder.vertices.push_back({vertex.position, vertex.color});
|
|
||||||
}
|
|
||||||
builder.indices = std::move(indices);
|
|
||||||
|
|
||||||
return impl->meshPool.create(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sophia
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#include "assets/pools/mesh_pool.hpp"
|
|
||||||
|
|
||||||
#include <sophia/util/assert.hpp>
|
|
||||||
|
|
||||||
namespace sophia {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr u32 GENERATION_MASK = (1u << 12) - 1;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
MeshPool::MeshPool(Device& device) : device{device} {}
|
|
||||||
|
|
||||||
MeshPool::~MeshPool() = default;
|
|
||||||
|
|
||||||
MeshHandle MeshPool::create(const Mesh::Builder& builder) {
|
|
||||||
auto mesh = std::make_unique<Mesh>(device, builder);
|
|
||||||
|
|
||||||
if (!freeList.empty()) {
|
|
||||||
u32 index = freeList.back();
|
|
||||||
freeList.pop_back();
|
|
||||||
slots[index].mesh = std::move(mesh);
|
|
||||||
return MeshHandle{index, slots[index].generation};
|
|
||||||
}
|
|
||||||
|
|
||||||
slots.push_back(Slot{std::move(mesh), 0});
|
|
||||||
return MeshHandle{static_cast<u32>(slots.size() - 1), 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
const Mesh& MeshPool::get(MeshHandle handle) const {
|
|
||||||
SOPHIA_ASSERT(handle.isValid(), "invalid MeshHandle");
|
|
||||||
SOPHIA_ASSERT(handle.index < slots.size(),
|
|
||||||
"MeshHandle index out of range:", handle.index);
|
|
||||||
|
|
||||||
const Slot& slot = slots[handle.index];
|
|
||||||
SOPHIA_ASSERT(slot.generation == handle.generation,
|
|
||||||
"stale MeshHandle (generation mismatch):", handle.index);
|
|
||||||
SOPHIA_ASSERT(slot.mesh != nullptr,
|
|
||||||
"MeshHandle refers to a released mesh:", handle.index);
|
|
||||||
return *slot.mesh;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MeshPool::release(MeshHandle handle) {
|
|
||||||
SOPHIA_ASSERT(handle.isValid(), "invalid MeshHandle");
|
|
||||||
SOPHIA_ASSERT(handle.index < slots.size(),
|
|
||||||
"MeshHandle index out of range:", handle.index);
|
|
||||||
|
|
||||||
Slot& slot = slots[handle.index];
|
|
||||||
SOPHIA_ASSERT(slot.generation == handle.generation,
|
|
||||||
"stale MeshHandle (generation mismatch):", handle.index);
|
|
||||||
SOPHIA_ASSERT(slot.mesh != nullptr,
|
|
||||||
"MeshHandle already released:", handle.index);
|
|
||||||
|
|
||||||
slot.mesh.reset();
|
|
||||||
slot.generation = (slot.generation + 1) & GENERATION_MASK;
|
|
||||||
freeList.push_back(handle.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace sophia
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "device.hpp"
|
|
||||||
#include "mesh.hpp"
|
|
||||||
|
|
||||||
namespace sophia {
|
|
||||||
|
|
||||||
class MeshPool {
|
|
||||||
public:
|
|
||||||
explicit MeshPool(Device& device);
|
|
||||||
~MeshPool();
|
|
||||||
|
|
||||||
MeshPool(const MeshPool&) = delete;
|
|
||||||
MeshPool& operator=(const MeshPool&) = delete;
|
|
||||||
|
|
||||||
MeshHandle create(const Mesh::Builder& builder);
|
|
||||||
const Mesh& get(MeshHandle handle) const;
|
|
||||||
void release(MeshHandle handle);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Slot {
|
|
||||||
std::unique_ptr<Mesh> mesh;
|
|
||||||
u32 generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
Device& device;
|
|
||||||
|
|
||||||
std::vector<Slot> slots;
|
|
||||||
std::vector<u32> freeList;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sophia
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
#include "imgui_renderer.hpp"
|
#include "rendering/imgui_renderer.hpp"
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <imgui_impl_glfw.h>
|
#include <imgui_impl_glfw.h>
|
||||||
#include <imgui_impl_vulkan.h>
|
#include <imgui_impl_vulkan.h>
|
||||||
#include <imgui_internal.h>
|
#include <imgui_internal.h>
|
||||||
|
|
||||||
|
#include "resources/render_target.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -20,7 +22,8 @@ void dockAll(const std::vector<std::string>& windows, ImGuiID nodeId) {
|
|||||||
|
|
||||||
ImGuiRenderer::ImGuiRenderer(Window& window, Device& device,
|
ImGuiRenderer::ImGuiRenderer(Window& window, Device& device,
|
||||||
vk::Format colorAttachmentFormat,
|
vk::Format colorAttachmentFormat,
|
||||||
u32 minImageCount) {
|
u32 minImageCount, ResourceRegistry& resources)
|
||||||
|
: resources{resources} {
|
||||||
// 1. Construct ImGui context
|
// 1. Construct ImGui context
|
||||||
IMGUI_CHECKVERSION();
|
IMGUI_CHECKVERSION();
|
||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
@@ -67,12 +70,17 @@ ImGuiRenderer::ImGuiRenderer(Window& window, Device& device,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGuiRenderer::~ImGuiRenderer() {
|
ImGuiRenderer::~ImGuiRenderer() {
|
||||||
|
for (const auto& [index, texture] : textures) {
|
||||||
|
ImGui_ImplVulkan_RemoveTexture(texture.descriptorSet);
|
||||||
|
}
|
||||||
ImGui_ImplVulkan_Shutdown();
|
ImGui_ImplVulkan_Shutdown();
|
||||||
ImGui_ImplGlfw_Shutdown();
|
ImGui_ImplGlfw_Shutdown();
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiRenderer::beginFrame() {
|
void ImGuiRenderer::beginFrame() {
|
||||||
|
releaseStaleTextures();
|
||||||
|
|
||||||
ImGui_ImplVulkan_NewFrame();
|
ImGui_ImplVulkan_NewFrame();
|
||||||
ImGui_ImplGlfw_NewFrame();
|
ImGui_ImplGlfw_NewFrame();
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
@@ -82,6 +90,46 @@ void ImGuiRenderer::beginFrame() {
|
|||||||
applyLayoutIfNeeded(dockspaceId, DOCKSPACE_FLAGS);
|
applyLayoutIfNeeded(dockspaceId, DOCKSPACE_FLAGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImTextureID ImGuiRenderer::getTextureId(RenderTargetHandle handle) {
|
||||||
|
vk::ImageView view =
|
||||||
|
resources.renderTargetPool.get(handle).getImage().getView();
|
||||||
|
|
||||||
|
auto it = textures.find(handle.index);
|
||||||
|
if (it != textures.end()) {
|
||||||
|
if (it->second.generation == handle.generation && it->second.view == view) {
|
||||||
|
return (ImTextureID)it->second.descriptorSet;
|
||||||
|
}
|
||||||
|
ImGui_ImplVulkan_RemoveTexture(it->second.descriptorSet);
|
||||||
|
textures.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImGui owns the sampler; it only needs the view and the layout the image is
|
||||||
|
// left in once the scene pass has finished.
|
||||||
|
VkDescriptorSet descriptorSet = ImGui_ImplVulkan_AddTexture(
|
||||||
|
static_cast<VkImageView>(view), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||||
|
textures[handle.index] = {handle.generation, view, descriptorSet};
|
||||||
|
return (ImTextureID)descriptorSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resizing and releasing a target both wait for the GPU before destroying its
|
||||||
|
// image, so nothing in flight can still be using a stale texture.
|
||||||
|
void ImGuiRenderer::releaseStaleTextures() {
|
||||||
|
for (auto it = textures.begin(); it != textures.end();) {
|
||||||
|
RenderTargetHandle handle{it->first, it->second.generation};
|
||||||
|
RenderTargetPool& renderTargetPool = resources.renderTargetPool;
|
||||||
|
|
||||||
|
bool stale =
|
||||||
|
!renderTargetPool.isAlive(handle) ||
|
||||||
|
renderTargetPool.get(handle).getImage().getView() != it->second.view;
|
||||||
|
if (stale) {
|
||||||
|
ImGui_ImplVulkan_RemoveTexture(it->second.descriptorSet);
|
||||||
|
it = textures.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ImGuiRenderer::setDefaultLayout(DockLayout layout) {
|
void ImGuiRenderer::setDefaultLayout(DockLayout layout) {
|
||||||
defaultLayout = std::move(layout);
|
defaultLayout = std::move(layout);
|
||||||
seedIfUnsaved = true;
|
seedIfUnsaved = true;
|
||||||
@@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
#include <sophia/types.hpp>
|
#include <sophia/types.hpp>
|
||||||
#include <sophia/ui/dock_layout.hpp>
|
#include <sophia/ui/dock_layout.hpp>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "device.hpp"
|
#include "resources/resource_registry.hpp"
|
||||||
|
#include "vulkan/device.hpp"
|
||||||
#include "window.hpp"
|
#include "window.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
@@ -16,17 +19,32 @@ class ImGuiRenderer {
|
|||||||
ImGuiRenderer& operator=(const ImGuiRenderer&) = delete;
|
ImGuiRenderer& operator=(const ImGuiRenderer&) = delete;
|
||||||
|
|
||||||
ImGuiRenderer(Window& window, Device& device,
|
ImGuiRenderer(Window& window, Device& device,
|
||||||
vk::Format colorAttachmentFormat, u32 minImageCount);
|
vk::Format colorAttachmentFormat, u32 minImageCount,
|
||||||
|
ResourceRegistry& resources);
|
||||||
~ImGuiRenderer();
|
~ImGuiRenderer();
|
||||||
|
|
||||||
void beginFrame();
|
void beginFrame();
|
||||||
void endFrame(vk::CommandBuffer commandBuffer);
|
void endFrame(vk::CommandBuffer commandBuffer);
|
||||||
|
|
||||||
|
ImTextureID getTextureId(RenderTargetHandle handle);
|
||||||
|
|
||||||
void setDefaultLayout(DockLayout layout);
|
void setDefaultLayout(DockLayout layout);
|
||||||
void resetLayout();
|
void resetLayout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void applyLayoutIfNeeded(ImGuiID dockspaceId, ImGuiDockNodeFlags flags);
|
void applyLayoutIfNeeded(ImGuiID dockspaceId, ImGuiDockNodeFlags flags);
|
||||||
|
void releaseStaleTextures();
|
||||||
|
|
||||||
|
struct Texture {
|
||||||
|
u32 generation;
|
||||||
|
vk::ImageView view;
|
||||||
|
VkDescriptorSet descriptorSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
ResourceRegistry& resources;
|
||||||
|
|
||||||
|
// Keyed by RenderTargetHandle::index.
|
||||||
|
std::unordered_map<u32, Texture> textures;
|
||||||
|
|
||||||
DockLayout defaultLayout;
|
DockLayout defaultLayout;
|
||||||
bool seedIfUnsaved = false;
|
bool seedIfUnsaved = false;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
#include "model_render_system.hpp"
|
#include "rendering/model_render_system.hpp"
|
||||||
|
|
||||||
#include <sophia/util/assert.hpp>
|
#include <sophia/util/assert.hpp>
|
||||||
|
|
||||||
+4
-4
@@ -4,10 +4,10 @@
|
|||||||
#include <sophia/frame_info.hpp>
|
#include <sophia/frame_info.hpp>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
#include "device.hpp"
|
#include "rendering/render_context.hpp"
|
||||||
#include "mesh.hpp"
|
#include "resources/mesh.hpp"
|
||||||
#include "pipelines/graphics_pipeline.hpp"
|
#include "vulkan/device.hpp"
|
||||||
#include "render_context.hpp"
|
#include "vulkan/pipelines/graphics_pipeline.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
#include "render_context.hpp"
|
#include "rendering/render_context.hpp"
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <sophia/util/assert.hpp>
|
#include <sophia/util/assert.hpp>
|
||||||
#include <sophia/util/logger.hpp>
|
#include <sophia/util/logger.hpp>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "vulkan/access.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
RenderContext::RenderContext(Window& window, Device& device)
|
RenderContext::RenderContext(Window& window, Device& device)
|
||||||
@@ -80,8 +82,7 @@ void RenderContext::endFrame() {
|
|||||||
vk::Semaphore waitSemaphore = frame.imageAvailable.get();
|
vk::Semaphore waitSemaphore = frame.imageAvailable.get();
|
||||||
vk::Semaphore signalSemaphore =
|
vk::Semaphore signalSemaphore =
|
||||||
this->renderFinishedSemaphores.at(this->currentImageIndex).get();
|
this->renderFinishedSemaphores.at(this->currentImageIndex).get();
|
||||||
vk::PipelineStageFlags waitStage =
|
vk::PipelineStageFlags waitStage = SWAPCHAIN_ACQUIRE_STAGE;
|
||||||
vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
|
||||||
vk::CommandBuffer commandBuffer = frame.commandBuffer;
|
vk::CommandBuffer commandBuffer = frame.commandBuffer;
|
||||||
|
|
||||||
vk::SubmitInfo submitInfo{};
|
vk::SubmitInfo submitInfo{};
|
||||||
@@ -6,8 +6,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
#include "device.hpp"
|
#include "vulkan/device.hpp"
|
||||||
#include "swapchain.hpp"
|
#include "vulkan/swapchain.hpp"
|
||||||
#include "window.hpp"
|
#include "window.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
@@ -31,14 +31,8 @@ class RenderContext {
|
|||||||
vk::CommandBuffer beginFrame();
|
vk::CommandBuffer beginFrame();
|
||||||
void endFrame();
|
void endFrame();
|
||||||
|
|
||||||
void transitionCurrentImage(vk::CommandBuffer commandBuffer,
|
Image& getCurrentImage() const {
|
||||||
vk::ImageLayout newLayout) const {
|
return this->swapchain->getImage(static_cast<int>(this->currentImageIndex));
|
||||||
this->swapchain->getImage(static_cast<int>(this->currentImageIndex))
|
|
||||||
.transitionLayout(commandBuffer, newLayout);
|
|
||||||
}
|
|
||||||
vk::ImageView getCurrentImageView() const {
|
|
||||||
return this->swapchain->getImageView(
|
|
||||||
static_cast<int>(this->currentImageIndex));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::Extent2D getExtent() const { return this->swapchain->getExtent(); }
|
vk::Extent2D getExtent() const { return this->swapchain->getExtent(); }
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
#include "rendering/render_graph.hpp"
|
||||||
|
|
||||||
|
#include <sophia/util/assert.hpp>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "vulkan/debug_label.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
ImageRef RenderGraph::importImage(Image& image, Access finalAccess) {
|
||||||
|
ImageRef ref{static_cast<u32>(this->images.size())};
|
||||||
|
this->images.push_back({&image, finalAccess, image.getAccessState()});
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::addPass(std::string name, const SetupFn& setup,
|
||||||
|
ExecuteFn execute) {
|
||||||
|
Pass& pass = this->passes.emplace_back();
|
||||||
|
pass.name = std::move(name);
|
||||||
|
pass.execute = std::move(execute);
|
||||||
|
|
||||||
|
PassBuilder builder{*this, pass};
|
||||||
|
setup(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::execute(vk::CommandBuffer commandBuffer) {
|
||||||
|
for (Pass& pass : this->passes) {
|
||||||
|
this->recordPass(commandBuffer, pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingBarriers pending;
|
||||||
|
for (u32 i = 0; i < this->images.size(); i++) {
|
||||||
|
this->transitionTo(ImageRef{i}, getAccessState(this->images[i].finalAccess),
|
||||||
|
pending);
|
||||||
|
}
|
||||||
|
this->flush(commandBuffer, pending);
|
||||||
|
|
||||||
|
for (ImportedImage& imported : this->images) {
|
||||||
|
imported.image->recordLayout(imported.state.layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->passes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::transitionTo(ImageRef ref, const AccessState& next,
|
||||||
|
PendingBarriers& pending) {
|
||||||
|
SOPHIA_ASSERT(ref.isValid() && ref.index < this->images.size(),
|
||||||
|
"ImageRef doesn't belong to this graph");
|
||||||
|
|
||||||
|
ImportedImage& imported = this->images[ref.index];
|
||||||
|
|
||||||
|
if (needsBarrier(imported.state, next)) {
|
||||||
|
pending.barriers.push_back(makeImageBarrier(
|
||||||
|
imported.image->get(), imported.image->getSubresourceRange(),
|
||||||
|
imported.state, next));
|
||||||
|
pending.srcStages |= imported.state.stage;
|
||||||
|
pending.dstStages |= next.stage;
|
||||||
|
imported.state = next;
|
||||||
|
} else {
|
||||||
|
// Same-layout read after read. Nothing to record, but a later write has
|
||||||
|
// to wait on this reader too.
|
||||||
|
imported.state.stage |= next.stage;
|
||||||
|
imported.state.access |= next.access;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::flush(vk::CommandBuffer commandBuffer,
|
||||||
|
PendingBarriers& pending) {
|
||||||
|
if (pending.barriers.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
commandBuffer.pipelineBarrier(pending.srcStages, pending.dstStages, {},
|
||||||
|
nullptr, nullptr, pending.barriers);
|
||||||
|
pending = PendingBarriers{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::recordPass(vk::CommandBuffer commandBuffer, Pass& pass) {
|
||||||
|
beginDebugLabel(commandBuffer, pass.name.c_str());
|
||||||
|
|
||||||
|
PendingBarriers pending;
|
||||||
|
for (const ImageUse& use : pass.uses) {
|
||||||
|
this->transitionTo(use.image, getAccessState(use.access), pending);
|
||||||
|
}
|
||||||
|
this->flush(commandBuffer, pending);
|
||||||
|
|
||||||
|
if (pass.colorAttachments.empty()) {
|
||||||
|
pass.execute(commandBuffer);
|
||||||
|
} else {
|
||||||
|
this->recordRendering(commandBuffer, pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
endDebugLabel(commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::recordRendering(vk::CommandBuffer commandBuffer, Pass& pass) {
|
||||||
|
std::vector<vk::RenderingAttachmentInfo> attachments;
|
||||||
|
attachments.reserve(pass.colorAttachments.size());
|
||||||
|
|
||||||
|
vk::Extent3D imageExtent =
|
||||||
|
this->images[pass.colorAttachments.front().image.index]
|
||||||
|
.image->getExtent();
|
||||||
|
vk::Extent2D extent{imageExtent.width, imageExtent.height};
|
||||||
|
|
||||||
|
for (const ColorAttachment& color : pass.colorAttachments) {
|
||||||
|
const Image& image = *this->images[color.image.index].image;
|
||||||
|
SOPHIA_ASSERT(image.getExtent().width == extent.width &&
|
||||||
|
image.getExtent().height == extent.height,
|
||||||
|
"Pass", pass.name,
|
||||||
|
"has color attachments of different sizes");
|
||||||
|
|
||||||
|
vk::RenderingAttachmentInfo attachment{};
|
||||||
|
attachment.imageView = image.getView();
|
||||||
|
attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
|
||||||
|
attachment.loadOp = color.loadOp;
|
||||||
|
attachment.storeOp = vk::AttachmentStoreOp::eStore;
|
||||||
|
attachment.clearValue.color = vk::ClearColorValue{color.clearColor};
|
||||||
|
attachments.push_back(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::RenderingInfo renderingInfo{};
|
||||||
|
renderingInfo.renderArea = vk::Rect2D{{0, 0}, extent};
|
||||||
|
renderingInfo.layerCount = 1;
|
||||||
|
renderingInfo.colorAttachmentCount = static_cast<u32>(attachments.size());
|
||||||
|
renderingInfo.pColorAttachments = attachments.data();
|
||||||
|
|
||||||
|
commandBuffer.beginRendering(renderingInfo);
|
||||||
|
|
||||||
|
vk::Viewport viewport{0.0f,
|
||||||
|
0.0f,
|
||||||
|
static_cast<float>(extent.width),
|
||||||
|
static_cast<float>(extent.height),
|
||||||
|
0.0f,
|
||||||
|
1.0f};
|
||||||
|
vk::Rect2D scissor{{0, 0}, extent};
|
||||||
|
commandBuffer.setViewport(0, viewport);
|
||||||
|
commandBuffer.setScissor(0, scissor);
|
||||||
|
|
||||||
|
pass.execute(commandBuffer);
|
||||||
|
|
||||||
|
commandBuffer.endRendering();
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeColor(
|
||||||
|
ImageRef image, vk::AttachmentLoadOp loadOp, ClearColor clearColor) {
|
||||||
|
this->use(image, Access::ColorWrite);
|
||||||
|
this->pass.colorAttachments.push_back({image, loadOp, clearColor});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraph::PassBuilder& RenderGraph::PassBuilder::sample(ImageRef image) {
|
||||||
|
return this->use(image, Access::SampledRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraph::PassBuilder& RenderGraph::PassBuilder::use(ImageRef image,
|
||||||
|
Access access) {
|
||||||
|
SOPHIA_ASSERT(image.isValid() && image.index < this->graph.images.size(),
|
||||||
|
"ImageRef doesn't belong to this graph");
|
||||||
|
for (const ImageUse& existing : this->pass.uses) {
|
||||||
|
SOPHIA_ASSERT(existing.image.index != image.index, "Pass", this->pass.name,
|
||||||
|
"uses the same image twice");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->pass.uses.push_back({image, access});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <functional>
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include "vulkan/access.hpp"
|
||||||
|
#include "vulkan/image.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Names an image within one RenderGraph. Only valid for the graph that
|
||||||
|
* returned it.
|
||||||
|
*/
|
||||||
|
struct ImageRef {
|
||||||
|
u32 index = INVALID_INDEX;
|
||||||
|
|
||||||
|
static constexpr u32 INVALID_INDEX = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
bool isValid() const { return this->index != INVALID_INDEX; }
|
||||||
|
};
|
||||||
|
|
||||||
|
using ClearColor = std::array<float, 4>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A frame's passes and the images they touch, recorded into a single command
|
||||||
|
* buffer. Passes run in the order they were added; the graph's job is to
|
||||||
|
* work out the barriers and layout transitions between them from what each
|
||||||
|
* pass says it does to each image, and to begin/end rendering for passes that
|
||||||
|
* write color attachments.
|
||||||
|
*
|
||||||
|
* Built fresh every frame:
|
||||||
|
*
|
||||||
|
* RenderGraph graph;
|
||||||
|
* ImageRef backbuffer = graph.importImage(swapchainImage, Access::Present);
|
||||||
|
* graph.addPass(
|
||||||
|
* "main", [&](PassBuilder& pass) { pass.writeColor(backbuffer); },
|
||||||
|
* [&](vk::CommandBuffer cb) { draw(cb); });
|
||||||
|
* graph.execute(commandBuffer);
|
||||||
|
*
|
||||||
|
* Imported images start in whatever layout Image reports and are left in
|
||||||
|
* their final access when execute() returns, with the Image's tracked layout
|
||||||
|
* updated to match.
|
||||||
|
*/
|
||||||
|
class RenderGraph {
|
||||||
|
public:
|
||||||
|
class PassBuilder;
|
||||||
|
|
||||||
|
using SetupFn = std::function<void(PassBuilder&)>;
|
||||||
|
using ExecuteFn = std::function<void(vk::CommandBuffer)>;
|
||||||
|
|
||||||
|
RenderGraph() = default;
|
||||||
|
|
||||||
|
RenderGraph(const RenderGraph&) = delete;
|
||||||
|
RenderGraph& operator=(const RenderGraph&) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brings an image the graph doesn't own into it. It must outlive
|
||||||
|
* execute(). After the last pass it is transitioned to `finalAccess`.
|
||||||
|
*/
|
||||||
|
ImageRef importImage(Image& image, Access finalAccess);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a pass. `setup` runs immediately and declares what the pass does to
|
||||||
|
* images; `execute` runs later, from execute(), with the barriers already
|
||||||
|
* recorded and (if the pass writes color) rendering begun. `name` labels
|
||||||
|
* the pass's commands in debugging tools such as RenderDoc.
|
||||||
|
*/
|
||||||
|
void addPass(std::string name, const SetupFn& setup, ExecuteFn execute);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records every pass, and the barriers between them, into `commandBuffer`.
|
||||||
|
*/
|
||||||
|
void execute(vk::CommandBuffer commandBuffer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ImageUse {
|
||||||
|
ImageRef image;
|
||||||
|
Access access;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ColorAttachment {
|
||||||
|
ImageRef image;
|
||||||
|
vk::AttachmentLoadOp loadOp;
|
||||||
|
ClearColor clearColor;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Pass {
|
||||||
|
std::string name;
|
||||||
|
std::vector<ImageUse> uses;
|
||||||
|
std::vector<ColorAttachment> colorAttachments;
|
||||||
|
ExecuteFn execute;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ImportedImage {
|
||||||
|
Image* image;
|
||||||
|
Access finalAccess;
|
||||||
|
// What the image was last left in, as of the barriers recorded so far.
|
||||||
|
AccessState state;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PendingBarriers {
|
||||||
|
std::vector<vk::ImageMemoryBarrier> barriers;
|
||||||
|
vk::PipelineStageFlags srcStages;
|
||||||
|
vk::PipelineStageFlags dstStages;
|
||||||
|
};
|
||||||
|
|
||||||
|
void transitionTo(ImageRef ref, const AccessState& next,
|
||||||
|
PendingBarriers& pending);
|
||||||
|
void flush(vk::CommandBuffer commandBuffer, PendingBarriers& pending);
|
||||||
|
void recordPass(vk::CommandBuffer commandBuffer, Pass& pass);
|
||||||
|
void recordRendering(vk::CommandBuffer commandBuffer, Pass& pass);
|
||||||
|
|
||||||
|
std::vector<ImportedImage> images;
|
||||||
|
std::vector<Pass> passes;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RenderGraph::PassBuilder {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Renders into `image` as a color attachment. Passes with one or more of
|
||||||
|
* these get rendering begun (and viewport/scissor set to the attachment's
|
||||||
|
* extent) before their execute callback runs.
|
||||||
|
*/
|
||||||
|
PassBuilder& writeColor(
|
||||||
|
ImageRef image,
|
||||||
|
vk::AttachmentLoadOp loadOp = vk::AttachmentLoadOp::eClear,
|
||||||
|
ClearColor clearColor = {0.f, 0.f, 0.f, 1.f});
|
||||||
|
|
||||||
|
/** Samples `image` in a shader. */
|
||||||
|
PassBuilder& sample(ImageRef image);
|
||||||
|
|
||||||
|
/** Any other use, e.g. storage or transfer access. */
|
||||||
|
PassBuilder& use(ImageRef image, Access access);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class RenderGraph;
|
||||||
|
|
||||||
|
PassBuilder(RenderGraph& graph, Pass& pass) : graph{graph}, pass{pass} {}
|
||||||
|
|
||||||
|
RenderGraph& graph;
|
||||||
|
Pass& pass;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#include "rendering/scene_renderer.hpp"
|
||||||
|
|
||||||
|
#include <sophia/components.hpp>
|
||||||
|
#include <sophia/util/assert.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct PushConstantData {
|
||||||
|
glm::mat4 transform{1.f};
|
||||||
|
glm::vec4 color{1.f};
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SceneRenderer::SceneRenderer(const SceneRegistry& sceneRegistry,
|
||||||
|
ResourceRegistry& resources)
|
||||||
|
: sceneRegistry{sceneRegistry}, resources{resources} {}
|
||||||
|
|
||||||
|
SceneRenderer::~SceneRenderer() {}
|
||||||
|
|
||||||
|
void SceneRenderer::render(SceneView sceneView) {
|
||||||
|
auto& handle = sceneView.scene;
|
||||||
|
auto& scene = this->sceneRegistry.get(handle);
|
||||||
|
|
||||||
|
auto& camera = sceneView.camera;
|
||||||
|
SOPHIA_ASSERT(camera.hasComponent<CameraComponent>(),
|
||||||
|
"camera does not have camera component!");
|
||||||
|
|
||||||
|
auto& registry = scene.registry;
|
||||||
|
|
||||||
|
auto view = registry.view<MeshComponent, TransformComponent>();
|
||||||
|
|
||||||
|
for (auto entity : view) {
|
||||||
|
auto& transform = view.get<TransformComponent>(entity);
|
||||||
|
log::info(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all mesh
|
||||||
|
// For each mesh, get all entities(ES) that have a transform and that mesh
|
||||||
|
// For e in ES: add e's transform component to a list
|
||||||
|
|
||||||
|
// We now have
|
||||||
|
/*
|
||||||
|
M = mesh
|
||||||
|
T = transform
|
||||||
|
|
||||||
|
Work Batch
|
||||||
|
|- M0
|
||||||
|
| |- T0
|
||||||
|
| \- T2
|
||||||
|
|
|
||||||
|
|- M1
|
||||||
|
| |- T1
|
||||||
|
| \- T5
|
||||||
|
|
|
||||||
|
\- M2
|
||||||
|
|- T3
|
||||||
|
\- T4
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
for batch in Work Batch:
|
||||||
|
batch.mesh.bind()
|
||||||
|
|
||||||
|
for transform in batch.transforms:
|
||||||
|
commandBuffer.pushConstants(transform)
|
||||||
|
batch.mesh.draw(commandBuffer)
|
||||||
|
|
||||||
|
NOTE: Use instanced rendering?
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sophia/scene_registry.hpp>
|
||||||
|
#include <sophia/scene_view.hpp>
|
||||||
|
|
||||||
|
#include "resources/resource_registry.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* !TEMP to be decoupled once scene batching and rendering impl
|
||||||
|
*/
|
||||||
|
class SceneRenderer {
|
||||||
|
public:
|
||||||
|
SceneRenderer(const SceneRenderer&) = delete;
|
||||||
|
SceneRenderer& operator=(const SceneRenderer&) = delete;
|
||||||
|
|
||||||
|
SceneRenderer(const SceneRegistry& sceneRegistry,
|
||||||
|
ResourceRegistry& resources);
|
||||||
|
~SceneRenderer();
|
||||||
|
|
||||||
|
void render(SceneView sceneView);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const SceneRegistry& sceneRegistry;
|
||||||
|
ResourceRegistry& resources;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "mesh.hpp"
|
#include "resources/mesh.hpp"
|
||||||
|
|
||||||
#include <sophia/util/assert.hpp>
|
#include <sophia/util/assert.hpp>
|
||||||
#include <sophia/util/logger.hpp>
|
#include <sophia/util/logger.hpp>
|
||||||
@@ -1,23 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sophia/asset_registry.hpp>
|
#include <sophia/handles.hpp>
|
||||||
|
#include <sophia/math.hpp>
|
||||||
#include <sophia/types.hpp>
|
#include <sophia/types.hpp>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define GLM_FORCE_RADIANS
|
#include "vulkan/buffer.hpp"
|
||||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
#include "vulkan/device.hpp"
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
|
||||||
#include "buffer.hpp"
|
|
||||||
#include "device.hpp"
|
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
class Mesh {
|
class Mesh {
|
||||||
public:
|
public:
|
||||||
using HandleType = MeshHandle;
|
|
||||||
|
|
||||||
struct Vertex {
|
struct Vertex {
|
||||||
glm::vec2 position{};
|
glm::vec2 position{};
|
||||||
glm::vec3 color{};
|
glm::vec3 color{};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#include "resources/pools/render_target_pool.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
RenderTargetPool::RenderTargetPool(Device& device, vk::Format format)
|
||||||
|
: device{device}, format{format} {}
|
||||||
|
|
||||||
|
RenderTargetHandle RenderTargetPool::create(u32 width, u32 height) {
|
||||||
|
return pool.create(device, format, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTargetPool::release(RenderTargetHandle handle) {
|
||||||
|
SOPHIA_ASSERT(pool.isAlive(handle), "invalid RenderTargetHandle");
|
||||||
|
device.waitIdle();
|
||||||
|
pool.release(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTargetPool::applyPendingResizes() {
|
||||||
|
pool.forEach([](RenderTarget& target) { target.applyPendingResize(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
|
#include <sophia/util/pool.hpp>
|
||||||
|
|
||||||
|
#include "resources/render_target.hpp"
|
||||||
|
#include "vulkan/device.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class RenderTargetPool {
|
||||||
|
public:
|
||||||
|
RenderTargetPool(Device& device, vk::Format format);
|
||||||
|
|
||||||
|
RenderTargetPool(const RenderTargetPool&) = delete;
|
||||||
|
RenderTargetPool& operator=(const RenderTargetPool&) = delete;
|
||||||
|
|
||||||
|
RenderTargetHandle create(u32 width, u32 height);
|
||||||
|
RenderTarget& get(RenderTargetHandle handle) { return pool.get(handle); }
|
||||||
|
const RenderTarget& get(RenderTargetHandle handle) const {
|
||||||
|
return pool.get(handle);
|
||||||
|
}
|
||||||
|
void release(RenderTargetHandle handle);
|
||||||
|
|
||||||
|
bool isAlive(RenderTargetHandle handle) const { return pool.isAlive(handle); }
|
||||||
|
|
||||||
|
void applyPendingResizes();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& device;
|
||||||
|
vk::Format format;
|
||||||
|
|
||||||
|
Pool<RenderTarget> pool{"RenderTarget"};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#include "resources/render_target.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
RenderTarget::RenderTarget(Device& device, vk::Format format, u32 width,
|
||||||
|
u32 height)
|
||||||
|
: device{device}, format{format} {
|
||||||
|
create(std::max(width, 1u), std::max(height, 1u));
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTarget::~RenderTarget() { destroy(); }
|
||||||
|
|
||||||
|
vk::Extent2D RenderTarget::getExtent() const {
|
||||||
|
vk::Extent3D extent = this->image->getExtent();
|
||||||
|
return {extent.width, extent.height};
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTarget::requestResize(u32 width, u32 height) {
|
||||||
|
this->pendingExtent = vk::Extent2D{std::max(width, 1u), std::max(height, 1u)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTarget::applyPendingResize() {
|
||||||
|
if (!this->pendingExtent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Extent2D extent = *this->pendingExtent;
|
||||||
|
this->pendingExtent.reset();
|
||||||
|
if (extent == getExtent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->device.waitIdle();
|
||||||
|
|
||||||
|
destroy();
|
||||||
|
create(extent.width, extent.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTarget::create(u32 width, u32 height) {
|
||||||
|
this->image = Image::Builder(this->device)
|
||||||
|
.setExtent(width, height)
|
||||||
|
.setFormat(this->format)
|
||||||
|
.asColorAttachment()
|
||||||
|
.withView()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderTarget::destroy() { this->image.reset(); }
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include "vulkan/device.hpp"
|
||||||
|
#include "vulkan/image.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A place rendering can go. Currently always backed by an offscreen color
|
||||||
|
* image.
|
||||||
|
*
|
||||||
|
* Owned by the ResourceRegistry and addressed through a RenderTargetHandle.
|
||||||
|
*/
|
||||||
|
class RenderTarget {
|
||||||
|
public:
|
||||||
|
RenderTarget(Device& device, vk::Format format, u32 width, u32 height);
|
||||||
|
~RenderTarget();
|
||||||
|
|
||||||
|
RenderTarget(const RenderTarget&) = delete;
|
||||||
|
RenderTarget& operator=(const RenderTarget&) = delete;
|
||||||
|
|
||||||
|
Image& getImage() const { return *this->image; }
|
||||||
|
|
||||||
|
vk::Extent2D getExtent() const;
|
||||||
|
|
||||||
|
void requestResize(u32 width, u32 height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a pending resize. Waits for the GPU, so it must run before any of
|
||||||
|
* the frame's work (or any UI reference to the image) is recorded.
|
||||||
|
*/
|
||||||
|
void applyPendingResize();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void create(u32 width, u32 height);
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
Device& device;
|
||||||
|
vk::Format format;
|
||||||
|
|
||||||
|
std::unique_ptr<Image> image;
|
||||||
|
|
||||||
|
std::optional<vk::Extent2D> pendingExtent;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sophia/handles.hpp>
|
||||||
|
#include <sophia/util/pool.hpp>
|
||||||
|
|
||||||
|
#include "resources/mesh.hpp"
|
||||||
|
#include "resources/pools/render_target_pool.hpp"
|
||||||
|
#include "vulkan/device.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class ResourceRegistry {
|
||||||
|
public:
|
||||||
|
ResourceRegistry(const ResourceRegistry&) = delete;
|
||||||
|
ResourceRegistry& operator=(const ResourceRegistry&) = delete;
|
||||||
|
|
||||||
|
ResourceRegistry(Device& device, vk::Format renderTargetFormat)
|
||||||
|
: device{device}, renderTargetPool{device, renderTargetFormat} {}
|
||||||
|
|
||||||
|
Device& device;
|
||||||
|
Pool<Mesh> meshPool{"Mesh"};
|
||||||
|
RenderTargetPool renderTargetPool;
|
||||||
|
// TexturePool textures;
|
||||||
|
// MaterialPool materials;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#include <sophia/resource_registry_ref.hpp>
|
||||||
|
#include <sophia/util/assert.hpp>
|
||||||
|
|
||||||
|
#include "resources/resource_registry.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
MeshHandle ResourceRegistryRef::load<Mesh>(const std::string& path) {
|
||||||
|
(void)path;
|
||||||
|
SOPHIA_ASSERT(false,
|
||||||
|
"Mesh::load(path) is not implemented yet - no model file "
|
||||||
|
"format is supported; build one via Mesh::Builder for now");
|
||||||
|
return MeshHandle{};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void ResourceRegistryRef::release<Mesh>(MeshHandle handle) {
|
||||||
|
registry->meshPool.release(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void ResourceRegistryRef::release<RenderTarget>(RenderTargetHandle handle) {
|
||||||
|
registry->renderTargetPool.release(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshHandle ResourceRegistryRef::createResource(
|
||||||
|
std::type_identity<Mesh>, std::vector<CanvasVertex> vertices,
|
||||||
|
std::vector<u32> indices) {
|
||||||
|
Mesh::Builder builder{};
|
||||||
|
builder.vertices.reserve(vertices.size());
|
||||||
|
for (const CanvasVertex& vertex : vertices) {
|
||||||
|
builder.vertices.push_back({vertex.position, vertex.color});
|
||||||
|
}
|
||||||
|
builder.indices = std::move(indices);
|
||||||
|
|
||||||
|
return registry->meshPool.create(registry->device, builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderTargetHandle ResourceRegistryRef::createResource(
|
||||||
|
std::type_identity<RenderTarget>, u32 width, u32 height) {
|
||||||
|
return registry->renderTargetPool.create(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResourceRegistryRef::requestResize(RenderTargetHandle handle, u32 width,
|
||||||
|
u32 height) {
|
||||||
|
registry->renderTargetPool.get(handle).requestResize(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec2 ResourceRegistryRef::getExtent(RenderTargetHandle handle) const {
|
||||||
|
vk::Extent2D extent = registry->renderTargetPool.get(handle).getExtent();
|
||||||
|
return {static_cast<float>(extent.width), static_cast<float>(extent.height)};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResourceRegistryRef::isAlive(RenderTargetHandle handle) const {
|
||||||
|
return registry->renderTargetPool.isAlive(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <entt/entt.hpp>
|
|
||||||
|
|
||||||
namespace sophia {
|
|
||||||
|
|
||||||
class Scene {
|
|
||||||
public:
|
|
||||||
Scene();
|
|
||||||
~Scene();
|
|
||||||
|
|
||||||
private:
|
|
||||||
entt::registry registry;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace sophia
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#include <sophia/entity.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
Entity::Entity(entt::entity handle, Scene* scene)
|
||||||
|
: handle(handle), scene(scene) {}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "scene.hpp"
|
|
||||||
|
|
||||||
#include <sophia/components.hpp>
|
#include <sophia/components.hpp>
|
||||||
|
#include <sophia/scene.hpp>
|
||||||
#include <sophia/util/logger.hpp>
|
#include <sophia/util/logger.hpp>
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace sophia::shaders {
|
||||||
|
|
||||||
|
inline constexpr std::array<uint32_t, 355> fullscreen_vert_spv = {
|
||||||
|
0x07230203, 0x00010000, 0x000d000b, 0x00000032,
|
||||||
|
0x00000000, 0x00020011, 0x00000001, 0x0006000b,
|
||||||
|
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
|
||||||
|
0x00000000, 0x0003000e, 0x00000000, 0x00000001,
|
||||||
|
0x0008000f, 0x00000000, 0x00000004, 0x6e69616d,
|
||||||
|
0x00000000, 0x00000018, 0x00000022, 0x0000002c,
|
||||||
|
0x00030003, 0x00000002, 0x000001c2, 0x000a0004,
|
||||||
|
0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70,
|
||||||
|
0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365,
|
||||||
|
0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f,
|
||||||
|
0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572,
|
||||||
|
0x00657669, 0x00040005, 0x00000004, 0x6e69616d,
|
||||||
|
0x00000000, 0x00050005, 0x0000000c, 0x69736f70,
|
||||||
|
0x6e6f6974, 0x00000073, 0x00030005, 0x00000015,
|
||||||
|
0x00736f70, 0x00060005, 0x00000018, 0x565f6c67,
|
||||||
|
0x65747265, 0x646e4978, 0x00007865, 0x00060005,
|
||||||
|
0x00000020, 0x505f6c67, 0x65567265, 0x78657472,
|
||||||
|
0x00000000, 0x00060006, 0x00000020, 0x00000000,
|
||||||
|
0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006,
|
||||||
|
0x00000020, 0x00000001, 0x505f6c67, 0x746e696f,
|
||||||
|
0x657a6953, 0x00000000, 0x00070006, 0x00000020,
|
||||||
|
0x00000002, 0x435f6c67, 0x4470696c, 0x61747369,
|
||||||
|
0x0065636e, 0x00070006, 0x00000020, 0x00000003,
|
||||||
|
0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e,
|
||||||
|
0x00030005, 0x00000022, 0x00000000, 0x00030005,
|
||||||
|
0x0000002c, 0x00007675, 0x00040047, 0x00000018,
|
||||||
|
0x0000000b, 0x0000002a, 0x00030047, 0x00000020,
|
||||||
|
0x00000002, 0x00050048, 0x00000020, 0x00000000,
|
||||||
|
0x0000000b, 0x00000000, 0x00050048, 0x00000020,
|
||||||
|
0x00000001, 0x0000000b, 0x00000001, 0x00050048,
|
||||||
|
0x00000020, 0x00000002, 0x0000000b, 0x00000003,
|
||||||
|
0x00050048, 0x00000020, 0x00000003, 0x0000000b,
|
||||||
|
0x00000004, 0x00040047, 0x0000002c, 0x0000001e,
|
||||||
|
0x00000000, 0x00020013, 0x00000002, 0x00030021,
|
||||||
|
0x00000003, 0x00000002, 0x00030016, 0x00000006,
|
||||||
|
0x00000020, 0x00040017, 0x00000007, 0x00000006,
|
||||||
|
0x00000002, 0x00040015, 0x00000008, 0x00000020,
|
||||||
|
0x00000000, 0x0004002b, 0x00000008, 0x00000009,
|
||||||
|
0x00000006, 0x0004001c, 0x0000000a, 0x00000007,
|
||||||
|
0x00000009, 0x00040020, 0x0000000b, 0x00000006,
|
||||||
|
0x0000000a, 0x0004003b, 0x0000000b, 0x0000000c,
|
||||||
|
0x00000006, 0x0004002b, 0x00000006, 0x0000000d,
|
||||||
|
0xbf800000, 0x0005002c, 0x00000007, 0x0000000e,
|
||||||
|
0x0000000d, 0x0000000d, 0x0004002b, 0x00000006,
|
||||||
|
0x0000000f, 0x3f800000, 0x0005002c, 0x00000007,
|
||||||
|
0x00000010, 0x0000000f, 0x0000000d, 0x0005002c,
|
||||||
|
0x00000007, 0x00000011, 0x0000000d, 0x0000000f,
|
||||||
|
0x0005002c, 0x00000007, 0x00000012, 0x0000000f,
|
||||||
|
0x0000000f, 0x0009002c, 0x0000000a, 0x00000013,
|
||||||
|
0x0000000e, 0x00000010, 0x00000011, 0x00000011,
|
||||||
|
0x00000010, 0x00000012, 0x00040020, 0x00000014,
|
||||||
|
0x00000007, 0x00000007, 0x00040015, 0x00000016,
|
||||||
|
0x00000020, 0x00000001, 0x00040020, 0x00000017,
|
||||||
|
0x00000001, 0x00000016, 0x0004003b, 0x00000017,
|
||||||
|
0x00000018, 0x00000001, 0x00040020, 0x0000001a,
|
||||||
|
0x00000006, 0x00000007, 0x00040017, 0x0000001d,
|
||||||
|
0x00000006, 0x00000004, 0x0004002b, 0x00000008,
|
||||||
|
0x0000001e, 0x00000001, 0x0004001c, 0x0000001f,
|
||||||
|
0x00000006, 0x0000001e, 0x0006001e, 0x00000020,
|
||||||
|
0x0000001d, 0x00000006, 0x0000001f, 0x0000001f,
|
||||||
|
0x00040020, 0x00000021, 0x00000003, 0x00000020,
|
||||||
|
0x0004003b, 0x00000021, 0x00000022, 0x00000003,
|
||||||
|
0x0004002b, 0x00000016, 0x00000023, 0x00000000,
|
||||||
|
0x0004002b, 0x00000006, 0x00000025, 0x00000000,
|
||||||
|
0x00040020, 0x00000029, 0x00000003, 0x0000001d,
|
||||||
|
0x00040020, 0x0000002b, 0x00000003, 0x00000007,
|
||||||
|
0x0004003b, 0x0000002b, 0x0000002c, 0x00000003,
|
||||||
|
0x0004002b, 0x00000006, 0x0000002e, 0x3f000000,
|
||||||
|
0x00050036, 0x00000002, 0x00000004, 0x00000000,
|
||||||
|
0x00000003, 0x000200f8, 0x00000005, 0x0004003b,
|
||||||
|
0x00000014, 0x00000015, 0x00000007, 0x0003003e,
|
||||||
|
0x0000000c, 0x00000013, 0x0004003d, 0x00000016,
|
||||||
|
0x00000019, 0x00000018, 0x00050041, 0x0000001a,
|
||||||
|
0x0000001b, 0x0000000c, 0x00000019, 0x0004003d,
|
||||||
|
0x00000007, 0x0000001c, 0x0000001b, 0x0003003e,
|
||||||
|
0x00000015, 0x0000001c, 0x0004003d, 0x00000007,
|
||||||
|
0x00000024, 0x00000015, 0x00050051, 0x00000006,
|
||||||
|
0x00000026, 0x00000024, 0x00000000, 0x00050051,
|
||||||
|
0x00000006, 0x00000027, 0x00000024, 0x00000001,
|
||||||
|
0x00070050, 0x0000001d, 0x00000028, 0x00000026,
|
||||||
|
0x00000027, 0x00000025, 0x0000000f, 0x00050041,
|
||||||
|
0x00000029, 0x0000002a, 0x00000022, 0x00000023,
|
||||||
|
0x0003003e, 0x0000002a, 0x00000028, 0x0004003d,
|
||||||
|
0x00000007, 0x0000002d, 0x00000015, 0x0005008e,
|
||||||
|
0x00000007, 0x0000002f, 0x0000002d, 0x0000002e,
|
||||||
|
0x00050050, 0x00000007, 0x00000030, 0x0000002e,
|
||||||
|
0x0000002e, 0x00050081, 0x00000007, 0x00000031,
|
||||||
|
0x0000002f, 0x00000030, 0x0003003e, 0x0000002c,
|
||||||
|
0x00000031, 0x000100fd, 0x00010038,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia::shaders
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace sophia::shaders {
|
||||||
|
|
||||||
|
inline constexpr std::array<uint32_t, 143> model_render_system_frag_spv = {
|
||||||
|
0x07230203, 0x00010000, 0x000d000b, 0x00000013, 0x00000000, 0x00020011,
|
||||||
|
0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
|
||||||
|
0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000004,
|
||||||
|
0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x0000000c, 0x00030010,
|
||||||
|
0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004,
|
||||||
|
0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c,
|
||||||
|
0x7269645f, 0x69746365, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f,
|
||||||
|
0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669, 0x00040005,
|
||||||
|
0x00000004, 0x6e69616d, 0x00000000, 0x00050005, 0x00000009, 0x4374756f,
|
||||||
|
0x726f6c6f, 0x00000000, 0x00050005, 0x0000000c, 0x67617266, 0x6f6c6f43,
|
||||||
|
0x00000072, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047,
|
||||||
|
0x0000000c, 0x0000001e, 0x00000000, 0x00020013, 0x00000002, 0x00030021,
|
||||||
|
0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017,
|
||||||
|
0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003,
|
||||||
|
0x00000007, 0x0004003b, 0x00000008, 0x00000009, 0x00000003, 0x00040017,
|
||||||
|
0x0000000a, 0x00000006, 0x00000003, 0x00040020, 0x0000000b, 0x00000001,
|
||||||
|
0x0000000a, 0x0004003b, 0x0000000b, 0x0000000c, 0x00000001, 0x0004002b,
|
||||||
|
0x00000006, 0x0000000e, 0x3f800000, 0x00050036, 0x00000002, 0x00000004,
|
||||||
|
0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x0000000a,
|
||||||
|
0x0000000d, 0x0000000c, 0x00050051, 0x00000006, 0x0000000f, 0x0000000d,
|
||||||
|
0x00000000, 0x00050051, 0x00000006, 0x00000010, 0x0000000d, 0x00000001,
|
||||||
|
0x00050051, 0x00000006, 0x00000011, 0x0000000d, 0x00000002, 0x00070050,
|
||||||
|
0x00000007, 0x00000012, 0x0000000f, 0x00000010, 0x00000011, 0x0000000e,
|
||||||
|
0x0003003e, 0x00000009, 0x00000012, 0x000100fd, 0x00010038,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia::shaders
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace sophia::shaders {
|
||||||
|
|
||||||
|
inline constexpr std::array<uint32_t, 345> model_render_system_vert_spv = {
|
||||||
|
0x07230203, 0x00010000, 0x000d000b, 0x00000029, 0x00000000, 0x00020011,
|
||||||
|
0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
|
||||||
|
0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0009000f, 0x00000000,
|
||||||
|
0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00000019, 0x00000025,
|
||||||
|
0x00000027, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47,
|
||||||
|
0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, 0x7269645f,
|
||||||
|
0x69746365, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45,
|
||||||
|
0x64756c63, 0x69645f65, 0x74636572, 0x00657669, 0x00040005, 0x00000004,
|
||||||
|
0x6e69616d, 0x00000000, 0x00060005, 0x0000000b, 0x505f6c67, 0x65567265,
|
||||||
|
0x78657472, 0x00000000, 0x00060006, 0x0000000b, 0x00000000, 0x505f6c67,
|
||||||
|
0x7469736f, 0x006e6f69, 0x00070006, 0x0000000b, 0x00000001, 0x505f6c67,
|
||||||
|
0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x0000000b, 0x00000002,
|
||||||
|
0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00070006, 0x0000000b,
|
||||||
|
0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e, 0x00030005,
|
||||||
|
0x0000000d, 0x00000000, 0x00040005, 0x00000011, 0x68737550, 0x00000000,
|
||||||
|
0x00060006, 0x00000011, 0x00000000, 0x6e617274, 0x726f6673, 0x0000006d,
|
||||||
|
0x00050006, 0x00000011, 0x00000001, 0x6f6c6f63, 0x00000072, 0x00040005,
|
||||||
|
0x00000013, 0x68737570, 0x00000000, 0x00050005, 0x00000019, 0x69736f70,
|
||||||
|
0x6e6f6974, 0x00000000, 0x00050005, 0x00000025, 0x67617266, 0x6f6c6f43,
|
||||||
|
0x00000072, 0x00040005, 0x00000027, 0x6f6c6f63, 0x00000072, 0x00030047,
|
||||||
|
0x0000000b, 0x00000002, 0x00050048, 0x0000000b, 0x00000000, 0x0000000b,
|
||||||
|
0x00000000, 0x00050048, 0x0000000b, 0x00000001, 0x0000000b, 0x00000001,
|
||||||
|
0x00050048, 0x0000000b, 0x00000002, 0x0000000b, 0x00000003, 0x00050048,
|
||||||
|
0x0000000b, 0x00000003, 0x0000000b, 0x00000004, 0x00030047, 0x00000011,
|
||||||
|
0x00000002, 0x00040048, 0x00000011, 0x00000000, 0x00000005, 0x00050048,
|
||||||
|
0x00000011, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00000011,
|
||||||
|
0x00000000, 0x00000023, 0x00000000, 0x00050048, 0x00000011, 0x00000001,
|
||||||
|
0x00000023, 0x00000040, 0x00040047, 0x00000019, 0x0000001e, 0x00000000,
|
||||||
|
0x00040047, 0x00000025, 0x0000001e, 0x00000000, 0x00040047, 0x00000027,
|
||||||
|
0x0000001e, 0x00000001, 0x00020013, 0x00000002, 0x00030021, 0x00000003,
|
||||||
|
0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007,
|
||||||
|
0x00000006, 0x00000004, 0x00040015, 0x00000008, 0x00000020, 0x00000000,
|
||||||
|
0x0004002b, 0x00000008, 0x00000009, 0x00000001, 0x0004001c, 0x0000000a,
|
||||||
|
0x00000006, 0x00000009, 0x0006001e, 0x0000000b, 0x00000007, 0x00000006,
|
||||||
|
0x0000000a, 0x0000000a, 0x00040020, 0x0000000c, 0x00000003, 0x0000000b,
|
||||||
|
0x0004003b, 0x0000000c, 0x0000000d, 0x00000003, 0x00040015, 0x0000000e,
|
||||||
|
0x00000020, 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000,
|
||||||
|
0x00040018, 0x00000010, 0x00000007, 0x00000004, 0x0004001e, 0x00000011,
|
||||||
|
0x00000010, 0x00000007, 0x00040020, 0x00000012, 0x00000009, 0x00000011,
|
||||||
|
0x0004003b, 0x00000012, 0x00000013, 0x00000009, 0x00040020, 0x00000014,
|
||||||
|
0x00000009, 0x00000010, 0x00040017, 0x00000017, 0x00000006, 0x00000002,
|
||||||
|
0x00040020, 0x00000018, 0x00000001, 0x00000017, 0x0004003b, 0x00000018,
|
||||||
|
0x00000019, 0x00000001, 0x0004002b, 0x00000006, 0x0000001b, 0x00000000,
|
||||||
|
0x0004002b, 0x00000006, 0x0000001c, 0x3f800000, 0x00040020, 0x00000021,
|
||||||
|
0x00000003, 0x00000007, 0x00040017, 0x00000023, 0x00000006, 0x00000003,
|
||||||
|
0x00040020, 0x00000024, 0x00000003, 0x00000023, 0x0004003b, 0x00000024,
|
||||||
|
0x00000025, 0x00000003, 0x00040020, 0x00000026, 0x00000001, 0x00000023,
|
||||||
|
0x0004003b, 0x00000026, 0x00000027, 0x00000001, 0x00050036, 0x00000002,
|
||||||
|
0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x00050041,
|
||||||
|
0x00000014, 0x00000015, 0x00000013, 0x0000000f, 0x0004003d, 0x00000010,
|
||||||
|
0x00000016, 0x00000015, 0x0004003d, 0x00000017, 0x0000001a, 0x00000019,
|
||||||
|
0x00050051, 0x00000006, 0x0000001d, 0x0000001a, 0x00000000, 0x00050051,
|
||||||
|
0x00000006, 0x0000001e, 0x0000001a, 0x00000001, 0x00070050, 0x00000007,
|
||||||
|
0x0000001f, 0x0000001d, 0x0000001e, 0x0000001b, 0x0000001c, 0x00050091,
|
||||||
|
0x00000007, 0x00000020, 0x00000016, 0x0000001f, 0x00050041, 0x00000021,
|
||||||
|
0x00000022, 0x0000000d, 0x0000000f, 0x0003003e, 0x00000022, 0x00000020,
|
||||||
|
0x0004003d, 0x00000023, 0x00000028, 0x00000027, 0x0003003e, 0x00000025,
|
||||||
|
0x00000028, 0x000100fd, 0x00010038,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia::shaders
|
||||||
@@ -12,6 +12,7 @@ DebugUI::DebugUI(PanelRegistry& registry, ConsoleCommandRegistry& commands)
|
|||||||
this->panelRegistryHandle = registry.emplace<PanelRegistryPanel>(registry);
|
this->panelRegistryHandle = registry.emplace<PanelRegistryPanel>(registry);
|
||||||
this->frameStatsHandle = registry.emplace<FrameStatsPanel>();
|
this->frameStatsHandle = registry.emplace<FrameStatsPanel>();
|
||||||
this->consoleHandle = registry.emplace<ConsolePanel>(commands);
|
this->consoleHandle = registry.emplace<ConsolePanel>(commands);
|
||||||
|
hide();
|
||||||
// #endif // !NDEBUG
|
// #endif // !NDEBUG
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,88 +1,23 @@
|
|||||||
#include <sophia/ui/panel_registry.hpp>
|
#include <sophia/ui/panel_registry.hpp>
|
||||||
#include <sophia/util/assert.hpp>
|
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr u32 GENERATION_MASK = (1u << 12) - 1;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
PanelHandle PanelRegistry::add(std::unique_ptr<Panel> panel) {
|
|
||||||
if (!freeList.empty()) {
|
|
||||||
u32 index = freeList.back();
|
|
||||||
freeList.pop_back();
|
|
||||||
slots[index].panel = std::move(panel);
|
|
||||||
return PanelHandle{index, slots[index].generation};
|
|
||||||
}
|
|
||||||
|
|
||||||
slots.push_back(Slot{std::move(panel), 0});
|
|
||||||
return PanelHandle{static_cast<u32>(slots.size() - 1), 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
const Panel& PanelRegistry::get(PanelHandle handle) const {
|
|
||||||
SOPHIA_ASSERT(handle.isValid(), "invalid PanelHandle");
|
|
||||||
SOPHIA_ASSERT(handle.index < slots.size(),
|
|
||||||
"PanelHandle index out of range:", handle.index);
|
|
||||||
|
|
||||||
const Slot& slot = slots[handle.index];
|
|
||||||
SOPHIA_ASSERT(slot.generation == handle.generation,
|
|
||||||
"stale PanelHandle (generation mismatch):", handle.index);
|
|
||||||
SOPHIA_ASSERT(slot.panel != nullptr,
|
|
||||||
"PanelHandle refers to a removed panel:", handle.index);
|
|
||||||
return *slot.panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PanelRegistry::remove(PanelHandle handle) {
|
|
||||||
SOPHIA_ASSERT(handle.isValid(), "invalid PanelHandle");
|
|
||||||
SOPHIA_ASSERT(handle.index < slots.size(),
|
|
||||||
"PanelHandle index out of range:", handle.index);
|
|
||||||
|
|
||||||
Slot& slot = slots[handle.index];
|
|
||||||
SOPHIA_ASSERT(slot.generation == handle.generation,
|
|
||||||
"stale PanelHandle (generation mismatch):", handle.index);
|
|
||||||
SOPHIA_ASSERT(slot.panel != nullptr,
|
|
||||||
"PanelHandle already removed:", handle.index);
|
|
||||||
|
|
||||||
slot.panel.reset();
|
|
||||||
slot.generation = (slot.generation + 1) & GENERATION_MASK;
|
|
||||||
freeList.push_back(handle.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PanelRegistry::isValid(PanelHandle handle) const {
|
|
||||||
if (!handle.isValid() || handle.index >= slots.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Slot& slot = slots[handle.index];
|
|
||||||
return slot.generation == handle.generation && slot.panel != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 PanelRegistry::size() const {
|
|
||||||
u32 count = 0;
|
|
||||||
for (const Slot& slot : slots) {
|
|
||||||
if (slot.panel) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 PanelRegistry::visibleCount() const {
|
u32 PanelRegistry::visibleCount() const {
|
||||||
u32 count = 0;
|
u32 count = 0;
|
||||||
for (const Slot& slot : slots) {
|
pool.forEach([&count](const Panel& panel) {
|
||||||
if (slot.panel && slot.panel->isVisible()) {
|
if (panel.isVisible()) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PanelRegistry::update(const FrameInfo& frameInfo) {
|
void PanelRegistry::update(const FrameInfo& frameInfo) {
|
||||||
for (auto& slot : slots) {
|
pool.forEach([&frameInfo](Panel& panel) {
|
||||||
if (slot.panel && slot.panel->isVisible()) {
|
if (panel.isVisible()) {
|
||||||
slot.panel->draw(frameInfo);
|
panel.draw(frameInfo);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sophia
|
} // namespace sophia
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#include <sophia/ui/ui.hpp>
|
||||||
|
#include <sophia/util/assert.hpp>
|
||||||
|
|
||||||
|
#include "rendering/imgui_renderer.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
void UI::image(RenderTargetHandle handle) {
|
||||||
|
const ImVec2 size = ImGui::GetContentRegionAvail();
|
||||||
|
if (size.x < 1.0f || size.y < 1.0f) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resources.requestResize(handle, static_cast<u32>(size.x),
|
||||||
|
static_cast<u32>(size.y));
|
||||||
|
ImTextureID textureId = getTextureId(handle);
|
||||||
|
ImGui::Image(textureId, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImTextureID UI::getTextureId(RenderTargetHandle handle) {
|
||||||
|
SOPHIA_ASSERT(renderer != nullptr,
|
||||||
|
"UI::getTextureId() called before the UI was set up");
|
||||||
|
return renderer->getTextureId(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
#include "vulkan/access.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr std::array<Access, 7> ALL_ACCESSES = {
|
||||||
|
Access::ColorWrite, Access::DepthWrite, Access::SampledRead,
|
||||||
|
Access::StorageReadWrite, Access::TransferSrc, Access::TransferDst,
|
||||||
|
Access::Present,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr vk::AccessFlags WRITE_ACCESSES =
|
||||||
|
vk::AccessFlagBits::eColorAttachmentWrite |
|
||||||
|
vk::AccessFlagBits::eDepthStencilAttachmentWrite |
|
||||||
|
vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eTransferWrite;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
AccessState getAccessState(Access access) {
|
||||||
|
switch (access) {
|
||||||
|
case Access::ColorWrite:
|
||||||
|
return {vk::ImageLayout::eColorAttachmentOptimal,
|
||||||
|
vk::PipelineStageFlagBits::eColorAttachmentOutput,
|
||||||
|
vk::AccessFlagBits::eColorAttachmentRead |
|
||||||
|
vk::AccessFlagBits::eColorAttachmentWrite};
|
||||||
|
case Access::DepthWrite:
|
||||||
|
return {vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
||||||
|
vk::PipelineStageFlagBits::eEarlyFragmentTests |
|
||||||
|
vk::PipelineStageFlagBits::eLateFragmentTests,
|
||||||
|
vk::AccessFlagBits::eDepthStencilAttachmentRead |
|
||||||
|
vk::AccessFlagBits::eDepthStencilAttachmentWrite};
|
||||||
|
case Access::SampledRead:
|
||||||
|
return {vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||||
|
vk::PipelineStageFlagBits::eFragmentShader,
|
||||||
|
vk::AccessFlagBits::eShaderRead};
|
||||||
|
case Access::StorageReadWrite:
|
||||||
|
return {
|
||||||
|
vk::ImageLayout::eGeneral, vk::PipelineStageFlagBits::eComputeShader,
|
||||||
|
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite};
|
||||||
|
case Access::TransferSrc:
|
||||||
|
return {vk::ImageLayout::eTransferSrcOptimal,
|
||||||
|
vk::PipelineStageFlagBits::eTransfer,
|
||||||
|
vk::AccessFlagBits::eTransferRead};
|
||||||
|
case Access::TransferDst:
|
||||||
|
return {vk::ImageLayout::eTransferDstOptimal,
|
||||||
|
vk::PipelineStageFlagBits::eTransfer,
|
||||||
|
vk::AccessFlagBits::eTransferWrite};
|
||||||
|
case Access::Present:
|
||||||
|
return {vk::ImageLayout::ePresentSrcKHR,
|
||||||
|
vk::PipelineStageFlagBits::eBottomOfPipe, vk::AccessFlags{}};
|
||||||
|
}
|
||||||
|
|
||||||
|
log::fatal("Unknown Access value.");
|
||||||
|
throw std::runtime_error("Unknown Access value.");
|
||||||
|
}
|
||||||
|
|
||||||
|
AccessState getStateForLayout(vk::ImageLayout layout) {
|
||||||
|
if (layout == vk::ImageLayout::eUndefined) {
|
||||||
|
return AccessState{};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Access access : ALL_ACCESSES) {
|
||||||
|
AccessState state = getAccessState(access);
|
||||||
|
if (state.layout == layout) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::fatal("Unsupported image layout:", vk::to_string(layout));
|
||||||
|
throw std::runtime_error("Unsupported image layout.");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool writesImage(const AccessState& state) {
|
||||||
|
return static_cast<bool>(state.access & WRITE_ACCESSES);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needsBarrier(const AccessState& from, const AccessState& to) {
|
||||||
|
return from.layout != to.layout || writesImage(from) || writesImage(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier makeImageBarrier(vk::Image image,
|
||||||
|
const vk::ImageSubresourceRange& range,
|
||||||
|
const AccessState& from,
|
||||||
|
const AccessState& to) {
|
||||||
|
vk::ImageMemoryBarrier barrier{};
|
||||||
|
barrier.oldLayout = from.layout;
|
||||||
|
barrier.newLayout = to.layout;
|
||||||
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
barrier.image = image;
|
||||||
|
barrier.subresourceRange = range;
|
||||||
|
// Only writes need to be made available; reads just need the execution
|
||||||
|
// dependency the stage masks already give.
|
||||||
|
barrier.srcAccessMask = from.access & WRITE_ACCESSES;
|
||||||
|
barrier.dstAccessMask = to.access;
|
||||||
|
return barrier;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How an image is used at a point in a frame. Each value implies an image
|
||||||
|
* layout plus the pipeline stages and memory accesses that go with it, so a
|
||||||
|
* barrier between any two of them can be derived instead of hand-written.
|
||||||
|
*/
|
||||||
|
enum class Access {
|
||||||
|
ColorWrite,
|
||||||
|
DepthWrite,
|
||||||
|
SampledRead,
|
||||||
|
StorageReadWrite,
|
||||||
|
TransferSrc,
|
||||||
|
TransferDst,
|
||||||
|
Present,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layout, stages and accesses an image is in after some use. Barriers go
|
||||||
|
* from one of these to another.
|
||||||
|
*/
|
||||||
|
struct AccessState {
|
||||||
|
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||||
|
vk::PipelineStageFlags stage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||||
|
vk::AccessFlags access;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The stage RenderContext waits on for the swapchain image-available
|
||||||
|
* semaphore. A barrier that takes a swapchain image out of its
|
||||||
|
* just-acquired state has to use this as its source stage, otherwise the
|
||||||
|
* layout transition isn't ordered after the acquire.
|
||||||
|
*/
|
||||||
|
inline constexpr vk::PipelineStageFlags SWAPCHAIN_ACQUIRE_STAGE =
|
||||||
|
vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||||
|
|
||||||
|
AccessState getAccessState(Access access);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of an image sitting in `layout` with no outstanding work.
|
||||||
|
* eUndefined maps to "nothing to wait on"; layouts with no matching Access
|
||||||
|
* throw.
|
||||||
|
*/
|
||||||
|
AccessState getStateForLayout(vk::ImageLayout layout);
|
||||||
|
|
||||||
|
bool writesImage(const AccessState& state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether going from `from` to `to` needs a barrier: a layout change, or any
|
||||||
|
* hazard involving a write. Read-after-read in the same layout doesn't.
|
||||||
|
*/
|
||||||
|
bool needsBarrier(const AccessState& from, const AccessState& to);
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier makeImageBarrier(vk::Image image,
|
||||||
|
const vk::ImageSubresourceRange& range,
|
||||||
|
const AccessState& from,
|
||||||
|
const AccessState& to);
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* Based on
|
* Based on
|
||||||
* https://github.com/blurrypiano/littleVulkanEngine/blob/main/src/lve_buffer.cpp
|
* https://github.com/blurrypiano/littleVulkanEngine/blob/main/src/lve_buffer.cpp
|
||||||
*/
|
*/
|
||||||
#include "buffer.hpp"
|
#include "vulkan/buffer.hpp"
|
||||||
|
|
||||||
#include <sophia/util/assert.hpp>
|
#include <sophia/util/assert.hpp>
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "device.hpp"
|
#include "vulkan/device.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#include "vulkan/debug_label.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
static PFN_vkCmdBeginDebugUtilsLabelEXT cmdBeginDebugUtilsLabel = nullptr;
|
||||||
|
static PFN_vkCmdEndDebugUtilsLabelEXT cmdEndDebugUtilsLabel = nullptr;
|
||||||
|
|
||||||
|
void loadDebugLabelFunctions(vk::Instance instance) {
|
||||||
|
cmdBeginDebugUtilsLabel = reinterpret_cast<PFN_vkCmdBeginDebugUtilsLabelEXT>(
|
||||||
|
vkGetInstanceProcAddr(instance, "vkCmdBeginDebugUtilsLabelEXT"));
|
||||||
|
cmdEndDebugUtilsLabel = reinterpret_cast<PFN_vkCmdEndDebugUtilsLabelEXT>(
|
||||||
|
vkGetInstanceProcAddr(instance, "vkCmdEndDebugUtilsLabelEXT"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void beginDebugLabel(vk::CommandBuffer commandBuffer, const char* name) {
|
||||||
|
if (!cmdBeginDebugUtilsLabel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkDebugUtilsLabelEXT label{};
|
||||||
|
label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
|
||||||
|
label.pLabelName = name;
|
||||||
|
cmdBeginDebugUtilsLabel(commandBuffer, &label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void endDebugLabel(vk::CommandBuffer commandBuffer) {
|
||||||
|
if (!cmdEndDebugUtilsLabel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdEndDebugUtilsLabel(commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
void loadDebugLabelFunctions(vk::Instance instance);
|
||||||
|
|
||||||
|
void beginDebugLabel(vk::CommandBuffer commandBuffer, const char* name);
|
||||||
|
|
||||||
|
void endDebugLabel(vk::CommandBuffer commandBuffer);
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
+1
-1
@@ -3,7 +3,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "device.hpp"
|
#include "vulkan/device.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "device.hpp"
|
#include "vulkan/device.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include "descriptor_pool.hpp"
|
#include "descriptor_pool.hpp"
|
||||||
#include "descriptor_set_layout.hpp"
|
#include "descriptor_set_layout.hpp"
|
||||||
#include "device.hpp"
|
#include "vulkan/device.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
#include "device.hpp"
|
#include "vulkan/device.hpp"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sophia/util/logger.hpp>
|
#include <sophia/util/logger.hpp>
|
||||||
|
|
||||||
|
#include "vulkan/debug_label.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
static VKAPI_ATTR vk::Bool32 VKAPI_CALL
|
static VKAPI_ATTR vk::Bool32 VKAPI_CALL
|
||||||
@@ -288,6 +290,8 @@ void Device::setupDebugMessenger() {
|
|||||||
nullptr, &this->debugMessenger) != VK_SUCCESS) {
|
nullptr, &this->debugMessenger) != VK_SUCCESS) {
|
||||||
throw std::runtime_error("failed to set up debug callback!");
|
throw std::runtime_error("failed to set up debug callback!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadDebugLabelFunctions(*instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Device::checkValidationLayerSupport() {
|
bool Device::checkValidationLayerSupport() {
|
||||||
@@ -1,81 +1,42 @@
|
|||||||
#include "image.hpp"
|
#include "vulkan/image.hpp"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <sophia/util/logger.hpp>
|
#include <sophia/util/logger.hpp>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#include "device.hpp"
|
#include "vulkan/device.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
void Image::transitionLayout(vk::CommandBuffer commandBuffer,
|
vk::ImageSubresourceRange Image::getSubresourceRange() const {
|
||||||
vk::ImageLayout newLayout) {
|
return vk::ImageSubresourceRange{this->aspectMask, 0, this->mipLevels, 0,
|
||||||
vk::ImageMemoryBarrier barrier{};
|
this->arrayLayers};
|
||||||
barrier.oldLayout = this->layout;
|
}
|
||||||
barrier.newLayout = newLayout;
|
|
||||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
||||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
||||||
barrier.image = this->get();
|
|
||||||
barrier.subresourceRange.aspectMask = this->aspectMask;
|
|
||||||
barrier.subresourceRange.baseMipLevel = 0;
|
|
||||||
barrier.subresourceRange.levelCount = this->mipLevels;
|
|
||||||
barrier.subresourceRange.baseArrayLayer = 0;
|
|
||||||
barrier.subresourceRange.layerCount = this->arrayLayers;
|
|
||||||
|
|
||||||
vk::PipelineStageFlags srcStage;
|
AccessState Image::getAccessState() const {
|
||||||
vk::PipelineStageFlags dstStage;
|
AccessState state = getStateForLayout(this->layout);
|
||||||
|
|
||||||
if (this->layout == vk::ImageLayout::eUndefined &&
|
// A wrapped (swapchain) image is handed to us by the acquire semaphore, so
|
||||||
newLayout == vk::ImageLayout::eTransferDstOptimal) {
|
// whatever we do next has to be ordered after that wait.
|
||||||
barrier.srcAccessMask = {};
|
if (this->isExternal() && (this->layout == vk::ImageLayout::eUndefined ||
|
||||||
barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
|
this->layout == vk::ImageLayout::ePresentSrcKHR)) {
|
||||||
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
state.stage = SWAPCHAIN_ACQUIRE_STAGE;
|
||||||
dstStage = vk::PipelineStageFlagBits::eTransfer;
|
|
||||||
} else if (this->layout == vk::ImageLayout::eTransferDstOptimal &&
|
|
||||||
newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) {
|
|
||||||
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
|
||||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
|
||||||
srcStage = vk::PipelineStageFlagBits::eTransfer;
|
|
||||||
dstStage = vk::PipelineStageFlagBits::eFragmentShader;
|
|
||||||
} else if (this->layout == vk::ImageLayout::eUndefined &&
|
|
||||||
newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) {
|
|
||||||
barrier.srcAccessMask = {};
|
|
||||||
barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead |
|
|
||||||
vk::AccessFlagBits::eDepthStencilAttachmentWrite;
|
|
||||||
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
|
||||||
dstStage = vk::PipelineStageFlagBits::eEarlyFragmentTests;
|
|
||||||
} else if (this->layout == vk::ImageLayout::eUndefined &&
|
|
||||||
newLayout == vk::ImageLayout::eColorAttachmentOptimal) {
|
|
||||||
barrier.srcAccessMask = {};
|
|
||||||
barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
|
||||||
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
|
||||||
dstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
|
||||||
} else if (this->layout == vk::ImageLayout::eUndefined &&
|
|
||||||
newLayout == vk::ImageLayout::eGeneral) {
|
|
||||||
barrier.srcAccessMask = {};
|
|
||||||
barrier.dstAccessMask =
|
|
||||||
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
|
||||||
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
|
||||||
dstStage = vk::PipelineStageFlagBits::eComputeShader;
|
|
||||||
} else if (this->layout == vk::ImageLayout::eColorAttachmentOptimal &&
|
|
||||||
newLayout == vk::ImageLayout::ePresentSrcKHR) {
|
|
||||||
barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
|
||||||
barrier.dstAccessMask = {};
|
|
||||||
srcStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
|
||||||
dstStage = vk::PipelineStageFlagBits::eBottomOfPipe;
|
|
||||||
} else if (this->layout == vk::ImageLayout::ePresentSrcKHR &&
|
|
||||||
newLayout == vk::ImageLayout::eColorAttachmentOptimal) {
|
|
||||||
barrier.srcAccessMask = {};
|
|
||||||
barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
|
||||||
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
|
||||||
dstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
|
||||||
} else {
|
|
||||||
log::fatal("Unsupported image layout transition.");
|
|
||||||
throw std::runtime_error("Unsupported image layout transition.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
commandBuffer.pipelineBarrier(srcStage, dstStage, {}, nullptr, nullptr,
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Image::transitionLayout(vk::CommandBuffer commandBuffer,
|
||||||
|
vk::ImageLayout newLayout) {
|
||||||
|
AccessState from = this->getAccessState();
|
||||||
|
AccessState to = getStateForLayout(newLayout);
|
||||||
|
|
||||||
|
if (needsBarrier(from, to)) {
|
||||||
|
vk::ImageMemoryBarrier barrier =
|
||||||
|
makeImageBarrier(this->get(), this->getSubresourceRange(), from, to);
|
||||||
|
commandBuffer.pipelineBarrier(from.stage, to.stage, {}, nullptr, nullptr,
|
||||||
barrier);
|
barrier);
|
||||||
|
}
|
||||||
|
|
||||||
this->layout = newLayout;
|
this->layout = newLayout;
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
#include <sophia/types.hpp>
|
#include <sophia/types.hpp>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include "vulkan/access.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
class Device;
|
class Device;
|
||||||
@@ -28,6 +30,20 @@ class Image {
|
|||||||
vk::Format getFormat() const { return this->format; }
|
vk::Format getFormat() const { return this->format; }
|
||||||
vk::Extent3D getExtent() const { return this->extent; }
|
vk::Extent3D getExtent() const { return this->extent; }
|
||||||
vk::ImageLayout getLayout() const { return this->layout; }
|
vk::ImageLayout getLayout() const { return this->layout; }
|
||||||
|
vk::ImageSubresourceRange getSubresourceRange() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layout this image was last left in, plus the stages and accesses that
|
||||||
|
* a barrier out of it has to wait on.
|
||||||
|
*/
|
||||||
|
AccessState getAccessState() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the tracked layout without recording a barrier. For code that
|
||||||
|
* does its own transitions (the RenderGraph) and needs to hand the final
|
||||||
|
* layout back.
|
||||||
|
*/
|
||||||
|
void recordLayout(vk::ImageLayout newLayout) { this->layout = newLayout; }
|
||||||
|
|
||||||
void transitionLayout(vk::CommandBuffer commandBuffer,
|
void transitionLayout(vk::CommandBuffer commandBuffer,
|
||||||
vk::ImageLayout newLayout);
|
vk::ImageLayout newLayout);
|
||||||
+2
-2
@@ -3,9 +3,9 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
#include "device.hpp"
|
|
||||||
#include "pipeline.hpp"
|
#include "pipeline.hpp"
|
||||||
#include "shader.hpp"
|
#include "vulkan/device.hpp"
|
||||||
|
#include "vulkan/shader.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
+2
-2
@@ -6,9 +6,9 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
#include "device.hpp"
|
|
||||||
#include "pipeline.hpp"
|
#include "pipeline.hpp"
|
||||||
#include "shader.hpp"
|
#include "vulkan/device.hpp"
|
||||||
|
#include "vulkan/shader.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
#include "device.hpp"
|
#include "vulkan/device.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "shader.hpp"
|
#include "vulkan/shader.hpp"
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <shaderc/shaderc.hpp>
|
#include <shaderc/shaderc.hpp>
|
||||||
@@ -74,8 +74,7 @@ void Shader::load(const std::string& path, ShaderStage shaderStage) {
|
|||||||
throw std::runtime_error("invalid SPIR-V binary: " + path);
|
throw std::runtime_error("invalid SPIR-V binary: " + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->spirv.assign(
|
this->spirv.assign(reinterpret_cast<const u32*>(bytes.data()),
|
||||||
reinterpret_cast<const u32*>(bytes.data()),
|
|
||||||
reinterpret_cast<const u32*>(bytes.data() + bytes.size()));
|
reinterpret_cast<const u32*>(bytes.data() + bytes.size()));
|
||||||
|
|
||||||
log::info("Loaded precompiled shader: " + path + " (" +
|
log::info("Loaded precompiled shader: " + path + " (" +
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "swapchain.hpp"
|
#include "vulkan/swapchain.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
#include "device.hpp"
|
#include "vulkan/device.hpp"
|
||||||
#include "image.hpp"
|
#include "vulkan/image.hpp"
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
|
|
||||||
@@ -23,9 +23,6 @@ class Swapchain {
|
|||||||
Image& getImage(int index) const { return *this->images.at(index); }
|
Image& getImage(int index) const { return *this->images.at(index); }
|
||||||
Image& getDepthImage(int index) const { return *this->depthImages.at(index); }
|
Image& getDepthImage(int index) const { return *this->depthImages.at(index); }
|
||||||
|
|
||||||
vk::ImageView getImageView(int index) const {
|
|
||||||
return this->images.at(index)->getView();
|
|
||||||
}
|
|
||||||
vk::ImageView getDepthImageView(int index) const {
|
vk::ImageView getDepthImageView(int index) const {
|
||||||
return this->depthImages.at(index)->getView();
|
return this->depthImages.at(index)->getView();
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/constants.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <sophia/application.hpp>
|
#include <sophia/application.hpp>
|
||||||
@@ -10,12 +13,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define GLM_FORCE_RADIANS
|
|
||||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtc/constants.hpp>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
|
|
||||||
struct Boid {
|
struct Boid {
|
||||||
glm::vec2 position;
|
glm::vec2 position;
|
||||||
glm::vec2 velocity;
|
glm::vec2 velocity;
|
||||||
@@ -91,7 +88,7 @@ class Boids2D : public sophia::Application {
|
|||||||
{{-BOID_LENGTH * 0.5f, BOID_WIDTH * 0.5f}, {1.0f, 1.0f, 1.0f}},
|
{{-BOID_LENGTH * 0.5f, BOID_WIDTH * 0.5f}, {1.0f, 1.0f, 1.0f}},
|
||||||
{{-BOID_LENGTH * 0.5f, -BOID_WIDTH * 0.5f}, {1.0f, 1.0f, 1.0f}},
|
{{-BOID_LENGTH * 0.5f, -BOID_WIDTH * 0.5f}, {1.0f, 1.0f, 1.0f}},
|
||||||
};
|
};
|
||||||
boidMesh = assets().loadMesh(std::move(vertices), {});
|
boidMesh = resources().create<sophia::Mesh>(std::move(vertices));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpdate(const sophia::FrameInfo& frameInfo) override {
|
void onUpdate(const sophia::FrameInfo& frameInfo) override {
|
||||||
|
|||||||
+28
-4
@@ -1,17 +1,41 @@
|
|||||||
#include <iostream>
|
#include <imgui.h>
|
||||||
|
|
||||||
#include <sophia/application.hpp>
|
#include <sophia/application.hpp>
|
||||||
|
#include <sophia/entity.hpp>
|
||||||
|
#include <sophia/frame_info.hpp>
|
||||||
|
#include <sophia/scene_view.hpp>
|
||||||
|
#include <sophia/ui/panel.hpp>
|
||||||
|
|
||||||
|
class Viewport : public sophia::Panel {
|
||||||
|
public:
|
||||||
|
Viewport(sophia::UI& ui, sophia::RenderTargetHandle target)
|
||||||
|
: Panel{{.name = "Viewport"}}, ui{ui}, target{target} {}
|
||||||
|
|
||||||
|
void onUpdate(const sophia::FrameInfo&) override {
|
||||||
|
ImGui::Text("Test ViewPort");
|
||||||
|
|
||||||
|
ui.image(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
sophia::UI& ui;
|
||||||
|
sophia::RenderTargetHandle target;
|
||||||
|
};
|
||||||
|
|
||||||
class Testbed : public sophia::Application {
|
class Testbed : public sophia::Application {
|
||||||
public:
|
public:
|
||||||
Testbed() : Application{{.width = 750, .height = 1000, .name = "Testbed"}} {}
|
Testbed() : Application{{.width = 750, .height = 1000, .name = "Testbed"}} {}
|
||||||
|
|
||||||
void onCreate() override {
|
void onCreate() override {
|
||||||
std::cout << "Testbed created" << std::endl;
|
view = views().create({.scene = scenes().create(),
|
||||||
|
.target = resources().create<sophia::RenderTarget>(),
|
||||||
|
.clearColor = {0.1f, 0.1f, 0.15f, 1.f}});
|
||||||
|
|
||||||
debugUI().show();
|
panels().emplace<Viewport>(ui(), views().get(view).target);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpdate(const sophia::FrameInfo& frameInfo) override {}
|
private:
|
||||||
|
sophia::SceneViewHandle view;
|
||||||
};
|
};
|
||||||
|
|
||||||
SOPHIA_MAIN(Testbed)
|
SOPHIA_MAIN(Testbed)
|
||||||
|
|||||||
Reference in New Issue
Block a user