Compare commits

..

32 Commits

Author SHA1 Message Date
446670bd29 Various debug to check lighting 2026-02-23 23:13:06 -05:00
581656568b Debug options: Stop moving light, camera and cubes.
F1 to have free camera
2026-02-23 22:05:46 -05:00
3277624ec2 Support lighting using a graphics buffer that is kep open! 2026-02-23 19:24:57 -05:00
4312ecd172 Fix crash on linux with proton 2026-02-22 22:22:42 -05:00
1056301981 Fix some errors with meshrenderer moving to engine that gemini didnt have time to fix
Will have to resync how to load more meshes. Probably need a command to do it but for now not needed.
2026-02-22 17:47:46 -05:00
932c45d844 New skyboxrenderer + moved meshrenderer inside engine and out from main to clean that up 2026-02-22 17:16:21 -05:00
a781facd48 Factorized Push Data 2026-02-22 15:54:09 -05:00
1e1ec84fa1 Added normals to vertexdata 2026-02-22 15:21:35 -05:00
f98be3c7f3 Made a ship version
Remove imgui from ship release, script to export fast. Can read assets from dev folder and ship folder.
2026-02-22 14:19:59 -05:00
bc6ce3afb6 remove log file 2026-02-22 12:22:34 -05:00
816fdc27b1 Fixed the weird stutter and added fps in title bar 2026-02-22 12:22:06 -05:00
b38cc5e3d5 Fix release 2026-02-22 12:08:03 -05:00
431015f009 Added rotation functions for matrix 2026-02-22 11:24:59 -05:00
a7947bfa17 Made MeshRenderer able to take a matrix transform for each mesh, and we are now able to draw 100 cubes.
Claude OPus helpes with implementation
2026-02-21 23:27:27 -05:00
8e83cd32f6 Fixed MeshRenderer, our cube is now totally drawn by mesh renderer ! 2026-02-21 22:45:20 -05:00
2362cefbc0 Fixed imgui and debug display renderer (nothing was actually broken but request were never flushed ... ) 2026-02-21 18:35:39 -05:00
bbd1095227 Some bug fix + debug level log that is not displayed by default 2026-02-21 18:16:13 -05:00
49fc0a24d1 Meshrenderer first step. Adding logs to debug a weird crash since the add of mesh renderer and some clean up. 2026-02-21 14:04:02 -05:00
e1eb9976ad na 2026-02-16 11:14:51 -05:00
3492cf52d8 Merge remote-tracking branch 'origin/main' 2026-02-16 11:14:32 -05:00
762cfe52cf Fix autoclose command + condition to pause the console only if no debugger and no autoclose 2026-02-16 11:14:21 -05:00
7a5bb0526f Fix autoclose command + condition to pause the console only if no debugger and no autoclose 2026-02-16 11:13:53 -05:00
b6e9d95552 misc change 2026-02-16 10:38:02 -05:00
87831d0fd6 Added basic concept of Mesh
Right now can create a quad or a cube.
Need a mesh renderer taht keep the buffer and all to optimally handle the mega buffer.
2026-02-15 17:44:48 -05:00
c2a5cb84b2 made claude opus update the skills and workflow 2026-02-15 13:35:49 -05:00
4ca3ef1706 Added temp arena and use it in hot reload 2026-02-15 13:33:27 -05:00
38f8662d80 Fixing some VectorArena to use external arena and reduce the number of alloc.
Made the passing of name to arena more flexible
2026-02-14 22:28:32 -05:00
36ee786128 Delete current_run.log 2026-02-14 21:28:22 -05:00
60e1656d88 Delete test.txt 2026-02-14 21:28:13 -05:00
f8fd1dc05e Merge pull request 'removing-memory-arena' (#2) from removing-memory-arena into main
Reviewed-on: #2
2026-02-14 21:26:10 -05:00
dfd7279e84 Fix build error in release 2026-02-14 18:12:19 -05:00
5a22a172a6 Removed the old Memory Arena and converted all to the new one 2026-02-14 18:09:47 -05:00
103 changed files with 2940 additions and 1935 deletions

View File

@@ -2,7 +2,7 @@
name: cpp-game-engine-programmer
description: An expert C++ systems programmer specialized in game engine architecture, memory management, and D3D12 graphics.
trusted_commands:
- "misc\agent_build.bat *"
- "misc\\agent_build.bat *"
---
# C++ Game Engine Programmer Skill
@@ -10,21 +10,16 @@ trusted_commands:
## Role
You are a senior engine architect for the Juliet project. Your expertise lies in high-performance C++ systems programming, specifically within the context of game engine development. You value performance, memory efficiency, and maintainability.
## Coding Guidelines
You must follow the project's `coding-guidelines.md` (always loaded). Do not deviate from any rule. Pay special attention to `static_cast`/`reinterpret_cast` (never C-style casts), `auto*`/`auto&`, mandatory braces, and `[[nodiscard]]`.
## Focus Areas
1. **High Performance**: Always consider cache locality and CPU cycle cost.
2. **Memory Management**: Prioritize manual memory management using Arenas (`MemoryArena`, `EngineArena`) over standard library containers or raw `new`/`delete`.
3. **Unified Style**: Enforce the project's coding style strictly.
## Coding Guidelines
You must always follow the project's `coding-guidelines.md`. Key tenets include:
- **No Exceptions**: Use asserts and return codes.
- **[[nodiscard]]**: Use this attribute aggressively for functions returning values.
- **Type Safety**: Use strong types (CamelCase).
- **Asserts**: Validate all assumptions, especially function parameters (`ASSERT`).
- **Self-Documenting Code**: Prefer clear naming over excessive comments.
## Workflows
- **Building**: Use the `/build` command (FastBuild) to compile the project.
- **Building**: Use the `/build` workflow (FastBuild) to compile the project.
- **Shaders**: If you modify HLSL files, use `/recompile_shaders` to update the shaders.
- **Unit Tests**: When creating a new system, always implement a unit test. Do not modify the framework solely for testing; use dependency injection or mock interfaces where appropriate.

View File

@@ -2,24 +2,21 @@
name: debugger-programmer
description: An expert C++ systems programmer specialized in game engine debugging
trusted_commands:
- "miscagent_build.bat *"
- "misc\\agent_build.bat *"
---
# C++ Game Engine Programmer Skill
# C++ Game Engine Debugger Skill
## Role
You are a senior engine architect for the Juliet project. Your expertise lies in debugging C++ game engine. You add logs and use debug tricks to find the root cause of the issues.
You are a senior engine architect for the Juliet project. Your expertise lies in debugging C++ game engines. You add logs and use debug tricks to find the root cause of issues.
## Coding Guidelines
You must always follow the project's `coding-guidelines.md`. Key tenets include:
- **No Exceptions**: Use asserts and return codes.
- **[[nodiscard]]**: Use this attribute aggressively for functions returning values.
- **Type Safety**: Use strong types (CamelCase).
- **Asserts**: Validate all assumptions, especially function parameters (`ASSERT`).
You must follow the project's `coding-guidelines.md` (always loaded). Do not deviate from any rule. Even in debug/diagnostic code, use proper casts, braces, and `[[nodiscard]]`.
## Workflows
- **Building**: Use the misc\Agent_build.bat or the /build command to compile the project.
- **Unit Tests**: After you found an issue suggests unit tests to detect the issue in the future.
- **Building**: Use the `/build` workflow to compile the project.
- **Launching**: Use the `/launch` workflow to run the application and check for issues.
- **Unit Tests**: After finding an issue, suggest unit tests to detect the issue in the future.
## Tone
Professional, technical, and precise. Focus on explaining your debugging strategy.

View File

@@ -1,6 +1,9 @@
---
name: graphics-programmer
description: An expert in 3D rendering, shader development (HLSL), and visual aesthetics, acting as a bridge between technical implementation and artistic vision.
trusted_commands:
- "misc\\agent_build.bat *"
- "misc\\recompile_shaders.bat *"
---
# Graphics Programmer & Tech Artist Skill
@@ -8,6 +11,13 @@ description: An expert in 3D rendering, shader development (HLSL), and visual ae
## Role
You are the bridge between code and art for the Juliet project. You are responsible for the final pixels on the screen, ensuring they are both performant and visually stunning. You speak fluent C++ (D3D12) and HLSL.
## Coding Guidelines
You must follow the project's `coding-guidelines.md` (always loaded). Do not deviate from any rule. This applies to both C++ (D3D12) code and HLSL shaders.
### HLSL Specifics
- Use meaningful variable names.
- Group constant buffers logically by frequency of update (Global, PerObject, etc.).
## Focus Areas
1. **Visual Excellence**: Never settle for "it works." Make it look "premium." Use lighting, shadows, and post-processing to enhance the aesthetic.
2. **Pipeline Mastery**: Deep understanding of the D3D12 pipeline (PSOs, Root Signatures, Resource Barriers, Descriptor Heaps).
@@ -15,16 +25,9 @@ You are the bridge between code and art for the Juliet project. You are responsi
## Workflows
- **Shader Iteration**: Use the `/recompile_shaders` workflow immediately after editing any `.hlsl` file to catch syntax errors early.
- **Building**: Use the `/build` workflow to compile C++ changes.
- **Performance**: Be mindful of GPU bandwidth and generic overdraw.
- **Debugging**: Think like a frame capture (PIX/RenderDoc). Visualize data (normals, depth, etc.) if unsure.
## Coding Guidelines
- **HLSL**:
- Use meaningful variable names.
- Group constant buffers logically by frequency of update (Global, PerObject, etc.).
- **C++ (D3D12)**:
- Follow the general `coding-guidelines.md`.
- Ensure correct `D3D12_RESOURCE_BARRIER` usage to avoid race conditions.
## Tone
Creative, collaborative, and highly technical. You are excited about graphics techniques (PBR, Raytracing, etc.) but grounded in the reality of shipping a performant frame.

View File

@@ -1,6 +1,9 @@
---
name: video-game-tester
description: A rigorous QA specialist and test automation engineer focused on verifying game stability and correctness.
trusted_commands:
- "misc\\agent_build.bat *"
- "misc\\launch.bat *"
---
# Video Game Tester Skill
@@ -8,6 +11,9 @@ description: A rigorous QA specialist and test automation engineer focused on ve
## Role
You are the gatekeeper of quality for the Juliet project. Your job is to break the game, find edge cases, and ensure that every change works as intended before it receives a seal of approval.
## Coding Guidelines
You must follow the project's `coding-guidelines.md` (always loaded). Do not deviate from any rule, even when writing test harness or verification code.
## Focus Areas
1. **Verification**: Never assume a change works. Always run the game.
2. **Reproduction**: If you find an issue, define clear steps to reproduce it.
@@ -17,7 +23,7 @@ You are the gatekeeper of quality for the Juliet project. Your job is to break t
- **Always Run the Game**: After any code change, even minor ones, verify by running the build.
- **Launch Command**: Use the `/launch` workflow.
- **IMPORTANT**: When running automated or quick verification, ALWAYS use the `autoclose` parameter (e.g., `/launch autoclose`) so the game exits automatically after the test sequence.
- **Log Analysis**: strict checking of `OutputDebugString` or log files for any `ERROR` or `WARNING` lines.
- **Log Analysis**: Strict checking of `misc\agent_output.log` for any `ERROR` or `WARNING` lines after a build or launch.
- **Visual Inspection**: Report any visual artifacts, flickering, or incorrect rendering immediately.
## Reporting

View File

@@ -3,15 +3,19 @@ description: Build the Juliet project using FastBuild
---
// turbo-all
// @auto-approve: true
This workflow sets up the Juliet build environment and runs `fbuild`.
This workflow builds the Juliet project and writes output to `misc\agent_output.log`.
1. To build a specific configuration (e.g., msvc-Debug):
`misc\agent_build.bat clang-Debug"`
1. To build the default (clang-Debug):
// turbo
`misc\agent_build.bat clang 2>&1 | tee misc\agent_output.log`
2. To build the default clang:
`misc\agent_build.bat clang"`
2. To build a specific configuration (e.g., clang-Debug, msvc-Debug):
// turbo
`misc\agent_build.bat clang-Debug 2>&1 | tee misc\agent_output.log`
3. To see all available targets:
`misc\agent_build.bat -showtargets"`
// turbo
`misc\agent_build.bat -showtargets 2>&1 | tee misc\agent_output.log`
4. Check build results by reading `misc\agent_output.log`.

View File

@@ -2,5 +2,12 @@
description: Launch the Juliet application
---
1. Run the launch script
misc\launch.bat autoclose
// turbo-all
This workflow launches the Juliet application and writes output to `misc\agent_output.log`.
1. Run the launch script:
// turbo
`misc\launch.bat autoclose 2>&1 | tee misc\agent_output.log`
2. Check launch results by reading `misc\agent_output.log`.

View File

@@ -4,7 +4,10 @@ description: Recompile shaders for the Juliet project
// turbo-all
This workflow recompiles all shaders using the `recompile_shaders.bat` script.
This workflow recompiles all shaders and writes output to `misc\agent_output.log`.
1. Recompile all shaders:
`misc\recompile_shaders.bat"`
// turbo
`misc\recompile_shaders.bat 2>&1 | tee misc\agent_output.log`
2. Check shader compilation results by reading `misc\agent_output.log`.

3
.gitignore vendored
View File

@@ -13,6 +13,7 @@
.idea/
.vs/
[Ii]ntermediate/
[sS]hip/
# Logs
build_*.txt
@@ -51,3 +52,5 @@ launch_*.txt
*.out
*.app
misc/agent_error.log
misc/agent_output_ship.log

View File

@@ -0,0 +1,292 @@
# Lighting & Skybox Plan
## Current State
| Item | Current |
|------|---------|
| Vertex format | `float3 Position` + `float4 Color` (28 bytes, no normals) |
| Vertex shader | [Triangle.vert.hlsl](file:///w:/Classified/Juliet/Assets/source/Triangle.vert.hlsl) — bindless buffer read, `mul(VP, mul(Model, pos))` |
| Fragment shader | [SolidColor.frag.hlsl](file:///w:/Classified/Juliet/Assets/source/SolidColor.frag.hlsl) — passthrough `return Color` |
| Push constants | `ViewProjection` + `Model` + `BufferIndex` + extras |
| Texturing | None — no sampler usage, no texture reads |
---
## Phase 1: Add Normals to Vertex Data
Lighting requires normals. This is the foundation for everything else.
### Vertex Format Change
```diff
struct Vertex
{
float Position[3];
+ float Normal[3];
float Color[4];
};
```
- **Stride**: 28 → 40 bytes
- Update `Triangle.vert.hlsl` stride constant: `uint stride = 40;`
- Load normal after position: `float3 normal = asfloat(buffer.Load3(offset + 12));`
- Load color shifts to: `float4 col = asfloat(buffer.Load4(offset + 24));`
### Files to modify
| File | Change |
|------|--------|
| [VertexData.h](file:///w:/Classified/Juliet/Juliet/include/Graphics/VertexData.h) | Add `float Normal[3]` to `Vertex` |
| [MeshRenderer.cpp](file:///w:/Classified/Juliet/Juliet/src/Graphics/MeshRenderer.cpp) | Update `AddCube()` to include per-face normals |
| [Triangle.vert.hlsl](file:///w:/Classified/Juliet/Assets/source/Triangle.vert.hlsl) | Update stride, load normal, pass to fragment |
---
## Phase 2: Basic Directional Light (Diffuse)
Simple single directional light with diffuse (Lambert) shading.
### Approach: Push light data through RootConstants
Add light direction and color to `RootConstants.hlsl` and the C++ `PushData`:
```hlsl
// RootConstants.hlsl additions
float3 LightDirection; // Normalized, world-space
float _LightPad;
float3 LightColor;
float AmbientIntensity;
```
```cpp
// PushData additions
Vector3 LightDirection;
float _LightPad;
Vector3 LightColor;
float AmbientIntensity;
```
### Shader Changes
**Vertex shader** — transform normal to world space and pass it to fragment:
```hlsl
// Triangle.vert.hlsl output struct
struct Output
{
float4 Color : TEXCOORD0;
float3 WorldNormal : TEXCOORD1;
float4 Position : SV_Position;
};
// In main():
float3 worldNormal = mul((float3x3)Model, normal);
output.WorldNormal = worldNormal;
```
> [!NOTE]
> Using `(float3x3)Model` for normal transform is only correct for uniform-scale transforms. For non-uniform scale, you'd need the inverse-transpose. Fine for now with translation-only transforms.
**Fragment shader** — apply Lambert diffuse:
```hlsl
// SolidColor.frag.hlsl → rename to Lit.frag.hlsl
#include "RootConstants.hlsl"
float4 main(float4 Color : TEXCOORD0, float3 WorldNormal : TEXCOORD1) : SV_Target0
{
float3 N = normalize(WorldNormal);
float NdotL = saturate(dot(N, -LightDirection));
float3 diffuse = Color.rgb * LightColor * NdotL;
float3 ambient = Color.rgb * AmbientIntensity;
return float4(diffuse + ambient, Color.a);
}
```
### Files to modify
| File | Change |
|------|--------|
| [RootConstants.hlsl](file:///w:/Classified/Juliet/Assets/source/RootConstants.hlsl) | Add light params |
| [MeshRenderer.h](file:///w:/Classified/Juliet/Juliet/include/Graphics/MeshRenderer.h) | Add light params to `PushData` |
| [Triangle.vert.hlsl](file:///w:/Classified/Juliet/Assets/source/Triangle.vert.hlsl) | Pass world normal to fragment |
| [SolidColor.frag.hlsl](file:///w:/Classified/Juliet/Assets/source/SolidColor.frag.hlsl) | Lambert diffuse + ambient |
| [DebugDisplayRenderer.cpp](file:///w:/Classified/Juliet/Juliet/src/Graphics/DebugDisplayRenderer.cpp) | Update push data struct to match new layout |
| [main.cpp](file:///w:/Classified/Juliet/JulietApp/main.cpp) | Set `LightDirection`, `LightColor`, `AmbientIntensity` |
---
## Phase 3: Skybox
A skybox renders a cubemap texture behind all geometry, giving the scene a background.
### Approach: Fullscreen-triangle with inverse VP
Render a fullscreen triangle as the very last thing (or first with depth write off), sample a cubemap using the camera view direction reconstructed from screen coordinates.
### New Assets
- **Skybox cubemap texture** — a `.dds` or 6 `.png` face images loaded as a `TextureCube`
- **New shaders**: `Skybox.vert.hlsl` + `Skybox.frag.hlsl`
### Shader Design
**Vertex shader** — fullscreen triangle using `SV_VertexID`:
```hlsl
// Skybox.vert.hlsl
#include "RootConstants.hlsl"
struct Output
{
float3 ViewDir : TEXCOORD0;
float4 Position : SV_Position;
};
Output main(uint vertexID : SV_VertexID)
{
Output output;
// Fullscreen triangle
float2 uv = float2((vertexID << 1) & 2, vertexID & 2);
float4 clipPos = float4(uv * 2.0 - 1.0, 1.0, 1.0);
clipPos.y = -clipPos.y;
output.Position = clipPos;
// Reconstruct view direction from clip space
// InverseViewProjection needs to be added to RootConstants
float4 worldPos = mul(InverseViewProjection, clipPos);
output.ViewDir = worldPos.xyz / worldPos.w;
return output;
}
```
**Fragment shader** — sample cubemap:
```hlsl
// Skybox.frag.hlsl
#include "RootConstants.hlsl"
float4 main(float3 ViewDir : TEXCOORD0) : SV_Target0
{
TextureCube skybox = ResourceDescriptorHeap[TextureIndex];
SamplerState samp = SamplerDescriptorHeap[0]; // Linear clamp
return skybox.Sample(samp, normalize(ViewDir));
}
```
### Pipeline Requirements
| Setting | Value |
|---------|-------|
| Depth write | **Off** (skybox is infinitely far) |
| Depth test | **LessEqual** or **Off** (render behind everything) |
| Cull mode | **None** (fullscreen triangle) |
| Draw call | `Draw(3, 0)` — no vertex buffer needed |
### New C++ Components
1. **Cubemap loading** — need to create `TextureCube` from 6 face images or a `.dds` cubemap file
2. **Skybox pipeline** — new `GraphicsPipeline` with the skybox shaders and the pipeline settings above
3. **Sampler** — need at least one linear sampler in the sampler heap (may already exist for ImGui)
4. **`InverseViewProjection`** — add to `RootConstants` and compute in C++ via a `MatrixInverse` function
> [!IMPORTANT]
> `MatrixInverse` needs to be implemented in `Matrix.h`. This is a non-trivial 4×4 matrix inversion (adjugate/determinant method or Gauss-Jordan).
### Files to modify/create
| File | Change |
|------|--------|
| [NEW] `Skybox.vert.hlsl` | Fullscreen triangle + view direction |
| [NEW] `Skybox.frag.hlsl` | Cubemap sample |
| [RootConstants.hlsl](file:///w:/Classified/Juliet/Assets/source/RootConstants.hlsl) | Add `InverseViewProjection` |
| [Matrix.h](file:///w:/Classified/Juliet/Juliet/include/Core/Math/Matrix.h) | Add `MatrixInverse()` |
| [MeshRenderer.h](file:///w:/Classified/Juliet/Juliet/include/Graphics/MeshRenderer.h) | Add `InverseViewProjection` to `PushData` |
| [main.cpp](file:///w:/Classified/Juliet/JulietApp/main.cpp) | Create skybox pipeline, load cubemap, render skybox |
---
## Recommended Implementation Order
```mermaid
graph LR
A["Phase 1<br/>Add Normals"] --> B["Phase 2<br/>Directional Light"]
B --> C["Phase 3<br/>Skybox"]
```
1. **Phase 1** (normals) is the smallest and unblocks Phase 2
2. **Phase 2** (lighting) gives the cubes visible 3D depth immediately
3. **Phase 3** (skybox) is independent of lighting but benefits from having `MatrixInverse` and sampler infrastructure
> [!TIP]
> Phases 1+2 together are a single session of work (~8 files). Phase 3 is larger due to cubemap loading and new pipeline creation.
---
## Open Questions (Answered)
1. **Cubemap source****Procedural gradient skybox** chosen because asset loading infrastructure is not yet established.
2. **Sampler heap** — Evaluate what exists. However, with a procedural skybox, we won't need a sampler for Phase 3! (The sky color can be procedurally generated from the reconstructed view direction).
3. **Specular****Blinn-Phong** specular, structured in an agnostic way so PBR (physically based rendering) parameters can be plugged in later.
---
## Phase 4: Forward Rendered Entity Lights
A simple, fast forward renderer using a Global Structured Buffer for entity-based point lights.
### Approach: Bindless Global Lights Buffer
Instead of passing each light via PushConstants, we pass a `StructuredBuffer<PointLight>` index and iterate over the active lights in the fragment shader.
### New C++ Components
```cpp
struct PointLight
{
Vector3 Position;
float Radius;
Vector3 Color;
float Intensity;
};
```
1. **Lights Buffer Allocation**: Allocate a `StructuredBuffer` large enough for all potential lights (e.g., max 1024).
2. **Buffer Upload**: Loop through the active game entity lights every frame and upload them using the TransferBuffer system.
3. **PushData Update**:
- `uint32 LightsBufferIndex`
- `uint32 ActiveLightCount`
### Shader Design
**Fragment Shader Expansion** loop over all `ActiveLightCount` to compute the attenuation and diffuse, then accumulate.
```hlsl
StructuredBuffer<PointLight> lights = ResourceDescriptorHeap[LightsBufferIndex];
float3 totalDiffuse = Color.rgb * LightColor * NdotL; // Include Directional Sun
for (uint i = 0; i < ActiveLightCount; ++i)
{
PointLight light = lights[i];
float3 lightVector = light.Position - WorldPosition;
float distance = length(lightVector);
if (distance > light.Radius) continue;
float3 L = lightVector / distance;
float NdotL = saturate(dot(N, L));
float attenuation = 1.0 - saturate(distance / light.Radius);
attenuation *= attenuation; // inverse square-ish
totalDiffuse += Color.rgb * light.Color * light.Intensity * NdotL * attenuation;
}
```
> [!NOTE]
> Passing `WorldPosition` to the Fragment shader from the Vertex shader is required for positional lighting.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -13,8 +13,8 @@ Output main(uint vertexIndex : SV_VertexID)
// Retrieve the vertex buffer using SM6.6 bindless syntax
ByteAddressBuffer buffer = ResourceDescriptorHeap[BufferIndex];
// TextureIndex is used as vertex offset for consolidated buffer (depth-tested at 0, overlay at halfMax)
uint actualVertexIndex = vertexIndex + TextureIndex;
// VertexOffset is used as vertex offset for consolidated buffer (depth-tested at 0, overlay at halfMax)
uint actualVertexIndex = vertexIndex + VertexOffset;
// Vertex layout: float3 Position (12 bytes) + float4 Color (16 bytes) = 28 bytes stride
uint stride = 28;

View File

@@ -8,31 +8,7 @@ struct Input
float4 main(Input input) : SV_Target0
{
// Retrieve the texture using SM6.6 bindless syntax
// Texture2D texture = ResourceDescriptorHeap[TextureIndex]; (Must cast to Texture2D<float4>)
// Wait, ResourceDescriptorHeap indexing returns a wrapper, usually we use Textures[TextureIndex]?
// Juliet seems to use `ResourceDescriptorHeap` for Buffers.
// Let's check Triangle.vert/frag.
// In bindless, usually:
// Texture2D<float4> tex = ResourceDescriptorHeap[TextureIndex];
// SamplerState samp = SamplerDescriptorHeap[0]; // Assuming static sampler or passed index
// I need to check how Juliet accesses textures.
// I'll assume standard SM6.6 usage.
Texture2D<float4> tex = ResourceDescriptorHeap[TextureIndex];
SamplerState samp = SamplerDescriptorHeap[0]; // Point sampler or Linear? ImGui usually uses Linear.
// D3D12GraphicsDevice.cpp created static samplers.
// Root signature has Static Samplers.
// RegisterSpace 0.
// Sampler register 0 is Point/Nearest?
// Let's check CreateGraphicsRootSignature in D3D12GraphicsDevice.cpp.
// It creates s_nearest at 0.
// If I want Linear, I might need another sampler or rely on s_nearest for font (pixel art font?)
// Default ImGui font is usually antialiased, so Linear is preferred.
// But pixel aligned UI...
// I will use `SamplerDescriptorHeap[0]` for now.
SamplerState samp = SamplerDescriptorHeap[0];
return input.Color * tex.Sample(samp, input.UV);
}

View File

@@ -18,14 +18,10 @@ Output main(uint vertexIndex : SV_VertexID)
{
Output output;
// Retrieve the vertex buffer using SM6.6 bindless syntax
ByteAddressBuffer buffer = ResourceDescriptorHeap[BufferIndex];
// Add VertexOffset for indexed drawing with bindless buffers
// (SV_VertexID in indexed draw is raw index from index buffer, doesn't include BaseVertexLocation)
uint actualVertexIndex = vertexIndex + VertexOffset;
// ImDrawVert stride = 20 bytes (Vec2 pos + Vec2 uv + uint color)
uint stride = 20;
uint offset = actualVertexIndex * stride;
@@ -33,34 +29,12 @@ Output main(uint vertexIndex : SV_VertexID)
float2 uv = asfloat(buffer.Load2(offset + 8));
uint col = buffer.Load(offset + 16);
// Unpack color (uint to float4)
// ImGui colors are 0xAABBGGRR (ABGR packed)
// We need to unpack to float4.
// HLSL unpacks as little endian.
// uint 0xAABBGGRR -> byte0=RR, byte1=GG, byte2=BB, byte3=AA
float4 c;
c.x = float(col & 0xFF) / 255.0f;
c.y = float((col >> 8) & 0xFF) / 255.0f;
c.z = float((col >> 16) & 0xFF) / 255.0f;
c.w = float((col >> 24) & 0xFF) / 255.0f;
// Transform
// ImGui sends pixel coordinates.
// We need to transform to NDC [-1, 1].
// PushConstants should contain Scale and Translate.
// float2 Scale = 2.0 / DisplaySize
// float2 Translate = -1.0 - (DisplayPos * Scale)
// We will assume PushConstants are float2 Scale, float2 Translate.
// Struct in RootConstants.hlsl?
// RootConstants.hlsl likely defines `cbuffer PushConstants : register(b0)`.
// Let's assume standard push constants usage.
// Debug.vert.hlsl used `ViewProjection`.
// We need to customize PushConstants or reuse `ViewProjection` slot?
// Juliet uses 128 bytes of push constants.
// We can map float4 ProjectionMatrix (or similar).
// Use Scale and Translate from RootConstants
output.Position = float4(pos * Scale + Translate, 0.0f, 1.0f);
output.Color = c;
output.UV = uv;

View File

@@ -1,15 +1,39 @@
#ifndef ROOT_CONSTANTS_HLSL
#define ROOT_CONSTANTS_HLSL
struct PointLight
{
float3 Position;
float Radius;
float3 Color;
float Intensity;
};
struct TransformData
{
row_major float4x4 Model;
};
cbuffer RootConstants : register(b0, space0)
{
row_major float4x4 ViewProjection;
uint MeshIndex;
uint TransformsBufferIndex;
uint BufferIndex;
uint TextureIndex;
uint TextureIndex;
uint VertexOffset; // Base vertex for indexed drawing with bindless buffers
uint _Padding; // Padding for alignment
float2 Scale; // 2D scale factor
float2 Translate; // 2D translation
float2 _Padding2; // Explicit padding to align LightDirection to 16 bytes
float3 GlobalLightDirection; // Normalized, world-space
float GlobalLightPad;
float3 GlobalLightColor;
float GlobalAmbientIntensity;
uint LightBufferIndex;
uint ActiveLightCount;
};

View File

@@ -0,0 +1,26 @@
#include "RootConstants.hlsl"
float4 main(float3 ViewDir : TEXCOORD0) : SV_Target0
{
float3 dir = normalize(ViewDir);
// Simple Procedural Gradient Skybox Colors
float3 skyZenithColor = float3(0.05f, 0.15f, 0.45f); // Deep blue top
float3 skyHorizonColor = float3(0.4f, 0.6f, 0.9f); // Light blue horizon
float3 groundColor = float3(0.1f, 0.1f, 0.15f); // Dark ground
float t = dir.z; // -1 to 1 based on vertical alignment
float3 finalColor = groundColor;
if (t > 0.0f) {
// Blend from horizon to zenith
finalColor = lerp(skyHorizonColor, skyZenithColor, t);
} else {
// Ground - quick blend from horizon to ground
finalColor = lerp(skyHorizonColor, groundColor, saturate(-t * 2.0f));
}
// Combine with overall ambient lighting scale
return float4(finalColor, 1.0f);
}

View File

@@ -0,0 +1,74 @@
#include "RootConstants.hlsl"
struct Output
{
float3 ViewDir : TEXCOORD0;
float4 Position : SV_Position;
};
float4x4 Inverse(float4x4 m)
{
float n11 = m[0][0], n12 = m[1][0], n13 = m[2][0], n14 = m[3][0];
float n21 = m[0][1], n22 = m[1][1], n23 = m[2][1], n24 = m[3][1];
float n31 = m[0][2], n32 = m[1][2], n33 = m[2][2], n34 = m[3][2];
float n41 = m[0][3], n42 = m[1][3], n43 = m[2][3], n44 = m[3][3];
float t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44;
float t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44;
float t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44;
float t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
float det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
float idet = 1.0f / det;
float4x4 ret;
ret[0][0] = t11 * idet;
ret[0][1] = (n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44) * idet;
ret[0][2] = (n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44) * idet;
ret[0][3] = (n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43) * idet;
ret[1][0] = t12 * idet;
ret[1][1] = (n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44) * idet;
ret[1][2] = (n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44) * idet;
ret[1][3] = (n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43) * idet;
ret[2][0] = t13 * idet;
ret[2][1] = (n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44) * idet;
ret[2][2] = (n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44) * idet;
ret[2][3] = (n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43) * idet;
ret[3][0] = t14 * idet;
ret[3][1] = (n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34) * idet;
ret[3][2] = (n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34) * idet;
ret[3][3] = (n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33) * idet;
return ret;
}
Output main(uint vertexID : SV_VertexID)
{
Output output;
// Fullscreen triangle properties
// vertexID 0 -> uv(0,0) -> pos(-1, 1)
// vertexID 1 -> uv(2,0) -> pos( 3, 1)
// vertexID 2 -> uv(0,2) -> pos(-1, -3)
float2 uv = float2((vertexID << 1) & 2, vertexID & 2);
// Map uv to clip space position.
// Z is set to 1.0 (far plane in reverse-Z or standard depth)
// Assuming standard Z projection ranges from 0 to 1, we use 1.0 for the far plane.
// Depending on reverse-z, this might need to be 0.0, but let's stick to standard 1.0
// depth for skyboxes.
float4 clipPos = float4(uv * 2.0 - 1.0, 1.0, 1.0);
clipPos.y = -clipPos.y; // Flip Y for D3D
output.Position = clipPos;
// Reconstruct view direction from clip space
float4x4 inverseVP = Inverse(ViewProjection);
float4 worldPos = mul(inverseVP, clipPos);
output.ViewDir = worldPos.xyz / worldPos.w;
return output;
}

View File

@@ -1,4 +1,46 @@
float4 main(float4 Color : TEXCOORD0) : SV_Target0
#include "RootConstants.hlsl"
struct Input
{
return Color;
float4 Color : TEXCOORD0;
float3 WorldNormal : TEXCOORD1;
float3 WorldPosition : TEXCOORD2;
};
float4 main(Input input) : SV_Target0
{
float3 normal = normalize(input.WorldNormal);
// Initial ambient component
float3 result = input.Color.rgb * GlobalLightColor * GlobalAmbientIntensity;
// Directional light contribution
float ndotl = max(dot(normal, -GlobalLightDirection), 0.0);
result += input.Color.rgb * GlobalLightColor * ndotl;
// Point lights
if (ActiveLightCount > 0)
{
StructuredBuffer<PointLight> pointLights = ResourceDescriptorHeap[LightBufferIndex];
for (uint i = 0; i < ActiveLightCount; ++i)
{
PointLight light = pointLights[i];
float3 lightDir = light.Position - input.WorldPosition;
float dist = length(lightDir);
if (dist < light.Radius)
{
lightDir = normalize(lightDir);
float attenuation = 1.0 - (dist / light.Radius);
attenuation = max(attenuation, 0.0);
float pndotl = max(dot(normal, lightDir), 0.0);
result += light.Color * input.Color.rgb * pndotl * attenuation * light.Intensity;
}
}
}
return float4(result, input.Color.a);
}

View File

@@ -1,6 +1,8 @@
struct Output
{
float4 Color : TEXCOORD0;
float3 WorldNormal : TEXCOORD1;
float3 WorldPosition : TEXCOORD2;
float4 Position : SV_Position;
};
@@ -10,20 +12,25 @@ Output main(uint vertexIndex : SV_VertexID)
{
Output output;
// Retrieve the buffer using SM6.6 bindless syntax
// Use BufferIndex from RootConstants (pushed via SetPushConstants)
ByteAddressBuffer buffer = ResourceDescriptorHeap[BufferIndex];
// Read position from buffer (Index * stride)
// Stride = 2 float (pos) + 4 float (color) = 6 * 4 = 24 bytes ?
// Let's assume just position 2D (8 bytes) + Color (16 bytes)
uint stride = 24;
uint stride = 40;
uint offset = vertexIndex * stride;
float2 pos = asfloat(buffer.Load2(offset));
float4 col = asfloat(buffer.Load4(offset + 8));
float3 pos = asfloat(buffer.Load3(offset));
float3 normal = asfloat(buffer.Load3(offset + 12));
float4 col = asfloat(buffer.Load4(offset + 24));
output.Position = float4(pos, 0.0f, 1.0f);
// Fetch Model Matrix
StructuredBuffer<TransformData> transformsBuffer = ResourceDescriptorHeap[TransformsBufferIndex];
float4x4 Model = transformsBuffer[MeshIndex].Model;
float4 worldPos = mul(Model, float4(pos, 1.0f));
output.Position = mul(ViewProjection, worldPos);
output.Color = col;
output.WorldPosition = worldPos.xyz;
float3 worldNormal = mul((float3x3)Model, normal);
output.WorldNormal = worldNormal;
return output;
}

2
External/Imgui vendored

View File

@@ -47,15 +47,18 @@ namespace Game
requires EntityConcept<EntityType>
EntityType* MakeEntity(EntityManager& manager, float x, float y)
{
auto* arena = Juliet::GetGameArena();
EntityType* result = Juliet::ArenaPushType<EntityType>(arena, ConstString("EntityType"));
Entity* base = result->Base = Juliet::ArenaPushType<Entity>(arena, ConstString("Entity"));
base->X = x;
base->Y = y;
base->Derived = result;
base->Kind = EntityType::Kind;
auto* arena = manager.Arena;
EntityType* result = Juliet::ArenaPushStruct<EntityType>(arena);
Entity base;
base.X = x;
base.Y = y;
base.Derived = result;
base.Kind = EntityType::Kind;
manager.Entities.PushBack(base);
RegisterEntity(manager, base);
result->Base = manager.Entities.Back();
RegisterEntity(manager, &base);
return result;
}

View File

@@ -11,7 +11,16 @@ namespace Game
EntityID EntityManager::ID = 0;
void InitEntityManager() {}
void InitEntityManager(Juliet::NonNullPtr<Juliet::Arena> arena)
{
Manager.Arena = arena.Get();
Manager.Entities.Create(arena JULIET_DEBUG_PARAM("Entities"));
}
void ShutdownEntityManager()
{
Manager.Entities.Destroy();
}
EntityManager& GetEntityManager()
{

View File

@@ -1,6 +1,7 @@
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Container/Vector.h>
namespace Game
{
@@ -10,10 +11,14 @@ namespace Game
struct EntityManager
{
static EntityID ID;
// May be this should contains the allocator for each entity types
Juliet::Arena* Arena;
// TODO: Should be a pool
Juliet::VectorArena<Entity, 1024> Entities;
};
void InitEntityManager();
void InitEntityManager(Juliet::NonNullPtr<Juliet::Arena> arena);
void ShutdownEntityManager();
EntityManager& GetEntityManager();
void RegisterEntity(EntityManager& manager, Entity* entity);
} // namespace Game

View File

@@ -31,7 +31,7 @@ namespace Game
using namespace Juliet;
extern "C" JULIET_API void GameInit(GameInitParams* /*params*/)
extern "C" JULIET_API void GameInit(GameInitParams* params)
{
// Example allocation in GameArena
struct GameState
@@ -40,7 +40,7 @@ extern "C" JULIET_API void GameInit(GameInitParams* /*params*/)
int Score;
};
auto* gameState = ArenaPushType<GameState>(GetGameArena(), ConstString("GameState"));
auto* gameState = ArenaPushStruct<GameState>(params->GameArena);
gameState->TotalTime = 0.0f;
gameState->Score = 0;
@@ -49,7 +49,7 @@ extern "C" JULIET_API void GameInit(GameInitParams* /*params*/)
using namespace Game;
// Entity Use case
InitEntityManager();
InitEntityManager(params->GameArena);
auto& manager = GetEntityManager();
Door* door = MakeEntity<Door>(manager, 10.0f, 2.0f);
door->IsOpened = true;
@@ -69,6 +69,10 @@ extern "C" JULIET_API void GameInit(GameInitParams* /*params*/)
extern "C" JULIET_API void __cdecl GameShutdown()
{
printf("Shutting down game...\n");
using namespace Game;
ShutdownEntityManager();
}
extern "C" JULIET_API void __cdecl GameUpdate([[maybe_unused]] float deltaTime)

View File

@@ -47,8 +47,11 @@
// --- DLL BUILD ---
DLL( '$ProjectName$-Lib-$Platform$-$BuildConfigName$' )
{
.Libraries = { '$ProjectName$-Objs-$Platform$-$BuildConfigName$',
'ImGui-Lib-$Platform$-$BuildConfigName$' }
.Libraries = { '$ProjectName$-Objs-$Platform$-$BuildConfigName$' }
If ( .BuildConfigName != 'Release' )
{
^Libraries + { 'ImGui-Lib-$Platform$-$BuildConfigName$' }
}
.LinkerOutput = '$BinPath$/$Platform$-$BuildConfigName$/$ProjectName$.dll' // Output .dll to Bin

View File

@@ -86,6 +86,7 @@
<CustomBuild Include="include\Core\Math\Vector.h" />
<CustomBuild Include="include\Core\Memory\Allocator.h" />
<CustomBuild Include="include\Core\Memory\MemoryArena.h" />
<CustomBuild Include="include\Core\Memory\MemoryArenaDebug.h" />
<CustomBuild Include="include\Core\Memory\ScratchArena.h" />
<CustomBuild Include="include\Core\Memory\Utils.h" />
<CustomBuild Include="include\Core\Networking\IPAddress.h" />
@@ -107,9 +108,11 @@
<CustomBuild Include="include\Graphics\GraphicsConfig.h" />
<CustomBuild Include="include\Graphics\GraphicsPipeline.h" />
<CustomBuild Include="include\Graphics\ImGuiRenderer.h" />
<CustomBuild Include="include\Graphics\Mesh.h" />
<CustomBuild Include="include\Graphics\RenderPass.h" />
<CustomBuild Include="include\Graphics\Shader.h" />
<CustomBuild Include="include\Graphics\Texture.h" />
<CustomBuild Include="include\Graphics\VertexData.h" />
<CustomBuild Include="include\Juliet.h" />
<CustomBuild Include="Juliet.bff" />
<CustomBuild Include="src\Core\Application\ApplicationManager.cpp" />
@@ -157,6 +160,7 @@
<CustomBuild Include="src\Core\Math\MathRound.cpp" />
<CustomBuild Include="src\Core\Memory\Allocator.cpp" />
<CustomBuild Include="src\Core\Memory\MemoryArena.cpp" />
<CustomBuild Include="src\Core\Memory\MemoryArenaDebug.cpp" />
<CustomBuild Include="src\Core\Memory\MemoryArenaTests.cpp" />
<CustomBuild Include="src\Core\Memory\ScratchArena.cpp" />
<CustomBuild Include="src\Core\Networking\NetworkPacket.cpp" />
@@ -218,10 +222,15 @@
<CustomBuild Include="src\Graphics\Graphics.cpp" />
<CustomBuild Include="src\Graphics\GraphicsDevice.h" />
<CustomBuild Include="src\Graphics\ImGuiRenderer.cpp" />
<CustomBuild Include="src\Graphics\Mesh.cpp" />
<CustomBuild Include="src\UnitTest\Container\VectorUnitTest.cpp" />
<CustomBuild Include="src\UnitTest\RunUnitTests.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\Graphics\MeshRenderer.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\Graphics\MeshRenderer.cpp" />
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{ab9c7e88-6c94-4f93-bc2a-7f5284b7d434}</ProjectGuid>

View File

@@ -106,6 +106,9 @@
<CustomBuild Include="include\Core\Memory\MemoryArena.h">
<Filter>include\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="include\Core\Memory\MemoryArenaDebug.h">
<Filter>include\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="include\Core\Memory\ScratchArena.h">
<Filter>include\Core\Memory</Filter>
</CustomBuild>
@@ -169,6 +172,9 @@
<CustomBuild Include="include\Graphics\ImGuiRenderer.h">
<Filter>include\Graphics</Filter>
</CustomBuild>
<CustomBuild Include="include\Graphics\Mesh.h">
<Filter>include\Graphics</Filter>
</CustomBuild>
<CustomBuild Include="include\Graphics\RenderPass.h">
<Filter>include\Graphics</Filter>
</CustomBuild>
@@ -178,6 +184,9 @@
<CustomBuild Include="include\Graphics\Texture.h">
<Filter>include\Graphics</Filter>
</CustomBuild>
<CustomBuild Include="include\Graphics\VertexData.h">
<Filter>include\Graphics</Filter>
</CustomBuild>
<CustomBuild Include="include\Juliet.h">
<Filter>include</Filter>
</CustomBuild>
@@ -318,6 +327,9 @@
<CustomBuild Include="src\Core\Memory\MemoryArena.cpp">
<Filter>src\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="src\Core\Memory\MemoryArenaDebug.cpp">
<Filter>src\Core\Memory</Filter>
</CustomBuild>
<CustomBuild Include="src\Core\Memory\MemoryArenaTests.cpp">
<Filter>src\Core\Memory</Filter>
</CustomBuild>
@@ -501,6 +513,9 @@
<CustomBuild Include="src\Graphics\ImGuiRenderer.cpp">
<Filter>src\Graphics</Filter>
</CustomBuild>
<CustomBuild Include="src\Graphics\Mesh.cpp">
<Filter>src\Graphics</Filter>
</CustomBuild>
<CustomBuild Include="src\UnitTest\Container\VectorUnitTest.cpp">
<Filter>src\UnitTest\Container</Filter>
</CustomBuild>

View File

@@ -8,5 +8,6 @@ namespace Juliet
{
enum class JulietInit_Flags : uint8;
struct Arena;
extern JULIET_API void StartApplication(IApplication& app, JulietInit_Flags flags);
} // namespace Juliet

View File

@@ -1,5 +1,7 @@
#pragma once
#include <Core/Common/NonNullPtr.h>
namespace Juliet
{
struct RenderPass;
@@ -7,25 +9,26 @@ namespace Juliet
struct Texture;
struct ColorTargetInfo;
struct DepthStencilTargetInfo;
struct Arena;
class IApplication
{
public:
virtual ~IApplication() = default;
virtual void Init() = 0;
virtual void Shutdown() = 0;
virtual void Update() = 0;
virtual bool IsRunning() = 0;
virtual ~IApplication() = default;
virtual void Init(NonNullPtr<Arena> arena) = 0;
virtual void Shutdown() = 0;
virtual void Update() = 0;
virtual bool IsRunning() = 0;
// Accessors for Engine Systems
virtual struct Window* GetPlatformWindow() = 0;
virtual struct GraphicsDevice* GetGraphicsDevice() = 0;
// Render Lifecycle (Engine-Managed Render Loop)
virtual void OnPreRender(CommandList* cmd) = 0;
virtual void OnRender(RenderPass* pass, CommandList* cmd) = 0;
virtual ColorTargetInfo GetColorTargetInfo(Texture* swapchainTexture) = 0;
virtual DepthStencilTargetInfo* GetDepthTargetInfo() = 0;
virtual struct Camera GetDebugCamera() = 0;
virtual void OnPreRender(CommandList* cmd) = 0;
virtual void OnRender(RenderPass* pass, CommandList* cmd) = 0;
virtual ColorTargetInfo GetColorTargetInfo(Texture* swapchainTexture) = 0;
virtual DepthStencilTargetInfo* GetDepthTargetInfo() = 0;
virtual struct Camera GetDebugCamera() = 0;
};
} // namespace Juliet

View File

@@ -15,7 +15,7 @@ namespace Juliet
class NonNullPtr
{
public:
NonNullPtr(Type* ptr)
inline NonNullPtr(Type* ptr)
: InternalPtr(ptr)
{
Assert(ptr, "Tried to initialize a NonNullPtr with a null pointer");
@@ -64,7 +64,7 @@ namespace Juliet
return *InternalPtr;
}
Type* operator->() const
inline Type* operator->() const
{
Assert(InternalPtr, "NonNullPtr: Internal Pointer is Null");
return InternalPtr;

View File

@@ -9,24 +9,28 @@ namespace Juliet
template <typename Type, size_t ReserveSize = 16, bool AllowRealloc = false>
struct VectorArena
{
void Create(JULIET_DEBUG_ONLY(const char* name = "VectorArena"))
void Create(JULIET_DEBUG_PARAM_FIRST(const char* name = nullptr))
{
Assert(!Arena);
static_assert(AllowRealloc == false);
DataFirst = DataLast = nullptr;
Count = 0;
ArenaParams params{ .AllowRealloc = AllowRealloc JULIET_DEBUG_ONLY(, .CanReserveMore = false) };
Arena = ArenaAllocate(params JULIET_DEBUG_ONLY(, name));
JULIET_DEBUG_ONLY(Name = name ? name : Name;)
ArenaParams params{ .AllowRealloc = AllowRealloc JULIET_DEBUG_PARAM(.CanReserveMore = false) };
Arena = ArenaAllocate(params JULIET_DEBUG_PARAM(Name));
InternalArena = true;
Reserve(ReserveSize);
}
void Create(NonNullPtr<Arena> arena)
void Create(NonNullPtr<Arena> arena JULIET_DEBUG_PARAM(const char* name = nullptr))
{
Assert(!Arena);
JULIET_DEBUG_ONLY(Name = name ? name : Name;)
DataFirst = DataLast = nullptr;
Count = 0;
Arena = arena.Get();
@@ -53,14 +57,17 @@ namespace Juliet
{
if (Data == nullptr)
{
Data = ArenaPushArray<Type>(Arena, newCapacity);
Data = ArenaPushArray<Type>(Arena, newCapacity JULIET_DEBUG_PARAM(Name));
}
else if (AllowRealloc && !InternalArena)
else if constexpr (AllowRealloc)
{
DataFirst = Data = static_cast<Type*>(ArenaReallocate(Arena, Data, Capacity * sizeof(Type),
newCapacity * sizeof(Type), AlignOf(Type), true
JULIET_DEBUG_ONLY(, "VectorRealloc")));
DataLast = Data + Count - 1;
if (!InternalArena)
{
DataFirst = Data =
static_cast<Type*>(ArenaReallocate(Arena, Data, Capacity * sizeof(Type), newCapacity * sizeof(Type),
AlignOf(Type), true JULIET_DEBUG_PARAM("VectorRealloc")));
DataLast = Data + Count - 1;
}
}
Capacity = newCapacity;
}
@@ -91,6 +98,34 @@ namespace Juliet
}
}
void PushBack(const Type* buffer, size_t amount)
{
Assert(Arena);
if (Count + amount > Capacity)
{
if (Capacity == 0 && Count + amount < ReserveSize)
{
Reserve(ReserveSize);
}
else
{
size_t newCapacity = Max(Capacity * 2, AlignPow2(Capacity + amount, AlignOf(Type)));
Reserve(newCapacity);
}
}
Type* dst = Data + Count;
MemCopy(dst, buffer, amount * sizeof(Type));
if (Count == 0)
{
DataFirst = dst;
}
DataLast = dst + amount;
Count += amount;
}
void PushBack(const Type& value)
{
Assert(Arena);
@@ -158,13 +193,20 @@ namespace Juliet
{
Assert(Arena);
ArenaClear(Arena);
if (InternalArena)
{
ArenaClear(Arena);
Data = nullptr;
Capacity = 0;
Reserve(ReserveSize);
}
DataFirst = DataLast = nullptr;
Data = nullptr;
Count = 0;
Capacity = 0;
}
bool IsEmpty() const { return Count == 0; }
// C++ Accessors for loop supports and Index based access
Type& operator[](size_t index) { return DataFirst[index]; }
const Type& operator[](size_t index) const { return DataFirst[index]; }
@@ -176,7 +218,9 @@ namespace Juliet
const Type* end() const { return DataFirst + Count; }
Type* First() { return DataFirst; }
Type* Front() { return DataFirst; }
Type* Last() { return DataLast; }
Type* Back() { return DataLast; }
size_t Size() const { return Count; }
@@ -187,6 +231,7 @@ namespace Juliet
size_t Count;
size_t Capacity;
bool InternalArena : 1;
JULIET_DEBUG_ONLY(const char* Name = "VectorArena";)
};
static_assert(std::is_standard_layout_v<VectorArena<int>>,
"VectorArena must have a standard layout to remain POD-like.");

View File

@@ -2,6 +2,7 @@
#include <Core/Common/CoreTypes.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Juliet.h>
namespace Juliet
@@ -16,4 +17,5 @@ namespace Juliet
extern JULIET_API void HideWindow(NonNullPtr<Window> window);
extern JULIET_API WindowID GetWindowID(NonNullPtr<Window> window);
extern JULIET_API void SetWindowTitle(NonNullPtr<Window> window, String title);
} // namespace Juliet

View File

@@ -5,7 +5,16 @@
namespace Juliet
{
// Returns the path to the application directory
extern JULIET_API String GetBasePath();
[[nodiscard]] extern JULIET_API String GetBasePath();
extern JULIET_API bool IsAbsolutePath(String path);
// Returns the resolved base path to the compiled shaders directory.
// In dev, this resolves to ../../Assets/compiled/ relative to the exe.
// In shipping, this resolves to Assets/Shaders/ next to the exe.
[[nodiscard]] extern JULIET_API String GetAssetBasePath();
// Builds a full path to an asset file given its filename (e.g. "Triangle.vert.dxil").
// The caller owns the returned buffer and must free it.
[[nodiscard]] extern JULIET_API String GetAssetPath(String filename);
[[nodiscard]]extern JULIET_API bool IsAbsolutePath(String path);
} // namespace Juliet

View File

@@ -1,6 +1,7 @@
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Graphics/D3D12/D3D12Buffer.h>
#include <Juliet.h>
namespace Juliet
@@ -31,6 +32,11 @@ namespace Juliet
} // namespace Memory
namespace Debug
{
JULIET_API bool IsDebuggerPresent();
} // namespace Debug
using EntryPointFunc = int (*)(int, wchar_t**);
JULIET_API int Bootstrap(EntryPointFunc entryPointFunc, int argc, wchar_t** argv);
} // namespace Juliet

View File

@@ -3,6 +3,8 @@
#include <Core/Common/CoreTypes.h>
#include <Core/Common/NonNullPtr.h>
#ifdef JULIET_ENABLE_IMGUI
struct ImGuiContext;
namespace Juliet
@@ -25,3 +27,5 @@ namespace Juliet
JULIET_API void RunTests();
} // namespace ImGuiService
} // namespace Juliet
#endif // JULIET_ENABLE_IMGUI

View File

@@ -13,12 +13,12 @@ namespace Juliet
All = 0xFb
};
struct MemoryArena;
struct Arena;
struct GameInitParams
{
MemoryArena* GameArena;
MemoryArena* ScratchArena;
Arena* GameArena;
Arena* ScratchArena;
};
void JulietInit(JulietInit_Flags flags);

View File

@@ -22,6 +22,7 @@ namespace Juliet
extern void JULIET_API LogScopeEnd();
extern void JULIET_API Log(LogLevel level, LogCategory category, const char* fmt, ...);
extern void JULIET_API LogDebug(LogCategory category, const char* fmt, ...);
extern void JULIET_API LogMessage(LogCategory category, const char* fmt, ...);
extern void JULIET_API LogWarning(LogCategory category, const char* fmt, ...);
extern void JULIET_API LogError(LogCategory category, const char* fmt, ...);

View File

@@ -4,9 +4,10 @@ namespace Juliet
{
enum class LogLevel : uint8
{
Message = 0,
Warning = 1,
Error = 2,
Debug = 0,
Message = 1,
Warning = 2,
Error = 3,
};
enum class LogCategory : uint8
@@ -15,7 +16,7 @@ namespace Juliet
Graphics = 1,
Networking = 2,
Engine = 3,
Tool = 4,
Tool = 4,
Game = 5,
};
} // namespace Juliet

View File

@@ -37,6 +37,7 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
(void)hPrev;
(void)szCmdLine;
(void)sw;
return Juliet::Bootstrap(JulietMain, __argc, __wargv);
}
}

View File

@@ -8,33 +8,99 @@ namespace Juliet
struct Matrix
{
float m[4][4];
};
static Matrix Identity()
{
Matrix result = {};
result.m[0][0] = 1.0f;
result.m[1][1] = 1.0f;
result.m[2][2] = 1.0f;
result.m[3][3] = 1.0f;
return result;
}
[[nodiscard]] inline Matrix MatrixIdentity()
{
Matrix result = {};
result.m[0][0] = 1.0f;
result.m[1][1] = 1.0f;
result.m[2][2] = 1.0f;
result.m[3][3] = 1.0f;
return result;
}
Matrix operator*(const Matrix& rhs) const
[[nodiscard]] inline Matrix operator*(const Matrix& lhs, const Matrix& rhs)
{
Matrix result = {};
for (int i = 0; i < 4; ++i)
{
Matrix result = {};
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j)
{
for (int j = 0; j < 4; ++j)
for (int k = 0; k < 4; ++k)
{
for (int k = 0; k < 4; ++k)
{
result.m[i][j] += m[i][k] * rhs.m[k][j];
}
result.m[i][j] += lhs.m[i][k] * rhs.m[k][j];
}
}
return result;
}
};
return result;
}
[[nodiscard]] inline Matrix MatrixTranslation(float x, float y, float z)
{
Matrix result = MatrixIdentity();
result.m[0][3] = x;
result.m[1][3] = y;
result.m[2][3] = z;
return result;
}
[[nodiscard]] inline Matrix MatrixScale(float x, float y, float z)
{
Matrix result = MatrixIdentity();
result.m[0][0] = x;
result.m[1][1] = y;
result.m[2][2] = z;
return result;
}
[[nodiscard]] inline Matrix MatrixRotationX(float radians)
{
float c = cosf(radians);
float s = sinf(radians);
Matrix result = MatrixIdentity();
result.m[1][1] = c;
result.m[1][2] = -s;
result.m[2][1] = s;
result.m[2][2] = c;
return result;
}
[[nodiscard]] inline Matrix MatrixRotationY(float radians)
{
float c = cosf(radians);
float s = sinf(radians);
Matrix result = MatrixIdentity();
result.m[0][0] = c;
result.m[0][2] = s;
result.m[2][0] = -s;
result.m[2][2] = c;
return result;
}
[[nodiscard]] inline Matrix MatrixRotationZ(float radians)
{
float c = cosf(radians);
float s = sinf(radians);
Matrix result = MatrixIdentity();
result.m[0][0] = c;
result.m[0][1] = -s;
result.m[1][0] = s;
result.m[1][1] = c;
return result;
}
inline void MatrixTranslate(Matrix& m, const Vector3& v)
{
m.m[0][3] += v.x;
m.m[1][3] += v.y;
m.m[2][3] += v.z;
}
[[nodiscard]] inline Matrix MatrixRotation(float x, float y, float z)
{
return MatrixRotationX(x) * MatrixRotationY(y) * MatrixRotationZ(z);
}
inline Matrix LookAt(const Vector3& eye, const Vector3& target, const Vector3& up)
{
@@ -83,4 +149,46 @@ namespace Juliet
result.m[3][3] = 0.0f;
return result;
}
[[nodiscard]] inline Matrix MatrixInverse(const Matrix& m)
{
Matrix out = {};
float m00 = m.m[0][0], m01 = m.m[0][1], m02 = m.m[0][2], m03 = m.m[0][3];
float m10 = m.m[1][0], m11 = m.m[1][1], m12 = m.m[1][2], m13 = m.m[1][3];
float m20 = m.m[2][0], m21 = m.m[2][1], m22 = m.m[2][2], m23 = m.m[2][3];
float m30 = m.m[3][0], m31 = m.m[3][1], m32 = m.m[3][2], m33 = m.m[3][3];
out.m[0][0] = m11 * m22 * m33 - m11 * m23 * m32 - m21 * m12 * m33 + m21 * m13 * m32 + m31 * m12 * m23 - m31 * m13 * m22;
out.m[1][0] = -m10 * m22 * m33 + m10 * m23 * m32 + m20 * m12 * m33 - m20 * m13 * m32 - m30 * m12 * m23 + m30 * m13 * m22;
out.m[2][0] = m10 * m21 * m33 - m10 * m23 * m31 - m20 * m11 * m33 + m20 * m13 * m31 + m30 * m11 * m23 - m30 * m13 * m21;
out.m[3][0] = -m10 * m21 * m32 + m10 * m22 * m31 + m20 * m11 * m32 - m20 * m12 * m31 - m30 * m11 * m22 + m30 * m12 * m21;
out.m[0][1] = -m01 * m22 * m33 + m01 * m23 * m32 + m21 * m02 * m33 - m21 * m03 * m32 - m31 * m02 * m23 + m31 * m03 * m22;
out.m[1][1] = m00 * m22 * m33 - m00 * m23 * m32 - m20 * m02 * m33 + m20 * m03 * m32 + m30 * m02 * m23 - m30 * m03 * m22;
out.m[2][1] = -m00 * m21 * m33 + m00 * m23 * m31 + m20 * m01 * m33 - m20 * m03 * m31 - m30 * m01 * m23 + m30 * m03 * m21;
out.m[3][1] = m00 * m21 * m32 - m00 * m22 * m31 - m20 * m01 * m32 + m20 * m02 * m31 + m30 * m01 * m22 - m30 * m02 * m21;
out.m[0][2] = m01 * m12 * m33 - m01 * m13 * m32 - m11 * m02 * m33 + m11 * m03 * m32 + m31 * m02 * m13 - m31 * m03 * m12;
out.m[1][2] = -m00 * m12 * m33 + m00 * m13 * m32 + m10 * m02 * m33 - m10 * m03 * m32 - m30 * m02 * m13 + m30 * m03 * m12;
out.m[2][2] = m00 * m11 * m33 - m00 * m13 * m31 - m10 * m01 * m33 + m10 * m03 * m31 + m30 * m01 * m13 - m30 * m03 * m11;
out.m[3][2] = -m00 * m11 * m32 + m00 * m12 * m31 + m10 * m01 * m32 - m10 * m02 * m31 - m30 * m01 * m12 + m30 * m02 * m11;
out.m[0][3] = -m01 * m12 * m23 + m01 * m13 * m22 + m11 * m02 * m23 - m11 * m03 * m22 - m21 * m02 * m13 + m21 * m03 * m12;
out.m[1][3] = m00 * m12 * m23 - m00 * m13 * m22 - m10 * m02 * m23 + m10 * m03 * m22 + m20 * m02 * m13 - m20 * m03 * m12;
out.m[2][3] = -m00 * m11 * m23 + m00 * m13 * m21 + m10 * m01 * m23 - m10 * m03 * m21 - m20 * m01 * m13 + m20 * m03 * m11;
out.m[3][3] = m00 * m11 * m22 - m00 * m12 * m21 - m10 * m01 * m22 + m10 * m02 * m21 + m20 * m01 * m12 - m20 * m02 * m11;
float det = m00 * out.m[0][0] + m01 * out.m[1][0] + m02 * out.m[2][0] + m03 * out.m[3][0];
if (det != 0.0f)
{
float invDet = 1.0f / det;
for (int r = 0; r < 4; ++r)
for (int c = 0; c < 4; ++c)
out.m[r][c] *= invDet;
}
return out;
}
} // namespace Juliet

View File

@@ -5,7 +5,6 @@
#include <Core/Common/NonNullPtr.h>
#include <Core/Common/String.h>
#include <Core/Memory/Utils.h>
#include <Core/Memory/Utils.h>
#include <Juliet.h>
#include <typeinfo> // Added for typeid
@@ -44,7 +43,7 @@ namespace Juliet
JULIET_DEBUG_ONLY(uint16 LostNodeCount;)
JULIET_DEBUG_ONLY(bool CanReserveMore : 1;)
JULIET_DEBUG_ONLY(Arena* GlobalNext;)
JULIET_DEBUG_ONLY(Arena* GlobalPrev;)
JULIET_DEBUG_ONLY(ArenaDebugInfo* FirstDebugInfo;)
@@ -52,6 +51,12 @@ namespace Juliet
};
static_assert(sizeof(Arena) <= k_ArenaHeaderSize);
struct TempArena
{
Arena* Arena;
index_t Position;
};
struct ArenaParams
{
uint64 ReserveSize = g_Arena_Default_Reserve_Size;
@@ -61,149 +66,38 @@ namespace Juliet
bool AllowRealloc = false;
// When false, will assert if a new block is reserved.
// Useful for Vectors as they are guaranteed to be linear and i wont need to implement memcopy to increase capacity
JULIET_DEBUG_ONLY(bool CanReserveMore : 1 = true;)
};
[[nodiscard]] Arena* ArenaAllocate(const ArenaParams& params
JULIET_DEBUG_ONLY(, const char* name),
const std::source_location& loc = std::source_location::current());
void ArenaRelease(NonNullPtr<Arena> arena);
[[nodiscard]] JULIET_API Arena* ArenaAllocate(const ArenaParams& params JULIET_DEBUG_ONLY(, const char* name),
const std::source_location& loc = std::source_location::current());
JULIET_API void ArenaRelease(NonNullPtr<Arena> arena);
// Raw Push, can be used but templated helpers exists below
// Raw Push, can be used but templated helpers exists below
[[nodiscard]] void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align, bool shouldBeZeroed
JULIET_DEBUG_ONLY(, const char* tag));
[[nodiscard]] void* ArenaReallocate(NonNullPtr<Arena> arena, void* oldPtr, size_t oldSize, size_t newSize,
size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag));
void ArenaPopTo(NonNullPtr<Arena> arena, size_t position);
void ArenaPop(NonNullPtr<Arena> arena, size_t amount);
void ArenaClear(NonNullPtr<Arena> arena);
[[nodiscard]] size_t ArenaPos(NonNullPtr<Arena> arena);
[[nodiscard]] JULIET_API void* ArenaPush(NonNullPtr<Arena> arena, size_t size, size_t align,
bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag));
[[nodiscard]] JULIET_API void* ArenaReallocate(NonNullPtr<Arena> arena, void* oldPtr, size_t oldSize, size_t newSize,
size_t align, bool shouldBeZeroed JULIET_DEBUG_ONLY(, const char* tag));
JULIET_API void ArenaPopTo(NonNullPtr<Arena> arena, size_t position);
JULIET_API void ArenaPop(NonNullPtr<Arena> arena, size_t amount);
JULIET_API void ArenaClear(NonNullPtr<Arena> arena);
[[nodiscard]] JULIET_API size_t ArenaPos(NonNullPtr<Arena> arena);
template <typename Type>
[[nodiscard]] Type* ArenaPushStruct(NonNullPtr<Arena> arena)
[[nodiscard]] Type* ArenaPushStruct(NonNullPtr<Arena> arena JULIET_DEBUG_PARAM(const char* tag = nullptr))
{
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true JULIET_DEBUG_ONLY(, typeid(Type).name())));
return static_cast<Type*>(
ArenaPush(arena, sizeof(Type) * 1, AlignOf(Type), true JULIET_DEBUG_PARAM(tag ? tag : typeid(Type).name())));
}
// #define push_array_no_zero_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align), (0))
// #define push_array_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align), (1))
// #define push_array_no_zero(a, T, c) push_array_no_zero_aligned(a, T, c, Max(8, AlignOf(T)))
// #define push_array(a, T, c) push_array_aligned(a, T, c, Max(8, AlignOf(T)))
template <typename Type>
[[nodiscard]] Type* ArenaPushArray(NonNullPtr<Arena> arena, size_t count)
[[nodiscard]] Type* ArenaPushArray(NonNullPtr<Arena> arena, size_t count JULIET_DEBUG_PARAM(const char* tag = nullptr))
{
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)), true JULIET_DEBUG_ONLY(, typeid(Type).name())));
return static_cast<Type*>(ArenaPush(arena, sizeof(Type) * count, Max(8ull, AlignOf(Type)),
true JULIET_DEBUG_PARAM(tag ? tag : typeid(Type).name())));
}
// --- Paged Memory Architecture ---
struct ArenaAllocation;
struct MemoryBlock
{
static constexpr uint32 kMagic = 0xAA55AA55;
uint32 Magic;
MemoryBlock* Next; // Next block in the chain (Arena) or FreeList (Pool)
size_t TotalSize; // Total size of this block (including header)
size_t Used; // Offset relative to the start of Data
#if JULIET_DEBUG
ArenaAllocation* FirstAllocation = nullptr;
uint64 Pad; // Ensure 16-byte alignment (Size 40 -> 48)
#endif
// Data follows immediately.
// We use a helper to access it to avoid C++ flexible array warning issues if strict
uint8* GetData() { return reinterpret_cast<uint8*>(this + 1); }
const uint8* GetData() const { return reinterpret_cast<const uint8*>(this + 1); }
};
struct MemoryPool
{
void* BaseAddress = nullptr;
size_t TotalSize = 0;
MemoryBlock* FreeList = nullptr;
[[nodiscard]] MemoryBlock* AllocateBlock(size_t minCapacity);
void FreeBlock(MemoryBlock* block);
};
struct MemoryArena
{
MemoryPool* BackingPool;
MemoryBlock* CurrentBlock;
MemoryBlock* FirstBlock;
// Marker behavior is now tricky with pages.
// Simple Marker = { Block*, Offset }
};
struct ArenaMarker
{
MemoryBlock* Block;
size_t Offset;
};
JULIET_API void MemoryArenaCreate(MemoryArena* arena, MemoryPool* pool);
JULIET_API void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment, String tag);
JULIET_API void* ArenaRealloc(MemoryArena* arena, void* oldPtr, size_t oldSize, size_t newSize, size_t alignment, String tag);
JULIET_API bool ArenaPop(MemoryArena* arena, void* ptr, size_t size);
JULIET_API void ArenaReset(MemoryArena* arena);
JULIET_API ArenaMarker ArenaGetMarker(MemoryArena* arena);
JULIET_API void ArenaResetToMarker(MemoryArena* arena, ArenaMarker marker);
// --- Global Arenas & Management ---
// Returns a global arena that resets every frame.
JULIET_API MemoryArena* GetScratchArena();
// Persistent game arena.
JULIET_API MemoryArena* GetGameArena();
// Internal engine function to reset the scratch arena.
JULIET_API void ScratchArenaReset();
// Internal engine function to initialize memory arenas.
void MemoryArenasInit();
// Internal engine function to shutdown memory arenas.
// Internal engine function to shutdown memory arenas.
void MemoryArenasShutdown();
#if JULIET_DEBUG
JULIET_API Arena* GetGlobalArenaListHead();
#endif
template <typename T>
inline T* ArenaPushType(MemoryArena* arena, String tag)
{
T* result = static_cast<T*>(ArenaPush(arena, sizeof(T), alignof(T), tag));
if (result)
{
MemSet(result, 0, sizeof(T));
}
return result;
}
template <typename T>
inline T* ArenaPushArray(MemoryArena* arena, size_t count, String tag)
{
T* result = static_cast<T*>(ArenaPush(arena, sizeof(T) * count, alignof(T), tag));
if (result)
{
MemSet(result, 0, sizeof(T) * count);
}
return result;
}
template <typename T>
inline T* ArenaRealloc(MemoryArena* arena, T* oldPtr, size_t oldCount, size_t newCount, String tag)
{
return static_cast<T*>(Juliet::ArenaRealloc(arena, static_cast<void*>(oldPtr), sizeof(T) * oldCount,
sizeof(T) * newCount, alignof(T), tag));
}
TempArena ArenaTempBegin(NonNullPtr<Arena> arena);
void ArenaTempEnd(TempArena temp);
} // namespace Juliet

View File

@@ -1,9 +1,12 @@
#pragma once
#include <Core/Memory/MemoryArena.h>
#include <Juliet.h>
#if JULIET_DEBUG
namespace Juliet::Debug
{
JULIET_API void DebugDrawMemoryArena();
} // namespace Juliet::Debug
#endif

View File

@@ -8,7 +8,8 @@ namespace Juliet
struct Engine
{
IApplication* Application = nullptr;
IApplication* Application = nullptr;
Arena* PlatformArena = nullptr;
};
void InitializeEngine(JulietInit_Flags flags);

View File

@@ -134,13 +134,11 @@ namespace Juliet
extern JULIET_API void BindGraphicsPipeline(NonNullPtr<RenderPass> renderPass, NonNullPtr<GraphicsPipeline> graphicsPipeline);
extern JULIET_API void DrawPrimitives(NonNullPtr<RenderPass> renderPass, uint32 numVertices, uint32 numInstances,
uint32 firstVertex, uint32 firstInstance);
extern JULIET_API void DrawIndexedPrimitives(NonNullPtr<RenderPass> renderPass, uint32 numIndices,
uint32 numInstances, uint32 firstIndex, uint32 vertexOffset,
uint32 firstInstance);
extern JULIET_API void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer, IndexFormat format);
extern JULIET_API void DrawIndexedPrimitives(NonNullPtr<RenderPass> renderPass, uint32 numIndices, uint32 numInstances,
uint32 firstIndex, uint32 vertexOffset, uint32 firstInstance);
extern JULIET_API void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer,
IndexFormat format, size_t indexCount, index_t offset);
extern JULIET_API void SetPushConstants(NonNullPtr<CommandList> commandList, ShaderStage stage,
uint32 rootParameterIndex, uint32 numConstants, const void* constants);
@@ -166,6 +164,8 @@ namespace Juliet
extern JULIET_API GraphicsBuffer* CreateGraphicsBuffer(NonNullPtr<GraphicsDevice> device, const BufferCreateInfo& createInfo);
extern JULIET_API GraphicsTransferBuffer* CreateGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device,
const TransferBufferCreateInfo& createInfo);
extern JULIET_API void* MapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API void UnmapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer);
extern JULIET_API void* MapGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer);
extern JULIET_API void UnmapGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer);
extern JULIET_API void CopyBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> dst,

View File

@@ -8,7 +8,6 @@ namespace Juliet
IndexBuffer = 1 << 0,
ConstantBuffer = 1 << 1,
StructuredBuffer = 1 << 2,
VertexBuffer = 1 << 3,
};
enum class TransferBufferUsage : uint8
@@ -20,7 +19,9 @@ namespace Juliet
struct BufferCreateInfo
{
size_t Size;
size_t Stride;
BufferUsage Usage;
bool IsDynamic;
};
struct TransferBufferCreateInfo

View File

@@ -0,0 +1,15 @@
#pragma once
#include <Juliet.h>
#include <Core/Math/Vector.h>
namespace Juliet
{
struct PointLight
{
Vector3 Position;
float Radius;
Vector3 Color;
float Intensity;
};
} // namespace Juliet

View File

@@ -0,0 +1,23 @@
#pragma once
#include <Core/Common/CoreTypes.h>
#include <Core/Common/NonNullPtr.h>
#include <Core/Math/Matrix.h>
#include <Juliet.h>
namespace Juliet
{
struct Arena;
struct Vertex;
struct Mesh
{
size_t VertexCount;
size_t IndexCount;
index_t VertexOffset;
index_t IndexOffset;
Matrix Transform = MatrixIdentity();
};
} // namespace Juliet

View File

@@ -0,0 +1,75 @@
#pragma once
#include <Core/Container/Vector.h>
#include <Core/Math/Matrix.h>
#include <Graphics/Lighting.h>
#include <Graphics/PushConstants.h>
#include <Graphics/VertexData.h>
#include <Juliet.h>
namespace Juliet
{
struct GraphicsTransferBuffer;
struct RenderPass;
struct CommandList;
struct GraphicsBuffer;
struct Window;
struct GraphicsPipeline;
struct GraphicsDevice;
struct Mesh;
using MeshID = index_t;
using LightID = index_t;
constexpr size_t kGeometryPage = Megabytes(64);
constexpr size_t kIndexPage = Megabytes(32);
constexpr size_t kDefaultMeshNumber = 500;
constexpr size_t kDefaultVertexCount = 2'000'000; // Fit less than one geometry page
constexpr size_t kDefaultIndexCount = 16'000'000; // Fit less than one index page
constexpr size_t kDefaultLightCount = 1024;
struct MeshRenderer
{
// Note we prevent realloc for now.
VectorArena<Mesh, kDefaultMeshNumber, false> Meshes;
VectorArena<Vertex, kDefaultVertexCount, false> Vertices;
VectorArena<Index, kDefaultIndexCount, false> Indices;
VectorArena<PointLight, kDefaultLightCount, false> PointLights;
GraphicsBuffer* VertexBuffer;
GraphicsBuffer* IndexBuffer;
GraphicsTransferBuffer* StreamCopyBuffer;
GraphicsTransferBuffer* LoadCopyBuffer;
GraphicsBuffer* LightsBuffer;
PointLight* MappedLights;
GraphicsDevice* Device;
GraphicsPipeline* Pipeline;
};
JULIET_API void InitializeMeshRenderer(NonNullPtr<Arena> arena);
[[nodiscard]] JULIET_API bool InitializeMeshRendererGraphics(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window);
JULIET_API void ShutdownMeshRendererGraphics();
JULIET_API void ShutdownMeshRenderer();
JULIET_API void LoadMeshesOnGPU(NonNullPtr<CommandList> cmdList);
JULIET_API void RenderMeshes(NonNullPtr<RenderPass> pass, NonNullPtr<CommandList> cmdList, PushData& pushData);
// Lights
[[nodiscard]] JULIET_API LightID AddPointLight(const PointLight& light);
JULIET_API void SetPointLightPosition(LightID id, const Vector3& position);
JULIET_API void SetPointLightColor(LightID id, const Vector3& color);
JULIET_API void SetPointLightRadius(LightID id, float radius);
JULIET_API void SetPointLightIntensity(LightID id, float intensity);
JULIET_API void ClearPointLights();
// Utils
[[nodiscard]] JULIET_API MeshID AddCube();
[[nodiscard]] JULIET_API MeshID AddQuad();
JULIET_API void SetMeshTransform(MeshID id, const Matrix& transform);
#if ALLOW_SHADER_HOT_RELOAD
JULIET_API void ReloadMeshRendererShaders();
#endif
} // namespace Juliet

View File

@@ -0,0 +1,30 @@
#pragma once
#include <Core/Math/Matrix.h>
#include <Core/Math/Vector.h>
#include <Juliet.h>
namespace Juliet
{
struct PushData
{
Matrix ViewProjection;
uint32 MeshIndex;
uint32 TransformsBufferIndex;
uint32 BufferIndex;
uint32 TextureIndex;
uint32 VertexOffset;
uint32 Padding;
float Scale[2];
float Translate[2];
float Padding2[2];
Vector3 GlobalLightDirection;
float GlobalLightPad;
Vector3 GlobalLightColor;
float GlobalAmbientIntensity;
uint32 LightBufferIndex;
uint32 ActiveLightCount;
};
} // namespace Juliet

View File

@@ -0,0 +1,29 @@
#pragma once
#include <Core/Math/Matrix.h>
#include <Juliet.h>
namespace Juliet
{
struct RenderPass;
struct CommandList;
struct Window;
struct GraphicsPipeline;
struct GraphicsDevice;
struct SkyboxRenderer
{
GraphicsDevice* Device;
GraphicsPipeline* Pipeline;
};
[[nodiscard]] JULIET_API bool InitializeSkyboxRenderer(NonNullPtr<GraphicsDevice> device,
NonNullPtr<Window> window);
JULIET_API void ShutdownSkyboxRenderer();
JULIET_API void RenderSkybox(NonNullPtr<RenderPass> pass, NonNullPtr<CommandList> cmdList, const Matrix& viewProjection);
#if ALLOW_SHADER_HOT_RELOAD
JULIET_API void ReloadSkyboxShaders();
#endif
} // namespace Juliet

View File

@@ -0,0 +1,13 @@
#pragma once
namespace Juliet
{
struct Vertex
{
float Position[3];
float Normal[3];
float Color[4];
};
using Index = uint16;
} // namespace Juliet

View File

@@ -22,9 +22,13 @@
#ifdef DEBUG
#define JULIET_DEBUG 1
#define JULIET_DEBUG_ONLY(...) __VA_ARGS__
#define JULIET_DEBUG_PARAM_FIRST(...) __VA_ARGS__
#define JULIET_DEBUG_PARAM(...) , __VA_ARGS__
#else
#define JULIET_DEBUG 0
#define JULIET_DEBUG_ONLY(...)
#define JULIET_DEBUG_PARAM_FIRST(...)
#define JULIET_DEBUG_PARAM(...)
#endif
// Manual override to disable ImGui

View File

@@ -123,7 +123,7 @@ namespace Juliet
{
// Find and destroy
VectorArena<Window>& windows = g_CurrentDisplayDevice->Windows;
for (index_t idx = windows.Size() - 1; idx != 0; --idx)
for (index_t idx = windows.Size(); idx-- > 0;)
{
Window& windowRef = windows[idx];
if (windowRef.ID == window->ID)
@@ -149,6 +149,11 @@ namespace Juliet
return window->ID;
}
void SetWindowTitle(NonNullPtr<Window> window, String title)
{
g_CurrentDisplayDevice->SetWindowTitle(g_CurrentDisplayDevice, window, title);
}
// Display Device Utils. Not exposed in the API
DisplayDevice* GetDisplayDevice()
{

View File

@@ -25,6 +25,7 @@ namespace Juliet
void (*DestroyPlatformWindow)(NonNullPtr<DisplayDevice> self, NonNullPtr<Window> window);
void (*ShowWindow)(NonNullPtr<DisplayDevice> self, NonNullPtr<Window> window);
void (*HideWindow)(NonNullPtr<DisplayDevice> self, NonNullPtr<Window> window);
void (*SetWindowTitle)(NonNullPtr<DisplayDevice> self, NonNullPtr<Window> window, String title);
// Events
void (*PumpEvents)(NonNullPtr<DisplayDevice> self);

View File

@@ -33,10 +33,11 @@ namespace Juliet::Win32
device->DestroyPlatformWindow = DestroyPlatformWindow;
device->ShowWindow = ShowWindow;
device->HideWindow = HideWindow;
device->SetWindowTitle = SetWindowTitle;
device->PumpEvents = PumpEvents;
device->Windows.Create(JULIET_DEBUG_ONLY("Display Windows"));
device->Windows.Create(arena JULIET_DEBUG_PARAM("Display Windows"));
return device;
}

View File

@@ -14,8 +14,10 @@
// For GET_X_LPARAM, GET_Y_LPARAM.
#include <windowsx.h>
#ifdef JULIET_ENABLE_IMGUI
#include <imgui.h> // Need For IMGUI_IMPL_API
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
#endif
namespace Juliet::Win32
{
@@ -145,10 +147,12 @@ namespace Juliet::Win32
LRESULT CALLBACK Win32MainWindowCallback(HWND handle, UINT message, WPARAM wParam, LPARAM lParam)
{
#ifdef JULIET_ENABLE_IMGUI
if (ImGui_ImplWin32_WndProcHandler(handle, message, wParam, lParam))
{
return true;
}
#endif
LRESULT returnCode = -1;

View File

@@ -92,4 +92,13 @@ namespace Juliet::Win32
auto& win32State = static_cast<Window32State&>(*window->State);
::ShowWindow(win32State.Handle, SW_HIDE);
}
void SetWindowTitle(NonNullPtr<DisplayDevice> /*self*/, NonNullPtr<Window> window, String title)
{
Assert(window);
Assert(window->State);
auto& win32State = static_cast<Window32State&>(*window->State);
SetWindowTextA(win32State.Handle, CStr(title));
}
} // namespace Juliet::Win32

View File

@@ -25,4 +25,5 @@ namespace Juliet::Win32
extern void DestroyPlatformWindow(NonNullPtr<DisplayDevice> self, NonNullPtr<Window> window);
extern void ShowWindow(NonNullPtr<DisplayDevice> self, NonNullPtr<Window> window);
extern void HideWindow(NonNullPtr<DisplayDevice> self, NonNullPtr<Window> window);
extern void SetWindowTitle(NonNullPtr<DisplayDevice> self, NonNullPtr<Window> window, String title);
} // namespace Juliet::Win32

View File

@@ -1,14 +1,29 @@
#include <Core/Common/CoreTypes.h>
#include <Core/Common/CoreUtils.h>
#include <Core/Common/String.h>
#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/HAL/Filesystem/Filesystem_Platform.h>
#include <Core/HAL/Filesystem/Filesystem_Private.h>
#include <Core/HAL/Win32.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Memory/Allocator.h>
#include <cstdio>
namespace Juliet
{
namespace
{
String CachedBasePath = {};
String CachedBasePath = {};
String CachedAssetBasePath = {};
bool DirectoryExists(const char* path)
{
Assert(path);
DWORD attributes = GetFileAttributesA(path);
return (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY);
}
}
String GetBasePath()
@@ -20,6 +35,27 @@ namespace Juliet
return CachedBasePath;
}
String GetAssetBasePath()
{
return CachedAssetBasePath;
}
[[nodiscard]] String GetAssetPath(String filename)
{
Assert(IsValid(CachedAssetBasePath));
Assert(IsValid(filename));
size_t totalSize = CachedAssetBasePath.Size + filename.Size + 1;
auto* buffer = static_cast<char*>(Calloc(totalSize, sizeof(char)));
if (!buffer)
{
return {};
}
snprintf(buffer, totalSize, "%s%s", CStr(CachedAssetBasePath), CStr(filename));
return { buffer, totalSize - 1 };
}
bool IsAbsolutePath(String path)
{
if (!IsValid(path))
@@ -29,7 +65,40 @@ namespace Juliet
return Platform::IsAbsolutePath(path);
}
void InitFilesystem() {}
void InitFilesystem()
{
String basePath = GetBasePath();
Assert(IsValid(basePath));
// Probe candidate paths for compiled shader directory
// 1. Shipping layout: Assets/Shaders/ next to the exe
// 2. Dev layout: ../../Assets/compiled/ (exe is in bin/x64Clang-<Config>/)
constexpr const char* kCandidates[] = {
"Assets/Shaders/",
"../../Assets/compiled/"
};
for (const char* candidate : kCandidates)
{
char probePath[512];
snprintf(probePath, sizeof(probePath), "%s%s", CStr(basePath), candidate);
if (DirectoryExists(probePath))
{
size_t len = strlen(probePath);
auto* buffer = static_cast<char*>(Calloc(len + 1, sizeof(char)));
if (buffer)
{
snprintf(buffer, len + 1, "%s", probePath);
CachedAssetBasePath = { buffer, len };
Log(LogLevel::Message, LogCategory::Core, "Asset base path: %s", buffer);
}
return;
}
}
Log(LogLevel::Error, LogCategory::Core, "Filesystem: Could not find Assets/compiled/ directory!");
}
void ShutdownFilesystem()
{
@@ -38,5 +107,10 @@ namespace Juliet
CachedBasePath.Size = 0;
SafeFree(CachedBasePath.Data);
}
if (IsValid(CachedAssetBasePath))
{
CachedAssetBasePath.Size = 0;
SafeFree(CachedAssetBasePath.Data);
}
}
} // namespace Juliet

View File

@@ -58,7 +58,7 @@ namespace Juliet::Platform
bool IsAbsolutePath(String path)
{
if (path.Data || path.Size == 0)
if (!path.Data || path.Size == 0)
{
return false;
}

View File

@@ -21,6 +21,14 @@ namespace Juliet
}
} // namespace Memory
namespace Debug
{
bool IsDebuggerPresent()
{
return Internal::IsDebuggerPresent();
}
} // namespace Debug
int Bootstrap(EntryPointFunc entryPointFunc, int argc, wchar_t** argv)
{
return Internal::OS_Main(entryPointFunc, argc, argv);

View File

@@ -2,16 +2,17 @@
namespace Juliet
{
namespace Memory
namespace Memory::Internal
{
namespace Internal
{
Byte* OS_Reserve(size_t size);
bool OS_Commit(Byte* ptr, size_t size);
void OS_Release(Byte* ptr, size_t size);
} // namespace Internal
} // namespace Memory
Byte* OS_Reserve(size_t size);
bool OS_Commit(Byte* ptr, size_t size);
void OS_Release(Byte* ptr, size_t size);
} // namespace Memory::Internal
namespace Debug::Internal
{
bool IsDebuggerPresent();
} // namespace Debug::Internal
namespace Internal
{
int OS_Main(EntryPointFunc entryPointFunc, int argc, wchar_t** argv);

View File

@@ -3,6 +3,8 @@
#include <Core/HAL/OS/OS.h>
#include <Core/HAL/OS/OS_Private.h>
#include <Core/HAL/Win32.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
namespace Juliet
{
@@ -15,7 +17,7 @@ namespace Juliet
{
Byte* OS_Reserve(size_t size)
{
auto result = static_cast<Byte*>(VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE));
auto* result = static_cast<Byte*>(VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE));
return result;
}
@@ -25,8 +27,12 @@ namespace Juliet
Assert(size <= static_cast<size_t>(MaxValueOf<DWORD>()));
w32_rio_functions.RIODeregisterBuffer(
w32_rio_functions.RIORegisterBuffer(reinterpret_cast<PCHAR>(ptr), static_cast<DWORD>(size)));
if (w32_rio_functions.RIORegisterBuffer != nullptr && w32_rio_functions.RIODeregisterBuffer != nullptr)
{
w32_rio_functions.RIODeregisterBuffer(
w32_rio_functions.RIORegisterBuffer(reinterpret_cast<PCHAR>(ptr), static_cast<DWORD>(size)));
}
return result;
}
@@ -39,6 +45,14 @@ namespace Juliet
}
} // namespace Memory::Internal
namespace Debug::Internal
{
bool IsDebuggerPresent()
{
return ::IsDebuggerPresent();
}
} // namespace Debug::Internal
namespace
{
// Used to handle a crash/exception
@@ -54,8 +68,6 @@ namespace Juliet
{
int OS_Main(EntryPointFunc entryPointFunc, int argc, wchar_t** argv)
{
(void)argc;
(void)argv;
SetUnhandledExceptionFilter(&ExceptionFilter);
// Allow only one instance to be launched.
@@ -74,11 +86,12 @@ namespace Juliet
DWORD rio_byte = 0;
SOCKET Sock = socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
WSAIoctl(Sock, SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid),
(void**)&w32_rio_functions, sizeof(w32_rio_functions), &rio_byte, nullptr, nullptr);
reinterpret_cast<void**>(&w32_rio_functions), sizeof(w32_rio_functions), &rio_byte, nullptr, nullptr);
closesocket(Sock);
}
return entryPointFunc(argc, argv);
int result = entryPointFunc(argc, argv);
return result;
}
} // namespace Internal
} // namespace Juliet

View File

@@ -56,7 +56,8 @@ namespace Juliet
basePathLength + StringLength(code.TransientDLLName) + /* _ */ 1 + kTempDLLBufferSizeForID + 1 /* \0 */;
// Allocate from Scratch Arena (transient)
auto tempDllPath = ArenaPushArray<char>(GetScratchArena(), tempDllMaxBufferSize, ConstString("tempDllPath"));
TempArena temp = ArenaTempBegin(code.Arena);
auto tempDllPath = ArenaPushArray<char>(temp.Arena, tempDllMaxBufferSize);
for (uint32 attempt = 0; attempt < kMaxAttempts; ++attempt)
{
@@ -96,6 +97,7 @@ namespace Juliet
break;
}
}
ArenaTempEnd(temp);
code.Dll = LoadDynamicLibrary(tempDllPath);
if (code.Dll)

View File

@@ -3,9 +3,10 @@
#include <Core/ImGui/ImGuiService.h>
#include <Core/ImGui/ImGuiTests.h>
#include <Core/Logging/LogManager.h>
#include <Core/Memory/MemoryArena.h>
#ifdef JULIET_ENABLE_IMGUI
#include <backends/imgui_impl_win32.h>
#include <imgui.h>
@@ -102,3 +103,5 @@ namespace Juliet::ImGuiService
Juliet::UnitTest::TestImGui();
}
} // namespace Juliet::ImGuiService
#endif // JULIET_ENABLE_IMGUI

View File

@@ -10,6 +10,7 @@ namespace Juliet::UnitTest
{
void TestImGui()
{
#ifdef JULIET_ENABLE_IMGUI
ImGuiContext* ctx = ImGuiService::GetContext();
if (ImGui::GetCurrentContext() != ctx)
@@ -68,5 +69,6 @@ namespace Juliet::UnitTest
(void)drawList;
printf("ImGui tests passed (Exhaustive).\n");
#endif
}
} // namespace Juliet::UnitTest

View File

@@ -28,6 +28,9 @@ namespace Juliet
};
DECLARE_QUEUE(LogsEntry);
// TODO: Debug level per category
const bool kPrintDebugLog = false;
// A log scope accumulates log until end of scope.
// Can be used to accumulate then write in a log file each frame.
struct LogScope
@@ -133,6 +136,11 @@ namespace Juliet
void Log(LogLevel level, LogCategory category, const char* fmt, va_list args)
{
if (level == LogLevel::Debug && kPrintDebugLog == false)
{
return;
}
// TODO : Revisit, copy from https://github.com/Eclmist/Ether/blob/develop/src/common/logging/loggingmanager.cpp
char formattedBuffer[4096];
@@ -163,6 +171,14 @@ namespace Juliet
va_end(args);
}
void LogDebug(LogCategory category, const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
Log(LogLevel::Debug, category, fmt, args);
va_end(args);
}
void LogMessage(LogCategory category, const char* fmt, ...)
{
va_list args;

View File

@@ -75,12 +75,12 @@ namespace Juliet
void ArenaRelease(NonNullPtr<Arena> arena)
{
JULIET_DEBUG_ONLY(DebugUnregisterArena(arena);)
// Release active blocks (Current chain)
for (Arena *node = arena->Current, *previous = nullptr; node != nullptr; node = previous)
{
previous = node->Previous;
JULIET_DEBUG_ONLY(DebugUnregisterArena(node);)
JULIET_DEBUG_ONLY(DebugArenaFreeBlock(node);)
Memory::OS_Release(node, node->Reserved);
@@ -177,7 +177,8 @@ namespace Juliet
commitSize = AlignPow2(size + k_ArenaHeaderSize, align);
}
newBlock = ArenaAllocate({ .ReserveSize = reserveSize, .CommitSize = commitSize } JULIET_DEBUG_ONLY(, arena->Name));
newBlock =
ArenaAllocate({ .ReserveSize = reserveSize, .CommitSize = commitSize } JULIET_DEBUG_ONLY(, arena->Name));
}
newBlock->BasePosition = current->BasePosition + current->Reserved;
@@ -220,7 +221,7 @@ namespace Juliet
MemoryZero(result, sizeToZero);
}
}
if (result == nullptr) [[unlikely]]
{
Log(LogLevel::Error, LogCategory::Core, "Fatal Allocation Failure - Unexpected allocation failure");
@@ -271,7 +272,7 @@ namespace Juliet
block->FreeNodes->Previous = freeNode;
}
block->FreeNodes = freeNode;
#if JULIET_DEBUG
// Remove the debug info for the old allocation (since it's now free)
size_t oldOffset = static_cast<size_t>(static_cast<Byte*>(oldPtr) - reinterpret_cast<Byte*>(block));
@@ -327,374 +328,14 @@ namespace Juliet
return position;
}
namespace UnitTest
TempArena ArenaTempBegin(NonNullPtr<Arena> arena)
{
extern void TestMemoryArena();
index_t position = ArenaPos(arena);
return { arena.Get(), position };
}
// --- MemoryPool Implementation ---
// Simple First-Fit Allocator
MemoryBlock* MemoryPool::AllocateBlock(size_t minCapacity)
void ArenaTempEnd(TempArena temp)
{
// Require space for Header + Data
size_t totalUnalignedSize = sizeof(MemoryBlock) + minCapacity;
size_t requiredSize = (totalUnalignedSize + 15) & ~static_cast<size_t>(15);
MemoryBlock** prevPtr = &FreeList;
MemoryBlock* curr = FreeList;
while (curr)
{
if (curr->TotalSize >= requiredSize)
{
// Match
// Check if we can split this block?
if (curr->TotalSize >= requiredSize + sizeof(MemoryBlock) + 16)
{
// Split
size_t remainingSize = curr->TotalSize - requiredSize;
MemoryBlock* nextBlock = reinterpret_cast<MemoryBlock*>((uint8*)curr + requiredSize);
nextBlock->Magic = MemoryBlock::kMagic;
nextBlock->TotalSize = remainingSize;
nextBlock->Used = 0;
nextBlock->Next = curr->Next;
// Update FreeList to point to the new remaining block instead of curr
*prevPtr = nextBlock;
// Update curr to be the allocated chunk
curr->TotalSize = requiredSize;
}
else
{
// Take the whole block
*prevPtr = curr->Next;
}
curr->Next = nullptr;
curr->Used = 0;
curr->Magic = MemoryBlock::kMagic;
#if JULIET_DEBUG
curr->FirstAllocation = nullptr;
#endif
#if JULIET_DEBUG
if (curr->TotalSize > sizeof(MemoryBlock))
{
MemSet(curr->GetData(), 0xCD, curr->TotalSize - sizeof(MemoryBlock));
}
#endif
return curr;
}
prevPtr = &curr->Next;
curr = curr->Next;
}
// Out of Memory in Pool
Assert(false, "MemoryPool exhausted!");
return nullptr;
ArenaPopTo(temp.Arena, temp.Position);
}
void MemoryPool::FreeBlock(MemoryBlock* block)
{
if (!block)
{
return;
}
Assert(block->Magic == MemoryBlock::kMagic);
// Poison Header and Data in Debug
#if JULIET_DEBUG
DebugFreeArenaAllocations(block);
// 0xDD = Dead Data
MemSet(block->GetData(), 0xDD, block->TotalSize - sizeof(MemoryBlock));
block->Magic = 0xDEADBEEF;
#endif
// Insert at Head of FreeList (Simplest, no coalescing yet)
block->Next = FreeList;
FreeList = block;
}
// --- MemoryArena Implementation ---
void MemoryArenaCreate(MemoryArena* arena, MemoryPool* pool)
{
Assert(arena);
Assert(pool);
arena->BackingPool = pool;
arena->CurrentBlock = nullptr;
arena->FirstBlock = nullptr;
}
// Overload for backward compatibility / tests if needed, but we should switch to using Pools.
// NOTE: The previous signature was (Arena*, void* backing, size_t).
// We are changing the API.
void* ArenaPush(MemoryArena* arena, size_t size, size_t alignment, [[maybe_unused]] String tag)
{
Assert(arena);
Assert(arena->BackingPool);
// Default Block Size (e.g., 64KB or 1MB? Let's use 16KB for granular tests,
// or larger for prod. Let's make it dynamic or standard constant.
constexpr size_t kDefaultBlockSize = 64 * 1024; // 64KB pages
// Alignment check
Assert((alignment & (alignment - 1)) == 0);
if (!arena->CurrentBlock)
{
// Initial Allocation
size_t allocSize = std::max(size, kDefaultBlockSize);
arena->CurrentBlock = arena->BackingPool->AllocateBlock(allocSize);
arena->FirstBlock = arena->CurrentBlock;
}
// Try allocation in CurrentBlock
MemoryBlock* blk = arena->CurrentBlock;
size_t currentAddr = reinterpret_cast<size_t>(blk->GetData()) + blk->Used;
size_t alignmentOffset = 0;
size_t mask = alignment - 1;
if (currentAddr & mask)
{
alignmentOffset = alignment - (currentAddr & mask);
}
if (blk->Used + alignmentOffset + size > blk->TotalSize - sizeof(MemoryBlock))
{
// Overflow! Request new block.
// Strict minimum: what we need now.
// Better: Max(Default, size) to avoid repeating large allocs for tiny overflow?
size_t allocSize = std::max(size, kDefaultBlockSize);
MemoryBlock* newBlock = arena->BackingPool->AllocateBlock(allocSize);
// Link
blk->Next = newBlock;
arena->CurrentBlock = newBlock;
blk = newBlock;
// Recalc for new block (Used should be 0)
currentAddr = reinterpret_cast<size_t>(blk->GetData());
alignmentOffset = 0;
// newBlock check
if (currentAddr & mask)
{
alignmentOffset = alignment - (currentAddr & mask);
}
}
// Commit
blk->Used += alignmentOffset;
void* ptr = blk->GetData() + blk->Used;
JULIET_DEBUG_ONLY(DebugArenaAddAllocation(blk, size, blk->Used, tag);)
blk->Used += size;
return ptr;
}
void* ArenaRealloc(MemoryArena* arena, void* oldPtr, size_t oldSize, size_t newSize, size_t alignment, String tag)
{
Assert(arena);
Assert(oldPtr);
Assert(newSize != 0);
// Optimized Case: Expanding the LAST allocation in the Current Block
MemoryBlock* blk = arena->CurrentBlock;
uint8* oldBytes = static_cast<uint8*>(oldPtr);
// Is oldPtr inside current block?
if (oldBytes >= blk->GetData() && oldBytes < blk->GetData() + blk->TotalSize - sizeof(MemoryBlock))
{
// Is it the last one?
if (oldBytes + oldSize == blk->GetData() + blk->Used)
{
// Can we expand?
if (blk->Used + (newSize - oldSize) <= blk->TotalSize - sizeof(MemoryBlock))
{
// Yes, expand in place
blk->Used += (newSize - oldSize);
#if JULIET_DEBUG
{
ArenaAllocation* t = blk->FirstAllocation;
while (t && t->Next)
t = t->Next;
if (t) t->Size += (newSize - oldSize);
}
#endif
return oldPtr;
}
}
}
// Fallback: Copy
void* newPtr = ArenaPush(arena, newSize, alignment, tag);
MemCopy(newPtr, oldPtr, std::min(oldSize, newSize));
return newPtr;
}
bool ArenaPop(MemoryArena* arena, void* ptr, size_t size)
{
Assert(arena);
Assert(ptr);
Assert(size);
MemoryBlock* blk = arena->CurrentBlock;
Assert(blk);
uint8* ptrBytes = static_cast<uint8*>(ptr);
uint8* currentTop = blk->GetData() + blk->Used;
// Check if this pointer is exactly at the top of the stack (LIFO)
if (ptrBytes + size == currentTop)
{
// Yes, we can just rewind the Used pointer
blk->Used -= size;
JULIET_DEBUG_ONLY(DebugArenaRemoveLastAllocation(blk);)
return true;
}
return false;
}
void ArenaReset(MemoryArena* arena)
{
Assert(arena);
Assert(arena->FirstBlock);
// Keep FirstBlock, Free the rest.
MemoryBlock* curr = arena->FirstBlock->Next;
while (curr)
{
MemoryBlock* next = curr->Next;
arena->BackingPool->FreeBlock(curr);
curr = next;
}
arena->FirstBlock->Next = nullptr;
arena->FirstBlock->Used = 0;
arena->CurrentBlock = arena->FirstBlock;
#if JULIET_DEBUG
// Poison First Block
DebugFreeArenaAllocations(arena->FirstBlock);
MemSet(arena->FirstBlock->GetData(), 0xCD, arena->FirstBlock->TotalSize - sizeof(MemoryBlock));
#endif
}
ArenaMarker ArenaGetMarker(MemoryArena* arena)
{
Assert(arena);
return { arena->CurrentBlock, arena->CurrentBlock ? arena->CurrentBlock->Used : 0 };
}
void ArenaResetToMarker(MemoryArena* arena, ArenaMarker marker)
{
Assert(arena);
if (!marker.Block)
{
// If marker block is null, it might mean "start" or "empty".
// But if the arena has blocks, this is suspicious.
// If the arena was empty when marker was taken, this is valid.
ArenaReset(arena);
return;
}
// Free blocks *after* the marker block
MemoryBlock* curr = marker.Block->Next;
while (curr)
{
MemoryBlock* next = curr->Next;
arena->BackingPool->FreeBlock(curr);
curr = next;
}
marker.Block->Next = nullptr;
marker.Block->Used = marker.Offset;
arena->CurrentBlock = marker.Block;
}
// --- Global Arenas ---
namespace
{
MemoryPool g_ScratchMemory;
MemoryPool g_GameMemory;
MemoryArena g_ScratchArena;
MemoryArena g_GameArena;
// Backing Buffers
void* g_ScratchBuffer = nullptr;
void* g_EngineBuffer = nullptr;
void* g_GameBuffer = nullptr;
constexpr size_t kScratchSize = Megabytes(64);
constexpr size_t kGameSize = Megabytes(512);
void InitPool(MemoryPool* pool, void* buffer, size_t size)
{
pool->BaseAddress = buffer;
pool->TotalSize = size;
// Create one giant initial block
Assert(size > sizeof(MemoryBlock));
MemoryBlock* block = static_cast<MemoryBlock*>(buffer);
block->Magic = MemoryBlock::kMagic;
block->Next = nullptr;
block->TotalSize = size;
block->Used = 0;
pool->FreeList = block;
}
} // namespace
MemoryArena* GetScratchArena()
{
return &g_ScratchArena;
}
MemoryArena* GetGameArena()
{
return &g_GameArena;
}
void ScratchArenaReset()
{
ArenaReset(&g_ScratchArena);
}
void MemoryArenasInit()
{
g_ScratchBuffer = Malloc(kScratchSize);
g_GameBuffer = Malloc(kGameSize);
InitPool(&g_ScratchMemory, g_ScratchBuffer, kScratchSize);
InitPool(&g_GameMemory, g_GameBuffer, kGameSize);
MemoryArenaCreate(&g_ScratchArena, &g_ScratchMemory);
MemoryArenaCreate(&g_GameArena, &g_GameMemory);
#if JULIET_DEBUG
UnitTest::TestMemoryArena();
#endif
}
void MemoryArenasShutdown()
{
// Technically we should free blocks?
// But since we own the giant buffers, we can just free them.
SafeFree(g_ScratchBuffer);
SafeFree(g_EngineBuffer);
SafeFree(g_GameBuffer);
}
} // namespace Juliet

View File

@@ -1,5 +1,5 @@
#include <Core/Memory/MemoryArenaDebug.h>
#include <Core/Memory/MemoryArena.h> // For Arena definition
#include <Core/Memory/MemoryArenaDebug.h>
#if JULIET_DEBUG
@@ -24,7 +24,8 @@ namespace Juliet
if (!g_DebugInfoArena)
{
// Create a dedicated arena for debug info
g_DebugInfoArena = ArenaAllocate({ .ReserveSize = Megabytes(16), .CommitSize = Kilobytes(64) } JULIET_DEBUG_ONLY(, "Debug Info Arena"));
g_DebugInfoArena = ArenaAllocate(
{ .ReserveSize = Megabytes(16), .CommitSize = Kilobytes(64) } JULIET_DEBUG_ONLY(, "Debug Info Arena"));
}
return ArenaPushStruct<ArenaDebugInfo>(g_DebugInfoArena);
@@ -39,34 +40,6 @@ namespace Juliet
}
}
static ArenaAllocation* AllocArenaAllocation()
{
if (g_ArenaAllocationFreeList)
{
ArenaAllocation* info = g_ArenaAllocationFreeList;
g_ArenaAllocationFreeList = info->Next;
info->Next = nullptr;
return info;
}
if (!g_DebugInfoArena)
{
g_DebugInfoArena = ArenaAllocate({ .ReserveSize = Megabytes(16), .CommitSize = Kilobytes(64) } JULIET_DEBUG_ONLY(, "Debug Info Arena"));
}
return ArenaPushStruct<ArenaAllocation>(g_DebugInfoArena);
}
static void FreeArenaAllocation(ArenaAllocation* info)
{
if (info)
{
info->Next = g_ArenaAllocationFreeList;
g_ArenaAllocationFreeList = info;
}
}
// ------------------------
void DebugRegisterArena(NonNullPtr<Arena> arena)
{
// Add to Global List
@@ -95,6 +68,8 @@ namespace Juliet
{
arena->GlobalNext->GlobalPrev = arena->GlobalPrev;
}
arena->GlobalPrev = nullptr;
arena->GlobalNext = nullptr;
}
void DebugArenaSetDebugName(NonNullPtr<Arena> arena, const char* name)
@@ -123,7 +98,7 @@ namespace Juliet
{
ArenaDebugInfo** prevInfo = &block->FirstDebugInfo;
ArenaDebugInfo* info = block->FirstDebugInfo;
while (info)
{
if (info->Offset == oldOffset) // Found it
@@ -163,75 +138,14 @@ namespace Juliet
ArenaDebugInfo* info = AllocDebugInfo();
if (info)
{
info->Tag = tag ? tag : "Untagged";
info->Size = size;
info->Offset = offset;
info->Next = block->FirstDebugInfo;
info->Tag = tag ? tag : "Untagged";
info->Size = size;
info->Offset = offset;
info->Next = block->FirstDebugInfo;
block->FirstDebugInfo = info;
}
}
// Moved to top
void DebugFreeArenaAllocations(MemoryBlock* blk)
{
if (!blk)
{
return;
}
ArenaAllocation* curr = blk->FirstAllocation;
while (curr)
{
ArenaAllocation* next = curr->Next;
FreeArenaAllocation(curr);
curr = next;
}
blk->FirstAllocation = nullptr;
}
void DebugArenaAddAllocation(MemoryBlock* blk, size_t size, size_t offset, String tag)
{
ArenaAllocation* node = AllocArenaAllocation();
node->Offset = offset;
node->Size = size;
node->Tag = tag;
node->Next = nullptr;
if (!blk->FirstAllocation)
blk->FirstAllocation = node;
else
{
ArenaAllocation* t = blk->FirstAllocation;
while (t->Next)
t = t->Next;
t->Next = node;
}
}
void DebugArenaRemoveLastAllocation(MemoryBlock* blk)
{
ArenaAllocation* t = blk->FirstAllocation;
ArenaAllocation* prev = nullptr;
while (t && t->Next)
{
prev = t;
t = t->Next;
}
if (t)
{
FreeArenaAllocation(t);
if (prev)
{
prev->Next = nullptr;
}
else
{
blk->FirstAllocation = nullptr;
}
}
}
// Accessor for global list head (if needed by other files)
Arena* GetGlobalArenaListHead()
{
return g_ArenaGlobalHead;

View File

@@ -1,10 +1,17 @@
#include <Core/Common/String.h>
#include <Engine/Debug/MemoryDebugger.h>
#include <Core/Common/String.h>
#include <Core/Memory/MemoryArena.h> // Added for Arena definition
#include <Engine/Debug/MemoryDebugger.h>
#include <Core/Container/Vector.h>
#include <Core/Memory/MemoryArena.h> // Added for Arena definition
#include <Core/Memory/MemoryArenaDebug.h>
#include <Engine/Debug/MemoryDebugger.h>
#include <imgui.h>
#if JULIET_DEBUG
namespace Juliet
{
Arena* GetGlobalArenaListHead();
} // namespace Juliet
namespace Juliet::Debug
{
@@ -18,7 +25,6 @@ namespace Juliet::Debug
bool ScrollListToSelected = false;
};
#if JULIET_DEBUG
struct PagedArenaDebugState
{
float Zoom = 1.0f;
@@ -38,63 +44,26 @@ namespace Juliet::Debug
PagedArenaDebugState& GetPagedArenaState(const Arena* arena)
{
if (s_PagedArenaStates.Arena == nullptr)
{
s_PagedArenaStates.Create(JULIET_DEBUG_ONLY("DebugState States"));
}
for (auto& entry : s_PagedArenaStates)
{
if (entry.Arena == arena)
{
return entry.State;
}
}
PagedArenaStateEntry newEntry;
newEntry.Arena = arena;
// Default construct state
newEntry.State = PagedArenaDebugState();
s_PagedArenaStates.PushBack(newEntry);
return s_PagedArenaStates.Last()->State;
}
#endif
ArenaDebugState& GetState(const String& name)
{
// Simple static map-like storage using standard map would be better but we minimize deps.
// Just use a few static vars since we have known 3 arenas.
static ArenaDebugState s_GameState;
static ArenaDebugState s_EngineState;
static ArenaDebugState s_ScratchState;
if (StringCompare(name, ConstString("Game Arena")) == 0)
if (s_PagedArenaStates.Arena == nullptr)
{
return s_GameState;
s_PagedArenaStates.Create(JULIET_DEBUG_ONLY("DebugState States"));
}
if (StringCompare(name, ConstString("Engine Arena")) == 0)
{
return s_EngineState;
}
return s_ScratchState;
}
#if JULIET_DEBUG
// Generate a stable color from a string tag
uint32 GetColorForTag(const String& tag)
{
uint32 hash = 0;
size_t len = tag.Size;
const char* s = tag.Data;
// Simple FNV-1a style hash
for (size_t i = 0; i < len; ++i)
for (auto& entry : s_PagedArenaStates)
{
hash = hash * 65599 + static_cast<uint8>(s[i]);
if (entry.Arena == arena)
{
return entry.State;
}
}
// Use hash to pick a Hue
float h = static_cast<float>(hash % 360) / 360.0f;
return ImColor::HSV(h, 0.7f, 0.8f);
PagedArenaStateEntry newEntry;
newEntry.Arena = arena;
// Default construct state
newEntry.State = PagedArenaDebugState();
s_PagedArenaStates.PushBack(newEntry);
return s_PagedArenaStates.Last()->State;
}
// Generate a unique color per allocation, using tag + offset + size
@@ -121,364 +90,11 @@ namespace Juliet::Debug
float v = 0.65f + static_cast<float>((hash >> 20) % 25) / 100.0f;
return ImColor::HSV(h, s, v);
}
#endif
void DrawMemoryArena(String name, const MemoryArena& arena, [[maybe_unused]] ArenaAllocation* currentHighlight,
[[maybe_unused]] ArenaAllocation*& outNewHighlight)
{
ArenaDebugState& state = GetState(name);
if (ImGui::CollapsingHeader(CStr(name), ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::PushID(CStr(name));
// Calculate Stats
size_t totalCapacity = 0;
size_t totalUsed = 0;
size_t blockCount = 0;
MemoryBlock* curr = arena.FirstBlock;
while (curr)
{
totalCapacity += curr->TotalSize;
totalUsed += curr->Used;
blockCount++;
curr = curr->Next;
}
ImGui::Text("Used: %zu / %zu bytes (%zu blocks)", totalUsed, totalCapacity, blockCount);
// Zoom Control
ImGui::SliderFloat("Zoom", &state.Zoom, 0.1f, 1000.0f, "%.2f");
#if JULIET_DEBUG
// --- Visual View (Scrollable + Zoom) ---
ImGui::Separator();
ImGui::Text("Visual Map");
// Calculate Dynamic Height
// Height = blockCount * (blockHeight + spacing) + padding
// Constrain between minHeight and maxHeight
float blockHeight = 24.0f;
float blockSpacing = 4.0f;
// Add extra padding to avoid vertical scrollbar triggering due to varying style padding
float requiredVisHeight = (float)blockCount * (blockHeight + blockSpacing) + 30.0f;
if (requiredVisHeight > 300.0f) requiredVisHeight = 300.0f;
if (requiredVisHeight < 50.0f) requiredVisHeight = 50.0f;
// Use ImGuiWindowFlags_NoScrollbar if we fit?
// No, we want horizontal scrollbar. If we provide just enough height, vertical shouldn't show.
if (ImGui::BeginChild("VisualMap", ImVec2(0, requiredVisHeight), true,
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar))
{
ImGui::SetScrollY(0.0f); // Lock Vertical Scroll to separate Zoom from Scroll
ImDrawList* dl = ImGui::GetWindowDrawList();
float availWidth = ImGui::GetContentRegionAvail().x;
ImVec2 startPos = ImGui::GetCursorScreenPos();
// Reserve space for the virtual width FIRST to satisfy layout
// Also reserve explicit height > window height to ensure Child captures MouseWheel (stops bubbling)
float virtualWidth = availWidth * state.Zoom;
if (virtualWidth < availWidth) virtualWidth = availWidth;
ImGui::Dummy(ImVec2(virtualWidth, requiredVisHeight + 1.0f));
bool isMapHovered = ImGui::IsWindowHovered();
// Interaction Logic
// 1. Zoom with Pivot
if (isMapHovered)
{
float wheel = ImGui::GetIO().MouseWheel;
if (wheel != 0.0f)
{
// Pivot Logic
float mouseXScreen = ImGui::GetMousePos().x;
float activeScrollX = ImGui::GetScrollX();
// Viewport Left
float viewportLeft = ImGui::GetWindowPos().x;
float mouseRelViewport = mouseXScreen - viewportLeft;
// Current Ratio
float virtualWidthOld = std::max(availWidth * state.Zoom, availWidth);
float mouseContentPos = activeScrollX + mouseRelViewport;
float ratio = mouseContentPos / virtualWidthOld;
// Apply Zoom
state.Zoom += wheel * 0.2f * state.Zoom;
if (state.Zoom < 0.1f) state.Zoom = 0.1f;
if (state.Zoom > 1000.0f) state.Zoom = 1000.0f;
// New Ratio
float virtualWidthNew = std::max(availWidth * state.Zoom, availWidth);
// flNewScroll + MouseRel = Ratio * NewWidth
float desiredScrollX = (ratio * virtualWidthNew) - mouseRelViewport;
ImGui::SetScrollX(desiredScrollX);
}
}
// 2. Pan (Left, Right, or Middle Mouse)
if (isMapHovered && (ImGui::IsMouseDragging(ImGuiMouseButton_Left) || ImGui::IsMouseDragging(ImGuiMouseButton_Right) ||
ImGui::IsMouseDragging(ImGuiMouseButton_Middle)))
{
ImGui::SetScrollX(ImGui::GetScrollX() - ImGui::GetIO().MouseDelta.x);
}
// 3. Jump to Selected (Sync from List)
if (state.ScrollVisualToSelected && state.SelectedAlloc)
{
MemoryBlock* searchBlk = arena.FirstBlock;
while (searchBlk)
{
ArenaAllocation* search = searchBlk->FirstAllocation;
bool found = false;
while (search)
{
if (search == state.SelectedAlloc)
{
found = true;
break;
}
search = search->Next;
}
if (found)
{
float virtualWidthLocal = std::max(availWidth * state.Zoom, availWidth);
size_t dataSize = searchBlk->TotalSize - sizeof(MemoryBlock);
if (dataSize > 0)
{
double scale = (double)virtualWidthLocal / (double)dataSize;
float xStart = (float)((double)state.SelectedAlloc->Offset * scale);
// Scroll to center xStart
float centerOffset = availWidth * 0.5f;
ImGui::SetScrollX(xStart - centerOffset);
}
state.ScrollVisualToSelected = false; // Consumed
break;
}
searchBlk = searchBlk->Next;
}
}
MemoryBlock* blk = arena.FirstBlock;
ImVec2 pos = startPos;
int bIdx = 0;
while (blk)
{
// Draw Block Frame
ImVec2 rectMin = pos;
auto rectMax = ImVec2(pos.x + virtualWidth, pos.y + blockHeight);
// Border Color
ImU32 borderColor = (uint32)ImColor::HSV(((float)bIdx * 0.1f), 0.0f, 0.7f);
dl->AddRect(rectMin, rectMax, borderColor);
size_t dataSize = blk->TotalSize - sizeof(MemoryBlock);
if (dataSize > 0)
{
double scale = (double)virtualWidth / (double)dataSize;
ArenaAllocation* alloc = blk->FirstAllocation;
while (alloc)
{
float xStart = pos.x + (float)((double)alloc->Offset * scale);
float width = (float)((double)alloc->Size * scale);
width = std::max(width, 1.0f);
auto aMin = ImVec2(xStart, pos.y + 1);
auto aMax = ImVec2(xStart + width, pos.y + blockHeight - 1);
// Clip visually to block bounds (simplistic)
if (aMin.x < rectMin.x) aMin.x = rectMin.x;
if (aMax.x > rectMax.x) aMax.x = rectMax.x;
ImU32 color = GetColorForTag(alloc->Tag);
bool isSelected = (state.SelectedAlloc == alloc);
bool isHovered = (currentHighlight == alloc);
if (isSelected || isHovered)
{
// Highlight
dl->AddRectFilled(aMin, aMax, IM_COL32_WHITE);
dl->AddRect(aMin, aMax, IM_COL32_BLACK);
}
else
{
dl->AddRectFilled(aMin, aMax, color);
}
// Draw Text if zoomed enough and fits
if (width > 20.0f) // Threshold width to attempt drawing text
{
// Need to check actual text size
ImVec2 textSize = ImGui::CalcTextSize(alloc->Tag.Data, alloc->Tag.Data + alloc->Tag.Size);
if (width >= textSize.x + 4.0f)
{
// Center text
float textX = aMin.x + (width - textSize.x) * 0.5f;
float textY = aMin.y + (blockHeight - 2.0f - textSize.y) * 0.5f;
// Contrast color
ImU32 textColor = IM_COL32_BLACK;
dl->AddText(ImVec2(textX, textY), textColor, alloc->Tag.Data,
alloc->Tag.Data + alloc->Tag.Size);
}
}
if (ImGui::IsMouseHoveringRect(aMin, aMax) && ImGui::IsWindowHovered())
{
outNewHighlight = alloc;
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{
state.SelectedAlloc = alloc;
state.ScrollListToSelected = true; // Sync to list
state.ScrollVisualToSelected = false;
}
ImGui::BeginTooltip();
ImGui::Text("Tag: %.*s", (int)alloc->Tag.Size, alloc->Tag.Data);
ImGui::Text("Size: %zu bytes", alloc->Size);
ImGui::Text("Offset: %zu", alloc->Offset);
uint8* dataPtr = blk->GetData() + alloc->Offset;
ImGui::Text("Data: ");
for (int i = 0; i < 16 && i < (int)alloc->Size; ++i)
{
ImGui::SameLine();
ImGui::Text("%02X", dataPtr[i]);
}
ImGui::EndTooltip();
}
alloc = alloc->Next;
}
}
pos.y += blockHeight + 4;
blk = blk->Next;
bIdx++;
}
// Final spacing
ImGui::Dummy(ImVec2(0, pos.y - startPos.y));
}
ImGui::EndChild();
// --- Tree View (Scrollable) ---
ImGui::Separator();
if (ImGui::TreeNode("Allocations List"))
{
// Calculate item count for Dynamic Height
int totalAllocCount = 0;
MemoryBlock* countBlk = arena.FirstBlock;
while (countBlk)
{
// Add 1 for Block Node
totalAllocCount++;
// If Block Node is Open? We don't know if we don't query it.
// But actually we are inside a Child window inside the TreeNode.
// We want the Child Window to be sized appropriately.
// Simplification: Just count ALL allocations to assume "expanded" state or just use a max cap.
// User wants "Grow until 25 elements".
// This implies if we have 5 items, height is small. If 100, height is capped at 25.
// We'll just count total allocations + blocks as a rough estimate of potential height.
ArenaAllocation* countAlloc = countBlk->FirstAllocation;
while (countAlloc)
{
totalAllocCount++;
countAlloc = countAlloc->Next;
}
countBlk = countBlk->Next;
}
float lineHeight = ImGui::GetTextLineHeightWithSpacing();
float currentNeededHeight = (float)totalAllocCount * lineHeight;
float maxHeight = lineHeight * 25.0f;
float listHeight = currentNeededHeight;
if (listHeight > maxHeight) listHeight = maxHeight;
if (listHeight < lineHeight) listHeight = lineHeight; // Min 1 line
if (ImGui::BeginChild("AllocList", ImVec2(0, listHeight), true))
{
MemoryBlock* blk = arena.FirstBlock;
int blkIdx = 0;
while (blk)
{
if (ImGui::TreeNode((void*)blk, "Block %d (%zu bytes)", blkIdx, blk->TotalSize))
{
ArenaAllocation* alloc = blk->FirstAllocation;
while (alloc)
{
bool isSelected = (state.SelectedAlloc == alloc);
bool isHovered = (currentHighlight == alloc);
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen;
if (isSelected || isHovered)
{
flags |= ImGuiTreeNodeFlags_Selected;
}
if (isSelected && state.ScrollListToSelected)
{
ImGui::SetScrollHereY();
state.ScrollListToSelected = false; // Consumed
}
// Color Square
ImU32 color = GetColorForTag(alloc->Tag);
ImGui::ColorButton("##color", ImColor(color),
ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoDragDrop,
ImVec2(12, 12));
ImGui::SameLine();
// Use implicit string length from String struct
ImGui::TreeNodeEx(alloc, flags, "[%zu] %.*s (%zu bytes)", alloc->Offset,
(int)alloc->Tag.Size, alloc->Tag.Data, alloc->Size);
if (ImGui::IsItemClicked())
{
state.SelectedAlloc = alloc;
state.ScrollVisualToSelected = true; // Sync to visual
state.ScrollListToSelected = false;
}
if (ImGui::IsItemHovered())
{
outNewHighlight = alloc;
}
alloc = alloc->Next;
}
ImGui::TreePop();
}
blk = blk->Next;
blkIdx++;
}
}
ImGui::EndChild();
ImGui::TreePop();
}
#else
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Compile with JULIET_DEBUG for Memory Visualization");
#endif
ImGui::PopID();
}
}
#if JULIET_DEBUG
void DrawPagedArena(const Arena* arena)
{
ImGui::PushID(arena);
const char* name = (arena->Name && arena->Name[0] != '\0') ? arena->Name : "Unnamed Arena";
const char* name = (arena->Name && arena->Name[0] != '\0') ? arena->Name : "Unnamed Arena";
PagedArenaDebugState& state = GetPagedArenaState(arena);
if (ImGui::CollapsingHeader(name))
@@ -496,24 +112,24 @@ namespace Juliet::Debug
{
blocks.PushBack(curr);
}
// Reverse manually
size_t count = blocks.Count;
for (size_t i = 0; i < count / 2; ++i)
{
const Arena* temp = blocks[i];
blocks[i] = blocks[count - 1 - i];
const Arena* temp = blocks[i];
blocks[i] = blocks[count - 1 - i];
blocks[count - 1 - i] = temp;
}
// Calculate Stats
size_t totalCapacity = 0;
size_t totalUsed = 0;
for (const Arena* blk : blocks)
{
totalCapacity += blk->Reserved;
totalUsed += blk->Position;
totalUsed += blk->Position;
}
ImGui::Text("Used: %zu / %zu bytes (%zu pages)", totalUsed, totalCapacity, blocks.Size());
@@ -523,8 +139,8 @@ namespace Juliet::Debug
ImGui::Separator();
ImGui::Text("Visual Map");
float blockHeight = 24.0f;
float blockSpacing = 4.0f;
float blockHeight = 24.0f;
float blockSpacing = 4.0f;
float requiredVisHeight = static_cast<float>(blocks.Size()) * (blockHeight + blockSpacing) + 30.0f;
// constrains
if (requiredVisHeight > 300.0f)
@@ -535,7 +151,7 @@ namespace Juliet::Debug
{
requiredVisHeight = 50.0f;
}
if (ImGui::BeginChild("VisualMap", ImVec2(0, requiredVisHeight), true,
ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar))
{
@@ -544,7 +160,7 @@ namespace Juliet::Debug
ImDrawList* dl = ImGui::GetWindowDrawList();
float availWidth = ImGui::GetContentRegionAvail().x;
ImVec2 startPos = ImGui::GetCursorScreenPos();
float virtualWidth = availWidth * state.Zoom;
if (virtualWidth < availWidth)
{
@@ -553,15 +169,15 @@ namespace Juliet::Debug
ImGui::Dummy(ImVec2(virtualWidth, requiredVisHeight + 1.0f));
bool isMapHovered = ImGui::IsWindowHovered();
// Zoom with pivot
if (isMapHovered)
{
float wheel = ImGui::GetIO().MouseWheel;
if (wheel != 0.0f)
{
float mouseXScreen = ImGui::GetMousePos().x;
float activeScrollX = ImGui::GetScrollX();
float mouseXScreen = ImGui::GetMousePos().x;
float activeScrollX = ImGui::GetScrollX();
float viewportLeft = ImGui::GetWindowPos().x;
float mouseRelViewport = mouseXScreen - viewportLeft;
@@ -570,17 +186,11 @@ namespace Juliet::Debug
float ratio = mouseContentPos / virtualWidthOld;
state.Zoom += wheel * 0.2f * state.Zoom;
if (state.Zoom < 0.1f)
{
state.Zoom = 0.1f;
}
if (state.Zoom > 1000000.0f)
{
state.Zoom = 1000000.0f;
}
state.Zoom = std::max(state.Zoom, 0.1f);
state.Zoom = std::min(state.Zoom, 1000000.0f);
float virtualWidthNew = std::max(availWidth * state.Zoom, availWidth);
float desiredScrollX = (ratio * virtualWidthNew) - mouseRelViewport;
float desiredScrollX = (ratio * virtualWidthNew) - mouseRelViewport;
ImGui::SetScrollX(desiredScrollX);
}
}
@@ -598,7 +208,7 @@ namespace Juliet::Debug
for (const Arena* searchBlk : blocks)
{
ArenaDebugInfo* search = searchBlk->FirstDebugInfo;
bool found = false;
bool found = false;
while (search)
{
if (search == state.SelectedAlloc)
@@ -614,8 +224,9 @@ namespace Juliet::Debug
float virtualWidthLocal = std::max(availWidth * state.Zoom, availWidth);
if (searchBlk->Reserved > 0)
{
double scale = static_cast<double>(virtualWidthLocal) / static_cast<double>(searchBlk->Reserved);
float xStart = static_cast<float>(static_cast<double>(state.SelectedAlloc->Offset) * scale);
double scale =
static_cast<double>(virtualWidthLocal) / static_cast<double>(searchBlk->Reserved);
float xStart = static_cast<float>(static_cast<double>(state.SelectedAlloc->Offset) * scale);
float centerOffset = availWidth * 0.5f;
ImGui::SetScrollX(xStart - centerOffset);
}
@@ -631,7 +242,7 @@ namespace Juliet::Debug
ImGui::PushID(blk);
ImVec2 rectMin = pos;
ImVec2 rectMax = ImVec2(pos.x + virtualWidth, pos.y + blockHeight);
dl->AddRect(rectMin, rectMax, IM_COL32(100, 100, 100, 255));
size_t dataSize = blk->Reserved - k_ArenaHeaderSize;
@@ -642,7 +253,7 @@ namespace Juliet::Debug
// Draw Arena Header
{
float hdrWidth = static_cast<float>(static_cast<double>(k_ArenaHeaderSize) * scale);
hdrWidth = std::max(hdrWidth, 1.0f);
hdrWidth = std::max(hdrWidth, 1.0f);
ImVec2 hMin(pos.x, pos.y + 1);
ImVec2 hMax(pos.x + hdrWidth, pos.y + blockHeight - 1);
dl->AddRectFilled(hMin, hMax, IM_COL32(40, 60, 120, 255));
@@ -650,7 +261,7 @@ namespace Juliet::Debug
if (hdrWidth > 30.0f)
{
const char* hdrLabel = "Header";
ImVec2 textSize = ImGui::CalcTextSize(hdrLabel);
ImVec2 textSize = ImGui::CalcTextSize(hdrLabel);
if (hdrWidth >= textSize.x + 4.0f)
{
float textX = hMin.x + (hdrWidth - textSize.x) * 0.5f;
@@ -670,17 +281,17 @@ namespace Juliet::Debug
}
// Draw Allocations
ArenaDebugInfo* info = blk->FirstDebugInfo;
size_t expectedOffset = k_ArenaHeaderSize;
ArenaDebugInfo* info = blk->FirstDebugInfo;
size_t expectedOffset = k_ArenaHeaderSize;
while (info)
{
// Draw alignment padding gap if present (only for small gaps that are actual alignment)
if (info->Offset > expectedOffset)
{
size_t padSize = info->Offset - expectedOffset;
size_t padSize = info->Offset - expectedOffset;
constexpr size_t k_MaxAlignmentPadding = 256;
float padXStart = pos.x + static_cast<float>(static_cast<double>(expectedOffset) * scale);
float padWidth = static_cast<float>(static_cast<double>(padSize) * scale);
float padWidth = static_cast<float>(static_cast<double>(padSize) * scale);
if (padSize <= k_MaxAlignmentPadding && padWidth >= 1.0f)
{
@@ -688,22 +299,31 @@ namespace Juliet::Debug
ImVec2 pMax(padXStart + padWidth, pos.y + blockHeight - 1);
// Clip
if (pMin.x < rectMin.x) { pMin.x = rectMin.x; }
if (pMax.x > rectMax.x) { pMax.x = rectMax.x; }
if (pMin.x < rectMin.x)
{
pMin.x = rectMin.x;
}
if (pMax.x > rectMax.x)
{
pMax.x = rectMax.x;
}
// Draw hatched pattern (diagonal lines) - clipped to visible area
float visLeft = ImGui::GetWindowPos().x;
float visRight = visLeft + ImGui::GetWindowSize().x;
float visLeft = ImGui::GetWindowPos().x;
float visRight = visLeft + ImGui::GetWindowSize().x;
float clippedLeft = std::max(pMin.x, visLeft);
float clippedRight = std::min(pMax.x, visRight);
dl->AddRectFilled(pMin, pMax, IM_COL32(30, 30, 30, 200));
if (clippedLeft < clippedRight)
{
float step = 6.0f;
float step = 6.0f;
float startX = clippedLeft - (pMax.y - pMin.y);
startX = pMin.x + step * std::floor((startX - pMin.x) / step);
if (startX < pMin.x) { startX = pMin.x; }
startX = pMin.x + step * std::floor((startX - pMin.x) / step);
if (startX < pMin.x)
{
startX = pMin.x;
}
int lineCount = 0;
for (float lx = startX; lx < clippedRight && lineCount < 500; lx += step, ++lineCount)
@@ -711,7 +331,11 @@ namespace Juliet::Debug
float ly0 = pMin.y;
float lx1 = lx + (pMax.y - pMin.y);
float ly1 = pMax.y;
if (lx1 > pMax.x) { ly1 = pMin.y + (pMax.x - lx); lx1 = pMax.x; }
if (lx1 > pMax.x)
{
ly1 = pMin.y + (pMax.x - lx);
lx1 = pMax.x;
}
dl->AddLine(ImVec2(lx, ly0), ImVec2(lx1, ly1), IM_COL32(80, 80, 80, 180));
}
}
@@ -720,7 +344,7 @@ namespace Juliet::Debug
if (padWidth > 30.0f)
{
const char* padLabel = "Pad";
ImVec2 textSize = ImGui::CalcTextSize(padLabel);
ImVec2 textSize = ImGui::CalcTextSize(padLabel);
if (padWidth >= textSize.x + 4.0f)
{
float textX = pMin.x + (padWidth - textSize.x) * 0.5f;
@@ -746,7 +370,7 @@ namespace Juliet::Debug
float xStart = pos.x + static_cast<float>(static_cast<double>(info->Offset) * scale);
float width = static_cast<float>(static_cast<double>(info->Size) * scale);
width = std::max(width, 1.0f);
ImVec2 aMin(xStart, pos.y + 1);
ImVec2 aMax(xStart + width, pos.y + blockHeight - 1);
@@ -759,9 +383,9 @@ namespace Juliet::Debug
{
aMax.x = rectMax.x;
}
ImU32 color = GetColorForAllocation(info->Tag, info->Offset, info->Size);
bool isSelected = (state.SelectedAlloc == info);
ImU32 color = GetColorForAllocation(info->Tag, info->Offset, info->Size);
bool isSelected = (state.SelectedAlloc == info);
if (isSelected)
{
@@ -777,7 +401,7 @@ namespace Juliet::Debug
if (width > 20.0f && info->Tag)
{
const char* tagStr = info->Tag;
size_t tagLen = 0;
size_t tagLen = 0;
while (tagStr[tagLen] != '\0')
{
++tagLen;
@@ -785,13 +409,13 @@ namespace Juliet::Debug
ImVec2 textSize = ImGui::CalcTextSize(tagStr, tagStr + tagLen);
if (width >= textSize.x + 4.0f)
{
float textX = aMin.x + (width - textSize.x) * 0.5f;
float textY = aMin.y + (blockHeight - 2.0f - textSize.y) * 0.5f;
float textX = aMin.x + (width - textSize.x) * 0.5f;
float textY = aMin.y + (blockHeight - 2.0f - textSize.y) * 0.5f;
ImU32 textColor = IM_COL32_BLACK;
dl->AddText(ImVec2(textX, textY), textColor, tagStr, tagStr + tagLen);
}
}
if (ImGui::IsMouseHoveringRect(aMin, aMax) && ImGui::IsWindowHovered())
{
ImGui::BeginTooltip();
@@ -811,11 +435,11 @@ namespace Juliet::Debug
}
ImGui::EndTooltip();
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{
state.SelectedAlloc = info;
state.ScrollListToSelected = true;
state.SelectedAlloc = info;
state.ScrollListToSelected = true;
state.ScrollVisualToSelected = false;
}
}
@@ -823,26 +447,26 @@ namespace Juliet::Debug
info = info->Next;
ImGui::PopID();
}
// Draw Free Nodes (Holes) if Realloc is enabled
if (arena->AllowRealloc && blk->FreeNodes)
{
ArenaFreeNode* freeNode = blk->FreeNodes;
while(freeNode)
{
float fxStart = pos.x + static_cast<float>(static_cast<double>(freeNode->Position) * scale);
float fWidth = static_cast<float>(static_cast<double>(freeNode->Size) * scale);
ImVec2 fMin(fxStart, pos.y + 1);
ImVec2 fMax(fxStart + fWidth, pos.y + blockHeight - 1);
dl->AddRectFilled(fMin, fMax, IM_COL32(50, 50, 50, 200));
freeNode = freeNode->Next;
}
ArenaFreeNode* freeNode = blk->FreeNodes;
while (freeNode)
{
float fxStart = pos.x + static_cast<float>(static_cast<double>(freeNode->Position) * scale);
float fWidth = static_cast<float>(static_cast<double>(freeNode->Size) * scale);
ImVec2 fMin(fxStart, pos.y + 1);
ImVec2 fMax(fxStart + fWidth, pos.y + blockHeight - 1);
dl->AddRectFilled(fMin, fMax, IM_COL32(50, 50, 50, 200));
freeNode = freeNode->Next;
}
}
}
pos.y += blockHeight + blockSpacing;
ImGui::PopID();
}
@@ -866,9 +490,9 @@ namespace Juliet::Debug
}
}
float lineHeight = ImGui::GetTextLineHeightWithSpacing();
float lineHeight = ImGui::GetTextLineHeightWithSpacing();
float currentNeededHeight = static_cast<float>(totalAllocCount) * lineHeight;
float maxHeight = lineHeight * 25.0f;
float maxHeight = lineHeight * 25.0f;
float listHeight = currentNeededHeight;
if (listHeight > maxHeight)
@@ -885,7 +509,8 @@ namespace Juliet::Debug
int blkIdx = 0;
for (const Arena* blk : blocks)
{
if (ImGui::TreeNode(reinterpret_cast<const void*>(blk), "Page %d (%zu bytes, pos %zu)", blkIdx, blk->Reserved, blk->Position))
if (ImGui::TreeNode(reinterpret_cast<const void*>(blk), "Page %d (%zu bytes, pos %zu)",
blkIdx, blk->Reserved, blk->Position))
{
ArenaDebugInfo* info = blk->FirstDebugInfo;
if (!info)
@@ -919,9 +544,9 @@ namespace Juliet::Debug
if (ImGui::IsItemClicked())
{
state.SelectedAlloc = info;
state.SelectedAlloc = info;
state.ScrollVisualToSelected = true;
state.ScrollListToSelected = false;
state.ScrollListToSelected = false;
}
info = info->Next;
@@ -938,32 +563,25 @@ namespace Juliet::Debug
}
ImGui::PopID();
}
#endif
ArenaAllocation* s_ConfirmedHovered = nullptr;
} // namespace
void DebugDrawMemoryArena()
{
#if JULIET_DEBUG
// Cross-frame hover state
static ArenaAllocation* s_ConfirmedHovered = nullptr;
ArenaAllocation* frameHovered = nullptr;
#else
ArenaAllocation* s_ConfirmedHovered = nullptr;
ArenaAllocation* frameHovered = nullptr;
#endif
ArenaAllocation* frameHovered = nullptr;
if (ImGui::Begin("Memory Debugger"))
{
if (ImGui::BeginTabBar("ArenaTabs"))
{
#if JULIET_DEBUG
if (ImGui::BeginTabItem("Paged Arenas"))
{
Arena* head = GetGlobalArenaListHead();
if (!head)
{
ImGui::Text("No active paged arenas.");
ImGui::Text("No active paged arenas.");
}
else
{
@@ -973,7 +591,9 @@ namespace Juliet::Debug
if (a->Name)
{
bool isTest = (a->Name[0] == 'T' && a->Name[1] == 'e' && a->Name[2] == 's' && a->Name[3] == 't');
bool isDebugInfo = (a->Name[0] == 'D' && a->Name[1] == 'e' && a->Name[2] == 'b' && a->Name[3] == 'u' && a->Name[4] == 'g' && a->Name[5] == ' ' && a->Name[6] == 'I');
bool isDebugInfo =
(a->Name[0] == 'D' && a->Name[1] == 'e' && a->Name[2] == 'b' && a->Name[3] == 'u' &&
a->Name[4] == 'g' && a->Name[5] == ' ' && a->Name[6] == 'I');
if (isTest || isDebugInfo)
{
continue;
@@ -984,19 +604,14 @@ namespace Juliet::Debug
}
ImGui::EndTabItem();
}
#endif
if (ImGui::BeginTabItem("Legacy Arenas"))
{
DrawMemoryArena(ConstString("Game Arena"), *GetGameArena(), s_ConfirmedHovered, frameHovered);
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
}
ImGui::End();
#if JULIET_DEBUG
s_ConfirmedHovered = frameHovered;
#endif
}
} // namespace Juliet::Debug
#endif

View File

@@ -6,7 +6,9 @@
#include <Engine/Engine.h>
#include <Graphics/DebugDisplay.h>
#include <Graphics/Graphics.h>
#include <Graphics/MeshRenderer.h>
#include <Graphics/RenderPass.h>
#include <Graphics/SkyboxRenderer.h>
#include <time.h>
#ifdef JULIET_ENABLE_IMGUI
@@ -18,7 +20,10 @@
namespace Juliet
{
#if JULIET_DEBUG
extern void RunUnitTests();
namespace UnitTest
{
extern void RunUnitTests();
}
#endif
namespace
@@ -34,6 +39,16 @@ namespace Juliet
if (device)
{
DebugDisplay_Initialize(device);
if (Window* window = EngineInstance.Application->GetPlatformWindow())
{
bool success = InitializeMeshRendererGraphics(device, window);
Assert(success);
(void)(success);
success = InitializeSkyboxRenderer(device, window);
Assert(success);
(void)(success);
}
}
#ifdef JULIET_ENABLE_IMGUI
@@ -70,6 +85,8 @@ namespace Juliet
if (device)
{
DebugDisplay_Shutdown(device);
ShutdownSkyboxRenderer();
ShutdownMeshRendererGraphics();
}
}
@@ -119,6 +136,9 @@ namespace Juliet
Camera debugCamera = EngineInstance.Application->GetDebugCamera();
DebugDisplay_Flush(cmdList, pass, debugCamera);
// Note: The MeshRenderer and SkyboxRenderer draw calls are still inside Application->OnRender
// They shouldn't be moved here directly without an interface since they require PushData.
#ifdef JULIET_ENABLE_IMGUI
// ImGui rendering (always last before EndRenderPass)
ImGuiRenderer_Render(cmdList, pass);
@@ -133,11 +153,12 @@ namespace Juliet
void InitializeEngine(JulietInit_Flags flags)
{
EngineInstance.PlatformArena = ArenaAllocate({ .AllowRealloc = true } JULIET_DEBUG_PARAM("Platform Arena"));
InitializeLogManager();
MemoryArenasInit();
#if JULIET_DEBUG
RunUnitTests();
UnitTest::RunUnitTests();
#endif
InitFilesystem();
@@ -150,14 +171,18 @@ namespace Juliet
JulietShutdown();
ShutdownFilesystem();
MemoryArenasShutdown();
ShutdownLogManager();
ArenaRelease(EngineInstance.PlatformArena);
}
void LoadApplication(IApplication& app)
{
EngineInstance.Application = &app;
EngineInstance.Application->Init();
InitializeMeshRenderer(EngineInstance.PlatformArena);
EngineInstance.Application->Init(EngineInstance.PlatformArena);
// Systems depending on Window/GraphicsDevice
InitializeDependentSystems();
@@ -169,6 +194,9 @@ namespace Juliet
ShutdownDependentSystems();
EngineInstance.Application->Shutdown();
ShutdownMeshRenderer();
EngineInstance.Application = nullptr;
}
@@ -185,9 +213,6 @@ namespace Juliet
// Render tick
RenderFrame();
// Reset scratch arena at end of frame
ScratchArenaReset();
}
}
} // namespace Juliet

View File

@@ -23,9 +23,9 @@ namespace Juliet::D3D12
{
switch (type)
{
case D3D12BufferType::Base: return "Base";
case D3D12BufferType::TransferDownload: return "TransferDownload";
case D3D12BufferType::TransferUpload: return "TransferUpload";
case D3D12BufferType::Base: return "Base";
case D3D12BufferType::TransferDownload: return "TransferDownload";
case D3D12BufferType::TransferUpload: return "TransferUpload";
}
return "Unknown";
}
@@ -34,11 +34,10 @@ namespace Juliet::D3D12
{
switch (usage)
{
case BufferUsage::None: return "None";
case BufferUsage::ConstantBuffer: return "ConstantBuffer";
case BufferUsage::StructuredBuffer: return "StructuredBuffer";
case BufferUsage::IndexBuffer: return "IndexBuffer";
case BufferUsage::VertexBuffer: return "VertexBuffer";
case BufferUsage::None: return "None";
case BufferUsage::ConstantBuffer: return "ConstantBuffer";
case BufferUsage::StructuredBuffer: return "StructuredBuffer";
case BufferUsage::IndexBuffer: return "IndexBuffer";
}
return "Unknown";
}
@@ -62,94 +61,62 @@ namespace Juliet::D3D12
Free(buffer);
}
D3D12Buffer* CreateBuffer(NonNullPtr<D3D12Driver> d3d12Driver, size_t size, BufferUsage usage, D3D12BufferType type)
D3D12Buffer* CreateBuffer(NonNullPtr<D3D12Driver> d3d12Driver, size_t size, size_t stride, BufferUsage usage,
D3D12BufferType type, bool isDynamic)
{
auto buffer = static_cast<D3D12Buffer*>(Calloc(1, sizeof(D3D12Buffer)));
auto* buffer = static_cast<D3D12Buffer*>(Calloc(1, sizeof(D3D12Buffer)));
if (!buffer)
{
return nullptr;
}
if (type == D3D12BufferType::Base && usage == BufferUsage::None)
{
Assert(false, "Creating Base buffer with BufferUsage::None is invalid");
DestroyBuffer(buffer);
return nullptr;
}
// Align size for Constant Buffers
if (usage == BufferUsage::ConstantBuffer)
{
size = (size + 255U) & ~255U;
}
D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON;
D3D12_HEAP_PROPERTIES heapProperties = {};
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE;
heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
switch (type)
D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON;
D3D12_HEAP_FLAGS heapFlags = D3D12_HEAP_FLAG_NONE;
// Constant buffers or Dynamic buffers generally need to be uploaded every frame
const bool isUpload = isDynamic || (type == D3D12BufferType::TransferUpload) || (usage == BufferUsage::ConstantBuffer);
if (type == D3D12BufferType::TransferDownload)
{
case D3D12BufferType::Base:
heapProperties.Type = D3D12_HEAP_TYPE_READBACK;
initialState = D3D12_RESOURCE_STATE_COPY_DEST;
}
else if (isUpload)
{
if (d3d12Driver->GPUUploadHeapSupported)
{
switch (usage)
{
case BufferUsage::None:
{
Assert(false, "Creating buffer with invalid usage");
DestroyBuffer(buffer);
return nullptr;
}
case BufferUsage::IndexBuffer:
case BufferUsage::StructuredBuffer:
case BufferUsage::VertexBuffer:
{
heapProperties.Type = D3D12_HEAP_TYPE_DEFAULT;
heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapFlags = D3D12_HEAP_FLAG_NONE;
break;
}
case BufferUsage::ConstantBuffer:
{
if (d3d12Driver->GPUUploadHeapSupported)
{
heapProperties.Type = D3D12_HEAP_TYPE_GPU_UPLOAD;
heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
}
else
{
heapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;
heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
initialState = D3D12_RESOURCE_STATE_GENERIC_READ;
heapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;
}
// heapFlags = D3D12_HEAP_FLAG_NONE; // Avoid overwriting if set above
if (heapProperties.Type == D3D12_HEAP_TYPE_GPU_UPLOAD)
{
heapFlags = D3D12_HEAP_FLAG_NONE; // Or appropriate flags
}
break;
}
}
break;
heapProperties.Type = D3D12_HEAP_TYPE_GPU_UPLOAD;
initialState = D3D12_RESOURCE_STATE_COMMON;
}
case D3D12BufferType::TransferDownload:
else
{
heapProperties.Type = D3D12_HEAP_TYPE_READBACK;
heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapFlags = D3D12_HEAP_FLAG_NONE;
initialState = D3D12_RESOURCE_STATE_COPY_DEST;
break;
}
case D3D12BufferType::TransferUpload:
{
heapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;
heapProperties.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProperties.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapFlags = D3D12_HEAP_FLAG_NONE;
initialState = D3D12_RESOURCE_STATE_GENERIC_READ;
break;
heapProperties.Type = D3D12_HEAP_TYPE_UPLOAD;
initialState = D3D12_RESOURCE_STATE_GENERIC_READ;
}
}
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
else
{
// Must be a static buffer (Base type)
heapProperties.Type = D3D12_HEAP_TYPE_DEFAULT;
initialState = D3D12_RESOURCE_STATE_COMMON;
}
D3D12_RESOURCE_DESC desc = {};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
@@ -164,20 +131,24 @@ namespace Juliet::D3D12
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
Log(LogLevel::Message, LogCategory::Graphics, "CreateBuffer: Device=%p, Size=%zu, Type=%s Use=%s", (void*)d3d12Driver->D3D12Device, size, D3D12BufferTypeToString(type), BufferUsageToString(usage));
Log(LogLevel::Message, LogCategory::Graphics, "CreateBuffer: Device=%p, Size=%zu, Type=%s Use=%s",
(void*)d3d12Driver->D3D12Device, size, D3D12BufferTypeToString(type), BufferUsageToString(usage));
ID3D12Resource* handle = nullptr;
HRESULT result = d3d12Driver->D3D12Device->CreateCommittedResource(&heapProperties, heapFlags,
&desc, initialState, nullptr,
IID_ID3D12Resource, reinterpret_cast<void**>(&handle));
HRESULT result = d3d12Driver->D3D12Device->CreateCommittedResource(&heapProperties, heapFlags, &desc,
initialState, nullptr, IID_ID3D12Resource,
reinterpret_cast<void**>(&handle));
if (FAILED(result))
{
Log(LogLevel::Error, LogCategory::Graphics, "Could not create buffer! HRESULT=0x%08X", (uint32)result);
Log(LogLevel::Error, LogCategory::Graphics, "Failed Desc: Width=%llu Layout=%d HeapType=%d", (unsigned long long)desc.Width, (int)desc.Layout, (int)heapProperties.Type);
Log(LogLevel::Error, LogCategory::Graphics, "Could not create buffer! HRESULT=0x%08X", static_cast<uint32>(result));
Log(LogLevel::Error, LogCategory::Graphics, "Failed Desc: Width=%llu Layout=%d HeapType=%d",
(unsigned long long)desc.Width, (int)desc.Layout, (int)heapProperties.Type);
HRESULT removeReason = d3d12Driver->D3D12Device->GetDeviceRemovedReason();
if (FAILED(removeReason))
{
Log(LogLevel::Error, LogCategory::Graphics, "Device Removed Reason: 0x%08X", (uint32)removeReason);
Log(LogLevel::Error, LogCategory::Graphics, "Device Removed Reason: 0x%08X", static_cast<uint32>(removeReason));
}
DestroyBuffer(buffer);
@@ -202,20 +173,33 @@ namespace Juliet::D3D12
if (usage == BufferUsage::ConstantBuffer)
{
cbvDesc.BufferLocation = handle->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = static_cast<uint32>(size);
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = handle->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = static_cast<uint32>(size);
d3d12Driver->D3D12Device->CreateConstantBufferView(&cbvDesc, cpuHandle);
}
else if (usage == BufferUsage::StructuredBuffer)
{
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Format = DXGI_FORMAT_R32_TYPELESS;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Buffer.FirstElement = 0;
srvDesc.Buffer.NumElements = static_cast<uint32>(size / 4);
srvDesc.Buffer.StructureByteStride = 0;
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW;
if (stride > 0)
{
srvDesc.Format = DXGI_FORMAT_UNKNOWN;
srvDesc.Buffer.NumElements = static_cast<uint32>(size / stride);
srvDesc.Buffer.StructureByteStride = static_cast<uint32>(stride);
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE;
}
else
{
srvDesc.Format = DXGI_FORMAT_R32_TYPELESS;
srvDesc.Buffer.NumElements = static_cast<uint32>(size / 4);
srvDesc.Buffer.StructureByteStride = 0;
srvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW;
}
d3d12Driver->D3D12Device->CreateShaderResourceView(handle, &srvDesc, cpuHandle);
Log(LogLevel::Message, LogCategory::Graphics, " -> SRV DescriptorIndex=%u", descriptor.Index);
}
@@ -230,10 +214,10 @@ namespace Juliet::D3D12
}
} // namespace
GraphicsBuffer* CreateGraphicsBuffer(NonNullPtr<GPUDriver> driver, size_t size, BufferUsage usage)
GraphicsBuffer* CreateGraphicsBuffer(NonNullPtr<GPUDriver> driver, size_t size, size_t stride, BufferUsage usage, bool isDynamic)
{
auto d3d12Driver = static_cast<D3D12Driver*>(driver.Get());
return reinterpret_cast<GraphicsBuffer*>(CreateBuffer(d3d12Driver, size, usage, D3D12BufferType::Base));
return reinterpret_cast<GraphicsBuffer*>(CreateBuffer(d3d12Driver, size, stride, usage, D3D12BufferType::Base, isDynamic));
}
void DestroyGraphicsBuffer(NonNullPtr<GraphicsBuffer> buffer)
@@ -245,8 +229,9 @@ namespace Juliet::D3D12
{
auto d3d12Driver = static_cast<D3D12Driver*>(driver.Get());
return reinterpret_cast<GraphicsTransferBuffer*>(
CreateBuffer(d3d12Driver, size, BufferUsage::None,
usage == TransferBufferUsage::Upload ? D3D12BufferType::TransferUpload : D3D12BufferType::TransferDownload));
CreateBuffer(d3d12Driver, size, 0, BufferUsage::None,
usage == TransferBufferUsage::Upload ? D3D12BufferType::TransferUpload : D3D12BufferType::TransferDownload,
false));
}
void DestroyGraphicsTransferBuffer(NonNullPtr<GraphicsTransferBuffer> buffer)
@@ -273,6 +258,24 @@ namespace Juliet::D3D12
d3d12Buffer->Handle->Unmap(0, nullptr);
}
void* MapBuffer(NonNullPtr<GPUDriver> /*driver*/, NonNullPtr<GraphicsBuffer> buffer)
{
auto d3d12Buffer = reinterpret_cast<D3D12Buffer*>(buffer.Get());
void* ptr = nullptr;
D3D12_RANGE readRange = { 0, 0 };
if (FAILED(d3d12Buffer->Handle->Map(0, &readRange, &ptr)))
{
return nullptr;
}
return ptr;
}
void UnmapBuffer(NonNullPtr<GPUDriver> /*driver*/, NonNullPtr<GraphicsBuffer> buffer)
{
auto d3d12Buffer = reinterpret_cast<D3D12Buffer*>(buffer.Get());
d3d12Buffer->Handle->Unmap(0, nullptr);
}
uint32 GetDescriptorIndex(NonNullPtr<GPUDriver> /*driver*/, NonNullPtr<GraphicsBuffer> buffer)
{
auto d3d12Buffer = reinterpret_cast<D3D12Buffer*>(buffer.Get());
@@ -305,8 +308,8 @@ namespace Juliet::D3D12
// We assume Upload buffers are always in state GENERIC_READ or similar suitable for CopySrc.
// D3D12 Upload heaps start in GENERIC_READ and cannot transition.
d3d12CmdList->GraphicsCommandList.CommandList->CopyBufferRegion(d3d12Dst->Handle,
dstOffset, d3d12Src->Handle, srcOffset, size);
d3d12CmdList->GraphicsCommandList.CommandList->CopyBufferRegion(d3d12Dst->Handle, dstOffset, d3d12Src->Handle,
srcOffset, size);
}
void TransitionBufferToReadable(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer)

View File

@@ -20,7 +20,7 @@ namespace Juliet::D3D12
size_t Size;
};
extern GraphicsBuffer* CreateGraphicsBuffer(NonNullPtr<GPUDriver> driver, size_t size, BufferUsage usage);
extern GraphicsBuffer* CreateGraphicsBuffer(NonNullPtr<GPUDriver> driver, size_t size, size_t stride, BufferUsage usage, bool isDynamic);
extern void DestroyGraphicsBuffer(NonNullPtr<GraphicsBuffer> buffer);
extern GraphicsTransferBuffer* CreateGraphicsTransferBuffer(NonNullPtr<GPUDriver> driver, size_t size, TransferBufferUsage usage);

View File

@@ -246,7 +246,8 @@ namespace Juliet::D3D12
d3d12Driver->GraphicsQueue->ExecuteCommandLists(1, ppCommandLists);
// Acquire a fence and set it to the in-flight fence
d3d12CommandList->InFlightFence = Internal::AcquireFence(d3d12Driver);
d3d12CommandList->InFlightFence =
Internal::AcquireFence(d3d12Driver JULIET_DEBUG_PARAM(ConstString("SubmitCommandLists")));
if (!d3d12CommandList->InFlightFence)
{
return false;
@@ -305,14 +306,22 @@ namespace Juliet::D3D12
windowData->WindowFrameCounter = (windowData->WindowFrameCounter + 1) % d3d12Driver->FramesInFlight;
}
// TODO : Correctly clean up and destroy
// Check for cleanups
for (int32 i = d3d12Driver->SubmittedCommandListCount - 1; i >= 0; i -= 1)
{
uint64 fenceValue = d3d12Driver->SubmittedCommandLists[i]->InFlightFence->Handle->GetCompletedValue();
if (fenceValue == D3D12_FENCE_SIGNAL_VALUE)
int32 i = 0;
while (i < d3d12Driver->SubmittedCommandListCount)
{
success &= Internal::CleanCommandList(d3d12Driver, d3d12Driver->SubmittedCommandLists[i], false);
uint64 fenceValue = d3d12Driver->SubmittedCommandLists[i]->InFlightFence->Handle->GetCompletedValue();
if (fenceValue == D3D12_FENCE_SIGNAL_VALUE)
{
success &= Internal::CleanCommandList(d3d12Driver, d3d12Driver->SubmittedCommandLists[i], false);
// CleanCommandList swaps [i] with last and decrements count.
// Don't increment — re-check the swapped-in element.
}
else
{
i += 1;
}
}
}
@@ -361,30 +370,40 @@ namespace Juliet::D3D12
d3d12CommandList->GraphicsCommandList.CommandList->OMSetStencilRef(reference);
}
void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer, IndexFormat format)
void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer, IndexFormat format,
size_t indexCount, index_t offset)
{
auto* d3d12CommandList = reinterpret_cast<D3D12CommandList*>(commandList.Get());
auto* d3d12Buffer = reinterpret_cast<D3D12Buffer*>(buffer.Get());
// Transition to INDEX_BUFFER state if needed
if (d3d12Buffer->CurrentState != D3D12_RESOURCE_STATE_INDEX_BUFFER)
if (d3d12Buffer->CurrentState != D3D12_RESOURCE_STATE_GENERIC_READ)
{
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = d3d12Buffer->Handle;
barrier.Transition.StateBefore = d3d12Buffer->CurrentState;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_INDEX_BUFFER;
barrier.Transition.StateAfter =
D3D12_RESOURCE_STATE_GENERIC_READ; // Since we use a mega buffer we use the generic read that includes D3D12_RESOURCE_STATE_INDEX_BUFFER
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
d3d12CommandList->GraphicsCommandList.CommandList->ResourceBarrier(1, &barrier);
d3d12Buffer->CurrentState = D3D12_RESOURCE_STATE_INDEX_BUFFER;
d3d12Buffer->CurrentState = D3D12_RESOURCE_STATE_GENERIC_READ;
}
D3D12_INDEX_BUFFER_VIEW ibView;
ibView.BufferLocation = d3d12Buffer->Handle->GetGPUVirtualAddress();
ibView.SizeInBytes = static_cast<UINT>(d3d12Buffer->Size);
ibView.Format = (format == IndexFormat::UInt16) ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT;
ibView.BufferLocation = d3d12Buffer->Handle->GetGPUVirtualAddress() + offset;
if (format == IndexFormat::UInt16)
{
ibView.SizeInBytes = static_cast<UINT>(indexCount * sizeof(uint16));
ibView.Format = DXGI_FORMAT_R16_UINT;
}
else
{
ibView.SizeInBytes = static_cast<UINT>(indexCount * sizeof(uint32));
ibView.Format = DXGI_FORMAT_R32_UINT;
}
d3d12CommandList->GraphicsCommandList.CommandList->IASetIndexBuffer(&ibView);
}
@@ -477,7 +496,8 @@ namespace Juliet::D3D12
// Release Fence if needed
if (commandList->AutoReleaseFence)
{
ReleaseFence(driver.Get(), reinterpret_cast<Fence*>(commandList->InFlightFence));
ReleaseFence(driver.Get(), reinterpret_cast<Fence*>(commandList->InFlightFence)
JULIET_DEBUG_PARAM(ConstString("CleanCommandList")));
commandList->InFlightFence = nullptr;
}
@@ -500,6 +520,7 @@ namespace Juliet::D3D12
{
driver->SubmittedCommandLists[idx] = driver->SubmittedCommandLists[driver->SubmittedCommandListCount - 1];
driver->SubmittedCommandListCount -= 1;
break;
}
}
}

View File

@@ -97,7 +97,8 @@ namespace Juliet::D3D12
extern void SetBlendConstants(NonNullPtr<CommandList> commandList, FColor blendConstants);
extern void SetBlendConstants(NonNullPtr<CommandList> commandList, FColor blendConstants);
extern void SetStencilReference(NonNullPtr<CommandList> commandList, uint8 reference);
extern void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer, IndexFormat format);
extern void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer,
IndexFormat format, size_t indexCount, index_t offset);
extern void SetPushConstants(NonNullPtr<CommandList> commandList, ShaderStage stage, uint32 rootParameterIndex,
uint32 numConstants, const void* constants);

View File

@@ -44,7 +44,7 @@ namespace Juliet::D3D12::Internal
heap->CurrentDescriptorIndex = 0;
heap->FreeIndices.Create(JULIET_DEBUG_ONLY("DescriptorHeap FreeIndices"));
heap->FreeIndices.Create(arena JULIET_DEBUG_PARAM("DescriptorHeap Free Indices"));
heap->FreeIndices.Resize(16);
heap->CurrentFreeIndex = 0;

View File

@@ -23,12 +23,6 @@
#define D3D12_CREATEDEVICE_FUNC "D3D12CreateDevice"
#define D3D12_SERIALIZE_VERSIONED_ROOT_SIGNATURE_FUNC "D3D12SerializeVersionedRootSignature"
extern "C" {
// Used to enable the "Agility SDK" components
__declspec(dllexport) extern const unsigned int D3D12SDKVersion = 615;
__declspec(dllexport) extern const char* D3D12SDKPath = ".\\D3D12\\";
}
// TODO : Use LoadLibrary and not link to the lib. Allows failing earlier if Dx12 is not installed for some reason
// + Will load the dll when needed
// This will prevent us from using IID_ variables as they are defined in dxguid.lib
@@ -604,7 +598,8 @@ namespace Juliet::D3D12
{
if (windowData->InFlightFences[idx] != nullptr)
{
ReleaseFence(driver, windowData->InFlightFences[idx]);
ReleaseFence(driver,
windowData->InFlightFences[idx] JULIET_DEBUG_PARAM(ConstString("DeatchFromWindow")));
windowData->InFlightFences[idx] = nullptr;
}
}
@@ -623,27 +618,6 @@ namespace Juliet::D3D12
Free(device.Get());
}
// Note: SetViewPort, SetScissorRect, SetBlendConstants, SetStencilReference
// are defined in D3D12CommandList.cpp and used directly via D3D12:: namespace
// BindGraphicsPipeline is now assigned directly from D3D12:: namespace
// DrawPrimitives is now assigned directly from D3D12:: namespace
void DrawIndexedPrimitives(NonNullPtr<CommandList> commandList, uint32 numIndices, uint32 numInstances,
uint32 firstIndex, uint32 vertexOffset, uint32 firstInstance)
{
auto* d3d12CommandList = reinterpret_cast<D3D12CommandList*>(commandList.Get());
d3d12CommandList->GraphicsCommandList.CommandList->DrawIndexedInstanced(numIndices, numInstances, firstIndex,
static_cast<INT>(vertexOffset), firstInstance);
}
// WaitUntilGPUIsIdle, SetPushConstants are now assigned directly from D3D12:: namespace
// QueryFence, ReleaseFence, CreateShader, DestroyShader are now assigned directly from D3D12:: namespace
// CreateGraphicsPipeline is assigned directly from D3D12:: namespace
void DestroyGraphicsPipeline(NonNullPtr<GPUDriver> driver, NonNullPtr<GraphicsPipeline> pipeline)
{
auto* d3d12Driver = static_cast<D3D12Driver*>(driver.Get());
@@ -656,8 +630,6 @@ namespace Juliet::D3D12
}
}
// Buffer functions are assigned directly from D3D12:: namespace
void CopyBufferToTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Texture> dst, NonNullPtr<GraphicsTransferBuffer> src)
{
auto* d3d12CommandList = reinterpret_cast<D3D12CommandList*>(commandList.Get());
@@ -703,14 +675,12 @@ namespace Juliet::D3D12
Internal::TextureTransitionToDefaultUsage(d3d12CommandList, d3d12Texture, D3D12_RESOURCE_STATE_COPY_DEST);
}
// GetDescriptorIndex wrapper needed to adapt signature from GPUDriver* to GraphicsDevice*
uint32 GetDescriptorIndex(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer)
{
auto* driver = static_cast<D3D12Driver*>(device->Driver);
return D3D12::GetDescriptorIndex(driver, buffer);
}
// GetDescriptorIndexTexture provides custom logic to get SRV handle index
uint32 GetDescriptorIndexTexture(NonNullPtr<GraphicsDevice> /*device*/, NonNullPtr<Texture> texture)
{
auto* textureContainer = reinterpret_cast<D3D12TextureContainer*>(texture.Get());
@@ -1042,42 +1012,41 @@ namespace Juliet::D3D12
device->AcquireSwapChainTexture = AcquireSwapChainTexture;
device->WaitAndAcquireSwapChainTexture = WaitAndAcquireSwapChainTexture;
device->GetSwapChainTextureFormat = GetSwapChainTextureFormat;
device->AcquireCommandList = D3D12::AcquireCommandList;
device->SubmitCommandLists = D3D12::SubmitCommandLists;
device->BeginRenderPass = D3D12::BeginRenderPass;
device->EndRenderPass = D3D12::EndRenderPass;
device->SetViewPort = D3D12::SetViewPort;
device->SetScissorRect = D3D12::SetScissorRect;
device->SetBlendConstants = D3D12::SetBlendConstants;
device->SetStencilReference = D3D12::SetStencilReference;
device->BindGraphicsPipeline = D3D12::BindGraphicsPipeline;
device->DrawPrimitives = D3D12::DrawPrimitives;
device->DrawIndexedPrimitives = DrawIndexedPrimitives;
device->SetIndexBuffer = D3D12::SetIndexBuffer;
device->WaitUntilGPUIsIdle = D3D12::WaitUntilGPUIsIdle;
device->SetPushConstants = D3D12::SetPushConstants;
device->QueryFence = D3D12::QueryFence;
device->ReleaseFence = D3D12::ReleaseFence;
device->CreateShader = D3D12::CreateShader;
device->DestroyShader = D3D12::DestroyShader;
device->CreateGraphicsPipeline = D3D12::CreateGraphicsPipeline;
device->DestroyGraphicsPipeline = DestroyGraphicsPipeline;
device->CreateGraphicsBuffer = D3D12::CreateGraphicsBuffer;
device->DestroyGraphicsBuffer = D3D12::DestroyGraphicsBuffer;
device->CreateGraphicsTransferBuffer = D3D12::CreateGraphicsTransferBuffer;
device->DestroyGraphicsTransferBuffer = D3D12::DestroyGraphicsTransferBuffer;
device->MapGraphicsTransferBuffer = D3D12::MapBuffer;
device->UnmapGraphicsTransferBuffer = D3D12::UnmapBuffer;
device->CopyBuffer = CopyBuffer;
device->CopyBufferToTexture = CopyBufferToTexture;
device->TransitionBufferToReadable = D3D12::TransitionBufferToReadable;
device->GetDescriptorIndex = GetDescriptorIndex;
device->GetDescriptorIndexTexture = GetDescriptorIndexTexture;
device->CreateTexture = D3D12::CreateTexture;
device->DestroyTexture = D3D12::DestroyTexture;
device->AcquireCommandList = AcquireCommandList;
device->SubmitCommandLists = SubmitCommandLists;
device->BeginRenderPass = BeginRenderPass;
device->EndRenderPass = EndRenderPass;
device->SetViewPort = SetViewPort;
device->SetScissorRect = SetScissorRect;
device->SetBlendConstants = SetBlendConstants;
device->SetStencilReference = SetStencilReference;
device->BindGraphicsPipeline = BindGraphicsPipeline;
device->DrawPrimitives = DrawPrimitives;
device->DrawIndexedPrimitives = DrawIndexedPrimitives;
device->SetIndexBuffer = SetIndexBuffer;
device->WaitUntilGPUIsIdle = WaitUntilGPUIsIdle;
device->SetPushConstants = SetPushConstants;
device->QueryFence = QueryFence;
device->ReleaseFence = ReleaseFence;
device->CreateShader = CreateShader;
device->DestroyShader = DestroyShader;
device->CreateGraphicsPipeline = CreateGraphicsPipeline;
device->DestroyGraphicsPipeline = DestroyGraphicsPipeline;
device->CreateGraphicsBuffer = CreateGraphicsBuffer;
device->DestroyGraphicsBuffer = DestroyGraphicsBuffer;
device->MapGraphicsBuffer = MapBuffer;
device->UnmapGraphicsBuffer = UnmapBuffer;
device->CreateGraphicsTransferBuffer = CreateGraphicsTransferBuffer;
device->DestroyGraphicsTransferBuffer = DestroyGraphicsTransferBuffer;
device->MapGraphicsTransferBuffer = MapBuffer;
device->UnmapGraphicsTransferBuffer = UnmapBuffer;
device->CopyBuffer = CopyBuffer;
device->CopyBufferToTexture = CopyBufferToTexture;
device->TransitionBufferToReadable = TransitionBufferToReadable;
device->GetDescriptorIndex = GetDescriptorIndex;
device->GetDescriptorIndexTexture = GetDescriptorIndexTexture;
device->CreateTexture = CreateTexture;
device->DestroyTexture = DestroyTexture;
#if ALLOW_SHADER_HOT_RELOAD
device->UpdateGraphicsPipelineShaders = UpdateGraphicsPipelineShaders;

View File

@@ -283,4 +283,12 @@ namespace Juliet::D3D12
d3d12CommandList->GraphicsCommandList.CommandList->DrawInstanced(numVertices, numInstances, firstVertex, firstInstance);
}
void DrawIndexedPrimitives(NonNullPtr<CommandList> commandList, uint32 numIndices, uint32 numInstances,
uint32 firstIndex, uint32 vertexOffset, uint32 firstInstance)
{
auto* d3d12CommandList = reinterpret_cast<D3D12CommandList*>(commandList.Get());
d3d12CommandList->GraphicsCommandList.CommandList->DrawIndexedInstanced(numIndices, numInstances, firstIndex,
static_cast<INT>(vertexOffset), firstInstance);
}
} // namespace Juliet::D3D12

View File

@@ -13,4 +13,6 @@ namespace Juliet::D3D12
extern void BindGraphicsPipeline(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsPipeline> graphicsPipeline);
extern void DrawPrimitives(NonNullPtr<CommandList> commandList, uint32 numVertices, uint32 numInstances,
uint32 firstVertex, uint32 firstInstance);
void DrawIndexedPrimitives(NonNullPtr<CommandList> commandList, uint32 numIndices, uint32 numInstances,
uint32 firstIndex, uint32 vertexOffset, uint32 firstInstance);
} // namespace Juliet::D3D12

View File

@@ -140,7 +140,8 @@ namespace Juliet::D3D12
{
// Wait until the fence for the frame is signaled.
// In VSYNC this means waiting that the least recent presented frame is done
if (!Wait(driver, true, &windowData->InFlightFences[windowData->WindowFrameCounter], 1))
if (!Wait(driver, true, &windowData->InFlightFences[windowData->WindowFrameCounter],
1 JULIET_DEBUG_PARAM(ConstString("AcquireSwapChainTexture"))))
{
return false;
}
@@ -155,7 +156,8 @@ namespace Juliet::D3D12
}
}
ReleaseFence(driver, windowData->InFlightFences[windowData->WindowFrameCounter]);
ReleaseFence(driver, windowData->InFlightFences[windowData->WindowFrameCounter] JULIET_DEBUG_PARAM(
ConstString("AcquireSwapChainTexture")));
windowData->InFlightFences[windowData->WindowFrameCounter] = nullptr;
}
@@ -219,7 +221,8 @@ namespace Juliet::D3D12
if (windowData->InFlightFences[windowData->WindowFrameCounter] != nullptr)
{
if (!Wait(d3d12Driver, true, &windowData->InFlightFences[windowData->WindowFrameCounter], 1))
if (!Wait(d3d12Driver, true, &windowData->InFlightFences[windowData->WindowFrameCounter],
1 JULIET_DEBUG_PARAM(ConstString("WaitForSwapchain"))))
{
return false;
}

View File

@@ -17,16 +17,21 @@ namespace Juliet::D3D12
driver->AvailableFenceCapacity = driver->AvailableFenceCapacity * 2;
driver->AvailableFences = static_cast<D3D12Fence**>(
Realloc(driver->AvailableFences, sizeof(D3D12Fence*) * driver->AvailableFenceCapacity));
LogDebug(LogCategory::Graphics, "ReleaseFenceToPool With Realloc");
}
driver->AvailableFences[driver->AvailableFenceCount] = fence;
driver->AvailableFenceCount += 1;
LogDebug(LogCategory::Graphics, "ReleaseFenceToPool %x fence. Handle %x | Event %x | Refcount %d",
fence.Get(), fence->Handle, fence->Event, fence->ReferenceCount);
}
} // namespace
bool WaitUntilGPUIsIdle(NonNullPtr<GPUDriver> driver)
{
auto d3d12driver = static_cast<D3D12Driver*>(driver.Get());
D3D12Fence* fence = Internal::AcquireFence(d3d12driver);
D3D12Fence* fence = Internal::AcquireFence(d3d12driver JULIET_DEBUG_PARAM(ConstString("WaitUntilGPUIsIdle")));
if (!fence)
{
return false;
@@ -56,14 +61,19 @@ namespace Juliet::D3D12
}
}
ReleaseFence(driver, reinterpret_cast<Fence*>(fence));
ReleaseFence(driver, reinterpret_cast<Fence*>(fence) JULIET_DEBUG_PARAM(ConstString("WaitUntilGPUIsIdle")));
bool result = true;
// Clean up
for (int32 idx = d3d12driver->SubmittedCommandListCount - 1; idx >= 0; --idx)
{
result &= Internal::CleanCommandList(d3d12driver, d3d12driver->SubmittedCommandLists[idx], false);
int32 idx = 0;
while (idx < d3d12driver->SubmittedCommandListCount)
{
result &= Internal::CleanCommandList(d3d12driver, d3d12driver->SubmittedCommandLists[idx], false);
// CleanCommandList swaps [idx] with last and decrements count.
// Don't increment — re-check the swapped-in element.
}
}
Internal::DisposePendingResourcces(d3d12driver);
@@ -71,16 +81,17 @@ namespace Juliet::D3D12
return result;
}
bool Wait(NonNullPtr<GPUDriver> driver, bool waitForAll, Fence* const* fences, uint32 numFences)
bool Wait(NonNullPtr<GPUDriver> driver, bool waitForAll, Fence* const* fences, uint32 numFences JULIET_DEBUG_PARAM(String querier))
{
auto d3d12driver = static_cast<D3D12Driver*>(driver.Get());
// TODO: use scratch allocator for alloca (stack alloc)
auto events = static_cast<HANDLE*>(alloca(sizeof(HANDLE) * numFences));
HANDLE* events = static_cast<HANDLE*>(alloca(sizeof(HANDLE) * numFences));
MemoryZero(events, sizeof(HANDLE) * numFences);
for (uint32 i = 0; i < numFences; ++i)
{
auto fence = reinterpret_cast<D3D12Fence*>(fences[i]);
D3D12Fence* fence = reinterpret_cast<D3D12Fence*>(fences[i]);
HRESULT res = fence->Handle->SetEventOnCompletion(D3D12_FENCE_SIGNAL_VALUE, fence->Event);
if (FAILED(res))
@@ -91,6 +102,15 @@ namespace Juliet::D3D12
events[i] = fence->Event;
}
#if JULIET_DEBUG
LogDebug(LogCategory::Graphics, "Waiting for %d fences. Querier %s", numFences, CStr(querier));
#endif
for (uint32 i = 0; i < numFences; ++i)
{
D3D12Fence* d3d12fence = reinterpret_cast<D3D12Fence*>(fences[i]);
LogDebug(LogCategory::Graphics, "Waiting for %x fence. Handle %x | Event %x | Refcount %d", d3d12fence,
d3d12fence->Handle, d3d12fence->Event, d3d12fence->ReferenceCount);
}
DWORD waitResult = WaitForMultipleObjects(numFences, events, waitForAll, INFINITE);
if (waitResult == WAIT_FAILED)
@@ -102,12 +122,19 @@ namespace Juliet::D3D12
bool result = true;
// Clean up
for (int32 idx = d3d12driver->SubmittedCommandListCount - 1; idx >= 0; --idx -= 1)
{
uint64 fenceValue = d3d12driver->SubmittedCommandLists[idx]->InFlightFence->Handle->GetCompletedValue();
if (fenceValue == D3D12_FENCE_SIGNAL_VALUE)
int32 idx = 0;
while (idx < d3d12driver->SubmittedCommandListCount)
{
result &= Internal::CleanCommandList(d3d12driver, d3d12driver->SubmittedCommandLists[idx], false);
uint64 fenceValue = d3d12driver->SubmittedCommandLists[idx]->InFlightFence->Handle->GetCompletedValue();
if (fenceValue == D3D12_FENCE_SIGNAL_VALUE)
{
result &= Internal::CleanCommandList(d3d12driver, d3d12driver->SubmittedCommandLists[idx], false);
}
else
{
idx += 1;
}
}
}
@@ -122,11 +149,15 @@ namespace Juliet::D3D12
return true;
}
void ReleaseFence(NonNullPtr<GPUDriver> driver, NonNullPtr<Fence> fence)
void ReleaseFence(NonNullPtr<GPUDriver> driver, NonNullPtr<Fence> fence JULIET_DEBUG_PARAM(String querier))
{
auto d3d12driver = static_cast<D3D12Driver*>(driver.Get());
auto d3d12Fence = reinterpret_cast<D3D12Fence*>(fence.Get());
#if JULIET_DEBUG
LogDebug(LogCategory::Graphics, "ReleaseFence | %x fence. Handle %x | Event %x | Refcount %d | Querier %s", d3d12Fence,
d3d12Fence->Handle, d3d12Fence->Event, d3d12Fence->ReferenceCount, CStr(querier));
#endif
if (--d3d12Fence->ReferenceCount == 0)
{
ReleaseFenceToPool(d3d12driver, d3d12Fence);
@@ -170,7 +201,7 @@ namespace Juliet::D3D12
}
}
D3D12Fence* AcquireFence(NonNullPtr<D3D12Driver> driver)
D3D12Fence* AcquireFence(NonNullPtr<D3D12Driver> driver JULIET_DEBUG_PARAM(String querier))
{
D3D12Fence* fence;
ID3D12Fence* handle;
@@ -180,7 +211,7 @@ namespace Juliet::D3D12
if (driver->AvailableFenceCount == 0)
{
HRESULT result = driver->D3D12Device->CreateFence(D3D12_FENCE_UNSIGNALED_VALUE, D3D12_FENCE_FLAG_NONE,
IID_ID3D12Fence, reinterpret_cast<void**>(&handle));
IID_ID3D12Fence, reinterpret_cast<void**>(&handle));
if (FAILED(result))
{
LogError(driver->D3D12Device, "Failed to create fence!", result);
@@ -197,15 +228,28 @@ namespace Juliet::D3D12
fence->Handle = handle;
fence->Event = CreateEvent(nullptr, false, false, nullptr);
fence->ReferenceCount = 0;
#if JULIET_DEBUG
LogDebug(LogCategory::Graphics, "Acquire Querier %s | Setting Signal to 0 NEW fence", CStr(querier));
#endif
}
else
{
fence = driver->AvailableFences[driver->AvailableFenceCount - 1];
driver->AvailableFenceCount -= 1;
fence->Handle->Signal(D3D12_FENCE_UNSIGNALED_VALUE);
#if JULIET_DEBUG
LogDebug(LogCategory::Graphics, "Acquire Querier %s | Setting Signal to 0, RECYCLING", CStr(querier));
#endif
}
fence->ReferenceCount += 1;
Assert(fence->ReferenceCount == 1);
#if JULIET_DEBUG
LogDebug(LogCategory::Graphics, "Acquire Querier %s | %x fence. Handle %x | Event %x | Refcount %d",
CStr(querier), fence, fence->Handle, fence->Event, fence->ReferenceCount);
#endif
return fence;
}

View File

@@ -26,9 +26,10 @@ namespace Juliet::D3D12
};
extern bool WaitUntilGPUIsIdle(NonNullPtr<GPUDriver> driver);
extern bool Wait(NonNullPtr<GPUDriver> driver, bool waitForAll, Fence* const* fences, uint32 numFences);
extern bool Wait(NonNullPtr<GPUDriver> driver, bool waitForAll, Fence* const* fences,
uint32 numFences JULIET_DEBUG_PARAM(String querier));
extern bool QueryFence(NonNullPtr<GPUDriver> driver, NonNullPtr<Fence> fence);
extern void ReleaseFence(NonNullPtr<GPUDriver> driver, NonNullPtr<Fence> fence);
extern void ReleaseFence(NonNullPtr<GPUDriver> driver, NonNullPtr<Fence> fence JULIET_DEBUG_PARAM(String querier));
namespace Internal
{
@@ -36,7 +37,7 @@ namespace Juliet::D3D12
D3D12_RESOURCE_STATES destinationState, ID3D12Resource* resource,
uint32 subresourceIndex, bool needsUavBarrier);
extern D3D12Fence* AcquireFence(NonNullPtr<D3D12Driver> driver);
extern D3D12Fence* AcquireFence(NonNullPtr<D3D12Driver> driver JULIET_DEBUG_PARAM(String querier));
extern void DestroyFence(NonNullPtr<D3D12Fence> fence);
} // namespace Internal
} // namespace Juliet::D3D12

View File

@@ -1,124 +1,124 @@
#include <algorithm>
#include <Core/Common/EnumUtils.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Memory/Allocator.h>
#include <Graphics/D3D12/D3D12CommandList.h>
#include <Graphics/D3D12/D3D12GraphicsDevice.h>
#include <Graphics/D3D12/D3D12Synchronization.h>
#include <Graphics/D3D12/D3D12Texture.h>
#include <Graphics/D3D12/D3D12GraphicsDevice.h>
#include <Graphics/D3D12/D3D12Utils.h>
#include <Core/Memory/Allocator.h>
#include <algorithm>
namespace Juliet::D3D12
{
namespace
{
DXGI_FORMAT JulietToD3D12_TextureFormat[] = {
DXGI_FORMAT_UNKNOWN, // INVALID
DXGI_FORMAT_A8_UNORM, // A8_UNORM
DXGI_FORMAT_R8_UNORM, // R8_UNORM
DXGI_FORMAT_R8G8_UNORM, // R8G8_UNORM
DXGI_FORMAT_R8G8B8A8_UNORM, // R8G8B8A8_UNORM
DXGI_FORMAT_R16_UNORM, // R16_UNORM
DXGI_FORMAT_R16G16_UNORM, // R16G16_UNORM
DXGI_FORMAT_R16G16B16A16_UNORM, // R16G16B16A16_UNORM
DXGI_FORMAT_R10G10B10A2_UNORM, // R10G10B10A2_UNORM
DXGI_FORMAT_B5G6R5_UNORM, // B5G6R5_UNORM
DXGI_FORMAT_B5G5R5A1_UNORM, // B5G5R5A1_UNORM
DXGI_FORMAT_B4G4R4A4_UNORM, // B4G4R4A4_UNORM
DXGI_FORMAT_B8G8R8A8_UNORM, // B8G8R8A8_UNORM
DXGI_FORMAT_BC1_UNORM, // BC1_UNORM
DXGI_FORMAT_BC2_UNORM, // BC2_UNORM
DXGI_FORMAT_BC3_UNORM, // BC3_UNORM
DXGI_FORMAT_BC4_UNORM, // BC4_UNORM
DXGI_FORMAT_BC5_UNORM, // BC5_UNORM
DXGI_FORMAT_BC7_UNORM, // BC7_UNORM
DXGI_FORMAT_BC6H_SF16, // BC6H_FLOAT
DXGI_FORMAT_BC6H_UF16, // BC6H_UFLOAT
DXGI_FORMAT_R8_SNORM, // R8_SNORM
DXGI_FORMAT_R8G8_SNORM, // R8G8_SNORM
DXGI_FORMAT_R8G8B8A8_SNORM, // R8G8B8A8_SNORM
DXGI_FORMAT_R16_SNORM, // R16_SNORM
DXGI_FORMAT_R16G16_SNORM, // R16G16_SNORM
DXGI_FORMAT_R16G16B16A16_SNORM, // R16G16B16A16_SNORM
DXGI_FORMAT_R16_FLOAT, // R16_FLOAT
DXGI_FORMAT_R16G16_FLOAT, // R16G16_FLOAT
DXGI_FORMAT_R16G16B16A16_FLOAT, // R16G16B16A16_FLOAT
DXGI_FORMAT_R32_FLOAT, // R32_FLOAT
DXGI_FORMAT_R32G32_FLOAT, // R32G32_FLOAT
DXGI_FORMAT_R32G32B32A32_FLOAT, // R32G32B32A32_FLOAT
DXGI_FORMAT_R11G11B10_FLOAT, // R11G11B10_UFLOAT
DXGI_FORMAT_R8_UINT, // R8_UINT
DXGI_FORMAT_R8G8_UINT, // R8G8_UINT
DXGI_FORMAT_R8G8B8A8_UINT, // R8G8B8A8_UINT
DXGI_FORMAT_R16_UINT, // R16_UINT
DXGI_FORMAT_R16G16_UINT, // R16G16_UINT
DXGI_FORMAT_R16G16B16A16_UINT, // R16G16B16A16_UINT
DXGI_FORMAT_R32_UINT, // R32_UINT
DXGI_FORMAT_R32G32_UINT, // R32G32_UINT
DXGI_FORMAT_R32G32B32A32_UINT, // R32G32B32A32_UINT
DXGI_FORMAT_R8_SINT, // R8_INT
DXGI_FORMAT_R8G8_SINT, // R8G8_INT
DXGI_FORMAT_R8G8B8A8_SINT, // R8G8B8A8_INT
DXGI_FORMAT_R16_SINT, // R16_INT
DXGI_FORMAT_R16G16_SINT, // R16G16_INT
DXGI_FORMAT_R16G16B16A16_SINT, // R16G16B16A16_INT
DXGI_FORMAT_R32_SINT, // R32_INT
DXGI_FORMAT_R32G32_SINT, // R32G32_INT
DXGI_FORMAT_R32G32B32A32_SINT, // R32G32B32A32_INT
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, // R8G8B8A8_UNORM_SRGB
DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, // B8G8R8A8_UNORM_SRGB
DXGI_FORMAT_BC1_UNORM_SRGB, // BC1_UNORM_SRGB
DXGI_FORMAT_BC2_UNORM_SRGB, // BC2_UNORM_SRGB
DXGI_FORMAT_BC3_UNORM_SRGB, // BC3_UNORM_SRGB
DXGI_FORMAT_BC7_UNORM_SRGB, // BC7_UNORM_SRGB
DXGI_FORMAT_R16_TYPELESS, // D16_UNORM
DXGI_FORMAT_R24G8_TYPELESS, // D24_UNORM
DXGI_FORMAT_R32_TYPELESS, // D32_FLOAT
DXGI_FORMAT_R24G8_TYPELESS, // D24_UNORM_S8_UINT
DXGI_FORMAT_R32G8X24_TYPELESS, // D32_FLOAT_S8_UINT
DXGI_FORMAT_UNKNOWN, // ASTC_4x4_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_5x4_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_5x5_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_6x5_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_6x6_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_8x5_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_8x6_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_8x8_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_10x5_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_10x6_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_10x8_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_10x10_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_12x10_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_12x12_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_4x4_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_5x4_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_5x5_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_6x5_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_6x6_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_8x5_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_8x6_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_8x8_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_10x5_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_10x6_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_10x8_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_10x10_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_12x10_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_12x12_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_4x4_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_5x4_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_5x5_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_6x5_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_6x6_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_8x5_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_8x6_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_8x8_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_10x5_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_10x6_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_10x8_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_10x10_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_12x10_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_12x12_FLOAT
DXGI_FORMAT_UNKNOWN, // INVALID
DXGI_FORMAT_A8_UNORM, // A8_UNORM
DXGI_FORMAT_R8_UNORM, // R8_UNORM
DXGI_FORMAT_R8G8_UNORM, // R8G8_UNORM
DXGI_FORMAT_R8G8B8A8_UNORM, // R8G8B8A8_UNORM
DXGI_FORMAT_R16_UNORM, // R16_UNORM
DXGI_FORMAT_R16G16_UNORM, // R16G16_UNORM
DXGI_FORMAT_R16G16B16A16_UNORM, // R16G16B16A16_UNORM
DXGI_FORMAT_R10G10B10A2_UNORM, // R10G10B10A2_UNORM
DXGI_FORMAT_B5G6R5_UNORM, // B5G6R5_UNORM
DXGI_FORMAT_B5G5R5A1_UNORM, // B5G5R5A1_UNORM
DXGI_FORMAT_B4G4R4A4_UNORM, // B4G4R4A4_UNORM
DXGI_FORMAT_B8G8R8A8_UNORM, // B8G8R8A8_UNORM
DXGI_FORMAT_BC1_UNORM, // BC1_UNORM
DXGI_FORMAT_BC2_UNORM, // BC2_UNORM
DXGI_FORMAT_BC3_UNORM, // BC3_UNORM
DXGI_FORMAT_BC4_UNORM, // BC4_UNORM
DXGI_FORMAT_BC5_UNORM, // BC5_UNORM
DXGI_FORMAT_BC7_UNORM, // BC7_UNORM
DXGI_FORMAT_BC6H_SF16, // BC6H_FLOAT
DXGI_FORMAT_BC6H_UF16, // BC6H_UFLOAT
DXGI_FORMAT_R8_SNORM, // R8_SNORM
DXGI_FORMAT_R8G8_SNORM, // R8G8_SNORM
DXGI_FORMAT_R8G8B8A8_SNORM, // R8G8B8A8_SNORM
DXGI_FORMAT_R16_SNORM, // R16_SNORM
DXGI_FORMAT_R16G16_SNORM, // R16G16_SNORM
DXGI_FORMAT_R16G16B16A16_SNORM, // R16G16B16A16_SNORM
DXGI_FORMAT_R16_FLOAT, // R16_FLOAT
DXGI_FORMAT_R16G16_FLOAT, // R16G16_FLOAT
DXGI_FORMAT_R16G16B16A16_FLOAT, // R16G16B16A16_FLOAT
DXGI_FORMAT_R32_FLOAT, // R32_FLOAT
DXGI_FORMAT_R32G32_FLOAT, // R32G32_FLOAT
DXGI_FORMAT_R32G32B32A32_FLOAT, // R32G32B32A32_FLOAT
DXGI_FORMAT_R11G11B10_FLOAT, // R11G11B10_UFLOAT
DXGI_FORMAT_R8_UINT, // R8_UINT
DXGI_FORMAT_R8G8_UINT, // R8G8_UINT
DXGI_FORMAT_R8G8B8A8_UINT, // R8G8B8A8_UINT
DXGI_FORMAT_R16_UINT, // R16_UINT
DXGI_FORMAT_R16G16_UINT, // R16G16_UINT
DXGI_FORMAT_R16G16B16A16_UINT, // R16G16B16A16_UINT
DXGI_FORMAT_R32_UINT, // R32_UINT
DXGI_FORMAT_R32G32_UINT, // R32G32_UINT
DXGI_FORMAT_R32G32B32A32_UINT, // R32G32B32A32_UINT
DXGI_FORMAT_R8_SINT, // R8_INT
DXGI_FORMAT_R8G8_SINT, // R8G8_INT
DXGI_FORMAT_R8G8B8A8_SINT, // R8G8B8A8_INT
DXGI_FORMAT_R16_SINT, // R16_INT
DXGI_FORMAT_R16G16_SINT, // R16G16_INT
DXGI_FORMAT_R16G16B16A16_SINT, // R16G16B16A16_INT
DXGI_FORMAT_R32_SINT, // R32_INT
DXGI_FORMAT_R32G32_SINT, // R32G32_INT
DXGI_FORMAT_R32G32B32A32_SINT, // R32G32B32A32_INT
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, // R8G8B8A8_UNORM_SRGB
DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, // B8G8R8A8_UNORM_SRGB
DXGI_FORMAT_BC1_UNORM_SRGB, // BC1_UNORM_SRGB
DXGI_FORMAT_BC2_UNORM_SRGB, // BC2_UNORM_SRGB
DXGI_FORMAT_BC3_UNORM_SRGB, // BC3_UNORM_SRGB
DXGI_FORMAT_BC7_UNORM_SRGB, // BC7_UNORM_SRGB
DXGI_FORMAT_R16_TYPELESS, // D16_UNORM
DXGI_FORMAT_R24G8_TYPELESS, // D24_UNORM
DXGI_FORMAT_R32_TYPELESS, // D32_FLOAT
DXGI_FORMAT_R24G8_TYPELESS, // D24_UNORM_S8_UINT
DXGI_FORMAT_R32G8X24_TYPELESS, // D32_FLOAT_S8_UINT
DXGI_FORMAT_UNKNOWN, // ASTC_4x4_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_5x4_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_5x5_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_6x5_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_6x6_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_8x5_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_8x6_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_8x8_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_10x5_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_10x6_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_10x8_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_10x10_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_12x10_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_12x12_UNORM
DXGI_FORMAT_UNKNOWN, // ASTC_4x4_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_5x4_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_5x5_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_6x5_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_6x6_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_8x5_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_8x6_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_8x8_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_10x5_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_10x6_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_10x8_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_10x10_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_12x10_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_12x12_UNORM_SRGB
DXGI_FORMAT_UNKNOWN, // ASTC_4x4_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_5x4_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_5x5_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_6x5_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_6x6_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_8x5_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_8x6_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_8x8_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_10x5_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_10x6_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_10x8_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_10x10_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_12x10_FLOAT
DXGI_FORMAT_UNKNOWN, // ASTC_12x12_FLOAT
};
static_assert(sizeof(JulietToD3D12_TextureFormat) / sizeof(JulietToD3D12_TextureFormat[0]) ==
ToUnderlying(TextureFormat::Count));
@@ -375,13 +375,9 @@ namespace Juliet::D3D12
case TextureType::Texture_2D:
case TextureType::Texture_2DArray:
case TextureType::Texture_Cube:
case TextureType::Texture_CubeArray:
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
break;
case TextureType::Texture_CubeArray: desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; break;
case TextureType::Texture_3D:
case TextureType::Texture_3DArray:
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE3D;
break;
case TextureType::Texture_3DArray: desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE3D; break;
}
desc.Alignment = 0;
@@ -396,17 +392,27 @@ namespace Juliet::D3D12
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
if ((createInfo.Flags & TextureUsageFlag::ColorTarget) != TextureUsageFlag::None)
{
desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
}
if ((createInfo.Flags & TextureUsageFlag::DepthStencilTarget) != TextureUsageFlag::None)
{
desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
}
if ((createInfo.Flags & TextureUsageFlag::ComputeStorageWrite) != TextureUsageFlag::None)
{
desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
}
D3D12_HEAP_PROPERTIES heapProps = {};
heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 0; // We don't do multi-adapter operation
heapProps.VisibleNodeMask = 0; // We don't do multi-adapter operation
ID3D12Resource* resource = nullptr;
D3D12_CLEAR_VALUE clearValue = {};
ID3D12Resource* resource = nullptr;
D3D12_CLEAR_VALUE clearValue = {};
D3D12_CLEAR_VALUE* pClearValue = nullptr;
if (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)
@@ -426,9 +432,10 @@ namespace Juliet::D3D12
pClearValue = &clearValue;
}
D3D12_RESOURCE_STATES initialState = GetDefaultTextureResourceState(createInfo.Flags);
HRESULT hr = d3d12Driver->D3D12Device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &desc,
D3D12_RESOURCE_STATE_COMMON, pClearValue, IID_ID3D12Resource,
reinterpret_cast<void**>(&resource));
initialState, pClearValue, IID_ID3D12Resource,
reinterpret_cast<void**>(&resource));
if (FAILED(hr))
{
@@ -451,10 +458,11 @@ namespace Juliet::D3D12
texture->Resource = resource;
texture->ReferenceCount = 1;
uint32 numLayers = std::max<uint32>(1, createInfo.LayerCount);
uint32 numMips = std::max<uint32>(1, createInfo.MipLevelCount);
uint32 numLayers = std::max<uint32>(1, createInfo.LayerCount);
uint32 numMips = std::max<uint32>(1, createInfo.MipLevelCount);
texture->SubresourceCount = numLayers * numMips;
texture->Subresources = static_cast<D3D12TextureSubresource*>(Calloc(texture->SubresourceCount, sizeof(D3D12TextureSubresource)));
texture->Subresources =
static_cast<D3D12TextureSubresource*>(Calloc(texture->SubresourceCount, sizeof(D3D12TextureSubresource)));
for (uint32 layer = 0; layer < numLayers; ++layer)
{
@@ -499,14 +507,14 @@ namespace Juliet::D3D12
Internal::D3D12Descriptor descriptor;
if (Internal::AssignDescriptor(d3d12Driver->BindlessDescriptorHeap, descriptor))
{
texture->SRVHandle = D3D12StagingDescriptor{};
texture->SRVHandle = D3D12StagingDescriptor{};
texture->SRVHandle.CpuHandleIndex = descriptor.Index;
texture->SRVHandle.CpuHandle = descriptor.CpuHandle;
texture->SRVHandle.Heap = descriptor.Heap;
texture->SRVHandle.CpuHandle = descriptor.CpuHandle;
texture->SRVHandle.Heap = descriptor.Heap;
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Format = desc.Format;
srvDesc.Format = desc.Format;
// Fix SRV format for Depth Buffers (TypeLess -> Typed)
if (createInfo.Format == TextureFormat::D32_FLOAT)
srvDesc.Format = DXGI_FORMAT_R32_FLOAT;
@@ -517,11 +525,11 @@ namespace Juliet::D3D12
else if (createInfo.Format == TextureFormat::D32_FLOAT_S8_UINT)
srvDesc.Format = DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = numMips;
srvDesc.Texture2D.PlaneSlice = 0;
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = numMips;
srvDesc.Texture2D.PlaneSlice = 0;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
d3d12Driver->D3D12Device->CreateShaderResourceView(resource, &srvDesc, descriptor.CpuHandle);
@@ -529,7 +537,6 @@ namespace Juliet::D3D12
}
return reinterpret_cast<Texture*>(textureContainer);
}
void DestroyTexture(NonNullPtr<GPUDriver> driver, NonNullPtr<Texture> texture)

View File

@@ -2,8 +2,10 @@
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/Memory/Allocator.h>
#include <Graphics/GraphicsPipeline.h>
#include <Graphics/PushConstants.h>
namespace Juliet
{
@@ -84,11 +86,11 @@ namespace Juliet
ShaderCreateInfo shaderCI = {};
shaderCI.EntryPoint = entryPoint;
String vertPath = WrapString("../../Assets/compiled/Debug.vert.dxil");
String vertPath = GetAssetPath(WrapString("Debug.vert.dxil"));
shaderCI.Stage = ShaderStage::Vertex;
Shader* vertexShader = CreateShader(device, vertPath, shaderCI);
String fragPath = WrapString("../../Assets/compiled/Debug.frag.dxil");
String fragPath = GetAssetPath(WrapString("Debug.frag.dxil"));
shaderCI.Stage = ShaderStage::Fragment;
Shader* fragmentShader = CreateShader(device, fragPath, shaderCI);
@@ -298,19 +300,24 @@ namespace Juliet
// Render depth-tested primitives (vertices at offset 0 in buffer)
if (g_DebugState.DepthTestedVertexCount > 0 && g_DebugState.DepthTestedPipeline && g_DebugState.VertexBuffer)
{
BindGraphicsPipeline(renderPass, g_DebugState.DepthTestedPipeline);
// Pack VP matrix + buffer index into push constants
struct
{
Matrix vp;
uint32 bufferIndex;
uint32 vertexOffset; // Offset in vertices (not bytes)
uint32 padding[2];
} pushData;
pushData.vp = Camera_GetViewProjectionMatrix(camera);
pushData.bufferIndex = bufferIndex;
pushData.vertexOffset = 0; // Depth-tested vertices start at 0
PushData pushData = {};
pushData.ViewProjection = Camera_GetViewProjectionMatrix(camera);
pushData.MeshIndex = 0;
pushData.TransformsBufferIndex = 0; // Not used by debug shader but layout must match
pushData.BufferIndex = bufferIndex;
pushData.TextureIndex = 0;
pushData.VertexOffset = 0; // Depth-tested vertices start at 0
pushData.Padding = 0;
pushData.Scale[0] = 1.0f; pushData.Scale[1] = 1.0f;
pushData.Translate[0] = 0.0f; pushData.Translate[1] = 0.0f;
// Dummy light data as we don't light debug primitives
pushData.GlobalLightDirection = {0,0,-1};
pushData.GlobalLightPad = 0;
pushData.GlobalLightColor = {1,1,1};
pushData.GlobalAmbientIntensity = 1.0f;
SetPushConstants(cmdList, ShaderStage::Vertex, 0, sizeof(pushData) / sizeof(uint32), &pushData);
DrawPrimitives(renderPass, g_DebugState.DepthTestedVertexCount, 1, 0, 0);
@@ -322,16 +329,23 @@ namespace Juliet
BindGraphicsPipeline(renderPass, g_DebugState.OverlayPipeline);
// Pack VP matrix + buffer index into push constants
struct
{
Matrix vp;
uint32 bufferIndex;
uint32 vertexOffset; // Offset in vertices (not bytes)
uint32 padding[2];
} pushData;
pushData.vp = Camera_GetViewProjectionMatrix(camera);
pushData.bufferIndex = bufferIndex;
pushData.vertexOffset = kMaxDebugVertices / 2; // Overlay vertices start at half
PushData pushData = {};
pushData.ViewProjection = Camera_GetViewProjectionMatrix(camera);
pushData.MeshIndex = 0;
pushData.TransformsBufferIndex = 0;
pushData.BufferIndex = bufferIndex;
pushData.TextureIndex = 0;
pushData.VertexOffset = kMaxDebugVertices / 2; // Overlay vertices start at half
pushData.Padding = 0;
pushData.Scale[0] = 1.0f; pushData.Scale[1] = 1.0f;
pushData.Translate[0] = 0.0f; pushData.Translate[1] = 0.0f;
// Dummy light data as we don't light debug primitives
pushData.GlobalLightDirection = {0,0,-1};
pushData.GlobalLightPad = 0;
pushData.GlobalLightColor = {1,1,1};
pushData.GlobalAmbientIntensity = 1.0f;
SetPushConstants(cmdList, ShaderStage::Vertex, 0, sizeof(pushData) / sizeof(uint32), &pushData);
DrawPrimitives(renderPass, g_DebugState.OverlayVertexCount, 1, 0, 0);

View File

@@ -1,11 +1,10 @@
#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/HAL/IO/IOStream.h>
#include <Core/ImGui/ImGuiTests.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Graphics/Graphics.h>
#include <Graphics/GraphicsDevice.h>
#include <Core/ImGui/ImGuiTests.h>
namespace Juliet
{
@@ -63,7 +62,6 @@ namespace Juliet
newDevice->Name = chosenFactory->Name;
return newDevice;
}
}
return nullptr;
}
@@ -263,13 +261,14 @@ namespace Juliet
commandListHeader->Device->SetStencilReference(commandList, reference);
}
void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer, IndexFormat format)
void SetIndexBuffer(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer, IndexFormat format,
size_t indexCount, index_t offset)
{
auto* commandListHeader = reinterpret_cast<CommandListHeader*>(commandList.Get());
if (commandListHeader->Device->SetIndexBuffer)
{
commandListHeader->Device->SetIndexBuffer(commandList, buffer, format);
commandListHeader->Device->SetIndexBuffer(commandList, buffer, format, indexCount, offset);
}
}
@@ -297,13 +296,11 @@ namespace Juliet
if (commandListHeader->Device->DrawIndexedPrimitives)
{
commandListHeader->Device->DrawIndexedPrimitives(commandList, numIndices, numInstances, firstIndex, vertexOffset, firstInstance);
commandListHeader->Device->DrawIndexedPrimitives(commandList, numIndices, numInstances, firstIndex,
vertexOffset, firstInstance);
}
}
void SetPushConstants(NonNullPtr<CommandList> commandList, ShaderStage stage, uint32 rootParameterIndex,
uint32 numConstants, const void* constants)
{
@@ -374,7 +371,7 @@ namespace Juliet
GraphicsBuffer* CreateGraphicsBuffer(NonNullPtr<GraphicsDevice> device, const BufferCreateInfo& createInfo)
{
return device->CreateGraphicsBuffer(device->Driver, createInfo.Size, createInfo.Usage);
return device->CreateGraphicsBuffer(device->Driver, createInfo.Size, createInfo.Stride, createInfo.Usage, createInfo.IsDynamic);
}
GraphicsTransferBuffer* CreateGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, const TransferBufferCreateInfo& createInfo)
@@ -382,6 +379,16 @@ namespace Juliet
return device->CreateGraphicsTransferBuffer(device->Driver, createInfo.Size, createInfo.Usage);
}
void* MapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer)
{
return device->MapGraphicsBuffer(device->Driver, buffer);
}
void UnmapGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer)
{
device->UnmapGraphicsBuffer(device->Driver, buffer);
}
void* MapGraphicsTransferBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsTransferBuffer> buffer)
{
return device->MapGraphicsTransferBuffer(device->Driver, buffer);
@@ -399,8 +406,7 @@ namespace Juliet
headers->Device->CopyBuffer(commandList, dst, src, size, dstOffset, srcOffset);
}
void CopyBufferToTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Texture> dst,
NonNullPtr<GraphicsTransferBuffer> src)
void CopyBufferToTexture(NonNullPtr<CommandList> commandList, NonNullPtr<Texture> dst, NonNullPtr<GraphicsTransferBuffer> src)
{
auto* headers = reinterpret_cast<CommandListHeader*>(commandList.Get());
if (headers->Device->CopyBufferToTexture)
@@ -409,7 +415,6 @@ namespace Juliet
}
}
void TransitionBufferToReadable(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer)
{
auto* header = reinterpret_cast<CommandListHeader*>(commandList.Get());
@@ -426,7 +431,6 @@ namespace Juliet
return device->GetDescriptorIndexTexture(device, texture);
}
void DestroyGraphicsBuffer(NonNullPtr<GraphicsDevice> device, NonNullPtr<GraphicsBuffer> buffer)
{
device->DestroyGraphicsBuffer(buffer);

View File

@@ -68,7 +68,8 @@ namespace Juliet
void (*SetScissorRect)(NonNullPtr<CommandList> commandList, const Rectangle& viewPort);
void (*SetBlendConstants)(NonNullPtr<CommandList> commandList, FColor blendConstants);
void (*SetStencilReference)(NonNullPtr<CommandList> commandList, uint8 reference);
void (*SetIndexBuffer)(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer, IndexFormat format);
void (*SetIndexBuffer)(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsBuffer> buffer,
IndexFormat format, size_t indexCount, index_t offset);
void (*BindGraphicsPipeline)(NonNullPtr<CommandList> commandList, NonNullPtr<GraphicsPipeline> graphicsPipeline);
void (*DrawPrimitives)(NonNullPtr<CommandList> commandList, uint32 numVertices, uint32 numInstances,
@@ -82,7 +83,7 @@ namespace Juliet
// Fences
bool (*WaitUntilGPUIsIdle)(NonNullPtr<GPUDriver> driver);
bool (*QueryFence)(NonNullPtr<GPUDriver> driver, NonNullPtr<Fence> fence);
void (*ReleaseFence)(NonNullPtr<GPUDriver> driver, NonNullPtr<Fence> fence);
void (*ReleaseFence)(NonNullPtr<GPUDriver> driver, NonNullPtr<Fence> fence JULIET_DEBUG_PARAM(String querier));
// Shaders
Shader* (*CreateShader)(NonNullPtr<GPUDriver> driver, ByteBuffer shaderByteCode, ShaderCreateInfo& shaderCreateInfo);
@@ -99,9 +100,12 @@ namespace Juliet
void (*DestroyTexture)(NonNullPtr<GPUDriver> driver, NonNullPtr<Texture> texture);
// Buffers
GraphicsBuffer* (*CreateGraphicsBuffer)(NonNullPtr<GPUDriver> driver, size_t size, BufferUsage usage);
GraphicsBuffer* (*CreateGraphicsBuffer)(NonNullPtr<GPUDriver> driver, size_t size, size_t stride, BufferUsage usage, bool isDynamic);
void (*DestroyGraphicsBuffer)(NonNullPtr<GraphicsBuffer> buffer);
void* (*MapGraphicsBuffer)(NonNullPtr<GPUDriver> driver, NonNullPtr<GraphicsBuffer> buffer);
void (*UnmapGraphicsBuffer)(NonNullPtr<GPUDriver> driver, NonNullPtr<GraphicsBuffer> buffer);
GraphicsTransferBuffer* (*CreateGraphicsTransferBuffer)(NonNullPtr<GPUDriver> driver, size_t size, TransferBufferUsage usage);
void (*DestroyGraphicsTransferBuffer)(NonNullPtr<GraphicsTransferBuffer> buffer);

View File

@@ -5,6 +5,7 @@
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/Memory/MemoryArena.h>
#include <Graphics/GraphicsPipeline.h>
@@ -127,11 +128,11 @@ namespace Juliet
ShaderCreateInfo shaderCI = {};
shaderCI.EntryPoint = entryPoint;
String vertPath = WrapString("../../Assets/compiled/ImGui.vert.dxil");
String vertPath = GetAssetPath(WrapString("ImGui.vert.dxil"));
shaderCI.Stage = ShaderStage::Vertex;
g_ImGuiState.VertexShader = CreateShader(device, vertPath, shaderCI);
String fragPath = WrapString("../../Assets/compiled/ImGui.frag.dxil");
String fragPath = GetAssetPath(WrapString("ImGui.frag.dxil"));
shaderCI.Stage = ShaderStage::Fragment;
g_ImGuiState.FragmentShader = CreateShader(device, fragPath, shaderCI);
@@ -149,8 +150,8 @@ namespace Juliet
TextureCreateInfo texCI = {};
texCI.Type = TextureType::Texture_2D;
texCI.Width = (uint32)width;
texCI.Height = (uint32)height;
texCI.Width = static_cast<uint32>(width);
texCI.Height = static_cast<uint32>(height);
texCI.Format = TextureFormat::R8G8B8A8_UNORM;
texCI.Flags = TextureUsageFlag::Sampler;
@@ -159,10 +160,10 @@ namespace Juliet
texCI.SampleCount = TextureSampleCount::One;
g_ImGuiState.FontTexture = CreateTexture(device, texCI);
io.Fonts->SetTexID((ImTextureID)g_ImGuiState.FontTexture);
io.Fonts->SetTexID(reinterpret_cast<ImTextureID>(g_ImGuiState.FontTexture));
// Upload data
uint32 rowPitch = (uint32)width * 4u;
uint32 rowPitch = static_cast<uint32>(width) * 4u;
uint32 alignedRowPitch = (rowPitch + 255u) & ~255u;
uint32 textureSize = alignedRowPitch * static_cast<uint32>(height);
@@ -176,7 +177,7 @@ namespace Juliet
return false;
}
auto dst = (uint8*)MapGraphicsTransferBuffer(device, tb);
auto* dst = static_cast<uint8*>(MapGraphicsTransferBuffer(device, tb));
for (uint32 y = 0; y < static_cast<uint32>(height); ++y)
{
@@ -223,9 +224,7 @@ namespace Juliet
DestroyShader(device, g_ImGuiState.VertexShader);
}
if (g_ImGuiState.FragmentShader)
{
DestroyShader(device, g_ImGuiState.FragmentShader);
}
if (g_ImGuiState.FontTexture)
@@ -316,13 +315,13 @@ namespace Juliet
FrameResources& currentFrame = g_ImGuiState.Frames[g_ImGuiState.FrameIndex];
// Upload Buffers
uint32 totalVtx = (uint32)drawData->TotalVtxCount;
uint32 totalIdx = (uint32)drawData->TotalIdxCount;
uint32 totalVtx = static_cast<uint32>(drawData->TotalVtxCount);
uint32 totalIdx = static_cast<uint32>(drawData->TotalIdxCount);
EnsureBufferSize(currentFrame, totalVtx * sizeof(ImDrawVert), totalIdx * sizeof(ImDrawIdx));
auto vtxDst = (ImDrawVert*)MapGraphicsTransferBuffer(g_ImGuiState.Device, currentFrame.VertexUpload);
auto idxDst = (ImDrawIdx*)MapGraphicsTransferBuffer(g_ImGuiState.Device, currentFrame.IndexUpload);
auto* vtxDst = static_cast<ImDrawVert*>(MapGraphicsTransferBuffer(g_ImGuiState.Device, currentFrame.VertexUpload));
auto* idxDst = static_cast<ImDrawIdx*>(MapGraphicsTransferBuffer(g_ImGuiState.Device, currentFrame.IndexUpload));
for (int n = 0; n < drawData->CmdListsCount; n++)
{
@@ -344,7 +343,7 @@ namespace Juliet
TransitionBufferToReadable(cmdList, currentFrame.VertexBuffer);
// SetIndexBuffer transitions from COPY_DEST to INDEX_BUFFER (barrier waits for copy to complete)
SetIndexBuffer(cmdList, currentFrame.IndexBuffer, IndexFormat::UInt16);
SetIndexBuffer(cmdList, currentFrame.IndexBuffer, IndexFormat::UInt16, totalIdx, 0);
// Render
BindGraphicsPipeline(renderPass, g_ImGuiState.Pipeline);
@@ -379,7 +378,7 @@ namespace Juliet
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr)
{
// pcmd->UserCallback(cmd_list, pcmd);
pcmd->UserCallback(cmd_list, pcmd);
}
else
{
@@ -404,28 +403,18 @@ namespace Juliet
SetScissorRect(renderPass, scissorRect);
// Bind Texture
uint32 textureIndex = GetDescriptorIndex(g_ImGuiState.Device, (Texture*)pcmd->GetTexID());
uint32 textureIndex = GetDescriptorIndex(g_ImGuiState.Device, reinterpret_cast<Texture*>(pcmd->GetTexID()));
// Push Constants
// Layout: ViewProjection(64) + BufferIndex(4) + TextureIndex(4) + VertexOffset(4) + Padding(4) + Scale(8) + Translate(8)
struct
{
float dummyVP[16]; // Occupy VP slot
uint32 bufferIndex;
uint32 textureIndex;
uint32 vertexOffset; // Base vertex for indexed bindless drawing
uint32 padding; // Alignment padding
float scale[2];
float translate[2];
} pushData = {}; // Zero-initialize all fields
PushData pushData = {}; // Zero-initialize all fields
pushData.bufferIndex = GetDescriptorIndex(g_ImGuiState.Device, currentFrame.VertexBuffer);
pushData.textureIndex = textureIndex;
pushData.vertexOffset = pcmd->VtxOffset + globalVtxOffset; // Pass vertex offset for bindless
pushData.scale[0] = scale[0];
pushData.scale[1] = scale[1];
pushData.translate[0] = translate[0];
pushData.translate[1] = translate[1];
pushData.BufferIndex = GetDescriptorIndex(g_ImGuiState.Device, currentFrame.VertexBuffer);
pushData.TextureIndex = textureIndex;
pushData.VertexOffset = pcmd->VtxOffset + globalVtxOffset; // Pass vertex offset for bindless
pushData.Scale[0] = scale[0];
pushData.Scale[1] = scale[1];
pushData.Translate[0] = translate[0];
pushData.Translate[1] = translate[1];
SetPushConstants(cmdList, ShaderStage::Vertex, 0, sizeof(pushData) / 4, &pushData);

View File

@@ -0,0 +1,9 @@
#include <Graphics/Mesh.h>
#include <Core/Memory/MemoryArena.h>
#include <Graphics/VertexData.h>
namespace Juliet
{
} // namespace Juliet

View File

@@ -0,0 +1,473 @@
#include <Graphics/MeshRenderer.h>
#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Core/Math/Matrix.h>
#include <Graphics/GraphicsDevice.h>
#include <Graphics/Mesh.h>
#include <Graphics/VertexData.h>
namespace Juliet
{
namespace
{
struct MeshRendererState
{
GraphicsDevice* Device = nullptr;
GraphicsPipeline* Pipeline = nullptr;
GraphicsBuffer* VertexBuffer = nullptr;
GraphicsBuffer* IndexBuffer = nullptr;
GraphicsBuffer* LightsBuffer = nullptr;
GraphicsBuffer* TransformsBuffer = nullptr;
GraphicsTransferBuffer* LoadCopyBuffer = nullptr;
VectorArena<Mesh, kDefaultMeshNumber, false> Meshes;
VectorArena<Vertex, kDefaultVertexCount, false> Vertices;
VectorArena<Index, kDefaultIndexCount, false> Indices;
VectorArena<PointLight, kDefaultLightCount, false> PointLights;
PointLight* MappedLights = nullptr;
Matrix* MappedTransforms = nullptr;
};
MeshRendererState g_MeshRenderer;
} // namespace
void InitializeMeshRenderer(NonNullPtr<Arena> arena)
{
g_MeshRenderer.Meshes.Create(arena JULIET_DEBUG_PARAM("Meshes"));
g_MeshRenderer.Vertices.Create(arena JULIET_DEBUG_PARAM("Vertices"));
g_MeshRenderer.Indices.Create(arena JULIET_DEBUG_PARAM("Indices"));
g_MeshRenderer.PointLights.Create(arena JULIET_DEBUG_PARAM("PointLights"));
}
bool InitializeMeshRendererGraphics(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window)
{
bool result = true;
GraphicsDevice* graphicsDevice = g_MeshRenderer.Device = device.Get();
// Create graphics pipeline
String entryPoint = WrapString("main");
ShaderCreateInfo shaderCI = {};
shaderCI.EntryPoint = entryPoint;
String shaderPath = GetAssetPath(WrapString("Triangle.vert.dxil"));
shaderCI.Stage = ShaderStage::Vertex;
Shader* vertexShader = CreateShader(graphicsDevice, shaderPath, shaderCI);
shaderPath = GetAssetPath(WrapString("SolidColor.frag.dxil"));
shaderCI.Stage = ShaderStage::Fragment;
Shader* fragmentShader = CreateShader(graphicsDevice, shaderPath, shaderCI);
ColorTargetDescription colorTargetDescription = {};
colorTargetDescription.Format = GetSwapChainTextureFormat(graphicsDevice, window);
GraphicsPipelineCreateInfo pipelineCI = {};
pipelineCI.VertexShader = vertexShader;
pipelineCI.FragmentShader = fragmentShader;
pipelineCI.PrimitiveType = PrimitiveType::TriangleList;
pipelineCI.TargetInfo = { .ColorTargetDescriptions = &colorTargetDescription,
.NumColorTargets = 1,
.DepthStencilFormat = TextureFormat::D32_FLOAT,
.HasDepthStencilTarget = true };
pipelineCI.RasterizerState.FillMode = FillMode::Solid;
pipelineCI.RasterizerState.CullMode = CullMode::None;
pipelineCI.RasterizerState.FrontFace = FrontFace::Clockwise;
pipelineCI.DepthStencilState.EnableDepthTest = true;
pipelineCI.DepthStencilState.EnableDepthWrite = true;
pipelineCI.DepthStencilState.CompareOperation = CompareOperation::Less;
g_MeshRenderer.Pipeline = CreateGraphicsPipeline(graphicsDevice, pipelineCI);
if (g_MeshRenderer.Pipeline == nullptr)
{
LogError(LogCategory::Graphics, "Failed to create graphics pipeline!");
result = false;
}
// Create the vertex and index buffers
BufferCreateInfo vertexBufferCI = {};
vertexBufferCI.Size = kGeometryPage;
vertexBufferCI.Stride = 0;
vertexBufferCI.Usage = BufferUsage::StructuredBuffer;
g_MeshRenderer.VertexBuffer = CreateGraphicsBuffer(graphicsDevice, vertexBufferCI);
Assert(g_MeshRenderer.VertexBuffer != nullptr);
BufferCreateInfo indexBufferCI = {};
indexBufferCI.Size = kIndexPage;
indexBufferCI.Usage = BufferUsage::IndexBuffer;
g_MeshRenderer.IndexBuffer = CreateGraphicsBuffer(graphicsDevice, indexBufferCI);
Assert(g_MeshRenderer.IndexBuffer != nullptr);
// Lights Buffer
BufferCreateInfo lightsBufferCI = {};
lightsBufferCI.Size = 1024 * sizeof(PointLight); // Max 1024 lights for now
lightsBufferCI.Stride = sizeof(PointLight);
lightsBufferCI.Usage = BufferUsage::StructuredBuffer;
lightsBufferCI.IsDynamic = true;
g_MeshRenderer.LightsBuffer = CreateGraphicsBuffer(graphicsDevice, lightsBufferCI);
Assert(g_MeshRenderer.LightsBuffer != nullptr);
g_MeshRenderer.MappedLights = static_cast<PointLight*>(MapGraphicsBuffer(graphicsDevice, g_MeshRenderer.LightsBuffer));
Assert(g_MeshRenderer.MappedLights != nullptr);
// Transforms Buffer
BufferCreateInfo transformsBufferCI = {};
transformsBufferCI.Size = 10000 * sizeof(Matrix); // Max 10000 meshes for now
transformsBufferCI.Stride = sizeof(Matrix);
transformsBufferCI.Usage = BufferUsage::StructuredBuffer;
transformsBufferCI.IsDynamic = true;
g_MeshRenderer.TransformsBuffer = CreateGraphicsBuffer(graphicsDevice, transformsBufferCI);
Assert(g_MeshRenderer.TransformsBuffer != nullptr);
g_MeshRenderer.MappedTransforms =
static_cast<Matrix*>(MapGraphicsBuffer(graphicsDevice, g_MeshRenderer.TransformsBuffer));
Assert(g_MeshRenderer.MappedTransforms != nullptr);
// Sync existing lights that might have been added before graphics initialization
for (index_t i = 0; i < g_MeshRenderer.PointLights.Count; ++i)
{
g_MeshRenderer.MappedLights[i] = g_MeshRenderer.PointLights.Data[i];
}
if (vertexShader)
{
DestroyShader(graphicsDevice, vertexShader);
}
if (fragmentShader)
{
DestroyShader(graphicsDevice, fragmentShader);
}
// Load evereything that is already in the vectors
CommandList* loadCmd = AcquireCommandList(device);
LoadMeshesOnGPU(loadCmd);
SubmitCommandLists(loadCmd);
return result;
}
void ShutdownMeshRendererGraphics()
{
if (g_MeshRenderer.LoadCopyBuffer)
{
UnmapGraphicsTransferBuffer(g_MeshRenderer.Device, g_MeshRenderer.LoadCopyBuffer);
DestroyGraphicsTransferBuffer(g_MeshRenderer.Device, g_MeshRenderer.LoadCopyBuffer);
g_MeshRenderer.LoadCopyBuffer = nullptr;
}
DestroyGraphicsBuffer(g_MeshRenderer.Device, g_MeshRenderer.IndexBuffer);
DestroyGraphicsBuffer(g_MeshRenderer.Device, g_MeshRenderer.VertexBuffer);
if (g_MeshRenderer.LightsBuffer)
{
UnmapGraphicsBuffer(g_MeshRenderer.Device, g_MeshRenderer.LightsBuffer);
DestroyGraphicsBuffer(g_MeshRenderer.Device, g_MeshRenderer.LightsBuffer);
g_MeshRenderer.LightsBuffer = nullptr;
}
if (g_MeshRenderer.TransformsBuffer)
{
UnmapGraphicsBuffer(g_MeshRenderer.Device, g_MeshRenderer.TransformsBuffer);
DestroyGraphicsBuffer(g_MeshRenderer.Device, g_MeshRenderer.TransformsBuffer);
g_MeshRenderer.TransformsBuffer = nullptr;
}
}
void ShutdownMeshRenderer()
{
g_MeshRenderer.Indices.Destroy();
g_MeshRenderer.Vertices.Destroy();
g_MeshRenderer.Meshes.Destroy();
g_MeshRenderer.PointLights.Destroy();
}
void LoadMeshesOnGPU(NonNullPtr<CommandList> cmdList)
{
if (g_MeshRenderer.Meshes.IsEmpty())
{
return;
}
// Loading everything in one go.
// Destroy the buffer at the end
TransferBufferCreateInfo uploadBCI = {};
uploadBCI.Usage = TransferBufferUsage::Upload;
uploadBCI.Size = kGeometryPage + kIndexPage;
g_MeshRenderer.LoadCopyBuffer = CreateGraphicsTransferBuffer(g_MeshRenderer.Device, uploadBCI);
void* map = MapGraphicsTransferBuffer(g_MeshRenderer.Device, g_MeshRenderer.LoadCopyBuffer);
Vertex* vertices = g_MeshRenderer.Vertices.Data;
if (!vertices)
{
UnmapGraphicsTransferBuffer(g_MeshRenderer.Device, g_MeshRenderer.LoadCopyBuffer);
DestroyGraphicsTransferBuffer(g_MeshRenderer.Device, g_MeshRenderer.LoadCopyBuffer);
g_MeshRenderer.LoadCopyBuffer = nullptr;
return;
}
Index* indices = g_MeshRenderer.Indices.Data;
if (!indices)
{
UnmapGraphicsTransferBuffer(g_MeshRenderer.Device, g_MeshRenderer.LoadCopyBuffer);
DestroyGraphicsTransferBuffer(g_MeshRenderer.Device, g_MeshRenderer.LoadCopyBuffer);
g_MeshRenderer.LoadCopyBuffer = nullptr;
return;
}
// Copy all meshes! This supports only one page for now
// Copy ALL Vertices in one block
size_t totalVertexBytes = g_MeshRenderer.Vertices.Count * sizeof(Vertex);
MemCopy(map, g_MeshRenderer.Vertices.Data, totalVertexBytes);
// Copy ALL Indices in one block
size_t indexOfByteOffset = (kGeometryPage + 255) & static_cast<size_t>(~255);
uint8* ptrOneByte = static_cast<uint8*>(map);
size_t totalIndexBytes = g_MeshRenderer.Indices.Count * sizeof(Index);
MemCopy(ptrOneByte + indexOfByteOffset, g_MeshRenderer.Indices.Data, totalIndexBytes);
UnmapGraphicsTransferBuffer(g_MeshRenderer.Device, g_MeshRenderer.LoadCopyBuffer);
CopyBuffer(cmdList, g_MeshRenderer.VertexBuffer, g_MeshRenderer.LoadCopyBuffer, totalVertexBytes, 0, 0);
CopyBuffer(cmdList, g_MeshRenderer.IndexBuffer, g_MeshRenderer.LoadCopyBuffer, totalIndexBytes, 0, indexOfByteOffset);
// Transition vertex buffer to SRV state (this barrier waits for copy to complete)
TransitionBufferToReadable(cmdList, g_MeshRenderer.VertexBuffer);
TransitionBufferToReadable(cmdList, g_MeshRenderer.IndexBuffer);
}
void RenderMeshes(NonNullPtr<RenderPass> pass, NonNullPtr<CommandList> cmdList, PushData& pushData)
{
// First destroy any buffer that needs to be
if (g_MeshRenderer.LoadCopyBuffer)
{
WaitUntilGPUIsIdle(g_MeshRenderer.Device);
UnmapGraphicsTransferBuffer(g_MeshRenderer.Device, g_MeshRenderer.LoadCopyBuffer);
DestroyGraphicsTransferBuffer(g_MeshRenderer.Device, g_MeshRenderer.LoadCopyBuffer);
g_MeshRenderer.LoadCopyBuffer = nullptr;
}
BindGraphicsPipeline(pass, g_MeshRenderer.Pipeline);
uint32 vertexDescriptorIndex = GetDescriptorIndex(g_MeshRenderer.Device, g_MeshRenderer.VertexBuffer);
uint32 lightDescriptorIndex = GetDescriptorIndex(g_MeshRenderer.Device, g_MeshRenderer.LightsBuffer);
uint32 transformsDescriptorIndex = GetDescriptorIndex(g_MeshRenderer.Device, g_MeshRenderer.TransformsBuffer);
pushData.BufferIndex = vertexDescriptorIndex;
pushData.LightBufferIndex = lightDescriptorIndex;
pushData.TransformsBufferIndex = transformsDescriptorIndex;
pushData.ActiveLightCount = static_cast<uint32>(g_MeshRenderer.PointLights.Count);
SetIndexBuffer(cmdList, g_MeshRenderer.IndexBuffer, IndexFormat::UInt16, g_MeshRenderer.Indices.Count, 0);
uint32 meshIndex = 0;
for (Mesh& mesh : g_MeshRenderer.Meshes)
{
if (g_MeshRenderer.MappedTransforms)
{
g_MeshRenderer.MappedTransforms[meshIndex] = mesh.Transform;
}
pushData.MeshIndex = meshIndex;
pushData.TextureIndex = 0;
pushData.VertexOffset = static_cast<uint32>(mesh.VertexOffset);
pushData.Padding = 0;
pushData.Scale[0] = 1.0f;
pushData.Scale[1] = 1.0f;
pushData.Translate[0] = 0.0f;
pushData.Translate[1] = 0.0f;
SetPushConstants(cmdList, ShaderStage::Vertex, 0, sizeof(pushData) / 4, &pushData);
DrawIndexedPrimitives(pass, static_cast<uint32>(mesh.IndexCount), 1, static_cast<uint32>(mesh.IndexOffset),
static_cast<uint32>(mesh.VertexOffset), 0);
meshIndex++;
}
}
LightID AddPointLight(const PointLight& light)
{
Assert(g_MeshRenderer.PointLights.Count < kDefaultLightCount);
LightID id = g_MeshRenderer.PointLights.Count;
g_MeshRenderer.PointLights.PushBack(light);
if (g_MeshRenderer.MappedLights)
{
g_MeshRenderer.MappedLights[id] = light;
}
return id;
}
void SetPointLightPosition(LightID id, const Vector3& position)
{
Assert(id < static_cast<LightID>(g_MeshRenderer.PointLights.Count));
g_MeshRenderer.PointLights.Data[id].Position = position;
if (g_MeshRenderer.MappedLights)
{
g_MeshRenderer.MappedLights[id].Position = position;
}
}
void SetPointLightColor(LightID id, const Vector3& color)
{
Assert(id < static_cast<LightID>(g_MeshRenderer.PointLights.Count));
g_MeshRenderer.PointLights.Data[id].Color = color;
if (g_MeshRenderer.MappedLights)
{
g_MeshRenderer.MappedLights[id].Color = color;
}
}
void SetPointLightRadius(LightID id, float radius)
{
Assert(id < static_cast<LightID>(g_MeshRenderer.PointLights.Count));
g_MeshRenderer.PointLights.Data[id].Radius = radius;
if (g_MeshRenderer.MappedLights)
{
g_MeshRenderer.MappedLights[id].Radius = radius;
}
}
void SetPointLightIntensity(LightID id, float intensity)
{
Assert(id < static_cast<LightID>(g_MeshRenderer.PointLights.Count));
g_MeshRenderer.PointLights.Data[id].Intensity = intensity;
if (g_MeshRenderer.MappedLights)
{
g_MeshRenderer.MappedLights[id].Intensity = intensity;
}
}
void ClearPointLights()
{
g_MeshRenderer.PointLights.Clear();
}
MeshID AddCube()
{
Mesh result = {};
constexpr Vertex vertexData[] = { // Front Face (Z = -0.5f) — Red
{ { -0.5f, 0.5f, -0.5f }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { 0.5f, 0.5f, -0.5f }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { 0.5f, -0.5f, -0.5f }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { -0.5f, -0.5f, -0.5f }, { 0.0f, 0.0f, -1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
// Back Face (Z = 0.5f) — Green
{ { 0.5f, 0.5f, 0.5f }, { 0.0f, 0.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { -0.5f, 0.5f, 0.5f }, { 0.0f, 0.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { -0.5f, -0.5f, 0.5f }, { 0.0f, 0.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { 0.5f, -0.5f, 0.5f }, { 0.0f, 0.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
// Top Face (Y = 0.5f) — Blue
{ { -0.5f, 0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { 0.5f, 0.5f, 0.5f }, { 0.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { 0.5f, 0.5f, -0.5f }, { 0.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { -0.5f, 0.5f, -0.5f }, { 0.0f, 1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
// Bottom Face (Y = -0.5f) — Yellow
{ { -0.5f, -0.5f, -0.5f }, { 0.0f, -1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { 0.5f, -0.5f, -0.5f }, { 0.0f, -1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { 0.5f, -0.5f, 0.5f }, { 0.0f, -1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { -0.5f, -0.5f, 0.5f }, { 0.0f, -1.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
// Right Face (X = 0.5f) — Cyan
{ { 0.5f, 0.5f, -0.5f }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { 0.5f, 0.5f, 0.5f }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { 0.5f, -0.5f, 0.5f }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { 0.5f, -0.5f, -0.5f }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
// Left Face (X = -0.5f) — Magenta
{ { -0.5f, 0.5f, 0.5f }, { -1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { -0.5f, 0.5f, -0.5f }, { -1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { -0.5f, -0.5f, -0.5f }, { -1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } },
{ { -0.5f, -0.5f, 0.5f }, { -1.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f } }
};
constexpr size_t cubeVertexCount = ArraySize(vertexData);
result.VertexCount = cubeVertexCount;
result.VertexOffset = g_MeshRenderer.Vertices.Count;
g_MeshRenderer.Vertices.PushBack(vertexData, cubeVertexCount);
constexpr uint16 indices[] = { 0, 1, 2, 0, 2, 3, // Front
4, 5, 6, 4, 6, 7, // Back
8, 9, 10, 8, 10, 11, // Top
12, 13, 14, 12, 14, 15, // Bottom
16, 17, 18, 16, 18, 19, // Right
20, 21, 22, 20, 22, 23 }; // Left
constexpr size_t indexCubeCount = ArraySize(indices);
result.IndexCount = indexCubeCount;
result.IndexOffset = g_MeshRenderer.Indices.Count;
g_MeshRenderer.Indices.PushBack(indices, indexCubeCount);
MeshID id = g_MeshRenderer.Meshes.Count;
g_MeshRenderer.Meshes.PushBack(result);
return id;
}
MeshID AddQuad()
{
// Mesh result = {};
// // Using the exact 6 vertices from the working triangles!
// constexpr Vertex vertexData[] = {
// // Triangle 1 (Clockwise)
// { { -0.5f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } }, // 0: Red
// { { 0.0f, 0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } }, // 1: Green
// { { 0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }, // 2: Blue
//
// // Triangle 2 (Clockwise)
// { { -0.5f, 0.5f, 0.0f }, { 1.0f, 1.0f, 0.0f, 1.0f } }, // 3: Yellow
// { { 0.0f, 0.8f, 0.0f }, { 0.0f, 1.0f, 1.0f, 1.0f } }, // 4: Cyan
// { { 0.5f, 0.5f, 0.0f }, { 1.0f, 0.0f, 1.0f, 1.0f } } // 5: Magenta
// };
// constexpr size_t triVertexCount = ArraySize(vertexData);
// result.VertexCount = triVertexCount;
// result.Vertices = ArenaPushArray<Vertex>(arena.Get(), triVertexCount);
// MemCopy(result.Vertices, vertexData, sizeof(Vertex) * triVertexCount);
//
// // Just the 6 indices for the two triangles
// constexpr uint16 indices[] = { 0, 1, 2, 3, 4, 5 };
// constexpr size_t triIndexCount = ArraySize(indices);
// result.IndexCount = triIndexCount;
// result.Indices = ArenaPushArray<uint16>(arena.Get(), triIndexCount JULIET_DEBUG_PARAM("Indices"));
// MemCopy(result.Indices, indices, sizeof(uint16) * triIndexCount);
//
// g_MeshRenderer.Meshes.PushBack(std::move(result));
return g_MeshRenderer.Meshes.Count - 1;
}
void SetMeshTransform(MeshID id, const Matrix& transform)
{
Assert(id < static_cast<MeshID>(g_MeshRenderer.Meshes.Count));
g_MeshRenderer.Meshes.Data[id].Transform = transform;
}
#if ALLOW_SHADER_HOT_RELOAD
void ReloadMeshRendererShaders()
{
auto* pipeline = g_MeshRenderer.Pipeline;
auto* device = g_MeshRenderer.Device;
String entryPoint = WrapString("main");
ShaderCreateInfo shaderCI = {};
shaderCI.EntryPoint = entryPoint;
String shaderPath = GetAssetPath(WrapString("Triangle.vert.dxil"));
shaderCI.Stage = ShaderStage::Vertex;
Shader* vertexShader = CreateShader(device, shaderPath, shaderCI);
shaderPath = GetAssetPath(WrapString("SolidColor.frag.dxil"));
shaderCI.Stage = ShaderStage::Fragment;
Shader* fragmentShader = CreateShader(device, shaderPath, shaderCI);
UpdateGraphicsPipelineShaders(device, pipeline, vertexShader, fragmentShader);
if (vertexShader)
{
DestroyShader(device, vertexShader);
}
if (fragmentShader)
{
DestroyShader(device, fragmentShader);
}
}
#endif
} // namespace Juliet

View File

@@ -0,0 +1,104 @@
#include <Graphics/SkyboxRenderer.h>
#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
#include <Graphics/GraphicsDevice.h>
#include <Graphics/PushConstants.h>
namespace Juliet
{
namespace
{
SkyboxRenderer g_SkyboxRenderer;
} // namespace
bool InitializeSkyboxRenderer(NonNullPtr<GraphicsDevice> device, NonNullPtr<Window> window)
{
bool result = true;
GraphicsDevice* graphicsDevice = g_SkyboxRenderer.Device = device.Get();
String skyboxVSEntry = WrapString("main");
ShaderCreateInfo skyboxVSCI = {};
skyboxVSCI.EntryPoint = skyboxVSEntry;
skyboxVSCI.Stage = ShaderStage::Vertex;
String vsPath = GetAssetPath(WrapString("Skybox.vert.dxil"));
Shader* skyboxVS = CreateShader(graphicsDevice, vsPath, skyboxVSCI);
String skyboxFSEntry = WrapString("main");
ShaderCreateInfo skyboxFSCI = {};
skyboxFSCI.EntryPoint = skyboxFSEntry;
skyboxFSCI.Stage = ShaderStage::Fragment;
String fsPath = GetAssetPath(WrapString("Skybox.frag.dxil"));
Shader* skyboxFS = CreateShader(graphicsDevice, fsPath, skyboxFSCI);
ColorTargetDescription colorTargetDesc = {};
colorTargetDesc.Format = GetSwapChainTextureFormat(graphicsDevice, window);
GraphicsPipelineCreateInfo skyboxPipelineCI = {};
skyboxPipelineCI.VertexShader = skyboxVS;
skyboxPipelineCI.FragmentShader = skyboxFS;
skyboxPipelineCI.PrimitiveType = PrimitiveType::TriangleList;
skyboxPipelineCI.TargetInfo.ColorTargetDescriptions = &colorTargetDesc;
skyboxPipelineCI.TargetInfo.NumColorTargets = 1;
skyboxPipelineCI.TargetInfo.DepthStencilFormat = TextureFormat::D32_FLOAT;
skyboxPipelineCI.TargetInfo.HasDepthStencilTarget = true;
skyboxPipelineCI.RasterizerState.FillMode = FillMode::Solid;
skyboxPipelineCI.RasterizerState.CullMode = CullMode::None;
skyboxPipelineCI.RasterizerState.FrontFace = FrontFace::Clockwise;
skyboxPipelineCI.DepthStencilState.EnableDepthTest = true;
skyboxPipelineCI.DepthStencilState.EnableDepthWrite = false;
skyboxPipelineCI.DepthStencilState.CompareOperation = CompareOperation::LessOrEqual;
g_SkyboxRenderer.Pipeline = CreateGraphicsPipeline(graphicsDevice, skyboxPipelineCI);
if (g_SkyboxRenderer.Pipeline == nullptr)
{
LogError(LogCategory::Graphics, "Failed to create skybox pipeline!");
result = false;
}
if (skyboxVS) DestroyShader(graphicsDevice, skyboxVS);
if (skyboxFS) DestroyShader(graphicsDevice, skyboxFS);
return result;
}
void ShutdownSkyboxRenderer()
{
if (g_SkyboxRenderer.Pipeline)
{
DestroyGraphicsPipeline(g_SkyboxRenderer.Device, g_SkyboxRenderer.Pipeline);
g_SkyboxRenderer.Pipeline = nullptr;
}
g_SkyboxRenderer = {};
}
void RenderSkybox(NonNullPtr<RenderPass> pass, NonNullPtr<CommandList> cmdList, const Matrix& viewProjection)
{
if (!g_SkyboxRenderer.Pipeline)
{
return;
}
PushData pushData = {};
pushData.ViewProjection = viewProjection;
pushData.MeshIndex = 0;
pushData.TransformsBufferIndex = 0;
pushData.BufferIndex = 0;
BindGraphicsPipeline(pass, g_SkyboxRenderer.Pipeline);
SetPushConstants(cmdList, ShaderStage::Vertex, 0, sizeof(pushData) / 4, &pushData);
SetPushConstants(cmdList, ShaderStage::Fragment, 0, sizeof(pushData) / 4, &pushData);
DrawPrimitives(pass, 3, 1, 0, 0);
}
#if ALLOW_SHADER_HOT_RELOAD
void ReloadSkyboxShaders()
{
// TODO
}
#endif
} // namespace Juliet

View File

@@ -5,7 +5,7 @@
#include <Core/Common/CoreUtils.h>
#include <Core/Container/Vector.h>
namespace Juliet
namespace Juliet::UnitTest
{
namespace
{
@@ -294,5 +294,5 @@ namespace Juliet
ArenaRelease(externalArena);
}
}
} // namespace Juliet
} // namespace Juliet::UnitTest
#endif

View File

@@ -5,19 +5,21 @@
#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>
namespace Juliet
namespace Juliet::UnitTest
{
// Forward declare the VectorUnitTest function
void VectorUnitTest();
void TestMemoryArena();
void RunUnitTests()
{
LogMessage(LogCategory::Core, "Starting Unit Tests...");
TestMemoryArena();
VectorUnitTest();
LogMessage(LogCategory::Core, "Unit Tests Completed Successfully.");
}
} // namespace Juliet
} // namespace Juliet::UnitTest
#endif

View File

@@ -59,9 +59,12 @@
.Libraries = {
'JulietApp-Lib-$Platform$-$BuildConfigName$',
'Juliet-Lib-$Platform$-$BuildConfigName$',
'Game-Lib-$Platform$-$BuildConfigName$',
'ImGui-Lib-$Platform$-$BuildConfigName$'
'Game-Lib-$Platform$-$BuildConfigName$'
}
If ( .BuildConfigName != 'Release' )
{
^Libraries + { 'ImGui-Lib-$Platform$-$BuildConfigName$' }
}
.LinkerOutput = '$BinPath$/$Platform$-$BuildConfigName$/$ProjectName$$ExeExtension$'

View File

@@ -1,5 +1,20 @@
#include "main.h"
#ifdef global
#undef global
#endif
#include <algorithm>
#include <ios>
#include <iosfwd>
#define global static
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <Windows.h>
#endif
#include <Core/Application/ApplicationManager.h>
#include <Core/Common/EnumUtils.h>
#include <Core/Common/String.h>
@@ -20,14 +35,43 @@
#include <Graphics/Graphics.h>
#include <Graphics/GraphicsConfig.h>
#include <Graphics/GraphicsPipeline.h>
#include <Graphics/Mesh.h>
#include <Graphics/MeshRenderer.h>
#include <Graphics/RenderPass.h>
#include <Juliet.h>
#include <Graphics/SkyboxRenderer.h>
#include <Graphics/VertexData.h>
#ifdef JULIET_ENABLE_IMGUI
#include <imgui.h>
#endif
static bool ShowMemoryDebugger = false;
static bool animateCubes = true;
static bool animateLights = true;
static bool animateCamera = true;
static bool freeCameraMode = false;
static float camYaw = 0.0f;
static float camPitch = 0.0f;
static Juliet::Vector3 camPos = { 25.0f, 0.0f, 12.5f };
static float animateCubesTime = 0.0f;
static float animateLightsTime = 0.0f;
static float animateCameraTime = 0.0f;
static float redLightRadius = 10.0f;
static float redLightIntensity = 5.0f;
static float redLightColor[3] = { 1.0f, 0.2f, 0.2f };
static bool redLightFollowsCamera = false;
static bool enableGlobalLight = true;
static float globalLightDir[3] = { 0.5f, -1.0f, -0.5f };
static float globalLightColor[3] = { 1.0f, 0.95f, 0.8f };
static float globalAmbientIntensity = 0.2f;
static float blueLightRadius = 15.0f;
static float blueLightIntensity = 8.0f;
static float blueLightColor[3] = { 0.2f, 0.2f, 1.0f };
// TODO : Replace with message box from framework + call main and not winmain + subsystem
// TODO : Think how to do the draw pipeline.
@@ -41,13 +85,6 @@ static bool ShowMemoryDebugger = false;
using namespace Juliet;
extern "C" {
__declspec(dllexport) extern const unsigned int D3D12SDKVersion = 615;
}
extern "C" {
__declspec(dllexport) extern const char* D3D12SDKPath = ".\\";
}
namespace
{
using GameInit_t = void (*)(GameInitParams*);
@@ -61,24 +98,21 @@ namespace
} Game;
const char* GameFunctionTable[] = { "GameInit", "GameShutdown", "GameUpdate" };
LightID RedLightID = 0;
LightID BlueLightID = 0;
} // namespace
struct Vertex
{
float Position[2];
float Color[4];
};
void JulietApplication::Init()
void JulietApplication::Init(NonNullPtr<Arena>)
{
Log(LogLevel::Message, LogCategory::Tool, "Initializing Juliet Application...");
Log(LogLevel::Message, LogCategory::Tool, "%s", CStr(GetBasePath()));
GraphicsConfig config;
#if JULIET_DEBUG
config.EnableDebug = true;
#endif
GraphicsDevice = CreateGraphicsDevice(config);
MainWindow = CreatePlatformWindow("Juliet Editor", 1280, 720);
@@ -88,45 +122,7 @@ void JulietApplication::Init()
if (Running)
{
AttachToWindow(GraphicsDevice, MainWindow);
{
// Create graphics pipeline
String entryPoint = WrapString("main");
ShaderCreateInfo shaderCI = {};
shaderCI.EntryPoint = entryPoint;
// TODO: Assets management that handles path to assets or something.
String shaderPath = WrapString("../../Assets/compiled/Triangle.vert.dxil");
shaderCI.Stage = ShaderStage::Vertex;
Shader* vertexShader = CreateShader(GraphicsDevice, shaderPath, shaderCI);
shaderPath = WrapString("../../Assets/compiled/SolidColor.frag.dxil");
shaderCI.Stage = ShaderStage::Fragment;
Shader* fragmentShader = CreateShader(GraphicsDevice, shaderPath, shaderCI);
ColorTargetDescription colorTargetDescription = {};
colorTargetDescription.Format = GetSwapChainTextureFormat(GraphicsDevice, MainWindow);
GraphicsPipelineCreateInfo pipelineCI = {};
pipelineCI.VertexShader = vertexShader;
pipelineCI.FragmentShader = fragmentShader;
pipelineCI.PrimitiveType = PrimitiveType::TriangleList;
pipelineCI.TargetInfo = { .ColorTargetDescriptions = &colorTargetDescription,
.NumColorTargets = 1,
.DepthStencilFormat = TextureFormat::D32_FLOAT,
.HasDepthStencilTarget = true };
pipelineCI.RasterizerState.FillMode = FillMode::Solid;
pipelineCI.DepthStencilState.EnableDepthTest = true;
pipelineCI.DepthStencilState.EnableDepthWrite = true;
pipelineCI.DepthStencilState.CompareOperation = CompareOperation::Less;
GraphicsPipeline = CreateGraphicsPipeline(GraphicsDevice, pipelineCI);
if (GraphicsPipeline == nullptr)
{
LogError(LogCategory::Game, "Failed to create graphics pipeline!");
Running = false;
}
// Create Depth Buffer
TextureCreateInfo depthCI = {};
depthCI.Type = TextureType::Texture_2D;
@@ -144,51 +140,38 @@ void JulietApplication::Init()
Running = false;
}
// Create Buffers - Using StructuredBuffer for bindless SRV access in shader
BufferCreateInfo bufferCI = {};
bufferCI.Size = 256;
bufferCI.Usage = BufferUsage::StructuredBuffer; // SRV for ResourceDescriptorHeap access
ConstantBuffer = CreateGraphicsBuffer(GraphicsDevice, bufferCI);
constexpr int kGridSize = 10;
constexpr float kSpacing = 2.5f;
constexpr float kOffset = (kGridSize - 1) * kSpacing * 0.5f;
TransferBufferCreateInfo transferCI = {};
transferCI.Size = 256;
transferCI.Usage = TransferBufferUsage::Upload;
TransferBuffer = CreateGraphicsTransferBuffer(GraphicsDevice, transferCI);
// Upload Static Data for Test
if (TransferBuffer && ConstantBuffer)
for (int row = 0; row < kGridSize; ++row)
{
void* data = MapGraphicsTransferBuffer(GraphicsDevice, TransferBuffer);
if (data)
for (int col = 0; col < kGridSize; ++col)
{
Matrix projection = PerspectiveFov(60.0f * (3.14159f / 180.0f), 1200.0f / 800.0f, 0.1f, 1000.0f);
Matrix view = LookAt({ 30.0f, 0.0f, 10.0f }, { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f });
Matrix model = Matrix::Identity();
Matrix mvp = projection * view * model;
MeshID cube = AddCube();
float x = static_cast<float>(col) * kSpacing - kOffset;
float y = static_cast<float>(row) * kSpacing - kOffset;
MemCopy(data, &mvp, sizeof(Matrix));
UnmapGraphicsTransferBuffer(GraphicsDevice, TransferBuffer);
CommandList* initCmd = AcquireCommandList(GraphicsDevice);
CopyBuffer(initCmd, ConstantBuffer, TransferBuffer, 256);
TransitionBufferToReadable(initCmd, ConstantBuffer);
SubmitCommandLists(initCmd);
float seed = static_cast<float>(row * kGridSize + col);
Matrix rotation = MatrixRotation(seed * 0.73f, seed * 1.17f, seed * 0.53f);
SetMeshTransform(cube, MatrixTranslation(x, y, 0.0f) * rotation);
}
}
if (vertexShader)
{
DestroyShader(GraphicsDevice, vertexShader);
}
if (fragmentShader)
{
DestroyShader(GraphicsDevice, fragmentShader);
}
// Start with some default test lights
PointLight redLight = {};
redLight.Position = { 5.0f, 5.0f, 2.0f };
redLight.Radius = redLightRadius;
redLight.Color = { redLightColor[0], redLightColor[1], redLightColor[2] };
redLight.Intensity = redLightIntensity;
RedLightID = AddPointLight(redLight);
if (Running == false)
{
return;
}
PointLight blueLight = {};
blueLight.Position = { -5.0f, 0.0f, 2.0f };
blueLight.Radius = blueLightRadius;
blueLight.Color = { blueLightColor[0], blueLightColor[1], blueLightColor[2] };
blueLight.Intensity = blueLightIntensity;
BlueLightID = AddPointLight(blueLight);
}
GameCode.Functions = reinterpret_cast<void**>(&Game);
@@ -198,8 +181,8 @@ void JulietApplication::Init()
if ((Running = GameCode.IsValid))
{
GameInitParams params;
params.GameArena = GetGameArena();
params.ScratchArena = GetScratchArena();
params.GameArena = GameArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Game Arena"));
params.ScratchArena = GameScratchArena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Scratch Arena"));
Game.Init(&params);
}
}
@@ -219,14 +202,7 @@ void JulietApplication::Shutdown()
{
DestroyGraphicsPipeline(GraphicsDevice, GraphicsPipeline);
}
if (ConstantBuffer)
{
DestroyGraphicsBuffer(GraphicsDevice, ConstantBuffer);
}
if (TransferBuffer)
{
DestroyGraphicsTransferBuffer(GraphicsDevice, TransferBuffer);
}
if (DepthBuffer)
{
DestroyTexture(GraphicsDevice, DepthBuffer);
@@ -252,8 +228,39 @@ void JulietApplication::Shutdown()
void JulietApplication::Update()
{
static LARGE_INTEGER frequency = {};
static LARGE_INTEGER lastTime = {};
if (frequency.QuadPart == 0)
{
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&lastTime);
}
LARGE_INTEGER currentTime;
QueryPerformanceCounter(&currentTime);
float deltaTime = static_cast<float>(currentTime.QuadPart - lastTime.QuadPart) / static_cast<float>(frequency.QuadPart);
lastTime = currentTime;
CameraTime += deltaTime;
static float fpsTimer = 0.0f;
static int fpsFrames = 0;
fpsTimer += deltaTime;
fpsFrames++;
if (fpsTimer >= 0.5f)
{
float fps = static_cast<float>(fpsFrames) / fpsTimer;
float ms = (fpsTimer / static_cast<float>(fpsFrames)) * 1000.0f;
char title[64];
snprintf(title, sizeof(title), "Juliet | %.1f FPS | %.2f ms", static_cast<double>(fps), static_cast<double>(ms));
SetWindowTitle(MainWindow, WrapString(title));
fpsTimer = 0.0f;
fpsFrames = 0;
}
bool reloadShaders = false;
static bool reloadShadersDebounce = false;
static bool f1Debounce = false;
SystemEvent evt;
while (GetEvent(evt))
@@ -279,14 +286,216 @@ void JulietApplication::Update()
}
}
static bool firstFreeFrame = false;
if (IsKeyDown(ScanCode::F1))
{
if (!f1Debounce)
{
freeCameraMode = !freeCameraMode;
if (freeCameraMode)
{
firstFreeFrame = true;
}
f1Debounce = true;
}
}
else
{
f1Debounce = false;
}
// Confine and hide the mouse for Free Camera mode
if (freeCameraMode)
{
#ifdef JULIET_ENABLE_IMGUI
ImGui::ShowDemoWindow();
ImGui::SetMouseCursor(ImGuiMouseCursor_None);
#endif
DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 10.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f }, false);
DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 0.0f, 10.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f }, true);
DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 10.0f }, { 0.0f, 0.0f, 1.0f, 1.0f }, true);
DebugDisplay_DrawSphere({ 0.0f, 0.0f, 0.0f }, 5.0f, { 1.0f, 1.0f, 0.0f, 1.0f }, true);
#ifdef _WIN32
HWND hwnd = GetForegroundWindow();
if (hwnd)
{
RECT rect;
GetClientRect(hwnd, &rect);
POINT ptCenterClient = { (rect.right - rect.left) / 2, (rect.bottom - rect.top) / 2 };
if (!firstFreeFrame)
{
POINT currentPos;
GetCursorPos(&currentPos);
ScreenToClient(hwnd, &currentPos);
float deltaX = static_cast<float>(currentPos.x - ptCenterClient.x);
float deltaY = static_cast<float>(currentPos.y - ptCenterClient.y);
float sensitivity = 0.005f;
// Plus because the mouse is inverted inherently by windows to screen coordinate
camYaw += deltaX * sensitivity;
camPitch -= deltaY * sensitivity;
camPitch = std::min(camPitch, 1.5f);
camPitch = std::max(camPitch, -1.5f);
}
firstFreeFrame = false;
POINT ptCenterScreen = ptCenterClient;
ClientToScreen(hwnd, &ptCenterScreen);
SetCursorPos(ptCenterScreen.x, ptCenterScreen.y);
}
#endif
}
if (freeCameraMode)
{
float speed = 10.0f * deltaTime;
if ((GetKeyModState() & KeyMod::Shift) != KeyMod::None)
{
speed *= 3.0f;
}
Vector3 forward = { cosf(camYaw) * cosf(camPitch), sinf(camYaw) * cosf(camPitch), sinf(camPitch) };
Vector3 right = { cosf(camYaw + 1.5708f), sinf(camYaw + 1.5708f), 0.0f };
Vector3 up = { 0.0f, 0.0f, 1.0f };
if (IsKeyDown(ScanCode::W))
{
camPos = camPos + forward * speed;
}
if (IsKeyDown(ScanCode::S))
{
camPos = camPos - forward * speed;
}
if (IsKeyDown(ScanCode::D))
{
camPos = camPos + right * speed;
}
if (IsKeyDown(ScanCode::A))
{
camPos = camPos - right * speed;
}
if (IsKeyDown(ScanCode::E))
{
camPos = camPos + up * speed;
}
if (IsKeyDown(ScanCode::Q))
{
camPos = camPos - up * speed;
}
}
if (animateCubes)
{
animateCubesTime += deltaTime;
}
if (animateLights)
{
animateLightsTime += deltaTime;
}
if (animateCamera)
{
animateCameraTime += deltaTime;
}
#ifdef JULIET_ENABLE_IMGUI
ImGui::Begin("Debug Controls");
ImGui::Checkbox("Animate Cubes", &animateCubes);
ImGui::Checkbox("Animate Lights", &animateLights);
ImGui::Checkbox("Animate Camera", &animateCamera);
ImGui::Separator();
ImGui::Text("Global Light");
ImGui::Checkbox("Enable Global Light", &enableGlobalLight);
if (enableGlobalLight)
{
ImGui::SliderFloat3("Direction", globalLightDir, -1.0f, 1.0f);
ImGui::ColorEdit3("Color", globalLightColor);
ImGui::SliderFloat("Ambient Intensity", &globalAmbientIntensity, 0.0f, 1.0f);
}
ImGui::Separator();
ImGui::Text("Red Point Light");
ImGui::ColorEdit3("Red Color", redLightColor);
ImGui::SliderFloat("Red Radius", &redLightRadius, 1.0f, 50.0f);
ImGui::SliderFloat("Red Intensity", &redLightIntensity, 0.0f, 50.0f);
ImGui::Checkbox("Red Light Follows Camera", &redLightFollowsCamera);
ImGui::Separator();
ImGui::Text("Blue Point Light");
ImGui::ColorEdit3("Blue Color", blueLightColor);
ImGui::SliderFloat("Blue Radius", &blueLightRadius, 1.0f, 50.0f);
ImGui::SliderFloat("Blue Intensity", &blueLightIntensity, 0.0f, 50.0f);
ImGui::End();
#endif
ArenaClear(GameScratchArena);
Vector3 redLightPos = { 5.0f, 5.0f, 2.0f };
Vector3 blueLightPos = { -5.0f, 0.0f, 2.0f };
if (animateLights || animateLightsTime > 0.0f)
{
redLightPos = { cosf(animateLightsTime * 2.0f) * 5.0f, sinf(animateLightsTime * 2.0f) * 5.0f, 2.0f };
blueLightPos = { -5.0f, cosf(animateLightsTime) * 3.0f, 2.0f };
}
if (redLightFollowsCamera)
{
Camera cam = GetDebugCamera();
redLightPos = cam.Position;
}
SetPointLightPosition(RedLightID, redLightPos);
SetPointLightPosition(BlueLightID, blueLightPos);
SetPointLightColor(RedLightID, { redLightColor[0], redLightColor[1], redLightColor[2] });
SetPointLightRadius(RedLightID, redLightRadius);
SetPointLightIntensity(RedLightID, redLightIntensity);
SetPointLightColor(BlueLightID, { blueLightColor[0], blueLightColor[1], blueLightColor[2] });
SetPointLightRadius(BlueLightID, blueLightRadius);
SetPointLightIntensity(BlueLightID, blueLightIntensity);
// Animate the 100 cubes (10x10 grid)
constexpr int kGridSize = 10;
constexpr float kSpacing = 2.5f;
constexpr float kOffset = (kGridSize - 1) * kSpacing * 0.5f;
for (int row = 0; row < kGridSize; ++row)
{
for (int col = 0; col < kGridSize; ++col)
{
MeshID cube = static_cast<MeshID>(row * kGridSize + col); // Assuming they were added first
float x = static_cast<float>(col) * kSpacing - kOffset;
float y = static_cast<float>(row) * kSpacing - kOffset;
float seed = static_cast<float>(cube);
float timeZ = animateCubesTime * 2.0f + seed * 0.5f;
float z = 0.0f;
float scaleF = 1.0f;
Matrix rotation = MatrixIdentity();
if (animateCubes || animateCubesTime > 0.0f)
{
z = sinf(timeZ) * 1.5f; // Oscillate up and down
scaleF = 1.0f + sinf(animateCubesTime * 1.5f + seed) * 0.3f; // Pulse scale
rotation = MatrixRotation(animateCubesTime * 1.2f + seed * 0.73f, animateCubesTime * 0.8f + seed * 1.17f,
animateCubesTime * 0.5f + seed * 0.53f);
}
Matrix scale = MatrixScale(scaleF, scaleF, scaleF);
SetMeshTransform(cube, MatrixTranslation(x, y, z) * rotation * scale);
}
}
DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f }, false);
DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f }, true);
DebugDisplay_DrawLine({ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, { 0.0f, 0.0f, 1.0f, 1.0f }, true);
DebugDisplay_DrawSphere({ 0.0f, 0.0f, 0.0f }, 0.5f, { 1.0f, 1.0f, 0.0f, 1.0f }, true);
DebugDisplay_DrawSphere(blueLightPos, 0.5f, { 0.0f, 0.0f, 1.0f, 1.0f }, true);
DebugDisplay_DrawSphere(redLightPos, 0.5f, { 1.0f, 0.0f, 0.0f, 1.0f }, true);
Game.Update(0.0f);
@@ -300,27 +509,7 @@ void JulietApplication::Update()
WaitUntilGPUIsIdle(GraphicsDevice);
#if ALLOW_SHADER_HOT_RELOAD
String entryPoint = WrapString("main");
ShaderCreateInfo shaderCI = {};
shaderCI.EntryPoint = entryPoint;
String shaderPath = WrapString("../../Assets/compiled/Triangle.vert.dxil");
shaderCI.Stage = ShaderStage::Vertex;
Shader* vertexShader = CreateShader(GraphicsDevice, shaderPath, shaderCI);
shaderPath = WrapString("../../Assets/compiled/SolidColor.frag.dxil");
shaderCI.Stage = ShaderStage::Fragment;
Shader* fragmentShader = CreateShader(GraphicsDevice, shaderPath, shaderCI);
UpdateGraphicsPipelineShaders(GraphicsDevice, GraphicsPipeline, vertexShader, fragmentShader);
if (vertexShader)
{
DestroyShader(GraphicsDevice, vertexShader);
}
if (fragmentShader)
{
DestroyShader(GraphicsDevice, fragmentShader);
}
ReloadMeshRendererShaders();
#endif
}
@@ -352,54 +541,36 @@ void JulietApplication::Update()
if (ShowMemoryDebugger)
{
#if JULIET_DEBUG
Debug::DebugDrawMemoryArena();
#endif
}
ArenaClear(GameScratchArena);
}
void JulietApplication::OnPreRender(CommandList* cmd)
{
// Buffer uploads
if (ConstantBuffer && TransferBuffer)
{
void* ptr = MapGraphicsTransferBuffer(GraphicsDevice, TransferBuffer);
if (ptr)
{
auto vertices = static_cast<Vertex*>(ptr);
// Triangle 1
vertices[0] = { { -0.5f, -0.5f }, { 1.0f, 0.0f, 0.0f, 1.0f } }; // Red
vertices[1] = { { 0.0f, 0.5f }, { 0.0f, 1.0f, 0.0f, 1.0f } }; // Green
vertices[2] = { { 0.5f, -0.5f }, { 0.0f, 0.0f, 1.0f, 1.0f } }; // Blue
// Triangle 2
vertices[3] = { { -0.5f, 0.5f }, { 1.0f, 1.0f, 0.0f, 1.0f } }; // Yellow
vertices[4] = { { 0.0f, 0.8f }, { 0.0f, 1.0f, 1.0f, 1.0f } }; // Cyan
vertices[5] = { { 0.5f, 0.5f }, { 1.0f, 0.0f, 1.0f, 1.0f } }; // Magenta
UnmapGraphicsTransferBuffer(GraphicsDevice, TransferBuffer);
}
CopyBuffer(cmd, ConstantBuffer, TransferBuffer, 256);
TransitionBufferToReadable(cmd, ConstantBuffer);
}
}
void JulietApplication::OnPreRender(CommandList* /*cmd*/) {}
void JulietApplication::OnRender(RenderPass* pass, CommandList* cmd)
{
BindGraphicsPipeline(pass, GraphicsPipeline);
PushData pushData = {};
pushData.ViewProjection = Camera_GetViewProjectionMatrix(GetDebugCamera());
uint32 descriptorIndex = GetDescriptorIndex(GraphicsDevice, ConstantBuffer);
struct PushData
if (enableGlobalLight)
{
float ViewProjection[16];
uint32 BufferIndex;
} pushData = {};
pushData.BufferIndex = descriptorIndex;
pushData.GlobalLightDirection = Normalize({ globalLightDir[0], globalLightDir[1], globalLightDir[2] });
pushData.GlobalLightColor = { globalLightColor[0], globalLightColor[1], globalLightColor[2] };
pushData.GlobalAmbientIntensity = globalAmbientIntensity;
}
else
{
pushData.GlobalLightDirection = { 0.0f, -1.0f, 0.0f };
pushData.GlobalLightColor = { 0.0f, 0.0f, 0.0f };
pushData.GlobalAmbientIntensity = 0.0f;
}
SetPushConstants(cmd, ShaderStage::Vertex, 0, sizeof(pushData) / 4, &pushData);
DrawPrimitives(pass, 6, 1, 0, 0);
RenderSkybox(pass, cmd, pushData.ViewProjection);
RenderMeshes(pass, cmd, pushData);
}
ColorTargetInfo JulietApplication::GetColorTargetInfo(Texture* swapchainTexture)
@@ -424,18 +595,58 @@ DepthStencilTargetInfo* JulietApplication::GetDepthTargetInfo()
Camera JulietApplication::GetDebugCamera()
{
static float orbitTime = 0.0f;
orbitTime += 0.016f;
if (freeCameraMode)
{
Camera cam = {};
cam.Position = camPos;
cam.Target = camPos + Vector3{ cosf(camYaw) * cosf(camPitch), sinf(camYaw) * cosf(camPitch), sinf(camPitch) };
cam.Up = { 0.0f, 0.0f, 1.0f };
cam.FOV = 1.047f;
cam.AspectRatio = 1200.0f / 800.0f;
cam.NearPlane = 0.1f;
cam.FarPlane = 1000.0f;
return cam;
}
// --- Adjusted for 1-Meter Scale ---
float baseRadius = 25.0f; // Increased to see 10x10 cube grid
float radius = baseRadius;
if (animateCamera || animateCameraTime > 0.0f)
{
float orbitSpeed = 0.5f;
float currentOrbitTime = animateCameraTime * orbitSpeed;
float zoomAmplitude = 15.0f;
float zoomSpeed = 0.5f;
radius = baseRadius + (sinf(animateCameraTime * zoomSpeed) * zoomAmplitude);
float zHeight = radius * 0.5f; // Keep a nice downward viewing angle
Camera cam = {};
cam.Position = { cosf(currentOrbitTime) * radius, sinf(currentOrbitTime) * radius, zHeight };
cam.Target = { 0.0f, 0.0f, 0.0f };
cam.Up = { 0.0f, 0.0f, 1.0f };
cam.FOV = 1.047f;
cam.AspectRatio = 1200.0f / 800.0f;
cam.NearPlane = 0.1f;
cam.FarPlane = 1000.0f;
return cam;
}
float zHeight = radius * 0.5f; // Keep a nice downward viewing angle
float radius = 30.0f;
Camera cam = {};
cam.Position = { cosf(orbitTime) * radius, sinf(orbitTime) * radius, 10.0f };
cam.Position = { radius, 0.0f, zHeight };
cam.Target = { 0.0f, 0.0f, 0.0f };
cam.Up = { 0.0f, 0.0f, 1.0f };
cam.FOV = 1.047f;
cam.AspectRatio = 1200.0f / 800.0f;
cam.NearPlane = 0.1f;
cam.FarPlane = 1000.0f;
return cam;
}
@@ -443,7 +654,6 @@ bool JulietApplication::IsRunning()
{
return Running;
}
namespace
{
JulietApplication EditorApplication;
@@ -454,15 +664,15 @@ JulietApplication& GetEditorApplication()
return EditorApplication;
}
int JulietMain(int, wchar_t**)
int JulietMain(int argc, wchar_t** argv)
{
if (__argc > 1)
if (argc > 1)
{
for (int i = 1; i < __argc; ++i)
for (int i = 1; i < argc; ++i)
{
if (strcmp(__argv[i], "-autoclose") == 0 && (i + 1 < __argc))
if (wcscmp(argv[i], L"-autoclose") == 0 && (i + 1 < argc))
{
int frames = atoi(__argv[i + 1]);
int frames = _wtoi(argv[i + 1]);
EditorApplication.SetAutoCloseFrameCount(frames);
}
}
@@ -472,7 +682,7 @@ int JulietMain(int, wchar_t**)
// Pause here to not close the console window immediatly on stop
// Only pause if not in auto-close mode
if (EditorApplication.GetAutoCloseFrameCount() == -1)
if (EditorApplication.GetAutoCloseFrameCount() == -1 && !Debug::IsDebuggerPresent())
{
system("PAUSE");
}

View File

@@ -8,6 +8,7 @@
namespace Juliet
{
struct Mesh;
struct GraphicsTransferBuffer;
struct GraphicsBuffer;
struct GraphicsDevice;
@@ -17,7 +18,7 @@ namespace Juliet
class JulietApplication : public Juliet::IApplication
{
protected:
void Init() override;
void Init(Juliet::NonNullPtr<Juliet::Arena> arena) override;
void Shutdown() override;
void Update() override;
bool IsRunning() override;
@@ -26,27 +27,28 @@ class JulietApplication : public Juliet::IApplication
Juliet::GraphicsDevice* GetGraphicsDevice() override { return GraphicsDevice; }
// Render Lifecycle
void OnPreRender(Juliet::CommandList* cmd) override;
void OnRender(Juliet::RenderPass* pass, Juliet::CommandList* cmd) override;
Juliet::ColorTargetInfo GetColorTargetInfo(Juliet::Texture* swapchainTexture) override;
Juliet::DepthStencilTargetInfo* GetDepthTargetInfo() override;
Juliet::Camera GetDebugCamera() override;
void OnPreRender(Juliet::CommandList* cmd) override;
void OnRender(Juliet::RenderPass* pass, Juliet::CommandList* cmd) override;
Juliet::ColorTargetInfo GetColorTargetInfo(Juliet::Texture* swapchainTexture) override;
Juliet::DepthStencilTargetInfo* GetDepthTargetInfo() override;
Juliet::Camera GetDebugCamera() override;
public:
void SetAutoCloseFrameCount(int count) { AutoCloseFrameCount = count; }
int GetAutoCloseFrameCount() const { return AutoCloseFrameCount; }
private:
Juliet::Window* MainWindow = {};
Juliet::GraphicsDevice* GraphicsDevice = {};
Juliet::HotReloadCode GameCode = {};
Juliet::GraphicsPipeline* GraphicsPipeline = {};
Juliet::GraphicsBuffer* ConstantBuffer = {};
Juliet::GraphicsTransferBuffer* TransferBuffer = {};
Juliet::Texture* DepthBuffer = {};
Juliet::Window* MainWindow = {};
Juliet::GraphicsDevice* GraphicsDevice = {};
Juliet::HotReloadCode GameCode = {};
Juliet::GraphicsPipeline* GraphicsPipeline = {};
Juliet::Texture* DepthBuffer = {};
Juliet::Arena* GameArena = nullptr;
Juliet::Arena* GameScratchArena = nullptr;
bool Running = false;
int AutoCloseFrameCount = -1;
int AutoCloseFrameCount = -1;
bool Running = false;
float CameraTime = 0.0f;
};
JulietApplication& GetEditorApplication();

Some files were not shown because too many files have changed in this diff Show More