constants;
+```
+
+
+
+
+
+
+
+
+
+
+ Matrix4x4
+
+ mul(vector, matrix)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SetConstantBuffer
+
+
+
+
+
+
+```csharp
+commandBuffer.SetPipeline(computePipeline);
+commandBuffer.SetConstantBuffer(constantBuffer, 0);
+commandBuffer.Dispatch(groupCountX, groupCountY, 1);
+```
+
+
+
+ commandBuffer
+ computePipeline
+ constantBuffer
+ BufferUsages.Constant
+
+
+
+
+ Input
+ inputTexture.SampledHandle
+ Output
+ outputTexture.StorageHandle
+ DescriptorHandle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ texture.SampledHandle
+
+
+ DescriptorHandle<Texture2D>
+
+
+
+
+
+
+
+ texture.StorageHandle
+
+
+ DescriptorHandle<RWTexture2D<float4>>
+
+
+
+
+
+
+
+ buffer.StorageReadOnlyHandle
+
+
+ DescriptorHandle<StructuredBuffer<T>>
+
+
+
+ T
+
+
+
+
+
+ buffer.StorageReadWriteHandle
+
+
+ DescriptorHandle<RWStructuredBuffer<T>>
+
+
+
+ T
+
+
+
+
+
+ sampler.Handle
+
+
+ DescriptorHandle<SamplerState>
+
+
+
+
+
+
+
+
+
+ ResourceHandle
+
+
+
+
+
+
+
+
+
+
+
+
+ Dispatch
+ [numthreads(16, 16, 1)]
+
+
+
+
+ ShaderDesc.ThreadGroupSize
+ Dispatch
+ ComputeShaderRenderer.cs
+
+
+
diff --git a/documents/learn/concepts/synchronization.md b/documents/learn/concepts/synchronization.md
new file mode 100644
index 00000000..5ee941e6
--- /dev/null
+++ b/documents/learn/concepts/synchronization.md
@@ -0,0 +1,236 @@
+---
+title: '@concepts.synchronization.title'
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```csharp
+commandBuffer.Barrier(BarrierStages.ComputeShading, BarrierStages.ComputeShading);
+```
+
+
+
+
+
+
+
+
+
+
+
+ BarrierStages.All
+
+
+
+
+
+
+
+
+
+
+ TextureLayout
+ Storage
+
+
+
+```csharp
+commandBuffer.Transition(outputTexture, default, TextureLayout.Storage, TextureLayout.Sampled);
+```
+
+
+
+ outputTexture
+ Sampled | Storage
+ Storage
+ default
+
+
+
+
+ Transition
+
+
+
+
+ Undefined
+
+
+
+
+
+
+
+ Transition
+ Barrier
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Submit()
+ TimelineValue
+ Value
+
+
+
+
+ producer
+ consumer
+
+
+
+```csharp
+TimelineValue produced = producer.Submit();
+
+TimelineValue consumed = consumer.Submit(produced);
+```
+
+
+
+ produced.Wait ()
+ consumed
+
+
+
+
+
+
+```csharp
+consumed.Wait();
+```
+
+
+
+ Wait()
+ queue.Timeline .Signal ()
+ Signal()
+
+
+
+
+
+
+
+ IsCompleted
+
+
+
+
+ CommandBuffer.Download
+ Wait()
+
+
+
+
+ IsCompleted
+
+
+
+
+
+
+
+
+
+
+
+ SwapChain.Resize
+
+
+
+
+ SwapChain.Present()
+
+
diff --git a/documents/learn/first-triangle.md b/documents/learn/first-triangle.md
new file mode 100644
index 00000000..7ea83be7
--- /dev/null
+++ b/documents/learn/first-triangle.md
@@ -0,0 +1,1110 @@
+---
+title: '@tutorial.title'
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.
+
+
+
+
+ net10.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CAMetalLayer.cs
+
+
+
+
+
+
+
+
+
+
+
+
+ 2.
+
+
+
+ FirstTriangle
+
+
+
+
+
+
+
+ Silk.NET.Windowing
+
+
+ Zenith.NET
+
+
+ Zenith.NET.Compiler
+
+
+ Zenith.NET.DirectX12
+
+
+ Zenith.NET.Metal
+
+
+ Zenith.NET.Vulkan
+
+
+
+
+ Zenith.NET
+ Zenith.NET.Compiler
+
+
+
+
+
+ AllowUnsafeBlocks
+
+
+
+
+
+
+ 3.
+
+
+
+ CocoaHelper.cs
+
+
+
+
+ Program.cs
+ Main
+
+
+
+```csharp
+using Silk.NET.Windowing;
+
+IWindow window = Window.Create(WindowOptions.Default with
+{
+ API = GraphicsAPI.None,
+ Title = "Zenith.NET - First Triangle"
+});
+
+window.Initialize();
+window.Center();
+
+window.Run();
+
+window.Dispose();
+```
+
+
+
+ GraphicsAPI.None
+ Initialize()
+ Center()
+ Run()
+ Dispose()
+
+
+
+
+
+
+
+
+
+ 4.
+
+
+
+
+
+
+ GraphicsContext
+
+
+
+
+ Program.cs
+
+
+
+```csharp
+using Zenith.NET;
+using Zenith.NET.DirectX12;
+using Zenith.NET.Metal;
+using Zenith.NET.Vulkan;
+```
+
+
+
+ IWindow window
+
+
+
+
+
+
+```csharp
+GraphicsContext context;
+if (OperatingSystem.IsWindows())
+{
+ context = GraphicsContext.CreateDirectX12(useValidationLayer: true);
+}
+else if (OperatingSystem.IsMacOS())
+{
+ context = GraphicsContext.CreateMetal(useValidationLayer: true);
+}
+else
+{
+ context = GraphicsContext.CreateVulkan(useValidationLayer: true);
+}
+
+context.ValidationMessage += static (_, args) => Console.WriteLine($"[{args.Severity}] {args.Message}");
+```
+
+
+
+ ValidationMessage
+
+
+
+
+
+
+
+ window.Center()
+ window.Run()
+
+
+
+
+
+
+```csharp
+uint width = (uint)window.FramebufferSize.X;
+uint height = (uint)window.FramebufferSize.Y;
+
+Surface surface;
+if (OperatingSystem.IsWindows())
+{
+ surface = Surface.Win32(window.Native!.Win32!.Value.Hwnd, width, height);
+}
+else if (OperatingSystem.IsMacOS())
+{
+ surface = Surface.Apple(CocoaHelper.CreateLayer(window.Native!.Cocoa!.Value), width, height);
+}
+else
+{
+ surface = Surface.Xlib(window.Native!.X11!.Value.Display, (nint)window.Native.X11.Value.Window, width, height);
+}
+```
+
+
+
+ Surface
+ FramebufferSize
+
+
+
+
+
+
+
+
+
+
+
+
+```csharp
+SwapChain swapChain = context.CreateSwapChain(new()
+{
+ Surface = surface,
+ Format = PixelFormat.B8G8R8A8UNorm
+});
+```
+
+
+
+ SwapChain
+ Drawable
+ Texture
+ Present()
+
+
+
+
+ B8G8R8A8UNorm
+ UNorm
+ swapChain.Desc .Format
+
+
+
+
+ window.Dispose();
+ Program.cs
+
+
+
+```csharp
+swapChain.Dispose();
+window.Dispose();
+
+context.Dispose();
+```
+
+
+
+
+
+ 5.
+
+
+
+
+
+
+ _
+ window.Run();
+
+
+
+
+
+
+```csharp
+window.Render += _ =>
+{
+ if (width is 0 || height is 0)
+ {
+ return;
+ }
+
+ CommandBuffer commandBuffer = context.GraphicsQueue.CommandBuffer();
+
+ commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment);
+
+ commandBuffer.BeginRenderPass([ColorAttachment.Clear(swapChain.Drawable, new(0.04f, 0.055f, 0.075f, 1.0f))], null);
+
+ // Add the triangle draw commands here later.
+
+ commandBuffer.EndRenderPass();
+
+ commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Present);
+
+ commandBuffer.Submit().Wait();
+
+ swapChain.Present();
+};
+```
+
+
+
+
+
+
+ GraphicsQueue .CommandBuffer ()
+
+
+
+
+ Transition
+ default
+ Undefined
+
+
+
+
+
+
+
+
+
+
+ ColorAttachment.Clear
+ BeginRenderPass
+
+
+
+
+ null
+
+
+
+
+ BeginRenderPass
+
+
+
+
+ EndRenderPass
+ Submit()
+ Wait()
+ Present()
+
+
+
+
+
+
+
+ FramebufferResize
+ window.Run();
+
+
+
+```csharp
+window.FramebufferResize += _ =>
+{
+ width = (uint)window.FramebufferSize.X;
+ height = (uint)window.FramebufferSize.Y;
+
+ if (width is 0 || height is 0)
+ {
+ return;
+ }
+
+ swapChain.Resize(width, height);
+};
+```
+
+
+
+
+
+
+
+
+ Present()
+
+
+
+
+
+
+
+
+
+
+ 6.
+
+
+
+
+
+
+
+
+
+ Program.cs
+
+
+
+```csharp
+using System.Numerics;
+using System.Runtime.InteropServices;
+```
+
+
+
+ Program.cs
+
+
+
+
+
+
+```csharp
+[StructLayout(LayoutKind.Sequential)]
+file struct Vertex(Vector3 position, Vector4 color)
+{
+ public Vector3 Position = position;
+
+ public Vector4 Color = color;
+}
+```
+
+
+
+ file
+ Program.cs
+ LayoutKind.Sequential
+ Position
+ Color
+
+
+
+
+
+
+
+ window.Render += _ =>
+
+
+
+
+
+
+```csharp
+Vertex[] vertices =
+[
+ new(new(0.0f, 0.6f, 0.0f), new(1.0f, 0.2f, 0.15f, 1.0f)),
+ new(new(0.6f, -0.5f, 0.0f), new(0.15f, 0.85f, 0.35f, 1.0f)),
+ new(new(-0.6f, -0.5f, 0.0f), new(0.2f, 0.45f, 1.0f, 1.0f))
+];
+```
+
+
+
+ Vertex
+ new
+ Vector3
+ Vector4
+ w = 1
+
+
+
+
+
+
+
+
+
+
+ Program.cs
+ using Buffer = Zenith.NET.Buffer ;
+ System.Buffer
+
+
+
+
+ Buffer
+
+
+
+```csharp
+Buffer vertexBuffer;
+
+unsafe
+{
+ vertexBuffer = context.CreateBuffer(new()
+ {
+ SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length),
+ StrideInBytes = (uint)sizeof(Vertex),
+ Usages = BufferUsages.Vertex,
+ Residency = MemoryResidency.CpuWriteOnly
+ });
+
+ fixed (Vertex* pointer = vertices)
+ {
+ vertexBuffer.Upload(0, new()
+ {
+ Pointer = (nint)pointer,
+ SizeInBytes = (uint)(sizeof(Vertex) * vertices.Length)
+ });
+ }
+}
+```
+
+
+
+ sizeof(Vertex) * vertices.Length
+ BufferUsages.Vertex
+ CpuWriteOnly
+
+
+
+
+ Upload
+
+
+
+
+ fixed
+ Upload
+
+
+
+
+ vertexBuffer.Dispose ();
+ swapChain.Dispose ();
+ Program.cs
+
+
+
+
+
+
+
+ 7.
+
+
+
+
+
+
+ Triangle.slang
+
+
+
+```slang
+struct VSInput
+{
+ float3 Position : POSITION0;
+
+ float4 Color : COLOR0;
+};
+
+struct FSInput
+{
+ float4 Position : SV_POSITION;
+
+ float4 Color : COLOR0;
+};
+```
+
+
+
+ VSInput
+ POSITION0
+ COLOR0
+
+
+
+
+ FSInput
+ SV_POSITION
+ COLOR0
+
+
+
+
+
+
+
+ Triangle.slang
+
+
+
+```slang
+[shader("vertex")]
+FSInput VSMain(VSInput input)
+{
+ FSInput output;
+ output.Position = float4(input.Position, 1.0);
+ output.Color = input.Color;
+
+ return output;
+}
+```
+
+
+
+ w = 1
+
+
+
+
+
+
+
+
+
+```slang
+[shader("fragment")]
+float4 FSMain(FSInput input) : SV_TARGET
+{
+ return input.Color;
+}
+```
+
+
+
+ FSMain
+ SV_TARGET
+
+
+
+
+
+
+
+ Triangle.slang
+
+
+
+
+
+
+ Program.cs
+
+
+
+```csharp
+string shaderPath = Path.Combine(AppContext.BaseDirectory, "Triangle.slang");
+
+Shader vertexShader = context.CreateShader(ZenithCompiler.CompileFromFile(context.GraphicsApi, shaderPath, "VSMain"));
+Shader fragmentShader = context.CreateShader(ZenithCompiler.CompileFromFile(context.GraphicsApi, shaderPath, "FSMain"));
+```
+
+
+
+ ZenithCompiler
+ CreateShader
+ context.GraphicsApi
+ VSMain
+ FSMain
+
+
+
+
+ AppContext.BaseDirectory
+
+
+
+
+
+
+
+
+
+
+```csharp
+InputLayout inputLayout = new();
+inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position });
+inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Color });
+```
+
+
+
+ InputLayout.Add
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vertex.Position
+
+ 0
+
+
+ Float3
+
+
+
+ POSITION0
+
+
+
+
+ Vertex.Color
+
+ 12
+
+
+ Float4
+
+
+
+ COLOR0
+
+
+
+
+
+
+ 28
+
+
+
+
+
+
+
+
+
+
+
+ SemanticIndex
+ POSITION0
+ COLOR0
+
+
+
+
+
+
+ 9.
+
+
+
+ GraphicsPipeline
+
+
+
+
+
+
+
+
+
+
+```csharp
+GraphicsPipeline pipeline = context.CreateGraphicsPipeline(new()
+{
+ VertexShader = vertexShader,
+ FragmentShader = fragmentShader,
+ InputLayouts = [inputLayout],
+ PrimitiveTopology = PrimitiveTopology.TriangleList,
+ AttachmentFormats = new()
+ {
+ ColorFormats = [swapChain.Desc.Format],
+ SampleCount = SampleCount.Count1
+ },
+ RenderState = new()
+ {
+ Rasterizer = RasterizerState.CullNone(),
+ DepthStencil = DepthStencilState.DepthNone(),
+ Blend = BlendState.Opaque()
+ }
+});
+
+vertexShader.Dispose();
+fragmentShader.Dispose();
+```
+
+
+
+ pipeline.Dispose ();
+ vertexBuffer.Dispose ();
+
+
+
+
+
+
+ 10.
+
+
+
+ window.Render
+ // Add the triangle draw commands here later.
+ BeginRenderPass
+ EndRenderPass
+
+
+
+```csharp
+commandBuffer.SetPipeline(pipeline);
+commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0);
+
+commandBuffer.Draw(3, 1, 0, 0);
+```
+
+
+
+ SetPipeline
+
+
+
+
+ SetVertexBuffer (vertexBuffer, 0, 0)
+ InputLayouts
+
+
+
+
+ Draw
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ vertexCount
+
+ 3
+
+
+
+
+
+
+ instanceCount
+
+ 1
+
+
+
+
+
+
+ firstVertex
+
+ 0
+
+
+
+
+
+
+ firstInstance
+
+ 0
+
+
+
+
+
+
+
+
+ Submit ().Wait ()
+ Present()
+
+
+
+
+
+
+ 11.
+
+
+
+ FirstTriangle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```text
+Get the current drawable
+ ↓
+Transition to ColorAttachment
+ ↓
+Clear and draw the triangle
+ ↓
+Transition to Present
+ ↓
+Submit and wait
+ ↓
+Present
+```
+
+
+
+ Run
+ window.Run();
+
+
+
+```csharp
+pipeline.Dispose();
+vertexBuffer.Dispose();
+swapChain.Dispose();
+window.Dispose();
+
+context.Dispose();
+```
+
+
+
+ Dispose()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Triangle.slang
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Program.cs
+
+
+
diff --git a/documents/learn/index.md b/documents/learn/index.md
new file mode 100644
index 00000000..83edf2c4
--- /dev/null
+++ b/documents/learn/index.md
@@ -0,0 +1,126 @@
+---
+title: '@learning.title'
+description: '@learning.meta.description'
+_pageKind: hub
+_disableAffix: true
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/documents/learn/samples.md b/documents/learn/samples.md
new file mode 100644
index 00000000..5ff864bc
--- /dev/null
+++ b/documents/learn/samples.md
@@ -0,0 +1,220 @@
+---
+title: '@samples.title'
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ HelloTriangleRenderer.cs
+ HelloTriangle.slang
+
+
+
+
+
+
+
+
+ D32FloatS8UInt
+
+
+
+
+
+
+
+
+
+ SpinningCubeRenderer.cs
+ SpinningCube.slang
+
+
+
+
+
+
+
+ Assets/Textures/shoko.png
+
+
+
+
+
+
+
+
+
+ ComputeShaderRenderer.cs
+ ComputeShader.slang
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ IndirectDrawingRenderer.cs
+ IndirectDrawing.slang
+
+
+
+
+
+
+
+ context.Capabilities .RayTracingSupported
+
+
+
+
+
+
+
+
+
+ RayTracingRenderer.cs
+ RayTracing.slang
+
+
+
+
+
+
+
+ context.Capabilities .MeshShadingSupported
+
+
+
+
+
+
+
+
+
+ MeshShadingRenderer.cs
+ MeshShading.slang
+
+
+
+
+
+
+
+ App.cs
+ IRenderer.cs
+
+
+
+
+ TexturePresenter.cs
+ PresentTexture.slang
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/documents/locales/de-DE/strings.yml b/documents/locales/de-DE/strings.yml
new file mode 100644
index 00000000..c4dea822
--- /dev/null
+++ b/documents/locales/de-DE/strings.yml
@@ -0,0 +1,593 @@
+### YamlMime:Resources
+concepts.execution.context.description: 'Ein {graphicsContext} verbindet die Anwendung über ein bestimmtes Backend mit einem GPU-Gerät und stellt dessen Grafik-, Compute- und Transferwarteschlangen bereit. Gemeinsam verwendete Ressourcen müssen demselben Kontext angehören: Puffer, Texturen, Shader, Pipelines und ihre Handles lassen sich nicht über Kontextgrenzen hinweg binden.'
+concepts.execution.context.details: '{capabilities} liefert den Gerätenamen und die unterstützten Funktionen. Prüfen Sie mit {rayTracingSupported} und {meshShadingSupported}, ob die jeweilige Funktion verfügbar ist, bevor Sie ihre Ressourcen erstellen. Die Beispiele für {link} und {detail} zeigen diese Prüfung bei der Initialisierung.'
+concepts.execution.context.details.detail: Mesh-Shading
+concepts.execution.context.details.link: Raytracing
+concepts.execution.context.title: 'Grafikkontext'
+concepts.execution.description: 'GPU-Arbeit beginnt auf der CPU: Die Anwendung erstellt Ressourcen, zeichnet Befehle zu ihrer Verwendung auf und reicht diese bei einer Warteschlange ein. Danach kann die CPU weiterarbeiten, während die GPU die Befehle ausführt. Die benötigten Ressourcen müssen bis zum Ende dieser Ausführung verfügbar bleiben.'
+concepts.execution.ownership.description: 'Der Besitz legt fest, wer ein Objekt freigibt; der GPU-Abschluss bestimmt, wann dies zulässig ist. {dispose} gibt native Ressourcen auch dann frei, wenn noch C#-Referenzen auf das Objekt bestehen. Das Einreichen von Befehlen überträgt den Besitz ihrer Ressourcen nicht an die Warteschlange.'
+concepts.execution.ownership.details: 'Stoppen Sie beim Beenden neue Einreichungen und warten Sie auf die letzte Ressourcennutzung in allen beteiligten Warteschlangen. Geben Sie danach die Anwendungsressourcen und zuletzt den Kontext frei. Der Kontext gibt seine Warteschlangen und internen Hilfsobjekte frei; dieser Schritt muss deshalb nach Abschluss der Anwendungsarbeit erfolgen.'
+concepts.execution.ownership.guidance: 'Verfolgen Sie während des Renderings den Abschluss aller Zugriffe auf Daten, die überschrieben, oder Ressourcen, die ersetzt werden sollen. Warten Sie auf diese Zugriffe, bevor Sie den betreffenden Speicher ändern oder freigeben. Wie Ansichten und ihre zugrunde liegenden Ressourcen in dieses Lebensdauermodell passen, beschreibt {link}.'
+concepts.execution.ownership.guidance.link: Ressourcenverwaltung
+concepts.execution.ownership.table.applicationResources.object: Von der Anwendung erstellte Ressourcen
+concepts.execution.ownership.table.applicationResources.ownership: Die Anwendung gibt sie nach ihrer letzten GPU-Verwendung frei.
+concepts.execution.ownership.table.commandBuffers.object: Geliehene Befehlspuffer
+concepts.execution.ownership.table.commandBuffers.ownership: Die Warteschlange verwaltet und recycelt sie.
+concepts.execution.ownership.table.headings.object: Objekt
+concepts.execution.ownership.table.headings.ownership: 'Verantwortung für die Freigabe'
+concepts.execution.ownership.table.queues.object: 'Kontextwarteschlangen und ihre Timelines'
+concepts.execution.ownership.table.queues.ownership: Der Kontext und die Warteschlangen verwalten sie.
+concepts.execution.ownership.table.swapChainDrawable.ownership: Die Swapchain verwaltet sie.
+concepts.execution.ownership.title: 'Besitz und Lebensdauer'
+concepts.execution.pipelines.description: 'Eine Grafikpipeline legt Shader, Vertex-Eingabelayout, Primitivzusammensetzung, Attachment-Formate und Renderzustand für Zeichenbefehle fest. Wählen Sie die Pipeline, bevor Sie Vertex-, Index- oder Konstantendaten binden. Attachment-Formate und Sample-Anzahl müssen mit dem Renderpass übereinstimmen.'
+concepts.execution.pipelines.details: 'Eine Compute-Pipeline wählt den Shader-Einstiegspunkt für einen Dispatch. Binden Sie Pipeline und Konstanten vor dem Dispatch, außerhalb eines Grafik-Renderpasses.'
+concepts.execution.pipelines.guidance: 'Der {link} zeigt die Grafiksequenz. Das {detail} ergänzt einen Compute-Dispatch vor dem Grafikpass, der dessen Ausgabe darstellt.'
+concepts.execution.pipelines.guidance.detail: Compute-Beispiel
+concepts.execution.pipelines.guidance.link: Render-Callback des Dreiecks
+concepts.execution.pipelines.title: 'Pipeline-Zustand'
+concepts.execution.queues.description: '{graphicsQueue} ist der übliche Ausgangspunkt für Frames mit Zeichnen, Berechnungen und Kopien. Abhängige Operationen auf einer Warteschlange lassen sich einfacher anordnen.'
+concepts.execution.queues.details: 'Mit getrennten Warteschlangen lassen sich unabhängige Aufgaben getrennt einreichen. Daraus folgt jedoch nicht, dass ihnen unabhängige Hardwareeinheiten zugeordnet sind. Zusätzliche Warteschlangen sind sinnvoll, wenn Aufgaben von einer überlappenden Ausführung profitieren. Verwendet eine Warteschlange Daten aus einer anderen, benötigen ihre Einreichungen eine explizite Abhängigkeit.'
+concepts.execution.queues.guidance: 'Auch innerhalb einer Warteschlange können abhängige Operationen Barrieren oder Texturübergänge benötigen, damit spätere Zugriffe die Ergebnisse früherer Schreibvorgänge sehen. Diese Speicherabhängigkeiten sind von der Einreichungsreihenfolge zu unterscheiden. Wie sie angegeben werden, erläutert {link}.'
+concepts.execution.queues.guidance.link: Synchronisation
+concepts.execution.queues.table.computeQueue.workToRecord: Compute-Dispatches und kompatible Transferarbeiten, ohne Grafik-Renderpässe.
+concepts.execution.queues.table.graphicsQueue.workToRecord: 'Renderpässe, Zeichenbefehle sowie Compute- und Transferoperationen.'
+concepts.execution.queues.table.headings.queue: Warteschlange
+concepts.execution.queues.table.headings.workToRecord: 'Unterstützte Operationen'
+concepts.execution.queues.table.transferQueue.workToRecord: Upload-, Download- und Kopierarbeiten, ohne Zeichnen oder Compute-Dispatches.
+concepts.execution.queues.title: 'Wahl der Warteschlange'
+concepts.execution.recording.context: 'Die Warteschlange nimmt abgeschlossene Befehlspuffer zur Wiederverwendung zurück. Fordern Sie für jede Aufzeichnung mit {queueCommandBuffer} einen Puffer an, zeichnen Sie jeweils nur von einem Thread aus auf und reichen Sie den Puffer einmal ein. Nach der Einreichung übernimmt die Warteschlange seine Verwaltung; ändern Sie ihn nicht mehr, reichen Sie ihn nicht erneut ein und geben Sie ihn nicht selbst frei.'
+concepts.execution.recording.description: '{queueCommandBuffer} liefert einen {commandBuffer} mit bereits begonnener Aufzeichnung. Zeichnen Sie die Operationen und ihre Abhängigkeiten auf und rufen Sie anschließend einmal {submit} auf. Dadurch endet die Aufzeichnung; der zurückgegebene {timelineValue} bezeichnet den Abschlusspunkt in dieser Warteschlange.'
+concepts.execution.recording.details: 'Nach der Aufzeichnung eines Frames in {commandBuffer} reicht das Dreieck-Tutorial die Befehle ein und wartet:'
+concepts.execution.recording.guidance: 'Die Aufzeichnung legt die Befehlsfolge fest; {submit} plant deren Ausführung ein. Ein anschließender Aufruf von {wait} blockiert den CPU-Thread bis zum Abschluss der Einreichung. Im Dreieck-Tutorial bleibt der Frame-Ablauf dadurch übersichtlich: Ein Frame ist abgeschlossen, bevor der nächste beginnt.'
+concepts.execution.recording.title: 'Aufzeichnung und Einreichung'
+concepts.execution.title: Ausführungsmodell
+concepts.platforms.description: 'Die Präsentation übernimmt entweder die Anwendung oder ein UI-Steuerelement. Damit steht fest, wer die Ausgabetextur des Frames abruft, Befehle aufzeichnet und einreicht und das Ergebnis präsentiert. Diese Ausgabetextur heißt Drawable.'
+concepts.platforms.frame.context: 'Zeichnen Sie Pipeline-Bindungen und Zeichenbefehle innerhalb des Passes auf und beenden Sie ihn, bevor der Handler zurückkehrt. Das Steuerelement reicht die Befehle ein und präsentiert das Ergebnis. Der Handler darf weder den Befehlspuffer einreichen noch eines der beiden bereitgestellten Objekte freigeben. Ändert er das Texturlayout, muss er vor seiner Rückkehr {colorAttachment} wiederherstellen, damit das Steuerelement den abschließenden Übergang ausführen kann.'
+concepts.platforms.frame.description: 'Die Steuerelemente implementieren {iZenithView}. Die Anwendung weist {graphicsContext} zu, aktualisiert die Szene in {updateRequested} und zeichnet in {renderRequested} anhand der übergebenen {renderEventArgs} auf.'
+concepts.platforms.frame.details: 'Für jeden Frame bezieht das Steuerelement eine Ausgabetextur und einen Befehlspuffer, überführt die Textur nach {colorAttachment} und ruft die Update- und Render-Handler auf. Anschließend reicht es die aufgezeichneten Befehle ein und zeigt das Bild über den Präsentationsmechanismus des Frameworks an.'
+concepts.platforms.frame.guidance: 'Ergänzen Sie {usingZenithNET} in der Datei. Für ein initialisiertes {iZenithView} namens {view} mit zugewiesenem Grafikkontext registrieren Sie diesen Handler, der die Ausgabetextur mit einer Farbe füllt:'
+concepts.platforms.frame.notes: 'Der anfängliche Übergang verwendet {undefined} und erlaubt damit, den bisherigen Bildinhalt zu verwerfen. Füllen oder zeichnen Sie deshalb in jedem Frame das gesamte Bild neu. Speichern Sie akkumulierte oder zwischengespeicherte Ergebnisse in einer anwendungseigenen Textur und zeichnen Sie diese anschließend in die Ausgabetextur des Steuerelements.'
+concepts.platforms.frame.reference: '{zenithViewHelperDrawableFormat} liefert das Standardfarbformat dieser Integrationen.'
+concepts.platforms.frame.title: 'Rendering über UI-Steuerelemente'
+concepts.platforms.integration.context: 'WinForms, WPF und die Windows-Integration von WinUI laufen unter Windows. Installieren Sie für MAUI die Workload der Zielplattform und registrieren Sie den Steuerelement-Handler mit {useZenithView}, wie in {extensionsCs} gezeigt.'
+concepts.platforms.integration.description: 'Die Ereignisschnittstelle ist gemeinsam, der Präsentationsmechanismus hängt jedoch vom Framework ab:'
+concepts.platforms.integration.details: 'Die Implementierungen unterscheiden sich darin, wie das GPU-Bild in die Oberfläche gelangt. Beim Bitmap-Rücklesen werden die Pixel vor der Anzeige in CPU-Speicher kopiert. Interop über gemeinsam genutzte Texturen vermeidet diese CPU-Kopie, kann aber weiterhin GPU-Kopien und Synchronisation erfordern. Berücksichtigen Sie bei der Wahl der Integration neben dem gemeinsamen Ereignis {renderRequested} auch diesen Präsentationsweg.'
+concepts.platforms.integration.guidance: 'Fügen Sie das Integrationspaket für Ihr UI-Framework über NuGet hinzu. Für Avalonia verwenden Sie {avaloniaPackage}.'
+concepts.platforms.integration.table.avalonia.integration: Avalonia
+concepts.platforms.integration.table.avalonia.presentation: 'Rendert in eine Textur und liest die Pixel in {writeableBitmap} zurück. Implementierung: {zenithViewCs} und {surfaceCs}.'
+concepts.platforms.integration.table.headings.integration: Integration
+concepts.platforms.integration.table.headings.presentation: 'Präsentationsmechanismus'
+concepts.platforms.integration.table.maui.integration: .NET MAUI
+concepts.platforms.integration.table.maui.presentation: 'Verwendet native Oberflächen unter Android, iOS und Mac Catalyst sowie Komposition über gemeinsam genutzte Texturen unter Windows. Siehe die {link}.'
+concepts.platforms.integration.table.maui.presentation.link: Plattformimplementierungen
+concepts.platforms.integration.table.uno.integration: Uno-Pfad
+concepts.platforms.integration.table.uno.presentation: 'Liest Pixel in {writeableBitmap} zurück. Implementierung: {zenithViewUnoCs}.'
+concepts.platforms.integration.table.winForms.integration: WinForms
+concepts.platforms.integration.table.winForms.presentation: 'Erstellt eine Zenith.NET-Swapchain für das Fensterhandle {hWND} des Steuerelements. Implementierung: {zenithViewCs}.'
+concepts.platforms.integration.table.winUI.integration: WinUI unter Windows
+concepts.platforms.integration.table.winUI.presentation: 'Rendert in eine importierte, gemeinsam genutzte Direct3D 11-Textur und kopiert das Ergebnis in eine an {swapChainPanel} angebundene Kompositions-Swapchain. Siehe {zenithViewWinUICs}.'
+concepts.platforms.integration.table.wpf.integration: WPF
+concepts.platforms.integration.table.wpf.presentation: 'Importiert eine gemeinsam genutzte Direct3D 11-Textur und kopiert ihren Inhalt über Direct3D-Interop in eine von {d3DImage} angezeigte Oberfläche. Siehe {surfaceCs}.'
+concepts.platforms.integration.title: 'Framework und Präsentationspfad'
+concepts.platforms.lifecycle.context: 'Verwenden Sie {descWidth}, {descHeight}, {descFormat} und {descSampleCount} der im Render-Ereignis bereitgestellten Ausgabetextur, um passende Attachments zu erstellen. Diese Textur liefert die tatsächlichen Maße des Renderziels. Richten Sie sich nach diesen Maßen, statt sie aus der Layoutgröße des Steuerelements oder der Anzeigeskalierung zu berechnen.'
+concepts.platforms.lifecycle.description: 'Die Swapchain verwendet Breite und Höhe des Framebuffers in Pixeln. Bei Anzeigeskalierung können sie von den logischen Fenstermaßen abweichen. Verschieben Sie Rendering und Swapchain-Größenanpassung, solange eine Abmessung null ist. Beziehen Sie {swapChainDrawable} für jeden Frame neu: Diese Textur gehört der Swapchain und kann nach Präsentation oder Größenänderung ersetzt werden.'
+concepts.platforms.lifecycle.details: 'Ändern sich die Framebuffer-Maße, warten Sie vor {swapChainResize} auf alle noch laufenden GPU-Zugriffe auf die alten Bilder. Soll das native Präsentationsziel ersetzt werden, übergeben Sie nach Abschluss dieser Zugriffe seine neue Beschreibung und Handles an {swapChainRefresh}. Geben Sie beim Beenden die Swapchain vor ihrem nativen Präsentationsziel frei.'
+concepts.platforms.lifecycle.guidance: 'Richten sich anwendungseigene Tiefen- oder Offscreen-Texturen nach der Fenstergröße, erstellen Sie sie bei einer Größenänderung neu und aktualisieren Sie Konstanten mit ihren Handles. Die Pipeline bleibt verwendbar, solange Attachment-Formate und Sample-Anzahlen weiterhin übereinstimmen. Der {link} zeigt, wie angepasste Texturen und eine wiederverwendete Pipeline zusammenarbeiten.'
+concepts.platforms.lifecycle.guidance.link: Renderer des rotierenden Würfels
+concepts.platforms.lifecycle.title: 'Swapchain-Abmessungen und Lebensdauer'
+concepts.platforms.ownership.description: 'Die Anwendung besitzt den zugewiesenen Kontext und ihre Rendering-Ressourcen, das Steuerelement seine Präsentationsressourcen. Ändert sich {graphicsContext} der Ansicht, erstellt der Scheduler die Steuerelement-Ressourcen für den neuen Kontext neu. Auch die Anwendung muss ihre Pipelines, Puffer und Texturen für diesen Kontext neu erstellen.'
+concepts.platforms.ownership.details: 'Halten Sie vor dem Ersetzen von Rendering-Ressourcen die Frame-Verarbeitung der Ansicht an und warten Sie auf die GPU-Arbeit, die diese Ressourcen verwendet. Erst danach dürfen die alten Ressourcen oder der Kontext freigegeben werden. Auch beim Entfernen der Ansicht bleibt die Anwendung für die Freigabe des zugewiesenen Kontexts verantwortlich.'
+concepts.platforms.ownership.guidance: 'Der gemeinsame {frameSchedulerCs} führt die Frame-Handler über den UI-Dispatcher der Ansicht aus. Beschränken Sie diese Handler auf Szenenaktualisierung und Befehlsaufzeichnung; aufwendige CPU-Arbeit verzögert dort auch die Benutzeroberfläche.'
+concepts.platforms.ownership.title: 'Verantwortung von Anwendung und Steuerelement'
+concepts.platforms.surface.description: 'Das {link} verwendet Silk.NET für Fenstererstellung und Ereignisverarbeitung. Zenith.NET erhält die nativen Fensterhandles und Abmessungen in einer {surface}-Beschreibung und erstellt damit eine {swapChain}. Für das Betriebssystemfenster bleibt die Anwendung verantwortlich.'
+concepts.platforms.surface.description.link: 'Dreieck-Tutorial'
+concepts.platforms.surface.details: 'Die nativen Handles müssen zum Backend passen:'
+concepts.platforms.surface.guidance: 'Der Beispielhost in {appCs} unterstützt Windows, macOS und X11. Unter Linux liefert sein Fenster Xlib-Handles; eine Wayland-Sitzung benötigt deshalb XWayland. Eine native Wayland-Anwendung übergibt Display- und Surface-Handles über {surfaceWayland}.'
+concepts.platforms.surface.table.android.backend: Vulkan unter Android.
+concepts.platforms.surface.table.apple.backend: Metal auf Apple-Plattformen.
+concepts.platforms.surface.table.headings.backend: 'Backend und Plattform'
+concepts.platforms.surface.table.headings.factory: Oberflächen-Factory
+concepts.platforms.surface.table.headings.handles: Vom Host bereitgestellte Handles
+concepts.platforms.surface.table.wayland.backend: Vulkan mit einem nativen Wayland-Host.
+concepts.platforms.surface.table.wayland.handles: Wayland-Display und -Surface
+concepts.platforms.surface.table.win32.backend: DirectX 12 oder Vulkan unter Windows.
+concepts.platforms.surface.table.xlib.backend: Vulkan unter Linux mit X11 oder XWayland.
+concepts.platforms.surface.table.xlib.handles: X11-Display und -Fenster
+concepts.platforms.surface.title: 'Native Präsentationsziele'
+concepts.platforms.title: Plattformintegration
+concepts.resources.allocation.context: 'Platzierte Ressourcen benötigen den Speicher ihres Heaps während ihrer gesamten Lebensdauer. Warten Sie auf ihre letzte GPU-Nutzung und geben Sie anschließend zuerst die Ressourcen und dann den Heap frei.'
+concepts.resources.allocation.description: 'Verwenden Sie einen {heap}, wenn die Anwendung die Speicherzuteilung selbst verwalten soll. Ressourcen werden darin an expliziten Byte-Offsets platziert. Die Anwendung legt diese Offsets fest und verfolgt die belegten Bereiche.'
+concepts.resources.allocation.details: 'Rufen Sie für jede Ressource {contextGetSizeAndAlignment} mit der Puffer- oder Texturbeschreibung auf, die auch für die Erstellung verwendet wird. Reservieren Sie die zurückgegebene Größe an einem Offset, der ein Vielfaches der angegebenen Ausrichtung ist. Die Allokation muss innerhalb des Heaps liegen und darf keine andere noch aktive Allokation überlappen. Texturgrößen können backendabhängige Auffüllungen enthalten und lassen sich deshalb nicht allein aus Pixelzahl und Bytes pro Pixel berechnen.'
+concepts.resources.allocation.guidance: 'Ein in einem Heap platzierter Puffer muss dieselbe Residenz wie der Heap angeben. Verwenden Sie für Texturen einen Heap mit GPU-only-Residenz.'
+concepts.resources.allocation.title: 'Heap-Allokation'
+concepts.resources.bufferUsages.table.constant.role: Shader-Parameter, die über eine Konstantenpuffer-Bindung gelesen werden.
+concepts.resources.bufferUsages.table.headings.role: 'Rolle'
+concepts.resources.bufferUsages.table.headings.usage: Nutzung
+concepts.resources.bufferUsages.table.index.role: Indizes, die beim indexierten Zeichnen verwendet werden.
+concepts.resources.bufferUsages.table.indirect.role: 'Argumente, die indirekte Zeichen- oder Dispatch-Befehle lesen.'
+concepts.resources.bufferUsages.table.none.role: 'Es sind keine Puffernutzungsflags deklariert.'
+concepts.resources.bufferUsages.table.storageReadOnly.role: 'Pufferdaten, auf die über eine schreibgeschützte Shader-Bindung zugegriffen wird.'
+concepts.resources.bufferUsages.table.storageReadWrite.role: 'Pufferdaten, auf die über eine Shader-Bindung mit Lese- und Schreibzugriff zugegriffen wird.'
+concepts.resources.bufferUsages.table.transferDst.role: 'Zielspeicher für eine GPU-Kopie.'
+concepts.resources.bufferUsages.table.transferSrc.role: 'Quelldaten für eine GPU-Kopie.'
+concepts.resources.bufferUsages.table.vertex.role: 'Vertexattribute, die für das Zeichnen abgerufen werden.'
+concepts.resources.bufferUsages.title: Puffernutzungen
+concepts.resources.description: 'Die Ressourcenbeschreibung legt fest, wie die Anwendung den Speicher verwenden darf. Soll eine Textur als Renderziel dienen, im Shader abgetastet und zur CPU zurückgelesen werden, benötigt sie die Nutzungsflags für alle drei Vorgänge. Die Speicherplatzierung bestimmt den CPU-Zugriff. Synchronisation und Lebensdauerverwaltung bestimmen dagegen, wann Daten zugänglich sind und Speicher freigegeben werden darf.'
+concepts.resources.memory.context: 'Bei CPU-zugänglichen Puffern liefert {map} die Adresse zum Lesen oder Schreiben der Daten. Weder das Mapping noch {unmap} wartet auf GPU-Arbeit. Vor dem Lesen eines GPU-Ergebnisses muss die Anwendung auf den Schreibvorgang warten. Bevor sie dieselben Bytes verändert, muss sie alle noch ausstehenden GPU-Zugriffe darauf abwarten.'
+concepts.resources.memory.description: 'Das Feld {residency} von {bufferDesc} beschreibt den vorgesehenen CPU-Zugriff auf die Allokation:'
+concepts.resources.memory.details: 'Die Residenz beschreibt den benötigten CPU-Zugriff, auch auf Systemen mit gemeinsamem physischem CPU- und GPU-Speicher. Das Dreieck verwendet {cpuWriteOnly}, um seinen kleinen Vertexpuffer direkt zu initialisieren. Für größere statische Geometrie kann ein Upload in GPU-only-Speicher geeigneter sein.'
+concepts.resources.memory.guidance: 'Direkt über den Kontext erstellte Texturen verwenden GPU-Speicher. {textureDesc} enthält deshalb kein Residenzfeld; Daten werden über Uploads und Downloads mit dem CPU-Speicher ausgetauscht.'
+concepts.resources.memory.table.cpuReadOnly.cPUAccess: 'Nur Lesen'
+concepts.resources.memory.table.cpuReadOnly.typicalRole: 'Von der GPU erzeugte Rücklesedaten'
+concepts.resources.memory.table.cpuWriteOnly.cPUAccess: 'Nur Schreiben'
+concepts.resources.memory.table.cpuWriteOnly.typicalRole: 'Von der CPU bereitgestellte oder aktualisierte Daten'
+concepts.resources.memory.table.gpuOnly.cPUAccess: 'Keiner'
+concepts.resources.memory.table.gpuOnly.typicalRole: 'Statische Geometrie und GPU-Arbeitsdaten'
+concepts.resources.memory.table.headings.cPUAccess: 'Vorgesehener CPU-Zugriff'
+concepts.resources.memory.table.headings.residency: Residenz
+concepts.resources.memory.table.headings.typicalRole: Typische Rolle
+concepts.resources.memory.title: 'CPU-Zugriff und Speicherplatzierung'
+concepts.resources.textureUsages.description: 'Transferflags geben an, ob eine Ressource als Quelle oder Ziel einer GPU-Kopie dient. Ob die CPU direkt auf ihren Speicher zugreifen kann, wird unabhängig davon durch die Speicherplatzierung festgelegt.'
+concepts.resources.textureUsages.details: 'Nutzungsflags geben die vorgesehenen Rollen einer Textur an. Für den Wechsel zwischen diesen Rollen gelten die unter {link} beschriebenen Anforderungen. Außerdem muss das Gerät die gewählten Verwendungszwecke für das Texturformat unterstützen.'
+concepts.resources.textureUsages.details.link: 'Layout und Synchronisation'
+concepts.resources.textureUsages.table.colorAttachment.role: 'Ein Farb-Attachment in einem Renderpass.'
+concepts.resources.textureUsages.table.depthStencilAttachment.role: 'Ein Tiefen-/Stencil-Attachment in einem Renderpass.'
+concepts.resources.textureUsages.table.headings.role: 'Rolle'
+concepts.resources.textureUsages.table.headings.usage: Nutzung
+concepts.resources.textureUsages.table.none.role: 'Es sind keine Texturnutzungsflags deklariert.'
+concepts.resources.textureUsages.table.sampled.role: 'Texturdaten, die über eine Bindung für abgetastete Texturen gelesen werden.'
+concepts.resources.textureUsages.table.storage.role: 'Texturdaten, auf die Shader über eine Storage-Textur-Bindung lesend oder schreibend zugreifen.'
+concepts.resources.textureUsages.table.transferDst.role: 'Zielbildspeicher für eine GPU-Kopie.'
+concepts.resources.textureUsages.table.transferSrc.role: 'Quellbilddaten für eine GPU-Kopie.'
+concepts.resources.textureUsages.title: Texturnutzungen
+concepts.resources.title: Ressourcenverwaltung
+concepts.resources.transfers.context: 'Ein Download kopiert GPU-Daten zunächst in einen internen Rücklesespeicher. Wenn die Bibliothek den abgeschlossenen Befehlspuffer zur Wiederverwendung zurücknimmt, kopiert sie die Daten an das bereitgestellte CPU-Ziel. Ein Aufruf von {wait} auf dem Abschlusswert der Download-Einreichung beendet beide Schritte. Bis zur Rückkehr muss der Zielspeicher erhalten und verwalteter Speicher fixiert bleiben.'
+concepts.resources.transfers.description: 'Uploads kopieren CPU-Daten in eine GPU-Ressource, Downloads kopieren Ressourcendaten in CPU-Speicher. Die Methoden auf Puffern und Texturen schließen diesen Transfer vor ihrer Rückkehr ab:'
+concepts.resources.transfers.details: 'Stellen Sie vor diesen Aufrufen die erforderlichen Abhängigkeiten zu Zugriffen auf anderen Warteschlangen her. Eine Warteoperation innerhalb der Methode deckt nur ihren eigenen Transfer ab; sie stellt keine Abhängigkeit zu einer anderen Warteschlange her. Bei direktem Zugriff auf gemappten Speicher muss der Aufrufer selbst auf GPU-Zugriffe warten, die mit dem Transfer in Konflikt stehen.'
+concepts.resources.transfers.guidance: 'Mit {commandBufferUpload} oder {commandBufferDownload} lassen sich Transfers zusammen mit anderer GPU-Arbeit aufzeichnen und einreichen. Für den Quellspeicher eines Uploads und den Zielspeicher eines Downloads gelten unterschiedliche Anforderungen an die Lebensdauer:'
+concepts.resources.transfers.item: 'Während des Upload-Aufrufs kopiert die Bibliothek die Quelldaten in einen temporären Upload-Speicher, den Staging-Speicher. Die GPU liest später aus diesem Zwischenspeicher. Der Quellzeiger der Anwendung wird deshalb nur bis zur Rückkehr des Aufrufs benötigt.'
+concepts.resources.transfers.notes: 'Bei Texturtransfers bezeichnet der Zeilen-Stride den Byte-Abstand zwischen aufeinanderfolgenden Zeilen der CPU-Daten, der Schicht-Stride den Abstand zwischen Tiefenschichten. Mithilfe dieser Werte kopiert der Transfer Daten zwischen CPU-Speicher und GPU-Textur. Warum GPU-Abschluss und Bereitstellung am Download-Ziel getrennte Schritte sind, erläutert {link}.'
+concepts.resources.transfers.notes.link: Synchronisation
+concepts.resources.transfers.table.bufferDownload.transfer: 'Kopiert bei {cpuReadOnly} direkt aus dem gemappten Speicher. Andernfalls reicht die Methode den Download bei der Transferwarteschlange ein und wartet auf dessen Abschluss.'
+concepts.resources.transfers.table.bufferUpload.transfer: 'Kopiert bei {cpuWriteOnly} direkt in den gemappten Speicher. Andernfalls reicht die Methode den Upload bei der Transferwarteschlange ein und wartet auf dessen Abschluss.'
+concepts.resources.transfers.table.headings.method: Methode
+concepts.resources.transfers.table.headings.transfer: 'Übertragungsverhalten'
+concepts.resources.transfers.table.textureTransfers.method: '{textureUpload} / {textureDownload}'
+concepts.resources.transfers.table.textureTransfers.transfer: 'Reicht die Kopie und ihre Texturübergänge bei der Grafikwarteschlange ein und wartet auf den Abschluss. Die ausgewählte Unterressource bleibt im angeforderten abschließenden Layout.'
+concepts.resources.transfers.title: 'Sofortige und aufgezeichnete Übertragungen'
+concepts.resources.usages.description: '{bufferUsages} und {textureUsages} sind Flag-Enums für die vorgesehenen Ressourcenrollen. Sie werden {bufferDescUsages} beziehungsweise {textureDescUsages} zugewiesen. Für mehrere Rollen lassen sich die benötigten Member mit {value} kombinieren.'
+concepts.resources.usages.title: 'Ressourcennutzungen'
+concepts.resources.views.description: 'Eine {bufferView} beschreibt einen Pufferbereich und die Interpretation seiner Daten. Eine {textureView} wählt einen Unterressourcenbereich sowie einen kompatiblen Texturtyp und ein passendes Format. Beide beziehen sich auf vorhandenen Speicher mit den bei seiner Erstellung angegebenen Nutzungen. Ansichtsbereiche beschreiben den Zugriff, gewährleisten aber keine einheitliche shaderseitige Bereichsprüfung über alle Backends hinweg.'
+concepts.resources.views.details: 'Der eigene Handle einer Ressource stellt ihre Standardansicht bereit. Erstellen Sie eine separate Ansicht, wenn ein anderer Bereich oder eine andere kompatible Interpretation benötigt wird. Ansicht und zugrunde liegende Ressource müssen bis zum Ende ihrer GPU-Nutzung erhalten bleiben; ein Ressourcenhandle hält keines dieser Objekte am Leben. Wird eines ersetzt, aktualisieren Sie alle Konstanten mit dem bisherigen {link}.'
+concepts.resources.views.details.link: 'Ressourcenhandle'
+concepts.resources.views.title: 'Ansichten und zugrunde liegende Ressourcen'
+concepts.shaders.binding.context: 'Im Compute-Beispiel erhält {input} den Wert {inputTextureSampledHandle} und {output} den Wert {outputTextureStorageHandle}. In Slang bestimmt der Typparameter von {descriptorHandle} den Ressourcenzugriff:'
+concepts.shaders.binding.description: '{setConstantBuffer} wählt einen vorhandenen Puffer und den Byte-Offset, ab dem die aktuelle Pipeline ihre Konstanten liest. Laden Sie die Parameterdaten in diesen Speicher, bevor der Shader darauf zugreift. Liest frühere GPU-Arbeit noch dieselben Bytes, muss sie vor der Aktualisierung abgeschlossen sein.'
+concepts.shaders.binding.details: 'Der Befehlspuffer, die Compute-Pipeline, der mit Daten gefüllte Konstantenpuffer und die Anzahl der Threadgruppen müssen bereits vorliegen. Zeichnen Sie den Dispatch dann in dieser Reihenfolge auf. Wählen Sie zuerst die Pipeline, da die Konstantenpufferbindung für diese Pipeline gilt:'
+concepts.shaders.binding.guidance: 'Hier zeichnet {commandBuffer} einen Dispatch mit {computePipeline} und den Parametern aus {constantBuffer} auf. Der Puffer benötigt die Nutzung {bufferUsagesConstant}; Offset 0 wählt seinen Anfang. Teilen sich mehrere Konstantendatensätze einen Puffer, muss jeder Anfangsoffset zusätzlich die Bindungsausrichtung des Backends erfüllen. Diese Anforderung gilt unabhängig von der Feldanordnung innerhalb des Datensatzes.'
+concepts.shaders.binding.notes: '{resourceHandle} enthält zwei 32-Bit-Felder, deren Interpretation vom Backend abhängt. Die Werte gelten nur innerhalb des Kontexts, in dem die Ressource erstellt wurde.'
+concepts.shaders.binding.reference: 'Ressource und zugehörige Ansicht müssen bis zum Abschluss des letzten Shader-Zugriffs erhalten bleiben. Ihr Ersatz erfordert eine Aktualisierung der Konstanten mit alten Handles. Die Besitzregeln erläutert {link}.'
+concepts.shaders.binding.reference.link: Ressourcenverwaltung
+concepts.shaders.binding.table.bufferStorageReadOnlyHandle.access: 'Lesen strukturierter Elemente des im Shader verwendeten Datentyps {t}'
+concepts.shaders.binding.table.bufferStorageReadWriteHandle.access: 'Lesen und Schreiben strukturierter Elemente des im Shader verwendeten Datentyps {t}'
+concepts.shaders.binding.table.headings.access: Beabsichtigter Zugriff
+concepts.shaders.binding.table.headings.cHandle: 'C#-Handle'
+concepts.shaders.binding.table.headings.slangFieldType: Slang-Feldtyp
+concepts.shaders.binding.table.samplerHandle.access: 'Zustand für die Texturabtastung'
+concepts.shaders.binding.table.textureSampledHandle.access: 'Lesen von Texturdaten'
+concepts.shaders.binding.table.textureStorageHandle.access: 'Lese- und Schreibzugriff auf Speichertexturen'
+concepts.shaders.binding.title: 'Konstantenpuffer und Ressourcenhandles'
+concepts.shaders.compilation.description: '{zenithCompiler} kompiliert einen benannten Slang-Einstiegspunkt zu einer {shaderDesc}. Die Eigenschaft {graphicsApi} bestimmt das Ziel: DXIL für DirectX 12, eine Metal-Bibliothek für Metal oder SPIR-V für Vulkan.'
+concepts.shaders.compilation.details: 'Mit einem bereits erstellten Grafikkontext namens {context} wird der Dreieck-Shader aus der Kopie neben der ausführbaren Datei kompiliert:'
+concepts.shaders.compilation.guidance: 'Der Einstiegspunktname {vSMain} muss mit der Deklaration in der Shader-Datei übereinstimmen. Kompilieren Sie Shader und erstellen Sie Pipelines während der Initialisierung; beim Rendering werden die Pipelines wiederverwendet. Nach Abschluss der Pipeline-Erstellung dürfen die Shader-Objekte freigegeben werden, wie im Tutorial {link} gezeigt.'
+concepts.shaders.compilation.guidance.link: Erstes Dreieck
+concepts.shaders.compilation.title: 'Shader-Kompilierung'
+concepts.shaders.description: 'C#- und Slang-Deklarationen müssen darin übereinstimmen, wo Werte gespeichert werden und wie der Shader darauf zugreift. Vertex-Eingabelayouts beschreiben Vertexattribute, Shader-Pufferdeklarationen beschreiben Parameter und Arrays, und Ressourcenhandles identifizieren die vom Shader verwendeten Ressourcen.'
+concepts.shaders.dispatch.description: '{dispatch} gibt die Anzahl der auszuführenden Threadgruppen an. Das Compute-Beispiel deklariert {numthreads}, sodass jede Gruppe 16 × 16 Threads enthält. Teilen Sie Breite und Höhe des Bildes jeweils durch 16 und runden Sie auf, um es vollständig abzudecken: Ein Bild mit 17 × 19 Pixeln benötigt 2 × 2 Gruppen. Der Shader prüft die Koordinaten jedes Threads und überspringt Positionen außerhalb des Bildes.'
+concepts.shaders.dispatch.details: 'Die deklarierte Gruppengröße ist über {shaderDescThreadGroupSize} verfügbar und kann zur Berechnung der an {dispatch} übergebenen Gruppenanzahl dienen. {computeShaderRendererCs} zeigt die vollständige Berechnung. Für Zugriffe auf das erzeugte Bild gelten anschließend die in {link} beschriebenen Anforderungen.'
+concepts.shaders.dispatch.details.link: Synchronisation
+concepts.shaders.dispatch.title: 'Threadgruppen'
+concepts.shaders.layout.context: 'Die {link} zeigen diese Anordnung anhand von Bildabmessungen und zwei Texturhandles. Explizite Offsets legen den Beginn jedes Feldes fest. Die Deklaration verwendet {systemRuntimeInteropServices} und {zenithNET}:'
+concepts.shaders.layout.context.link: 'C#-Konstanten des Compute-Beispiels'
+concepts.shaders.layout.description: 'Für Vertexeingaben beschreibt {inputLayout} das Format, den Offset und die Semantik jedes Attributs sowie den Stride zwischen Vertices. Beim Dreieck folgt auf eine {float3}-Position eine {float4}-Farbe. Die Farbe beginnt am Byte-Offset 12; jeder Vertex belegt 28 Bytes. {inputLayoutAdd} berechnet diese dicht gepackte Anordnung aus den Elementformaten. Enthält die C#-Struktur Auffüllungen, müssen Offsets und Stride diese berücksichtigen.'
+concepts.shaders.layout.details: 'Konstantenpuffer enthalten Shader-Parameter, strukturierte Puffer Arrays typisierter Elemente. Ihre Anordnung folgt den Slang-Deklarationen und nicht {inputLayout}. Stimmen Sie C#-Feldoffsets, Elementgrößen und Auffüllungen mit dem kompilierten Shader-Layout ab. Ein nicht verwalteter C#-Typ lässt sich als Bytes kopieren, doch seine Speicheranordnung entspricht nicht zwangsläufig dem Shader-Layout. Ein im Vertex dicht gepacktes {float3} kann in einem Shader-Puffer eine andere Ausrichtung benötigen.'
+concepts.shaders.layout.guidance: 'Shader-Parameter können neben gewöhnlichen Werten auch Ressourcenhandles enthalten. Ein Handle bezeichnet bereits vorhandenen Speicher. Wird er in einen Konstantenpuffer kopiert, werden deshalb nicht die Inhalte der Ressource mitkopiert.'
+concepts.shaders.layout.notes: 'Die zugehörige Deklaration in {computeShaderSlang} lautet:'
+concepts.shaders.layout.reference: 'Die beiden Abmessungen belegen die ersten acht Bytes; darauf folgen zwei Handles mit je acht Bytes. Das C#-Beispiel reserviert insgesamt 256 Bytes für den Datensatz. Die Größe anderer Datensätze ist anhand ihrer eigenen Felder sowie der benötigten Anordnung und Ausrichtung zu bestimmen.'
+concepts.shaders.layout.title: 'Datenlayout'
+concepts.shaders.matrices.description: 'Der Compiler wählt zeilenweise Matrixspeicherung mit zusammenhängenden Zeilen. Der {link} speichert drei {matrix4x4}-Werte an den Byte-Offsets 0, 64 und 128. Sein {detail} führt {mulVectorMatrix} in Modell-, View- und Projektionsreihenfolge aus.'
+concepts.shaders.matrices.description.detail: Shader
+concepts.shaders.matrices.description.link: Renderer des rotierenden Würfels
+concepts.shaders.matrices.details: 'Die Speicherreihenfolge bestimmt die Anordnung der Matrixelemente; die Multiplikationsreihenfolge bestimmt die Transformation. C# und Slang müssen aufeinander abgestimmte Konventionen verwenden.'
+concepts.shaders.matrices.title: 'Matrixkonventionen'
+concepts.shaders.title: Shader-Daten und Bindung
+concepts.synchronization.backends.description: 'DirectX 12 und Vulkan setzen Texturübergänge als Layout- und Zugriffsänderungen um. Für Metal führt die Implementierung von {transition} in Zenith.NET keine entsprechende Operation aus. Das Metal-Backend fasst Befehle stattdessen in Render- und Compute-Encodern zusammen und fügt beim Ende eines Encoders Sichtbarkeitsbarrieren ein. Abhängige Operationen innerhalb desselben Encoders, etwa aufeinanderfolgende Compute-Dispatches, verwenden explizite Aufrufe von {barrier}.'
+concepts.synchronization.backends.details: 'Im {link} beendet der Beginn des Grafik-Renderpasses den Compute-Encoder und macht dessen Ausgabe für die Grafikbefehle sichtbar. Zwei aufeinanderfolgende Compute-Dispatches bleiben dagegen im selben Encoder. Ihre Abhängigkeit benötigt deshalb eine Barriere, auch wenn dazwischen ein Texturübergang aufgezeichnet wird. Portable Befehlsfolgen berücksichtigen daher sowohl Zugriffsabhängigkeiten als auch Texturlayouts.'
+concepts.synchronization.backends.details.link: Compute-Beispiel
+concepts.synchronization.backends.title: 'Backend-Verhalten'
+concepts.synchronization.barriers.context: 'Wählen Sie Stufen, die die relevanten Zugriffe umfassen und von der Warteschlange unterstützt werden. {barrierStagesAll} schließt alle unterstützten Stufen ein. Beschränken Sie die Auswahl, wenn die Abhängigkeit nur einen Teil davon betrifft.'
+concepts.synchronization.barriers.description: 'Angenommen, zwei Compute-Dispatches stehen im selben Befehlspuffer: Der erste schreibt Simulationsdaten, der zweite liest sie. Eine Barriere dazwischen stellt sicher, dass der zweite Dispatch die abgeschlossenen Schreibvorgänge sieht. Bei einem Speicherpuffer genügt es, die Zugriffe zu ordnen; ein Texturlayoutübergang ist nicht erforderlich:'
+concepts.synchronization.barriers.details: 'Das erste Argument benennt die Stufen, die die Daten erzeugt haben; das zweite die Stufen, die sie verwenden werden. Gemeinsam beschreiben sie die benötigte Ausführungsreihenfolge und Speichersichtbarkeit zwischen den beiden Dispatches.'
+concepts.synchronization.barriers.guidance: 'Die {link} verwendet dieses Muster zwischen Simulationsschritten. Jede Barriere steht nach dem Dispatch, der die Daten erzeugt, und vor dem Dispatch, der sie verwendet.'
+concepts.synchronization.barriers.guidance.link: FluidTank-Simulation
+concepts.synchronization.barriers.title: 'Pipeline-Barrieren'
+concepts.synchronization.dependencies.description: 'Ermitteln Sie für jede Ressource, welche Zugriffe dieselben Daten betreffen. Sobald einer davon schreibt, muss die benötigte Reihenfolge erhalten bleiben. Zwei Lesezugriffe stehen nicht im Konflikt; Anforderungen an Texturlayout und Präsentation gelten dennoch. Auch CPU-Aktualisierungen und Ressourcenfreigaben müssen warten, bis die GPU den betroffenen Speicher nicht mehr verwendet.'
+concepts.synchronization.dependencies.details: 'Der Mechanismus richtet sich nach dem Ausführungsort der Operationen:'
+concepts.synchronization.dependencies.guidance: 'Eine Aufgabe kann mehrere Mechanismen benötigen. Stammt eine Textur etwa aus einer anderen Warteschlange, sind sowohl eine Einreichungsabhängigkeit als auch das passende Layout für den nächsten Zugriff erforderlich. Die CPU muss dafür nur blockiert werden, wenn sie selbst auf die Daten zugreifen oder deren Speicher freigeben soll.'
+concepts.synchronization.dependencies.table.cpuAccess.mechanism: '{wait} auf dem relevanten Abschlusswert aufrufen.'
+concepts.synchronization.dependencies.table.cpuAccess.situation: 'CPU-Zugriff oder Ressourcenfreigabe erfordert den Abschluss der GPU-Arbeit'
+concepts.synchronization.dependencies.table.crossQueue.mechanism: 'Den {timelineValue} der erzeugenden Einreichung an {submit} der weiterverarbeitenden Einreichung übergeben.'
+concepts.synchronization.dependencies.table.crossQueue.situation: 'Eine Warteschlange verwendet Daten aus einer anderen Warteschlange'
+concepts.synchronization.dependencies.table.headings.mechanism: Mechanismus
+concepts.synchronization.dependencies.table.headings.situation: Situation
+concepts.synchronization.dependencies.table.layoutChange.mechanism: '{commandBufferTransition} mit vorherigem und folgendem Layout.'
+concepts.synchronization.dependencies.table.layoutChange.situation: Eine Textur-Unterressource ändert ihre Verwendungsart
+concepts.synchronization.dependencies.table.pipelineStages.mechanism: '{commandBufferBarrier} mit den Erzeuger- und Verbraucherstufen.'
+concepts.synchronization.dependencies.table.pipelineStages.situation: Abhängige GPU-Stufen in einem Befehlsstrom
+concepts.synchronization.dependencies.title: 'Zugriffsabhängigkeiten'
+concepts.synchronization.description: 'Synchronisation legt die Reihenfolge von Zugriffen auf gemeinsam verwendete Daten fest und stellt sicher, dass spätere Operationen die benötigten Ergebnisse sehen. Bei einer Schreib-Lese-Abhängigkeit ist die schreibende Operation der Erzeuger und die lesende der Verbraucher. Barrieren und Texturübergänge koordinieren GPU-Zugriffe; Timelines verbinden Einreichungen und ermöglichen es der CPU, auf GPU-Arbeit zu warten.'
+concepts.synchronization.layouts.context: 'Verwenden Sie {undefined} als vorheriges Layout, wenn der bestehende Inhalt verworfen werden darf, etwa bei einem neuen Bild oder einem vollständig neu gefüllten Attachment. Soll der Inhalt geladen, abgetastet oder zur Akkumulation weiterverwendet werden, ist das Layout des vorherigen Zugriffs anzugeben.'
+concepts.synchronization.layouts.description: 'Die Nutzungsflags einer Textur geben ihre erlaubten Rollen an. {textureLayout} beschreibt den Zugriff auf eine bestimmte Unterressource zu einem bestimmten Zeitpunkt der Befehlsfolge. Diese Unterressource wird durch Mip-Level und Array-Layer bestimmt. Ändert sich der Zugriff, zeichnen Sie einen Übergang auf. Das Compute-Beispiel schreibt etwa in eine Ausgabetextur mit dem Layout {storage} und bereitet sie anschließend für die Abtastung in einem Grafikpass vor:'
+concepts.synchronization.layouts.details: '{outputTexture} wurde mit {sampledStorage} erstellt; die gewählte Unterressource liegt in {storage}. {default} bezeichnet Mip-Level 0 und Array-Layer 0. Andere Level und Layer bleiben unverändert.'
+concepts.synchronization.layouts.guidance: 'Geben Sie bei {transition} das Layout der vorherigen Unterressourcennutzung an. Die Anwendung verfolgt diesen Zustand beim Aufzeichnen der Befehle und bei der Übergabe von Texturen zwischen Rendering-Stufen.'
+concepts.synchronization.layouts.title: 'Texturlayouts'
+concepts.synchronization.readback.description: '{isCompleted} meldet den GPU-Abschluss. Bei einem aufgezeichneten Download steht danach noch ein Schritt aus: die Kopie aus dem internen Rücklesespeicher in den Zielspeicher der Anwendung.'
+concepts.synchronization.readback.details: '{commandBufferDownload} zeichnet die GPU-Kopie in diesen internen Speicher auf. Nach Abschluss der Einreichung führt die Bibliothek die CPU-Kopie aus, während sie den fertigen Befehlspuffer zurücknimmt. {wait} auf dem Abschlusswert der Download-Einreichung wartet auf die GPU und verarbeitet die abgeschlossenen Puffer dieser Warteschlange. Dadurch werden beide Schritte abgeschlossen. Bis zur Rückkehr muss der Zielspeicher erhalten und verwalteter Speicher fixiert bleiben.'
+concepts.synchronization.readback.guidance: 'Allein das Abfragen von {isCompleted} führt die abschließende CPU-Kopie nicht aus. Auch das Warten auf eine andere Warteschlange nimmt die abgeschlossenen Puffer der Download-Warteschlange nicht zurück. Wann Uploads und Downloads direkt auf Speicher zugreifen oder einen Zwischenspeicher nutzen, beschreibt {link}.'
+concepts.synchronization.readback.guidance.link: Ressourcenverwaltung
+concepts.synchronization.readback.title: 'Abschluss des Rücklesens'
+concepts.synchronization.reuse.description: 'Das Dreieck wartet auf den Abschluss jedes Frames, bevor es dessen Daten wiederverwendet. Sollen sich Frames in der Ausführung überlappen, benötigt jeder noch laufende Frame eigenen veränderlichen Speicher. Verfolgen Sie, wann dieser Speicher wieder verfügbar ist. Vor Datenänderungen müssen noch laufende GPU-Zugriffe auf dieselben Bytes, vor einer Ressourcenfreigabe alle ausstehenden Nutzungen der Ressource abgeschlossen sein.'
+concepts.synchronization.reuse.details: 'Für Größenänderungen und das Beenden gilt dieselbe Reihenfolge: neue Nutzungen stoppen, bestehende abwarten und erst dann Ressourcen ersetzen oder freigeben. Halten Sie dies vor {swapChainResize} sowie vor der Freigabe von Ressourcen oder Kontext ein. Da das Dreieck jeden Frame abwartet, kann es die Größe zwischen den Callbacks ändern.'
+concepts.synchronization.reuse.guidance: '{swapChainPresent} setzt nach der Präsentation einen Abschlusspunkt in der Grafikwarteschlange und wartet darauf. Das Dreieck wartet daher auch dann bei der Präsentation, wenn die ausdrückliche Warteoperation nach der Einreichung entfällt. Unabhängig eingereichte Arbeit anderer Warteschlangen benötigt eine eigene Abschlussverfolgung.'
+concepts.synchronization.reuse.title: 'Ressourcenwiederverwendung'
+concepts.synchronization.timelines.context: 'Soll die CPU nach der Weiterverarbeitung das Ergebnis lesen, gemeinsame Daten überschreiben oder Ressourcen freigeben, wartet sie auf diese Einreichung:'
+concepts.synchronization.timelines.description: 'Eine Timeline kennzeichnet den Warteschlangenfortschritt durch steigende Werte. {submit} liefert die Timeline und den Einreichungswert gemeinsam als {timelineValue}. Die numerischen {value}-Felder verschiedener Timelines sind nicht unmittelbar vergleichbar.'
+concepts.synchronization.timelines.details: '{producer} und {consumer} seien aufgezeichnete Befehlspuffer zweier Warteschlangen desselben Kontexts; der Verbraucher nutzt die Daten des Erzeugers. Ihre Einreichung erhält diese Abhängigkeit:'
+concepts.synchronization.timelines.guidance: 'Die weiterverarbeitende Warteschlange wartet vor der Ausführung ihrer Befehle auf den übergebenen Abschlusswert. Die CPU kann ohne {producedWait} weiterarbeiten. Erhalten Sie die gemeinsam verwendeten Ressourcen bis zum Abschluss von {consumed} und zeichnen Sie die für den nachfolgenden Zugriff erforderlichen Texturübergänge auf.'
+concepts.synchronization.timelines.notes: 'Der Rückgabewert von {queueTimelineSignal} kann ebenso mit {wait} abgewartet oder als Abhängigkeit an eine andere Warteschlange übergeben werden. {signal} setzt diesen Abschlusspunkt hinter die zuvor eingereichte Arbeit. Die GPU erreicht ihn, sobald diese Arbeit beendet ist.'
+concepts.synchronization.timelines.title: 'Timelines und Warteschlangenabhängigkeiten'
+concepts.synchronization.title: Synchronisation
+home.actions.reference: API durchsuchen
+home.actions.tutorial: Mit einem Dreieck beginnen
+home.entries.description: 'Tutorials, Beispiele und API-Referenz'
+home.entries.label: 'Bereiche der Dokumentation'
+home.entries.reference.action: API erkunden
+home.entries.reference.category: Referenz
+home.entries.reference.description: 'API-Typen, Member und Deklarationen nachschlagen'
+home.entries.reference.title: API-Referenz
+home.entries.samples.action: Beispiele durchsuchen
+home.entries.samples.category: Beispiele
+home.entries.samples.description: 'Ausführbare Beispiele für Grafik- und Compute-Verfahren'
+home.entries.samples.title: Beispiele
+home.entries.title: 'Dokumentation'
+home.entries.tutorial.action: Tutorial starten
+home.entries.tutorial.category: Tutorial
+home.entries.tutorial.description: 'Vom leeren Projekt zum ersten Dreieck auf dem Bildschirm'
+home.entries.tutorial.title: Erstes Dreieck
+home.introduction.accent: 'auf mehreren Plattformen'
+home.introduction.description: 'Zenith.NET bündelt Grafik und Compute für DirectX, Metal und Vulkan in einer gemeinsamen API'
+home.introduction.title: 'Grafik für .NET{lineBreak}{accent}'
+home.meta.description: 'Plattformübergreifende Grafik und Compute für .NET mit einer gemeinsamen API für DirectX, Metal und Vulkan'
+home.scene.description: 'Interaktive rotierende Geometrie. Ansicht durch Ziehen oder mit den Pfeiltasten verändern.'
+home.scene.label: Geometrieanzeige
+home.scene.surface: Oberfläche
+home.scene.wireframe: Drahtgitter
+home.title: Modernes RHI für .NET
+learning.concepts.description: 'Ressourcennutzungen wählen, Shader-Daten beschreiben und GPU-Ausführung koordinieren'
+learning.concepts.execution.description: 'Aufzeichnung, Einreichung und Ressourcenlebensdauer'
+learning.concepts.platforms.description: 'Native Präsentation und UI-Rendering-Steuerelemente'
+learning.concepts.resources.description: 'Ressourcennutzungen, Speicherplatzierung und Ansichten'
+learning.concepts.shaders.description: 'Layout- und Bindungskonventionen für C# und Slang'
+learning.concepts.synchronization.description: 'Abhängigkeiten, Sichtbarkeit und Abschluss'
+learning.concepts.title: Kernkonzepte
+learning.description: 'Beginnen Sie mit dem Dreieck-Tutorial und erstellen Sie Ihre erste Anwendung. Anschließend erläutern die Konzeptartikel Ressourcenverwaltung, Shader-Daten und Synchronisation. Für einzelne Grafikverfahren stehen ausführbare Beispiele bereit.'
+learning.entries.samples.action: Beispiele durchsuchen
+learning.entries.samples.description: 'C#- und Slang-Implementierungen für Geometrie, Compute, Ray-Queries und Mesh-Shading'
+learning.entries.tutorial.action: Tutorial öffnen
+learning.entries.tutorial.description: 'Eine Konsolenanwendung mit Fenster und gerendertem Dreieck'
+learning.meta.description: 'Tutorials und Konzepte zum Programmiermodell von Zenith.NET'
+learning.title: Lernen
+reference.description: 'Referenz zu Ressourcen, Befehlen und Plattformintegration'
+reference.entries.commands.description: 'Grafik-, Compute- und Transferbefehle'
+reference.entries.commands.label: 'Befehle aufzeichnen'
+reference.entries.context.description: 'GPU-Ressourcenerstellung für das gewählte Backend'
+reference.entries.context.label: Hier beginnen
+reference.meta.description: 'Namespaces, Typen und Member von Zenith.NET'
+reference.navigation.core: 'Referenz der Kern-API'
+reference.title: API-Referenz
+samples.computeShader.description: 'Das Beispiel lädt {assetsTexturesShokoPng}, wandelt das Bild mit einem Compute-Dispatch in Graustufen um und zeigt die Ausgabetextur an. Die Umwandlung erfolgt einmal; spätere Frames zeigen dasselbe Ergebnis. Der Renderer zeigt die Bindung abtastbarer und beschreibbarer Texturen, das Aufrunden der Threadgruppenanzahl zur vollständigen Bildabdeckung und die Bereichsprüfung im Shader.'
+samples.computeShader.details: '{computeShaderRendererCs} · {computeShaderSlang}'
+samples.computeShader.title: Compute-Shader
+samples.cube.description: 'Der Würfel ergänzt indexiertes Zeichnen, Modell-, View- und Projektionsmatrizen in einem Konstantenpuffer sowie Tiefentests. Seine Tiefen-/Stencil-Textur im Format {d32FloatS8UInt} wird bei Größenänderungen neu erstellt.'
+samples.cube.details: '{spinningCubeRendererCs} · {spinningCubeSlang}'
+samples.cube.title: Rotierender Würfel
+samples.description: 'Jedes Beispiel verbindet einen C#-Renderer mit seinem Slang-Shader und behandelt ein bestimmtes Verfahren. Den vollständigen Anwendungsaufbau und die Rendersequenz erläutert {link}.'
+samples.description.link: Erstes Dreieck
+samples.execution.description: 'Starten Sie das Projekt {link} und wählen Sie ein Beispiel im Konsolenmenü.'
+samples.execution.description.link: ZenithTutorials
+samples.execution.title: 'Beispiele ausführen'
+samples.host.description: '{appCs} initialisiert Kontext, Fenster und Swapchain. Pro Frame bezieht der Host einen Befehlspuffer, ruft den Renderer auf, reicht die Befehle ein und präsentiert das Ergebnis. {iRendererCs} definiert Aktualisierung, Rendering, Größenanpassung und Freigabe.'
+samples.host.details: 'Die Compute- und Raytracing-Renderer erzeugen Offscreen-Texturen. {texturePresenterCs} und {presentTextureSlang} zeichnen diese in die aktuelle Ausgabetextur. Anschließend reicht der gemeinsame Host die Befehle ein und präsentiert den Frame.'
+samples.host.title: 'Host und Präsentation'
+samples.indirectDrawing.description: 'Indirektes Zeichnen stellt mehrere Würfelinstanzen aus einem Puffer für indexierte Zeichenargumente und einem strukturierten Puffer mit Instanzdaten dar. Die CPU liefert die Argumente und aktualisiert die Transformationen.'
+samples.indirectDrawing.details: '{indirectDrawingRendererCs} · {indirectDrawingSlang}'
+samples.indirectDrawing.title: Indirektes Zeichnen
+samples.meshShading.description: 'Ein Task-Shader sortiert nicht sichtbare Kugelinstanzen aus und übergibt die verbleibenden Indizes an den Mesh-Shader, der die Geometrie erzeugt. Vor der Pipeline-Erstellung prüft der Renderer {contextCapabilitiesMeshShadingSupported}.'
+samples.meshShading.details: '{meshShadingRendererCs} · {meshShadingSlang}'
+samples.meshShading.title: Mesh-Shading
+samples.rayTracing.description: 'Das Beispiel erstellt Beschleunigungsstrukturen der unteren und oberen Ebene für einen Boden und prozedurale Kugeln und führt Inline-Rayqueries in einem Compute-Shader aus. Vor der Initialisierung prüft es {contextCapabilitiesRayTracingSupported}.'
+samples.rayTracing.details: '{rayTracingRendererCs} · {rayTracingSlang}'
+samples.rayTracing.title: Raytracing
+samples.title: Beispiele
+samples.triangle.description: 'Das Dreieck verbindet einen Vertexpuffer mit dem passenden Eingabelayout und zeichnet drei Vertices durch eine Grafikpipeline.'
+samples.triangle.details: '{helloTriangleRendererCs} · {helloTriangleSlang} · {link}'
+samples.triangle.details.link: Vollständiges Tutorial
+samples.triangle.title: Hallo Dreieck
+samples.workloads.description: 'Das {link} im Haupt-Repository enthält größere Anwendungen, die den Bibliotheksquellcode referenzieren.'
+samples.workloads.description.link: Verzeichnis Experiments
+samples.workloads.details: 'Die Fensterexperimente nutzen dieselben Präsentationspfade für Windows, macOS und Linux/X11. UI-Steuerelemente für Avalonia, WinForms, WPF, WinUI/Uno und MAUI behandelt {link}, einschließlich Frame-Ablauf und Implementierungsquellen.'
+samples.workloads.details.link: Plattformintegration
+samples.workloads.table.cornellBox.example: '{link}'
+samples.workloads.table.cornellBox.example.link: CornellBox
+samples.workloads.table.cornellBox.whatItDemonstratesAndRequires: 'Pathtracing, Rauschunterdrückung, Tone-Mapping, Hochskalierung und ImGui-Steuerelemente. Erfordert Raytracing-Unterstützung.'
+samples.workloads.table.fluidTank.example: '{link}'
+samples.workloads.table.fluidTank.example.link: FluidTank
+samples.workloads.table.fluidTank.whatItDemonstratesAndRequires: 'Compute-Fluidsimulation, Abhängigkeiten zwischen Simulationsstufen und mehrpassiges Oberflächen-Rendering.'
+samples.workloads.table.headings.example: Beispiel
+samples.workloads.table.headings.whatItDemonstratesAndRequires: 'Verfahren und Voraussetzungen'
+samples.workloads.table.inkCanvas.example: '{link}'
+samples.workloads.table.inkCanvas.example.link: InkCanvas
+samples.workloads.table.inkCanvas.whatItDemonstratesAndRequires: Skia-Zeichnen und Eingabeverarbeitung in einem nativen Fenster. Erfordert das Backend und die native Skia-Bibliothek für die Zielplattform.
+samples.workloads.table.platformDetection.example: '{link}'
+samples.workloads.table.platformDetection.example.link: PlatformDetection
+samples.workloads.table.platformDetection.whatItDemonstratesAndRequires: 'Versucht für jedes Backend einen Kontext zu erstellen und meldet die Gerätefunktionen. Ein fehlgeschlagener Versuch wird als nicht unterstützt gemeldet; Ursache können das Gerät, der Treiber oder eine fehlende Laufzeitabhängigkeit sein.'
+samples.workloads.title: 'Umfangreichere Anwendungen'
+tutorial.context.description: 'Ein {graphicsContext} verbindet die Anwendung über ein Backend mit der GPU und stellt die Methoden zum Erstellen der Rendering-Ressourcen bereit.'
+tutorial.context.details: 'Fügen Sie die Kontextinitialisierung {emphasis} ein:'
+tutorial.context.details.emphasis: nach den using-Direktiven, vor {iWindowWindow}
+tutorial.context.guidance: 'Jeder Zweig wählt das Backend der aktuellen Plattform und fordert Validierung an. Meldungen von {validationMessage} werden in der Konsole ausgegeben.'
+tutorial.context.imports: 'Ergänzen Sie am Anfang von {programCs} die folgenden using-Direktiven:'
+tutorial.context.title: 'Grafikkontext'
+tutorial.description: 'In diesem Tutorial entsteht aus einem leeren .NET-Konsolenprojekt eine Anwendung, die ein Fenster öffnet und ein farbiges Dreieck zeichnet.'
+tutorial.details: 'Das Tutorial führt über drei überprüfbare Zwischenstände zum Ergebnis: ein leeres Fenster, eine einfarbige Fläche und schließlich das farbige Dreieck. Führen Sie die Anwendung nach jedem dieser Schritte aus, bevor Sie den nächsten Teil ergänzen.'
+tutorial.draw.context: 'Die Argumente für {draw} sind:'
+tutorial.draw.description: 'Ersetzen Sie im Callback {windowRender} den Kommentar {addTheTriangleDrawCommandsHereLater} durch folgende Befehle zwischen {beginRenderPass} und {endRenderPass}:'
+tutorial.draw.details: '{setPipeline} wählt Shader und Renderzustand. Der Aufruf muss vor der Vertexbindung stehen, weil deren Interpretation vom Eingabelayout der Pipeline abhängt.'
+tutorial.draw.guidance: '{setVertexBufferVertexBuffer} bindet die hochgeladenen Vertexdaten ab Byte-Offset 0 an Eingabeslot 0. Dieser entspricht dem einzigen Eintrag in {inputLayouts}.'
+tutorial.draw.notes: 'Diese Aufrufe zeichnen die Zeichenbefehle auf. Das vorhandene {submitWait} reicht sie ein und wartet auf ihren GPU-Abschluss; {present} fordert anschließend die Anzeige des fertigen Bildes an. Behalten Sie das Füllen mit der Hintergrundfarbe am Anfang des Passes bei, damit auch die Fläche außerhalb des Dreiecks einen definierten Inhalt hat.'
+tutorial.draw.table.firstInstance.meaning: Instanznummerierung bei null beginnen.
+tutorial.draw.table.firstVertex.meaning: 'Mit dem ersten Vertex im Puffer beginnen.'
+tutorial.draw.table.headings.argument: Argument
+tutorial.draw.table.headings.description: Wert
+tutorial.draw.table.headings.meaning: Bedeutung
+tutorial.draw.table.instanceCount.meaning: Eine Kopie des Dreiecks zeichnen.
+tutorial.draw.table.vertexCount.meaning: 'Drei Vertices lesen.'
+tutorial.draw.title: 'Zeichenbefehle'
+tutorial.execution.description: 'Erstellen und starten Sie das Projekt {firstTriangle}.'
+tutorial.execution.details: 'Auf dem dunklen Hintergrund erscheint ein Dreieck: oben rot, rechts unten grün und links unten blau. Durch Interpolation gehen die Vertexfarben im Inneren fließend ineinander über.'
+tutorial.execution.guidance: 'Beim Ändern der Fenstergröße ändern sich die Pixelmaße des Bildes, während die Vertexkoordinaten gleich bleiben. Der Viewport bildet sie auf die neue Breite und Höhe ab. Das Dreieck wird deshalb gestreckt, wenn sich das Seitenverhältnis des Fensters ändert.'
+tutorial.execution.title: 'Anwendung ausführen'
+tutorial.frame.commands.context: 'Nach {endRenderPass} bereitet der zweite Übergang die Präsentation vor. {submit} beendet die Aufzeichnung und reicht den Befehlspuffer ein; {wait} blockiert bis zu dessen Abschluss. Danach fordert {present} die Bildanzeige an.'
+tutorial.frame.commands.description: 'Ein Renderpass fasst Zeichenoperationen zusammen, die in dieselben Attachments schreiben. Hier nimmt die Ausgabetextur der Swapchain die Farbausgabe auf. {colorAttachmentClear} legt fest, dass dieses Attachment zu Beginn mit einer RGBA-Farbe gefüllt und sein Inhalt am Ende erhalten wird. {beginRenderPass} zeichnet den Beginn des Passes mit diesen Einstellungen auf.'
+tutorial.frame.commands.details: 'Die Sammlung enthält ein Farb-Attachment. {null} gibt an, dass der Pass kein Tiefen-/Stencil-Attachment verwendet.'
+tutorial.frame.commands.guidance: '{beginRenderPass} setzt Viewport und Scissor auf die gesamte Attachment-Fläche. Der Viewport bildet Renderkoordinaten auf Pixelpositionen ab; der Scissor begrenzt den Bereich, in den geschrieben werden darf.'
+tutorial.frame.commands.title: 'Löschen, Einreichung und Präsentation'
+tutorial.frame.description: 'Füllen Sie das Fenster zunächst mit einer Hintergrundfarbe. Diese Operation heißt Löschen. Sobald die Farbe sichtbar ist, arbeiten das native Präsentationsziel, die Befehlseinreichung und die Präsentation zusammen. Darauf bauen die Vertices und Shader des Dreiecks auf.'
+tutorial.frame.details: 'Silk.NET ruft den Render-Callback auf, sobald ein Frame ansteht. Das Zeitargument wird nicht verwendet und erhält den Namen {value}. Registrieren Sie den Callback {emphasis}:'
+tutorial.frame.details.emphasis: vor {windowRun}
+tutorial.frame.target.description: '{graphicsQueueCommandBuffer} liefert einen aufzeichnungsbereiten Befehlspuffer. Die Aufzeichnung beschreibt GPU-Arbeit; ausgeführt wird sie erst nach der Einreichung.'
+tutorial.frame.target.details: 'Ein Texturlayout beschreibt, welchen Zugriff eine Operation benötigt. Der erste Aufruf von {transition} bereitet die Ausgabetextur als Farb-Attachment vor. {default} wählt Mip-Level 0 und Array-Layer 0, also das Basisbild dieser Textur. Da der Pass das gesamte Bild mit einer Hintergrundfarbe füllt, darf {undefined} den bisherigen Inhalt verwerfen.'
+tutorial.frame.target.guidance: 'Verwenden Sie in jedem Frame die aktuelle Ausgabetextur, da die Präsentation zum nächsten Bild wechseln kann.'
+tutorial.frame.target.title: Ausgabetextur vorbereiten
+tutorial.frame.title: 'Der erste Frame'
+tutorial.geometry.data.description: 'Fügen Sie das Vertexarray {emphasis} ein. Auch die anschließende Ressourceninitialisierung steht in der angegebenen Reihenfolge vor diesem Callback.'
+tutorial.geometry.data.description.emphasis: vor {windowRender}
+tutorial.geometry.data.details: 'Jeder Eintrag erstellt einen {vertex}; die beiden inneren {new}-Ausdrücke liefern {vector3}-Position und {vector4}-Farbe. Die Positionen bilden die obere, rechte untere und linke untere Ecke. Der Vertex-Shader ergänzt {w} als vierte Koordinate. Bei w = 1 sind x und y zugleich normalisierte Gerätekoordinaten: -1 und +1 markieren die Bildgrenzen, 0 die Mitte, unabhängig von Pixelmaßen.'
+tutorial.geometry.data.guidance: 'Jede Farbe hat Rot-, Grün-, Blau- und Alpha-Komponenten; Alpha ist 1. Alle drei Vertices liegen mit z = 0 in derselben Ebene.'
+tutorial.geometry.data.title: 'Vertexpositionen und Farben'
+tutorial.geometry.description: 'Jeder der drei Vertices besitzt eine Position und eine Farbe. Aus den Positionen ergibt sich die Form des Dreiecks; die GPU interpoliert die Farben über dessen Fläche.'
+tutorial.geometry.format.description: 'Ergänzen Sie die Vertexdeklaration {emphasis}:'
+tutorial.geometry.format.description.emphasis: am Ende von {programCs}, nach den Bereinigungsanweisungen
+tutorial.geometry.format.details: '{file} beschränkt den Typ auf {programCs}, und {layoutKindSequential} erhält die Deklarationsreihenfolge seiner Felder. {position} enthält drei 32-Bit-Gleitkommazahlen und beginnt am Byte-Offset 0; {color} enthält vier und beginnt am Byte-Offset 12. Ein Vertex belegt damit 28 Bytes. Das Eingabelayout beschreibt diese Anordnung, damit die GPU die Felder korrekt liest.'
+tutorial.geometry.format.imports: 'Ergänzen Sie am Anfang von {programCs} diese using-Direktiven für die Vektortypen und das Attribut zur Festlegung des Strukturlayouts:'
+tutorial.geometry.format.title: 'Vertexdarstellung'
+tutorial.geometry.title: 'Vertexdaten'
+tutorial.geometry.upload.context: '{fixed} verhindert, dass die Garbage Collection das Array verschiebt, solange dessen Adresse verwendet wird. Bei diesem CPU-beschreibbaren Puffer mappt {upload} den Speicher, kopiert die Daten und hebt das Mapping vor der Rückkehr auf. Das Array muss daher nur während des Aufrufs fixiert bleiben. Laden Sie die Vertices einmal bei der Initialisierung hoch und geben Sie den Puffer erst nach Abschluss des Renderings frei.'
+tutorial.geometry.upload.description: 'Speichern Sie die Vertices in einem {buffer}, damit die GPU sie beim Zeichnen lesen kann. Fügen Sie Allokation und Upload direkt nach dem Array ein:'
+tutorial.geometry.upload.details: '{sizeofVertexVerticesLength} ergibt drei Vertices zu 28 Bytes, insgesamt 84 Bytes. {bufferUsagesVertex} deklariert Vertex-Eingabezugriff, {cpuWriteOnly} ermöglicht die direkte CPU-Initialisierung. Der Stride beschreibt die Elementgröße; das anschließende Eingabelayout bestimmt die Interpretation jedes Attributs.'
+tutorial.geometry.upload.guidance: 'Die Allokation initialisiert den Inhalt nicht. {upload} kopiert das Array ab Ziel-Byte-Offset 0 mit dem Quellzeiger und der Bytezahl der Datenbeschreibung.'
+tutorial.geometry.upload.imports: 'Ergänzen Sie {bufferAlias} bei den using-Direktiven in {programCs}, um den Puffertyp von Zenith.NET von {systemBuffer} zu unterscheiden.'
+tutorial.geometry.upload.notes: 'Setzen Sie {vertexBufferDispose} direkt vor {swapChainDispose} in die Bereinigung am Ende von {programCs}. GPU-only-Speicher und Transfer-Uploads für größere Geometrie erläutert {link}.'
+tutorial.geometry.upload.notes.link: Ressourcenverwaltung
+tutorial.geometry.upload.title: 'Pufferallokation und Upload'
+tutorial.input.description: 'Die GPU muss wissen, wo Attribute wie Position und Farbe gespeichert sind. Das Eingabelayout gibt Format und Byte-Offset jedes Attributs sowie den Stride an, also den Byte-Abstand zum nächsten Vertex. Fügen Sie diesen Code nach der Shader-Erstellung ein:'
+tutorial.input.details: '{inputLayoutAdd} fügt jedes Attribut unmittelbar nach dem vorherigen ein und erhöht den Stride um die Größe des neu hinzugefügten Attributs. Die Offsets ergeben sich aus den angegebenen Formaten; die C#-Struktur wird dabei nicht untersucht. Für diese beiden Felder entsteht folgendes Layout:'
+tutorial.input.guidance: 'Der Standardwert von {semanticIndex} ist 0 und entspricht damit {pOSITION0} und {cOLOR0} im Shader. Position und Farbe stehen gemeinsam in einem Vertexpuffer, weshalb die Pipeline ein Eingabelayout an Slot 0 verwendet. Passen Sie dieses Layout an, wenn sich Feldreihenfolge oder Auffüllung der C#-Struktur ändern.'
+tutorial.input.table.headings.byteOffset: Byte-Offset
+tutorial.input.table.headings.cField: 'C#-Feld'
+tutorial.input.table.headings.format: Format
+tutorial.input.table.headings.shaderInput: Shader-Eingabe
+tutorial.input.table.nextVertex.cField: 'Nächster Vertex'
+tutorial.input.table.nextVertex.format: 'Gesamter Stride: 28 Bytes'
+tutorial.input.table.nextVertex.shaderInput: Nächster Eingabedatensatz
+tutorial.input.table.vertexColor.format: '{float4} (16 Bytes)'
+tutorial.input.table.vertexPosition.format: '{float3} (12 Bytes)'
+tutorial.input.title: 'Vertex-Eingabelayout'
+tutorial.lifecycle.description: 'Jeder Frame folgt dieser Sequenz:'
+tutorial.lifecycle.details: 'Nach dem Schließen des Fensters kehrt {run} zurück, und die Ausführung wird mit der Bereinigung fortgesetzt. Da jeder Frame bereits auf den Abschluss seiner eingereichten Befehle gewartet hat, können die dafür verwendeten Ressourcen nun freigegeben werden. Nach {windowRun} steht die vollständige Bereinigung:'
+tutorial.lifecycle.guidance: 'Geben Sie die Swapchain vor ihrem nativen Fenster und den Kontext nach seinen Ressourcen frei. Eingereichte Befehlspuffer werden von der Warteschlange wiederverwendet; die Ausgabetextur wird von der Swapchain verwaltet. Beide benötigen daher keinen eigenen {dispose}-Aufruf.'
+tutorial.lifecycle.title: 'Frame-Ablauf und Bereinigung'
+tutorial.pipeline.behavior: 'Erstellen Sie nach dem Eingabelayout die Pipeline:'
+tutorial.pipeline.context: '{cullNone} deaktiviert das Face-Culling. Das Dreieck wird dadurch gezeichnet, unabhängig davon, ob seine Vertices im oder gegen den Uhrzeigersinn angeordnet erscheinen.'
+tutorial.pipeline.description: 'Eine {graphicsPipeline} verbindet das Vertexlayout und die beiden Shader mit den Einstellungen für die Zusammenstellung der Primitive und die Ausgabe in Attachments.'
+tutorial.pipeline.details: 'Die Shader verarbeiten Vertices und Fragmente. Die übrigen Pipeline-Einstellungen bestimmen die Primitivzusammenstellung, das Face-Culling, den Tiefentest und das Blending:'
+tutorial.pipeline.guidance: 'Das Farbformat muss dem Renderpass-Attachment entsprechen und wird deshalb aus {swapChainDescFormat} übernommen. {count1} legt ein Sample pro Pixel fest.'
+tutorial.pipeline.item: '{triangleList} setzt jeweils drei aufeinanderfolgende Vertices zu einem Dreieck zusammen.'
+tutorial.pipeline.notes: '{depthNone} deaktiviert Tiefentest und Tiefenschreiben entsprechend dem fehlenden Tiefen-Attachment.'
+tutorial.pipeline.reference: '{opaque} deaktiviert Blending. Die Fragmentfarbe ersetzt damit den Hintergrund innerhalb des Dreiecks.'
+tutorial.pipeline.title: 'Grafikpipeline'
+tutorial.pipeline.usage: 'Shader-Objekte dürfen nach der Pipeline-Erstellung freigegeben werden. Geben Sie die Pipeline dagegen erst nach Abschluss des Renderings frei und ergänzen Sie dafür {pipelineDispose} vor {vertexBufferDispose} in der Bereinigung.'
+tutorial.presentation.title: 'Grafikkontext und Präsentation'
+tutorial.project.context: 'Aktivieren Sie {emphasis} ({allowUnsafeBlocks}) in den Buildeinstellungen. Die nativen Fensteraufrufe und der Vertexupload benötigen unsicheren Code.'
+tutorial.project.context.emphasis: Unsicheren Code zulassen
+tutorial.project.description: 'Erstellen Sie ein C#-Konsolenprojekt namens {firstTriangle} mit .NET 10 als Zielframework.'
+tutorial.project.details: 'Fügen Sie dem Projekt die folgenden NuGet-Pakete hinzu:'
+tutorial.project.guidance: '{zenithNET} liefert die gemeinsame API, die drei Backend-Pakete ihre Plattformimplementierungen. {zenithNETCompiler} übersetzt die unten eingeführten Slang-Shader für das Backend. Silk.NET übernimmt Fenster und Ereignisse.'
+tutorial.project.title: 'Projekteinrichtung'
+tutorial.requirements.context: 'Das {link} und sein {detail} dienen als Referenzimplementierungen für dieses Tutorial.'
+tutorial.requirements.context.detail: gemeinsamer Host
+tutorial.requirements.context.link: Hallo-Dreieck-Beispiel
+tutorial.requirements.description: 'Sie benötigen das {emphasis} und C#-Grundkenntnisse. Die Anwendung verwendet das Zielframework {net10}. GPU und Treiber müssen das für Ihre Plattform gewählte Backend unterstützen:'
+tutorial.requirements.description.emphasis: .NET 10 SDK
+tutorial.requirements.guidance: 'Zenith.NET rendert in ein von der Anwendung bereitgestelltes Präsentationsziel. Silk.NET.Windowing erstellt das Fenster und verarbeitet Ereignisse. Unter macOS dient ein in {cAMetalLayerCs} repräsentierter Metal-Layer als Ziel; der Fenster-Helper verbindet ihn mit dem Fenster.'
+tutorial.requirements.table.headings.graphicsBackend: Grafik-Backend
+tutorial.requirements.table.headings.platform: Plattform
+tutorial.requirements.table.linux.graphicsBackend: Vulkan 1.4
+tutorial.requirements.table.linux.platform: 'Linux (X11 oder XWayland)'
+tutorial.requirements.table.macOS.graphicsBackend: Metal 4
+tutorial.requirements.table.macOS.platform: macOS
+tutorial.requirements.table.windows.graphicsBackend: DirectX 12
+tutorial.requirements.table.windows.platform: Windows
+tutorial.requirements.title: 'Voraussetzungen'
+tutorial.resize.context: 'Erstellen und starten Sie die Anwendung erneut. Auch beim Ändern der Fenstergröße sollte der Hintergrund das gesamte Fenster ausfüllen: Jeder neue Renderpass übernimmt die Abmessungen des angepassten Attachments für Viewport und Scissor. Behalten Sie den Render-Callback bei; in diesem Pass wird später das Dreieck gezeichnet.'
+tutorial.resize.description: 'Die Swapchain muss Änderungen der Framebuffer-Größe übernehmen. Registrieren Sie nach dem Render-Callback und vor {windowRun} einen Callback für {framebufferResize}:'
+tutorial.resize.details: 'Reagieren Sie auf {link}, die in Pixeln angegeben werden, statt auf Änderungen der logischen Fenstergröße. Wird beim Minimieren eine der beiden Framebuffer-Abmessungen null, lässt der Resize-Callback die Swapchain unverändert. Auch der Render-Callback kehrt bei einer Nullgröße sofort zurück. Das Rendering wird erst fortgesetzt, wenn wieder eine nutzbare Größe vorliegt.'
+tutorial.resize.details.link: Framebuffer-Größenänderungen
+tutorial.resize.guidance: 'Der Render-Callback wartet vor {present}, bis die eingereichten Befehle des Frames abgeschlossen sind. Der Resize-Callback kann die Swapchain-Bilder daher ersetzen, ohne noch laufende Arbeit eines früheren Frames zu beeinträchtigen. Der Artikel {link} erläutert den Zusammenhang zwischen GPU-Abschluss und Ressourcenlebensdauer.'
+tutorial.resize.guidance.link: Synchronisation
+tutorial.resize.title: 'Größenänderungen'
+tutorial.shaders.compilation.context: 'Mit {appContextBaseDirectory} wird die kopierte Shader-Datei aus dem Ausgabeverzeichnis der Anwendung geladen. Ihr Speicherort wird deshalb auch dann korrekt ermittelt, wenn die Anwendung aus einem anderen Arbeitsverzeichnis gestartet wird.'
+tutorial.shaders.compilation.description: 'Setzen Sie für {triangleSlang} die Dateieigenschaft {emphasis} auf {detail}. Der Build kopiert die Shader-Datei damit neben die ausführbare Datei.'
+tutorial.shaders.compilation.description.detail: Kopieren, wenn neuer
+tutorial.shaders.compilation.description.emphasis: In Ausgabeverzeichnis kopieren
+tutorial.shaders.compilation.details: 'Fügen Sie in {programCs} nach dem Vertexupload und vor dem Render-Callback die Shader-Initialisierung ein:'
+tutorial.shaders.compilation.guidance: '{zenithCompiler} kompiliert den Quelltext für {contextGraphicsApi}; {createShader} erstellt aus der kompilierten Beschreibung ein Shader-Objekt. Die Einstiegspunktnamen {vSMain} und {fSMain} wählen die beiden Funktionen aus der Slang-Datei aus.'
+tutorial.shaders.compilation.title: 'Shader kompilieren'
+tutorial.shaders.context: '{fSInput} verbindet die beiden Shader-Stufen. Der Vertex-Shader schreibt eine Position im Clipraum nach {sVPOSITION}; daraus ermittelt die GPU den sichtbaren Teil des Dreiecks. {cOLOR0} enthält die Vertexfarbe, die vor der Übergabe an den Fragment-Shader über das Dreieck interpoliert wird. Typ und Semantik dieses Feldes müssen zwischen den Stufen übereinstimmen.'
+tutorial.shaders.description: 'Shader sind Programme, die auf der GPU laufen. Der Vertex-Shader bestimmt die Position jedes Vertex, der Fragment-Shader die Farbe an den vom Dreieck bedeckten Bildpositionen. Beide Stufen stehen in einer Slang-Datei und haben jeweils einen eigenen Einstiegspunkt, also eine Funktion, mit der die Ausführung beginnt.'
+tutorial.shaders.details: 'Erstellen Sie {triangleSlang} mit diesen Eingabe- und Ausgabestrukturen:'
+tutorial.shaders.fragment.description: 'Ergänzen Sie den Fragment-Einstiegspunkt in derselben Datei:'
+tutorial.shaders.fragment.details: 'Die Rasterisierung ermittelt die vom Dreieck bedeckten Bildpositionen und erzeugt dort Fragmente. Jedes Fragment erhält eine aus den drei Vertexfarben interpolierte Farbe. {fSMain} gibt diese Farbe zurück; {sVTARGET} leitet das Ergebnis an das erste Farb-Attachment weiter.'
+tutorial.shaders.fragment.title: 'Fragment-Shading'
+tutorial.shaders.guidance: '{vSInput} definiert die Vertex-Shader-Eingaben. {pOSITION0} und {cOLOR0} identifizieren Attribute über ihre Semantik; C#-Feldnamen stellen diese Zuordnung nicht her.'
+tutorial.shaders.title: 'Shader-Stufen'
+tutorial.shaders.vertex.description: 'Ergänzen Sie den Vertex-Einstiegspunkt in {triangleSlang}:'
+tutorial.shaders.vertex.details: 'Die GPU ruft den Einstiegspunkt für jeden Vertex auf, ergänzt die homogene Koordinate {w} und übergibt die Farbe. Nach der Vertexverarbeitung wird die Position durch w geteilt. Hier ist w = 1; x, y und z bleiben unverändert und entsprechen normalisierten Gerätekoordinaten.'
+tutorial.shaders.vertex.title: 'Vertex-Shading'
+tutorial.surface.description: 'Definieren Sie das native Präsentationsziel {emphasis}:'
+tutorial.surface.description.emphasis: nach {windowCenter} und vor {windowRun}
+tutorial.surface.details: 'Der Framebuffer ist das Fensterbild in Pixeln. {surface} beschreibt das native Präsentationsziel und seine Abmessungen; {framebufferSize} liefert die Pixelgröße. Bei aktiver Anzeigeskalierung kann sie von der logischen Fenstergröße abweichen.'
+tutorial.surface.guidance: 'Die Oberflächenbeschreibung liefert die nativen Handles des gewählten Backends: ein Win32-Fenster unter Windows, X11-Display und -Fenster unter Linux oder den angebundenen Metal-Layer unter macOS.'
+tutorial.surface.title: 'Native Oberfläche'
+tutorial.swapChain.context: 'Die Swapchain hängt von Kontext und Fenster ab. Ersetzen Sie {windowDispose} am Ende von {programCs} durch diese Freigabereihenfolge:'
+tutorial.swapChain.description: 'Erstellen Sie unmittelbar nach der Oberflächenbeschreibung die Swapchain:'
+tutorial.swapChain.details: 'Die {swapChain} verwaltet die angezeigten Bilder. {drawable} liefert das aktuelle Bild als {texture}, den Bildressourcentyp der API. Nach dem Rendering fordert {present} die Anzeige an und wechselt zur nächsten Ausgabetextur.'
+tutorial.swapChain.guidance: '{b8G8R8A8UNorm} speichert vier 8-Bit-Kanäle in Blau-, Grün-, Rot- und Alpha-Reihenfolge. {uNorm} bildet die Ganzzahlen von 0 bis 255 auf den Bereich von 0 bis 1 ab. Der Shader gibt die Farbe weiterhin in der logischen RGBA-Reihenfolge aus; das Format bestimmt ihre Speicherung. Die Pipeline übernimmt das Farbformat aus {swapChainDescFormat}, damit es zum Attachment passt.'
+tutorial.swapChain.title: 'Swapchain'
+tutorial.title: Erstes Dreieck
+tutorial.troubleshooting.description: 'Konsolendiagnosen und die drei Kontrollpunkte grenzen den Fehler ein:'
+tutorial.troubleshooting.details: 'Der {link} und der {detail} dienen als Referenz für Vertexdaten, Eingabelayout und Zeichenbefehle. Das Beispiel verwaltet Fenster und Befehlseinreichung in einem gemeinsamen Host; dieses Tutorial belässt den Anwendungsablauf in {programCs}. Das Beispiel {context} ergänzt indexiertes Zeichnen, Transformationen und Tiefentests.'
+tutorial.troubleshooting.details.context: Rotierender Würfel
+tutorial.troubleshooting.details.detail: Slang-Shader
+tutorial.troubleshooting.details.link: Beispiel-Renderer
+tutorial.troubleshooting.table.backend.check: Überprüfen Sie, ob das ausgewählte Backend vom Gerät und dem installierten Grafiktreiber unterstützt wird.
+tutorial.troubleshooting.table.backend.symptom: Backend-Erstellung schlägt fehl
+tutorial.troubleshooting.table.drawing.check: Überprüfen Sie die Upload-Größe, das Eingabelayout, die Pipeline-Auswahl und die Platzierung der Zeichenbefehle innerhalb des Renderpasses.
+tutorial.troubleshooting.table.drawing.symptom: Der Hintergrund erscheint, aber das Dreieck nicht
+tutorial.troubleshooting.table.headings.check: Prüfung
+tutorial.troubleshooting.table.headings.symptom: Symptom
+tutorial.troubleshooting.table.input.check: Überprüfen Sie den Stride von 28 Bytes und die Positions-/Farboffsets von 0 und 12.
+tutorial.troubleshooting.table.input.symptom: 'Vertexpositionen oder -farben sind fehlerhaft'
+tutorial.troubleshooting.table.shaders.check: Überprüfen Sie die kopierte Datei {triangleSlang}, die Schreibweise des Einstiegspunkts und die vom Compiler gemeldete Diagnose.
+tutorial.troubleshooting.table.shaders.symptom: Der Schritt mit der Hintergrundfarbe funktioniert, aber das Laden der Shader schlägt fehl
+tutorial.troubleshooting.table.window.check: Bestätigen Sie, dass das .NET-Projekt kompiliert wird und der reine Fensterschritt in einer Desktop-Sitzung ausgeführt wird. Überprüfen Sie unter Linux, ob X11 oder XWayland verfügbar ist.
+tutorial.troubleshooting.table.window.symptom: Es öffnet sich kein Fenster
+tutorial.troubleshooting.title: 'Fehlerdiagnose'
+tutorial.window.description: 'Laden Sie {cocoaHelperCs} in das Projekt herunter. Die Hilfsklasse bindet den Metal-Layer an das macOS-Fenster.'
+tutorial.window.details: 'Ersetzen Sie den erzeugten Inhalt von {programCs} durch den folgenden Code. Er verwendet Top-Level-Anweisungen, die ohne explizite {main}-Methode der Reihe nach laufen. Die folgenden Abschnitte geben jeweils die Stelle für zusätzlichen Code an:'
+tutorial.window.guidance: '{graphicsAPINone} überlässt den Grafikkontext Zenith.NET. {initialize} erstellt das native Fenster und macht die Handles des Fenstersystems verfügbar. {center} platziert das Fenster in der Bildschirmmitte. {run} verarbeitet Ereignisse bis zum Schließen; danach gibt {dispose} das Fenster frei.'
+tutorial.window.notes: 'Erstellen und starten Sie die Anwendung. Prüfen Sie, ob sich das Fenster öffnen, verschieben, in der Größe ändern und schließen lässt. Sein Inhalt ist noch nicht definiert, da keine Renderbefehle eingereicht wurden. Schließen Sie das Fenster, bevor Sie fortfahren.'
+tutorial.window.title: 'Fenstererstellung'
+ui.action.copy: Kopieren
+ui.action.navigate: Navigieren
+ui.action.open: Öffnen
+ui.action.source: Quellcode anzeigen
+ui.api.browse: Namespaces und Typen durchsuchen.
+ui.api.clearTypeSearch: Typsuche löschen
+ui.api.findType: Typ suchen
+ui.api.loadError: Typen konnten nicht geladen werden. Laden Sie diese Seite neu, um es erneut zu versuchen.
+ui.api.loadingNamespaces: Namespaces werden geladen…
+ui.api.loadingTypes: Typen werden geladen…
+ui.api.matchingTypes: Passende Typen
+ui.api.namespace: Namespace
+ui.api.namespaces: Namespaces
+ui.api.noNamespaceTypes: Keine Typen in diesem Namespace.
+ui.api.noTypes: Keine passenden Typen.
+ui.api.types: Typen
+ui.content.reference: Referenz
+ui.content.source: Quellcode
+ui.content.tutorial: Tutorial
+ui.contents.close: Inhaltsverzeichnis schließen
+ui.contents.open: Inhaltsverzeichnis öffnen
+ui.contents.title: Inhalt
+ui.footer.caption: 'Modernes RHI für .NET'
+ui.footer.label: Fußzeile
+ui.language.label: Sprache
+ui.language.unavailable: Nicht übersetzt
+ui.members.clear: Membersuche löschen
+ui.members.close: 'Membersuche schließen'
+ui.members.empty: Keine passenden Member.
+ui.members.open: Zum Member wechseln
+ui.members.placeholder: Name oder Signatur…
+ui.members.search: Member suchen
+ui.members.title: Member suchen
+ui.members.undeclared: Dieser Typ hat keine deklarierten Member.
+ui.navigation.api: API
+ui.navigation.concepts: Konzepte
+ui.navigation.home: Startseite
+ui.navigation.label: Hauptnavigation
+ui.navigation.skip: Zum Inhalt springen
+ui.navigation.toggle: Navigation umschalten
+ui.outline.title: Auf dieser Seite
+ui.reference.assembly: Assembly
+ui.reference.classes: Klassen
+ui.reference.constructors: Konstruktoren
+ui.reference.declaration: 'C#-Typdeklaration'
+ui.reference.delegates: Delegaten
+ui.reference.derived: Abgeleitete Typen
+ui.reference.enums: Enumerationen
+ui.reference.events: Ereignisse
+ui.reference.examples: Beispiele
+ui.reference.exceptions: Ausnahmen
+ui.reference.extensionMethods: Erweiterungsmethoden
+ui.reference.fields: Felder
+ui.reference.implementations: Explizite Schnittstellenimplementierungen
+ui.reference.implements: Implementiert
+ui.reference.inheritance: Vererbung
+ui.reference.inheritedMembers: Geerbte Member
+ui.reference.interfaces: Schnittstellen
+ui.reference.metadata: Typ-Metadaten
+ui.reference.methods: Methoden
+ui.reference.namespace: Namespace
+ui.reference.open: Referenz für {name} öffnen
+ui.reference.operators: Operatoren
+ui.reference.parameters: Parameter
+ui.reference.properties: Eigenschaften
+ui.reference.related: Siehe auch
+ui.reference.relationships: Vererbung und Metadaten
+ui.reference.remarks: Hinweise
+ui.reference.returns: Rückgabe
+ui.reference.structs: Strukturen
+ui.reference.typeParameters: Typparameter
+ui.reference.value: Wert
+ui.reference.values: Werte
+ui.search.close: Suche schließen
+ui.search.empty: Keine Ergebnisse für „{query}“.
+ui.search.hint: Tutorials, Konzepte, Beispiele und die API-Referenz durchsuchen.
+ui.search.loading: Suche wird geladen…
+ui.search.more: Weitere Ergebnisse anzeigen
+ui.search.pending: Suche läuft…
+ui.search.placeholder: Wonach suchen Sie?
+ui.search.resultCount: 'Ergebnisse: {count}'
+ui.search.title: Dokumentation durchsuchen
+ui.search.unavailable: Die Suche ist nicht verfügbar. Sie können weiterhin die folgenden Seiten aufrufen.
diff --git a/documents/locales/de-DE/translation.md b/documents/locales/de-DE/translation.md
new file mode 100644
index 00000000..6896825a
--- /dev/null
+++ b/documents/locales/de-DE/translation.md
@@ -0,0 +1,85 @@
+# Übersetzungsrichtlinien für Deutsch
+
+## Ressourcen und Aufbau
+
+Das [englische Ressourcenwörterbuch](../en-US/strings.yml) ist die einzige Textquelle. Die Projektregeln stehen im [Leitfaden zur Dokumentationspflege](../../maintenance.md). Für eine neue Übersetzung die vollständige englische Datei als `strings.yml` in dieses Verzeichnis kopieren. Die erste Zeile `### YamlMime:Resources` und die UTF-8-Kodierung beibehalten. Spätere Änderungen mit dem englischen Stand abgleichen, ohne vorhandene Übersetzungen zu überschreiben. Diese `translation.md` bleibt dauerhaft erhalten und wird nicht auf der Website veröffentlicht.
+
+Navigation, Kapitelreihenfolge, Anker, Links, Formatierung, Code und Bilder werden außerhalb von `locales/` gemeinsam gepflegt. Hier keine Artikel, TOCs, Vorlagen, Skripte oder Konfigurationen anlegen.
+
+Bei der Übersetzung nur die Werte der eigenen `strings.yml` ändern. Die Schlüsselmenge muss exakt der englischen entsprechen. Keine Schlüssel eigenständig hinzufügen, löschen oder umbenennen, auch nicht bei ähnlich lautenden oder noch nicht gesehenen Oberflächentexten. Fehler im Original mit dem betroffenen Schlüssel an die Projektverantwortlichen melden. Zuerst die englische Quelle und die gemeinsamen Seiten berichtigen, dann die Übersetzungen nachziehen.
+
+## Stil und Kontext
+
+Der Text soll wie eine technische Einführung lesbar sein: präzise, sachlich und zusammenhängend. Zuerst die Beziehung zwischen Vorgängen erklären, dann die Bedingungen und Grenzen. In Tutorials klare Arbeitsschritte und überprüfbare Ergebnisse nennen. Ein wissenschaftlicher Stil verlangt weder Passivkonstruktionen noch unnötig lange Sätze.
+
+Jeden Artikel in seiner Lesereihenfolge bearbeiten, mit angrenzenden Absätzen, Tabellen und Code. Ressourcenschlüssel sind Speichergrenzen, keine isolierten Übersetzungseinheiten. Linktexte und hervorgehobene Fragmente müssen im vollständigen Satz grammatisch passen.
+
+Überschriften, Bedienelemente sowie die sichtbaren Kurztexte der Startseite und die Fußzeilenbeschriftung erhalten keinen Schlusspunkt. Fließtext, Diagnosen und Bedienhinweise für assistive Technik behalten reguläre Satzzeichen. Pro Begriff eine etablierte Bezeichnung verwenden; englische Entsprechungen nicht vereinzelt in Klammern ergänzen. API-Namen, Dateien und Bezeichner tatsächlicher Einstellungen bleiben unverändert.
+
+Ein Satz sollte die aktuelle Handlung, eine Entwurfsentscheidung, ein überprüfbares Ergebnis oder eine notwendige Bedingung erklären. Aufzählungen ungenutzter Funktionen, pauschale Plattformhinweise und wiederholte Voraussetzungen entfallen. Überschriften und stilistisch aufgeteilte Sätze werden als Ganzes in natürlicher deutscher Wortstellung übersetzt. Die Slots `{accent}` und `{lineBreak}` des Startseitentitels sind verschiebbar; Gestaltung legt keine englische Wortstellung fest. Entfernte Passagen und ihre Schlüssel werden von der Projektpflege in Ausgangstext, gemeinsamem Markup und allen Wörterbüchern zusammen bereinigt.
+
+## Texte und Platzhalter
+
+Ganze Sätze oder Absätze in natürliches, sachliches Deutsch übersetzen. Voraussetzungen, Verneinungen, Einheiten, Handlungsschritte und Lebensdauerbedingungen erhalten. Keine zusätzlichen technischen Zusagen, Plattformanforderungen, Paketversionen oder Versionshinweise einführen. Produkt- und Paketnamen, Dateinamen sowie C#- und Slang-Bezeichner einschließlich Groß- und Kleinschreibung unverändert lassen.
+
+Jeder Wert muss eine nicht leere Zeichenfolge mit reinem Text sein, ohne HTML oder Markdown. YAML-Anführungszeichen und Escape-Sequenzen beachten; bei Doppelpunkten, Rauten oder zahlenähnlichen Werten nötigenfalls Anführungszeichen setzen. Die englische Schlüsselreihenfolge für die Prüfung beibehalten.
+
+Benannte Platzhalter wie `{context}` und `{link}` können gemeinsamen Code, Links oder hervorgehobenen Text einfügen. Platzhalter der Oberfläche wie `{name}`, `{query}` und `{count}` können auch zur Laufzeit mit Text gefüllt werden. Die Bedeutung anhand der jeweiligen Verwendung auf der gemeinsamen Seite oder in der Oberfläche prüfen, nicht aus dem Namen erraten. Linkbeschriftungen und hervorgehobene Texte mit eigenen Ressourcenschlüsseln ebenfalls übersetzen.
+
+Platzhalter dürfen der deutschen Satzstellung folgen. Für jeden Schlüssel müssen jedoch alle Namen, ihre Schreibweise und die geschweiften ASCII-Klammern unverändert erhalten bleiben. Keine Platzhalter hinzufügen, entfernen, umbenennen oder durch angezeigte API-Namen, Linktexte oder Beispielzahlen ersetzen. Keine Schlüssel aufteilen, um Sätze neu zusammenzusetzen.
+
+## API-Links und technische Bedeutung
+
+Links gehören zu den gemeinsamen Seiten. Eindeutige Verweise auf lokale API-Bezeichner in Fließtext und Tabellen führen zum jeweiligen Typ oder Member. Variablen, Parameternamen, Beispieltypen, Paket- und Dateinamen sowie externe Symbole ohne lokale Referenzseite bleiben einfacher Codetext. Gleiche Schreibweisen können verschiedene Symbole bezeichnen. Gewöhnliche Codeblöcke des Tutorials erhalten keine automatischen Links. Ganze Platzhalter dürfen verschoben werden; Linkziele, gemeinsamer Code und dessen Formatierung bleiben unverändert.
+
+Fehlt für eine API ein generierter Referenzeintrag, bleibt ihr Bezeichner auf der gemeinsamen Seite einfacher Codetext mit einem klar bezeichneten Quellcodelink in der Nähe. Keinen anderen Typ oder Member als Linkziel einsetzen.
+
+Bei der Übersetzung diese Unterschiede erhalten:
+
+- Befehle aufzeichnen, einreichen, auf der GPU ausführen und abschließen sind unterschiedliche Schritte.
+- Ressourcennutzung, Speicherresidenz beziehungsweise vorgesehener CPU-Zugriff, Texturlayout und Pixelformat sind unterschiedliche Begriffe.
+- Besitz bezeichnet die Verantwortung für die Freigabe. Ein Handle hält die Ressource nicht automatisch am Leben; das Einreichen von Befehlen überträgt keinen Besitz.
+- GPU-Abschluss bedeutet nicht immer, dass zwischengespeicherte Rücklesedaten bereits am CPU-Ziel der Anwendung liegen. Die Hinweise zum Warten auf die zuständige Queue erhalten.
+- Ein Drawable ist die Ausgabetextur des aktuellen Frames und nicht zwangsläufig ein Swapchain-Bild. Ressourcenansichten von UI-Steuerelementen unterscheiden.
+
+## Build und Prüfung vor der Abgabe
+
+Die Befehle im Stammverzeichnis des Repositorys mit dem vom Projekt benötigten .NET SDK und DocFX ausführen. In einem frischen Checkout fehlen die generierten Metadaten in `documents/api/`. Zuerst den vollständigen Build einschließlich API-Extraktion ausführen:
+
+```sh
+docfx documents/docfx.json --warningsAsErrors
+```
+
+Sind die API-Metadaten bereits vorhanden und entsprechen dem aktuellen Quellcode, genügen für reine Wörterbuchänderungen der normale Build und die Vorschau:
+
+```sh
+docfx build documents/docfx.json --warningsAsErrors
+docfx serve documents/_site --hostname 127.0.0.1 --port 8080
+```
+
+Deutsch ist bereits registriert. Das vollständige Wörterbuch wird nach einem erfolgreichen Build ohne Konfigurationsänderung verfügbar. Fehlende oder zusätzliche Schlüssel, leere Werte, Werte eines anderen Typs und abweichende Platzhalternamen lassen den Build fehlschlagen. Der Build erkennt weder ungenaue Übersetzungen noch verbliebenen englischen Text; eine redaktionelle Prüfung ist erforderlich.
+
+Alle Sprachen teilen dieselben Seitenpfade. `?lang=de-DE` wählt Deutsch; beim Sprachwechsel bleiben Pfad und Anker erhalten. Ohne ausdrückliche Auswahl gelten nacheinander die gespeicherte manuelle Auswahl, die Browsersprachen und Englisch. Ist diese Übersetzung noch nicht veröffentlicht oder scheitert das Laden beziehungsweise die Prüfung der Seitenressourcen, bleibt die Seite vollständig auf Englisch, statt einzelne Sätze zu mischen. Die Sprachliste ist nach Sprachcode sortiert.
+
+Vor der Abgabe Schlüssel und Platzhalter abgleichen. Startseite, Learn-Einstieg, Dreieck-Tutorial, Konzepte, Beispiele und API-Oberfläche auf dem Desktop und bei 320 Pixel Breite prüfen. Das Tutorial aus Sicht eines Einsteigers lesen; Terminologie, Schaltflächen, Suche, Codekopie, API-Links und den Abschnitt nach einem Sprachwechsel kontrollieren. Bezeichner, Deklarationen und Code bleiben gemeinsam; übersetzt werden nur die Texte im Wörterbuch. Testwörterbücher und Prüfdateien außerhalb des Repositorys halten und keine Platzhalterübersetzungen einreichen.
+
+## Terminologie
+
+| Englischer Begriff | Festgelegte Form |
+| ------------------ | -------------------------------------------------------------------------------------------------------------------- |
+| API / GPU / RHI | Abkürzungen beibehalten; nach dem Original erläutern |
+| barrier | Barriere |
+| buffer | Puffer; `Buffer` bleibt unverändert |
+| command buffer | Befehlspuffer |
+| command queue | Befehlswarteschlange |
+| drawable | Ausgabetextur des aktuellen Frames; `Drawable` bleibt unverändert, nicht grundsätzlich als Swapchain-Bild bezeichnen |
+| graphics context | Grafikkontext |
+| pipeline | Pipeline |
+| readback | Rücklesen von GPU-Daten |
+| render pass | Renderpass |
+| resource | Ressource |
+| shader | Shader |
+| swap chain | Swapchain |
+| texture | Textur |
+| timeline | Timeline |
+| upload | Hochladen von Daten |
diff --git a/documents/locales/en-US/strings.yml b/documents/locales/en-US/strings.yml
new file mode 100644
index 00000000..aad0d256
--- /dev/null
+++ b/documents/locales/en-US/strings.yml
@@ -0,0 +1,951 @@
+### YamlMime:Resources
+concepts.execution.context.description:
+ A {graphicsContext} connects the application to a GPU device through a particular
+ backend and provides its graphics, compute, and transfer queues. Resources used together must belong to the same
+ context; buffers, textures, shaders, pipelines, and their handles cannot be bound across contexts.
+concepts.execution.context.details:
+ '{capabilities} reports the device name and supported features. Use {rayTracingSupported}
+ and {meshShadingSupported} to check support before creating the resources for those workloads. The {link} and
+ {detail} samples show these checks during initialization.'
+concepts.execution.context.details.detail: mesh shading
+concepts.execution.context.details.link: ray tracing
+concepts.execution.context.title: Graphics context
+concepts.execution.description: 'GPU work begins on the CPU: the application creates resources, records commands
+ that use them, and submits those commands to a queue. Submission allows the CPU to continue while the GPU executes
+ the work. The resources must remain available until that execution finishes.'
+concepts.execution.ownership.description: Ownership determines who releases an object; GPU completion determines
+ when it can be released. Calling {dispose} releases native resources even if C# references to the object remain.
+ Submitting commands does not transfer ownership of their resources to the queue.
+concepts.execution.ownership.details: At shutdown, stop submitting work and wait for the last resource use on every
+ participating queue. Then release the application's resources and dispose the context. The context releases its
+ queues and internal helpers, so this final step belongs after application work has completed.
+concepts.execution.ownership.guidance: During rendering, track the completion of work that uses data you intend
+ to overwrite or resources you intend to replace. Wait for those uses to finish before modifying or releasing the
+ affected storage. {link} describes how views and their backing resources fit into this lifetime model.
+concepts.execution.ownership.guidance.link: Resource Management
+concepts.execution.ownership.table.applicationResources.object: Resources created by the application
+concepts.execution.ownership.table.applicationResources.ownership: The application disposes them after their last
+ GPU use.
+concepts.execution.ownership.table.commandBuffers.object: Borrowed command buffers
+concepts.execution.ownership.table.commandBuffers.ownership: The queue manages and recycles them.
+concepts.execution.ownership.table.headings.object: Object
+concepts.execution.ownership.table.headings.ownership: Disposal responsibility
+concepts.execution.ownership.table.queues.object: Context queues and their timelines
+concepts.execution.ownership.table.queues.ownership: The context and queues manage them.
+concepts.execution.ownership.table.swapChainDrawable.ownership: The swap chain manages it.
+concepts.execution.ownership.title: Ownership and lifetime
+concepts.execution.pipelines.description: A graphics pipeline defines the shaders, vertex input layout, primitive
+ assembly, attachment formats, and render state used by draw commands. Select the pipeline before binding vertex,
+ index, or constant data. Its attachment formats and sample count must agree with those of the render pass.
+concepts.execution.pipelines.details: A compute pipeline selects the shader entry point for a dispatch. Bind the
+ pipeline and its constants before dispatching, outside a graphics render pass.
+concepts.execution.pipelines.guidance: The {link} illustrates the graphics sequence. The {detail} adds a compute
+ dispatch before the graphics pass that displays its output.
+concepts.execution.pipelines.guidance.detail: compute sample
+concepts.execution.pipelines.guidance.link: triangle's render callback
+concepts.execution.pipelines.title: Pipeline state
+concepts.execution.queues.description: '{graphicsQueue} is the usual starting point for a frame that combines drawing,
+ computation, and copies. Keeping dependent operations on one queue simplifies their ordering.'
+concepts.execution.queues.details: Separate queues allow independent workloads to be submitted separately, but they
+ do not necessarily correspond to independent hardware engines. Use additional queues when the work can benefit
+ from overlapping execution. When one queue uses data produced by another, connect their submissions with an explicit
+ dependency.
+concepts.execution.queues.guidance:
+ Within a queue, dependent operations can also require barriers or texture transitions
+ so that later accesses observe earlier writes. These memory dependencies are distinct from submission order; {link}
+ explains how to express them.
+concepts.execution.queues.guidance.link: Synchronization
+concepts.execution.queues.table.computeQueue.workToRecord: Compute dispatches and compatible transfer work, without
+ graphics render passes.
+concepts.execution.queues.table.graphicsQueue.workToRecord: Render passes and drawing, plus compute and transfer
+ operations used by the frame.
+concepts.execution.queues.table.headings.queue: Queue
+concepts.execution.queues.table.headings.workToRecord: Supported work
+concepts.execution.queues.table.transferQueue.workToRecord: Upload, download and copy work, without drawing or compute
+ dispatches.
+concepts.execution.queues.title: Queue selection
+concepts.execution.recording.context:
+ 'The queue reclaims completed command buffers for reuse. Obtain one with {queueCommandBuffer}
+ for each recording, record from one thread at a time, and submit it once. After submission, leave the buffer to
+ the queue: do not modify, resubmit, or dispose it.'
+concepts.execution.recording.description: '{queueCommandBuffer} returns a {commandBuffer} with recording already
+ in progress. After recording the operations and their dependencies, call {submit} once. Submission ends recording
+ and returns a {timelineValue} that identifies the completion point on that queue.'
+concepts.execution.recording.details: 'After recording a frame in {commandBuffer}, the triangle tutorial submits
+ it and waits for completion:'
+concepts.execution.recording.guidance: Recording builds the command sequence; {submit} schedules that sequence for
+ execution. Calling {wait} then blocks the CPU thread until the submission completes. This makes the tutorial's
+ frame loop easy to follow, because each frame finishes before the next begins.
+concepts.execution.recording.title: Recording and submission
+concepts.execution.title: Execution Model
+concepts.platforms.description: Presentation can be managed by the application or by a UI rendering control. This
+ determines who acquires the current frame's output texture, records and submits the frame, and presents the result.
+ That output texture is called the drawable.
+concepts.platforms.frame.context: Record pipeline bindings and draw commands inside the pass, then end it before
+ the handler returns. The control submits the commands and presents the result. The handler must not submit the
+ command buffer or dispose either supplied object. If it changes the drawable's layout, restore {colorAttachment}
+ before returning so the control can perform its final transition.
+concepts.platforms.frame.description: The controls implement {iZenithView}. The application assigns {graphicsContext},
+ updates scene data in {updateRequested}, and records commands in {renderRequested} using the supplied {renderEventArgs}.
+concepts.platforms.frame.details: For each frame, the control acquires a drawable and a command buffer, transitions
+ the drawable to {colorAttachment}, and calls the update and render handlers. It then submits the recorded work
+ and presents the image using the framework's presentation mechanism.
+concepts.platforms.frame.guidance: 'Add {usingZenithNET} to the file. For an initialized {iZenithView} named {view}
+ with a graphics context assigned, register this handler to clear its drawable:'
+concepts.platforms.frame.notes: The initial transition uses {undefined}, which allows the previous image contents
+ to be discarded. Draw or clear the full image on each frame. Keep accumulated or cached rendering in an application-owned
+ texture, then draw that texture into the control's drawable.
+concepts.platforms.frame.reference: '{zenithViewHelperDrawableFormat} provides the default color format used by
+ these integrations.'
+concepts.platforms.frame.title: Rendering through a UI control
+concepts.platforms.integration.context: WinForms, WPF, and the Windows WinUI integration run on Windows. For MAUI,
+ install the workload for the target platform and register the control handler with {useZenithView}, as shown in
+ {extensionsCs}.
+concepts.platforms.integration.description: 'The event interface is shared, but the presentation mechanism depends
+ on the framework:'
+concepts.platforms.integration.details: These implementations differ in how the GPU image reaches the UI. Bitmap
+ readback copies pixels into CPU memory before display. Shared-texture interop avoids that CPU copy but can still
+ require GPU copies and synchronization. Consider this presentation path, as well as the shared {renderRequested}
+ event, when choosing an integration.
+concepts.platforms.integration.guidance: Add the integration package for your UI framework through NuGet. For Avalonia,
+ use {avaloniaPackage}.
+concepts.platforms.integration.table.avalonia.integration: Avalonia
+concepts.platforms.integration.table.avalonia.presentation:
+ Renders to a texture and downloads pixels into a {writeableBitmap}.
+ See {zenithViewCs} and {surfaceCs}.
+concepts.platforms.integration.table.headings.integration: Integration
+concepts.platforms.integration.table.headings.presentation: Presentation mechanism
+concepts.platforms.integration.table.maui.integration: .NET MAUI
+concepts.platforms.integration.table.maui.presentation: Uses native surfaces on Android, iOS and Mac Catalyst, and
+ shared-texture composition on Windows. See the {link}.
+concepts.platforms.integration.table.maui.presentation.link: platform implementations
+concepts.platforms.integration.table.uno.integration: Uno path
+concepts.platforms.integration.table.uno.presentation: Downloads pixels into a {writeableBitmap}. See {zenithViewUnoCs}.
+concepts.platforms.integration.table.winForms.integration: WinForms
+concepts.platforms.integration.table.winForms.presentation: Creates a Zenith.NET swap chain for the control's {hWND}.
+ See {zenithViewCs}.
+concepts.platforms.integration.table.winUI.integration: WinUI on Windows
+concepts.platforms.integration.table.winUI.presentation: Renders into an imported shared Direct3D 11 texture, then
+ copies to a composition swap chain attached to {swapChainPanel}. See {zenithViewWinUICs}.
+concepts.platforms.integration.table.wpf.integration: WPF
+concepts.platforms.integration.table.wpf.presentation: Imports a shared Direct3D 11 texture, then copies through
+ Direct3D interop to a surface displayed by {d3DImage}. See {surfaceCs}.
+concepts.platforms.integration.title: Framework and presentation path
+concepts.platforms.lifecycle.context: Use {descWidth}, {descHeight}, {descFormat}, and {descSampleCount} from the
+ drawable supplied by the render event when creating matching attachments. The event texture gives the render target's
+ actual dimensions; they should not be inferred from the control's layout size or display scale.
+concepts.platforms.lifecycle.description: "The swap chain uses the framebuffer's width and height in pixels, which
+ can differ from the window's logical dimensions under display scaling. Defer rendering and swap-chain resizing
+ while either dimension is zero. Obtain {swapChainDrawable} for each frame: the swap chain owns this texture and
+ may replace it after presentation or resizing."
+concepts.platforms.lifecycle.details: When framebuffer dimensions change, wait for outstanding GPU uses of the old
+ images before calling {swapChainResize}. To replace the native presentation target, pass its new surface description
+ and handles to {swapChainRefresh} after those uses finish. At shutdown, release the swap chain before destroying
+ its native presentation target.
+concepts.platforms.lifecycle.guidance: If application-owned depth or offscreen textures follow the window size,
+ recreate them when that size changes and update constants that contain their handles. The pipeline can be reused
+ as long as the attachment formats and sample counts still match. The {link} demonstrates this separation between
+ resized textures and a reusable pipeline.
+concepts.platforms.lifecycle.guidance.link: spinning cube renderer
+concepts.platforms.lifecycle.title: Swap-chain dimensions and lifetime
+concepts.platforms.ownership.description: The application owns the assigned context and its rendering resources;
+ the control owns its presentation resources. When the view's {graphicsContext} changes, the scheduler recreates
+ the control's resources for the new context. Recreate the application's pipelines, buffers, and textures for that
+ context as well.
+concepts.platforms.ownership.details: Before replacing rendering resources, stop the view's frame activity and wait
+ for the GPU work that uses them. Only then release the old resources or context. The application remains responsible
+ for releasing the assigned context when the view is removed.
+concepts.platforms.ownership.guidance:
+ The shared {frameSchedulerCs} runs frame handlers through the view's UI dispatcher.
+ Keep those handlers focused on updating scene data and recording commands; lengthy CPU work there also delays
+ the interface.
+concepts.platforms.ownership.title: Application and control ownership
+concepts.platforms.surface.description: The {link} uses Silk.NET to create the window and process its events. Zenith.NET
+ receives the native window handles and dimensions through a {surface} description, which it uses to create a {swapChain}.
+ The application remains responsible for the operating-system window.
+concepts.platforms.surface.description.link: triangle tutorial
+concepts.platforms.surface.details: 'Surface handles must match the selected backend:'
+concepts.platforms.surface.guidance: The sample host in {appCs} supports Windows, macOS, and X11. Its Linux window
+ supplies Xlib handles, so a Wayland session needs XWayland to run it. A native Wayland application supplies its
+ display and surface handles through {surfaceWayland}.
+concepts.platforms.surface.table.android.backend: Vulkan on Android.
+concepts.platforms.surface.table.apple.backend: Metal on Apple platforms.
+concepts.platforms.surface.table.headings.backend: Backend and platform
+concepts.platforms.surface.table.headings.factory: Surface factory
+concepts.platforms.surface.table.headings.handles: Handles supplied by the host
+concepts.platforms.surface.table.wayland.backend: Vulkan with a native Wayland host.
+concepts.platforms.surface.table.wayland.handles: Wayland display and surface
+concepts.platforms.surface.table.win32.backend: DirectX 12 or Vulkan on Windows.
+concepts.platforms.surface.table.xlib.backend: Vulkan on Linux with X11 or XWayland.
+concepts.platforms.surface.table.xlib.handles: X11 display and window
+concepts.platforms.surface.title: Native presentation surfaces
+concepts.platforms.title: Platform Integration
+concepts.resources.allocation.context: Resources placed in a heap depend on its storage for their entire lifetime.
+ After their final GPU use, release the resources before releasing the heap.
+concepts.resources.allocation.description: Use a {heap} when the application needs to manage allocation itself.
+ A heap provides storage in which resources are placed at explicit byte offsets; the application chooses those
+ offsets and tracks the occupied regions.
+concepts.resources.allocation.details: For each resource, call {contextGetSizeAndAlignment} with the buffer or texture
+ description that will be used to create it. Reserve the returned size at an offset that is a multiple of the returned
+ alignment, within the heap's bounds and without overlapping another live allocation. Texture sizes may include
+ backend-specific padding, so pixel count and bytes per pixel alone are insufficient to calculate their allocation
+ size.
+concepts.resources.allocation.guidance: A buffer placed in a heap must specify the same residency as that heap.
+ For texture storage, use a GPU-only heap.
+concepts.resources.allocation.title: Heap allocation
+concepts.resources.bufferUsages.table.constant.role: Shader parameters read through a constant-buffer binding.
+concepts.resources.bufferUsages.table.headings.role: Role
+concepts.resources.bufferUsages.table.headings.usage: Usage
+concepts.resources.bufferUsages.table.index.role: Indices used by indexed drawing.
+concepts.resources.bufferUsages.table.indirect.role: Arguments read by indirect draw or dispatch commands.
+concepts.resources.bufferUsages.table.none.role: No buffer usage flags are declared.
+concepts.resources.bufferUsages.table.storageReadOnly.role: Buffer data accessed through a read-only shader binding.
+concepts.resources.bufferUsages.table.storageReadWrite.role: Buffer data accessed through a read/write shader binding.
+concepts.resources.bufferUsages.table.transferDst.role: Destination storage for a GPU copy.
+concepts.resources.bufferUsages.table.transferSrc.role: Source data for a GPU copy.
+concepts.resources.bufferUsages.table.vertex.role: Vertex attributes fetched for drawing.
+concepts.resources.bufferUsages.title: Buffer usages
+concepts.resources.description: A resource's description establishes how the application may use its storage. For
+ example, a texture used as a render target, sampled by a shader, and copied back to the CPU needs the usage flags
+ for all three operations. Memory placement determines CPU access, while synchronization and lifetime management
+ determine when the data can be accessed or the storage released.
+concepts.resources.memory.context: 'For CPU-accessible buffers, {map} provides the address through which the CPU
+ reads or writes data. Mapping and {unmap} do not wait for GPU work: the application must wait for a GPU write
+ before reading its result, or for outstanding GPU accesses before modifying the same bytes.'
+concepts.resources.memory.description: 'The {residency} field of {bufferDesc} specifies the intended CPU access
+ to the allocation:'
+concepts.resources.memory.details: Residency expresses CPU access requirements, including on systems where the CPU
+ and GPU share physical memory. The triangle uses {cpuWriteOnly} to initialize its small vertex buffer directly.
+ For larger static geometry, an upload into GPU-only storage may be more suitable.
+concepts.resources.memory.guidance: Textures created directly through the context use GPU storage. {textureDesc}
+ therefore has no residency field; the application exchanges texture data with CPU memory through uploads and downloads.
+concepts.resources.memory.table.cpuReadOnly.cPUAccess: Read-only
+concepts.resources.memory.table.cpuReadOnly.typicalRole: Readback data produced by the GPU
+concepts.resources.memory.table.cpuWriteOnly.cPUAccess: Write-only
+concepts.resources.memory.table.cpuWriteOnly.typicalRole: Data supplied or updated by the CPU
+concepts.resources.memory.table.gpuOnly.cPUAccess: None
+concepts.resources.memory.table.gpuOnly.typicalRole: Static geometry and GPU working data
+concepts.resources.memory.table.headings.cPUAccess: Intended CPU access
+concepts.resources.memory.table.headings.residency: Residency
+concepts.resources.memory.table.headings.typicalRole: Typical role
+concepts.resources.memory.title: CPU access and memory placement
+concepts.resources.textureUsages.description: The transfer flags describe a resource's role as the source or destination
+ of a GPU copy. Whether the CPU can access the storage directly is a separate memory-placement choice.
+concepts.resources.textureUsages.details: Usage flags declare the roles a texture may have. Moving between those
+ roles requires the corresponding {link}, and its format must support the requested uses on the selected device.
+concepts.resources.textureUsages.details.link: layout and synchronization
+concepts.resources.textureUsages.table.colorAttachment.role: A color attachment in a render pass.
+concepts.resources.textureUsages.table.depthStencilAttachment.role: A depth/stencil attachment in a render pass.
+concepts.resources.textureUsages.table.headings.role: Role
+concepts.resources.textureUsages.table.headings.usage: Usage
+concepts.resources.textureUsages.table.none.role: No texture usage flags are declared.
+concepts.resources.textureUsages.table.sampled.role: Texture data read through a sampled-texture binding.
+concepts.resources.textureUsages.table.storage.role: Texture data accessed through a storage-texture binding for
+ shader reads or writes.
+concepts.resources.textureUsages.table.transferDst.role: Destination image storage for a GPU copy.
+concepts.resources.textureUsages.table.transferSrc.role: Source image data for a GPU copy.
+concepts.resources.textureUsages.title: Texture usages
+concepts.resources.title: Resource Management
+concepts.resources.transfers.context: A download first copies GPU data into internal readback storage. The library
+ then copies it to the supplied CPU destination when it reclaims the completed command buffer. Calling {wait} on
+ the download's submission value completes both steps. Keep the destination allocated, and managed memory pinned,
+ until that call returns.
+concepts.resources.transfers.description: 'Uploads copy CPU data into a GPU resource; downloads copy resource data
+ into CPU memory. The methods on buffers and textures complete this transfer before returning:'
+concepts.resources.transfers.details: Before calling these methods, resolve any conflicting accesses on other queues.
+ A wait inside the method covers its own transfer; it does not establish a dependency on another queue. The direct
+ mapped-memory paths rely entirely on the caller to wait for conflicting GPU accesses.
+concepts.resources.transfers.guidance: 'To submit transfers together with other GPU work, record them with {commandBufferUpload} or {commandBufferDownload}. Upload sources and download destinations have different lifetime requirements:'
+concepts.resources.transfers.item: During an upload call, the library copies the source data into temporary upload
+ storage, called staging memory. The GPU later reads from this storage, so the application's source pointer is
+ needed only until the call returns.
+concepts.resources.transfers.notes: For texture transfers, row stride is the byte distance between successive rows
+ of CPU data; slice stride is the distance between successive depth slices. These values let the transfer copy
+ the CPU data into or out of the texture's GPU storage. {link} explains why GPU completion and delivery to a download
+ destination are separate steps.
+concepts.resources.transfers.notes.link: Synchronization
+concepts.resources.transfers.table.bufferDownload.transfer: Copies directly from mapped memory for {cpuReadOnly}.
+ Otherwise, submits the download to the transfer queue and waits for it to finish.
+concepts.resources.transfers.table.bufferUpload.transfer: Copies directly into mapped memory for {cpuWriteOnly}.
+ Otherwise, submits the upload to the transfer queue and waits for it to finish.
+concepts.resources.transfers.table.headings.method: Method
+concepts.resources.transfers.table.headings.transfer: Transfer behavior
+concepts.resources.transfers.table.textureTransfers.method: '{textureUpload} / {textureDownload}'
+concepts.resources.transfers.table.textureTransfers.transfer: Submits the copy and its texture transitions to the
+ graphics queue, then waits for completion. The selected subresource is left in the requested final layout.
+concepts.resources.transfers.title: Immediate and recorded transfers
+concepts.resources.usages.description: '{bufferUsages} and {textureUsages} are flag enums for declaring resource
+ roles. Assign them to {bufferDescUsages} and {textureDescUsages}, respectively. When a resource serves several
+ roles, combine the required members with {value}.'
+concepts.resources.usages.title: Resource usages
+concepts.resources.views.description: A {bufferView} describes a region of a buffer and how its data is interpreted.
+ A {textureView} selects a subresource range and a compatible texture type and format. Both refer to existing storage,
+ with the usages declared when that storage was created. View ranges describe access; they do not provide consistent
+ shader bounds checking across backends.
+concepts.resources.views.details: A resource's own handle provides its default view. Create a separate view to select
+ a different region or compatible interpretation. Keep both the view and its backing resource alive until the GPU
+ has finished using them; a resource handle does not retain either object. When replacing either one, update any
+ constants containing the old {link}.
+concepts.resources.views.details.link: resource handle
+concepts.resources.views.title: Views and backing resources
+concepts.shaders.binding.context:
+ 'The compute sample assigns {inputTextureSampledHandle} to {input} and {outputTextureStorageHandle}
+ to {output}. In Slang, the type parameter of {descriptorHandle} defines how the shader accesses each resource:'
+concepts.shaders.binding.description: '{setConstantBuffer} selects an existing buffer and byte offset from which
+ the current pipeline reads its constants. Upload the parameter data into that storage before it is used by the
+ shader. If the same bytes are still being read by earlier GPU work, wait for that work before updating them.'
+concepts.shaders.binding.details: 'With a command buffer, compute pipeline, populated constant buffer, and thread-group
+ counts ready, record the dispatch in this order. Select the pipeline first, because the constant-buffer binding
+ applies to that pipeline:'
+concepts.shaders.binding.guidance: Here, {commandBuffer} records a dispatch using {computePipeline} and the parameters
+ stored in {constantBuffer}. The buffer requires {bufferUsagesConstant} usage, and offset 0 selects its beginning.
+ If several constant records share one buffer, each record's starting offset must also meet the backend's binding
+ alignment, independently of the layout of fields within the record.
+concepts.shaders.binding.notes: '{resourceHandle} contains two 32-bit fields whose interpretation depends on the
+ backend. Its values are valid only within the context that created the resource.'
+concepts.shaders.binding.reference: Retain the resource and any view used to obtain its handle until the shader's
+ final access completes. Replacing a texture or view requires updating constants that still contain the old handle.
+ The ownership rules are described in {link}.
+concepts.shaders.binding.reference.link: Resource Management
+concepts.shaders.binding.table.bufferStorageReadOnlyHandle.access: Reads of structured elements of shader type {t}
+concepts.shaders.binding.table.bufferStorageReadWriteHandle.access: Reads and writes of structured elements of shader
+ type {t}
+concepts.shaders.binding.table.headings.access: Intended access
+concepts.shaders.binding.table.headings.cHandle: C# handle
+concepts.shaders.binding.table.headings.slangFieldType: Slang field type
+concepts.shaders.binding.table.samplerHandle.access: Texture sampling state
+concepts.shaders.binding.table.textureSampledHandle.access: Texture reads
+concepts.shaders.binding.table.textureStorageHandle.access: Storage-texture reads and writes
+concepts.shaders.binding.title: Constant buffers and resource handles
+concepts.shaders.compilation.description: "{zenithCompiler} compiles a named Slang entry point into a {shaderDesc}.
+ The context's {graphicsApi} selects the required target: DXIL for DirectX 12, a Metal library for Metal, or SPIR-V
+ for Vulkan."
+concepts.shaders.compilation.details: 'With a graphics context named {context} already created, compile the triangle
+ shader from the copy placed beside the executable:'
+concepts.shaders.compilation.guidance: The entry-point name {vSMain} must match the declaration in the shader file.
+ Compile shaders and create pipelines during initialization, then reuse the pipelines while rendering. Once pipeline
+ creation has completed, the shader objects may be released, as shown in {link}.
+concepts.shaders.compilation.guidance.link: First Triangle
+concepts.shaders.compilation.title: Shader compilation
+concepts.shaders.description: The C# and Slang declarations must agree on where each value is stored and how the
+ shader accesses it. Vertex input layouts describe vertex attributes; shader buffer declarations describe parameters
+ and arrays; resource handles identify the resources used by a shader.
+concepts.shaders.dispatch.description: "{dispatch} specifies how many thread groups to execute. The compute sample
+ declares {numthreads}, so each group contains 16 × 16 threads. To cover an image, divide its width and height
+ by 16 and round each result up: a 17 × 19 image requires 2 × 2 groups. The shader checks each thread's coordinates
+ and skips those outside the image."
+concepts.shaders.dispatch.details: The declared group size is available in {shaderDescThreadGroupSize} and can be
+ used to calculate the group counts passed to {dispatch}. {computeShaderRendererCs} shows the complete calculation.
+ After the dispatch, access to the resulting image must follow the requirements described in {link}.
+concepts.shaders.dispatch.details.link: Synchronization
+concepts.shaders.dispatch.title: Thread groups
+concepts.shaders.layout.context: 'The {link} illustrate this arrangement with image dimensions and two texture handles.
+ Explicit offsets specify where each field begins. The declaration uses {systemRuntimeInteropServices} and {zenithNET}:'
+concepts.shaders.layout.context.link: compute sample's C# constants
+concepts.shaders.layout.description: "For vertex input, {inputLayout} describes each attribute's format, offset,
+ and semantic, together with the stride between vertices. The triangle places a {float3} position before a {float4}
+ color: the color starts at byte offset 12, and each vertex occupies 28 bytes. {inputLayoutAdd} calculates this
+ packed layout from the element formats. If the C# struct contains padding, supply offsets and a stride that account
+ for it."
+concepts.shaders.layout.details: "Constant buffers hold shader parameters, and structured buffers hold arrays of typed elements. Their layouts follow the Slang declarations rather than {inputLayout}. Match the C# field offsets, element sizes, and padding to the compiled shader layout. A C# unmanaged type can be copied as bytes, but its memory layout does not necessarily match the shader's layout; the tightly packed {float3} used for a vertex may require different alignment in a shader buffer."
+concepts.shaders.layout.guidance: Shader parameters can include resource handles as well as ordinary values. A handle
+ identifies storage that already exists, so copying the handle into a constant buffer does not copy the resource's
+ contents.
+concepts.shaders.layout.notes: 'The matching declaration in {computeShaderSlang} is:'
+concepts.shaders.layout.reference: The two dimensions occupy the first eight bytes, followed by two eight-byte handles.
+ The C# sample reserves 256 bytes for the complete record. For another record, determine its size from its own
+ fields and the required layout and alignment.
+concepts.shaders.layout.title: Data layout
+concepts.shaders.matrices.description: The compiler selects row-major matrix storage, keeping each row contiguous.
+ The {link} stores three {matrix4x4} values at offsets 0, 64, and 128. Its {detail} applies {mulVectorMatrix} in
+ model, view, and projection order.
+concepts.shaders.matrices.description.detail: shader
+concepts.shaders.matrices.description.link: spinning cube renderer
+concepts.shaders.matrices.details: Storage order specifies how matrix elements are arranged in memory; multiplication
+ order specifies the transformation. Use matching conventions in C# and Slang.
+concepts.shaders.matrices.title: Matrix conventions
+concepts.shaders.title: Shader Data and Binding
+concepts.synchronization.backends.description: DirectX 12 and Vulkan encode texture transitions as layout and access
+ changes. Metal has no equivalent operation in Zenith.NET's {transition} implementation. The Metal backend instead
+ groups commands into render and compute encoders and inserts visibility barriers when an encoder ends. Dependent
+ operations within one encoder, such as successive compute dispatches, use explicit {barrier} calls.
+concepts.synchronization.backends.details: In the {link}, starting the graphics render pass ends the compute encoder,
+ making its output visible to the graphics work. Two successive compute dispatches remain in the same encoder,
+ so their dependency needs a barrier even if a texture transition is recorded between them. This is why portable
+ command sequences account for access dependencies as well as texture layouts.
+concepts.synchronization.backends.details.link: compute sample
+concepts.synchronization.backends.title: Backend behavior
+concepts.synchronization.barriers.context: Choose stages that include the relevant accesses and are supported by
+ the queue. {barrierStagesAll} includes every supported stage; select a narrower set when the dependency only involves
+ those stages.
+concepts.synchronization.barriers.description: 'Consider two compute dispatches recorded into the same command buffer,
+ where the first writes simulation data and the second reads it. Place a barrier between them so that the second
+ dispatch sees the completed writes. For a storage buffer, this orders the accesses without a texture layout transition:'
+concepts.synchronization.barriers.details: The first argument identifies the stages that produced the data; the
+ second identifies the stages that will consume it. Together they describe the execution order and memory visibility
+ required between the two dispatches.
+concepts.synchronization.barriers.guidance: The {link} uses this pattern between simulation steps. Each barrier
+ follows the dispatch that produces the data and precedes the dispatch that uses it.
+concepts.synchronization.barriers.guidance.link: FluidTank simulation
+concepts.synchronization.barriers.title: Pipeline barriers
+concepts.synchronization.dependencies.description: For each resource, identify the accesses that touch the same
+ data. If either access writes, preserve the required order between them. Two reads do not conflict, although texture
+ layout and presentation requirements still apply. CPU updates and resource disposal also need to wait until the
+ GPU has finished using the affected storage.
+concepts.synchronization.dependencies.details: 'The appropriate mechanism depends on where the operations execute:'
+concepts.synchronization.dependencies.guidance: A workload may need more than one mechanism. For example, a texture
+ produced on another queue needs a submission dependency and the appropriate layout for its next use. Neither operation
+ requires blocking the CPU unless the CPU itself needs to access or release the data.
+concepts.synchronization.dependencies.table.cpuAccess.mechanism: Call {wait} on the relevant completion value.
+concepts.synchronization.dependencies.table.cpuAccess.situation: CPU access or resource release depends on GPU completion
+concepts.synchronization.dependencies.table.crossQueue.mechanism: Pass the producer's {timelineValue} to the consumer's
+ {submit}.
+concepts.synchronization.dependencies.table.crossQueue.situation: One queue consumes data produced on another queue
+concepts.synchronization.dependencies.table.headings.mechanism: Mechanism
+concepts.synchronization.dependencies.table.headings.situation: Situation
+concepts.synchronization.dependencies.table.layoutChange.mechanism: '{commandBufferTransition} with its previous
+ and next layouts.'
+concepts.synchronization.dependencies.table.layoutChange.situation: A texture subresource changes how it is used
+concepts.synchronization.dependencies.table.pipelineStages.mechanism: '{commandBufferBarrier} with the producing
+ and consuming stages.'
+concepts.synchronization.dependencies.table.pipelineStages.situation: Dependent GPU stages in a command stream
+concepts.synchronization.dependencies.title: Access dependencies
+concepts.synchronization.description: Synchronization establishes the order in which operations access shared data
+ and ensures that later operations see the results they need. In a write-to-read dependency, the writer is the
+ producer and the reader is the consumer. Barriers and texture transitions coordinate GPU accesses; timelines connect
+ submissions and let the CPU wait for GPU work to finish.
+concepts.synchronization.layouts.context: Use {undefined} as the previous layout when the existing contents can
+ be discarded, such as for a new image or an attachment that will be cleared completely. To preserve the contents
+ for loading, sampling, or accumulation, use the layout from the preceding access.
+concepts.synchronization.layouts.description: "A texture's usage flags declare its permitted roles. {textureLayout}
+ describes how a particular subresource, identified by a mip level and array layer, is accessed at a given point
+ in the command sequence. When that access changes, record a transition. For example, the compute sample writes
+ an output texture in {storage} layout, then prepares it for sampling in a graphics pass:"
+concepts.synchronization.layouts.details: Here, {outputTexture} was created with {sampledStorage} usage and the
+ selected subresource is in {storage} layout. {default} selects mip level 0 and array layer 0. Other mip levels
+ and layers are unaffected by this transition.
+concepts.synchronization.layouts.guidance: For {transition}, supply the layout from the subresource's previous use.
+ The application tracks this state as it records commands and passes textures between rendering stages.
+concepts.synchronization.layouts.title: Texture layouts
+concepts.synchronization.readback.description: "{isCompleted} reports GPU completion. A recorded download has one
+ further step: copying the result from internal readback storage into the application's destination memory."
+concepts.synchronization.readback.details: "{commandBufferDownload} records the GPU copy into that internal storage.
+ After the submission finishes, the library performs the CPU copy while reclaiming the completed command buffer.
+ Calling {wait} on the download's submission value waits for the GPU and processes that queue's completed buffers,
+ completing both steps. Keep the destination allocated, and managed memory pinned, until the call returns."
+concepts.synchronization.readback.guidance: Polling {isCompleted} alone does not perform the final CPU copy. A wait
+ on another queue does not reclaim the download queue's completed buffers either. {link} describes when uploads
+ and downloads use direct memory access or staging storage.
+concepts.synchronization.readback.guidance.link: Resource Management
+concepts.synchronization.readback.title: Readback completion
+concepts.synchronization.reuse.description: The triangle waits for each frame to finish before reusing its data.
+ To overlap frames, give each in-flight frame its own mutable storage and track when that storage becomes available
+ again. Before changing data, wait for GPU work that still accesses the affected bytes; before releasing a resource,
+ wait for all of its outstanding uses.
+concepts.synchronization.reuse.details: 'Resizing and shutdown follow the same rule: stop new uses, wait for existing
+ uses to finish, then replace or release the resources. Establish this order before calling {swapChainResize} or
+ disposing resources or the context. The triangle already waits for each frame, so it can resize between callbacks.'
+concepts.synchronization.reuse.guidance: '{swapChainPresent} signals and waits on the graphics queue after presentation.
+ The triangle therefore waits at presentation even if its explicit submission wait is removed. Work submitted independently
+ to other queues requires its own completion tracking.'
+concepts.synchronization.reuse.title: Resource reuse
+concepts.synchronization.timelines.context: 'If the CPU needs to read the result, overwrite the shared data, or
+ release the resources after the consumer finishes, wait for that submission:'
+concepts.synchronization.timelines.description: A timeline identifies queue completion with increasing values. {submit}
+ returns a {timelineValue} containing the timeline and the submission's value. Numeric {value} fields from different
+ timelines cannot be compared directly.
+concepts.synchronization.timelines.details: 'Let {producer} and {consumer} be recorded command buffers from two
+ queues in the same context, with the consumer using data written by the producer. Submit them with this dependency:'
+concepts.synchronization.timelines.guidance: The consuming queue waits for the supplied completion value before
+ executing its commands. The CPU can continue without calling {producedWait}. Keep the shared resources alive until
+ {consumed} completes, and record the texture transitions required by the consumer's access.
+concepts.synchronization.timelines.notes: The value returned by {queueTimelineSignal} can likewise be waited on
+ with {wait} or used as a dependency for another queue. {signal} places this completion point after previously
+ submitted work; the GPU reaches it once that work has finished.
+concepts.synchronization.timelines.title: Timelines and queue dependencies
+concepts.synchronization.title: Synchronization
+home.actions.reference: Browse API
+home.actions.tutorial: Start with a triangle
+home.entries.description: Tutorials, examples, and API reference
+home.entries.label: Documentation sections
+home.entries.reference.action: Explore the API
+home.entries.reference.category: Reference
+home.entries.reference.description: Look up API types, members, and declarations
+home.entries.reference.title: API Reference
+home.entries.samples.action: Browse samples
+home.entries.samples.category: Examples
+home.entries.samples.description: Working examples of graphics and compute techniques
+home.entries.samples.title: Samples
+home.entries.title: Documentation
+home.entries.tutorial.action: Start tutorial
+home.entries.tutorial.category: Tutorial
+home.entries.tutorial.description: From an empty project to your first triangle on screen
+home.entries.tutorial.title: First Triangle
+home.introduction.accent: for .NET
+home.introduction.description: Zenith.NET provides one API for graphics and compute across DirectX, Metal, and Vulkan
+home.introduction.title: Cross-platform graphics{lineBreak}{accent}
+home.meta.description: Cross-platform graphics and compute for .NET with one API for DirectX, Metal, and Vulkan
+home.scene.description: Interactive rotating geometry. Drag or use the arrow keys to adjust the view.
+home.scene.label: Geometry display
+home.scene.surface: Surface
+home.scene.wireframe: Wireframe
+home.title: Modern RHI for .NET
+learning.concepts.description: How to choose resource usages, describe shader data, and coordinate GPU execution
+learning.concepts.execution.description: Command recording, submission, and resource lifetime
+learning.concepts.platforms.description: Native presentation and UI rendering controls
+learning.concepts.resources.description: Resource usages, memory placement, and views
+learning.concepts.shaders.description: Data-layout and binding conventions for C# and Slang
+learning.concepts.synchronization.description: Dependencies, visibility, and completion
+learning.concepts.title: Core concepts
+learning.description:
+ Start with the triangle tutorial to build your first application. Then explore resource management,
+ shader data, and synchronization in the concept articles, or choose a sample for the technique you want to implement.
+learning.entries.samples.action: Browse samples
+learning.entries.samples.description: C# and Slang implementations of geometry, compute, ray queries, and mesh shading
+learning.entries.tutorial.action: Open tutorial
+learning.entries.tutorial.description: A console application that opens a window and renders a triangle
+learning.meta.description: Tutorials and conceptual guidance for the Zenith.NET programming model
+learning.title: Learn
+reference.description: Reference documentation for resources, commands, and platform integrations
+reference.entries.commands.description: Graphics, compute, and transfer commands
+reference.entries.commands.label: Record commands
+reference.entries.context.description: GPU resource creation for the selected backend
+reference.entries.context.label: Start here
+reference.meta.description: Namespaces, types, and members of Zenith.NET
+reference.navigation.core: Core API reference
+reference.title: API Reference
+samples.computeShader.description: The sample loads {assetsTexturesShokoPng}, converts it to grayscale in a compute
+ dispatch, and displays the output texture. The conversion runs once; later frames display the same result. The
+ renderer shows how to bind sampled and writable textures, round up thread-group counts to cover the image, and
+ check image bounds in the shader.
+samples.computeShader.details: '{computeShaderRendererCs} · {computeShaderSlang}'
+samples.computeShader.title: Compute Shader
+samples.cube.description: The cube adds indexed drawing, model, view, and projection matrices in a constant buffer,
+ and depth testing. Its {d32FloatS8UInt} depth/stencil texture is recreated on resize.
+samples.cube.details: '{spinningCubeRendererCs} · {spinningCubeSlang}'
+samples.cube.title: Spinning Cube
+samples.description: Each example pairs a C# renderer with its Slang shader and concentrates on a particular technique.
+ For the complete application setup and rendering sequence, begin with {link}.
+samples.description.link: First Triangle
+samples.execution.description: Run the {link} project and choose an example from its console menu.
+samples.execution.description.link: ZenithTutorials
+samples.execution.title: Running the examples
+samples.host.description: "{appCs} initializes the context, window, and swap chain. On each frame it obtains a command
+ buffer, invokes the selected renderer, submits the commands, and presents the result. {iRendererCs} defines the
+ renderer's update, render, resize, and disposal operations."
+samples.host.details: The compute and ray tracing renderers produce offscreen textures. {texturePresenterCs} and
+ {presentTextureSlang} draw those textures into the current drawable; the shared host then submits the commands
+ and presents the frame.
+samples.host.title: Host and presentation
+samples.indirectDrawing.description: Indirect drawing renders multiple cube instances from a buffer of indexed draw
+ arguments and a structured buffer of per-instance data. The CPU supplies the arguments and updates the transforms.
+samples.indirectDrawing.details: '{indirectDrawingRendererCs} · {indirectDrawingSlang}'
+samples.indirectDrawing.title: Indirect Drawing
+samples.meshShading.description: A task shader culls sphere instances and passes the retained indices to a mesh
+ shader that emits geometry. The renderer checks {contextCapabilitiesMeshShadingSupported} before creating the
+ pipeline.
+samples.meshShading.details: '{meshShadingRendererCs} · {meshShadingSlang}'
+samples.meshShading.title: Mesh Shading
+samples.rayTracing.description: This example builds bottom-level and top-level acceleration structures for a floor
+ and procedural spheres, then performs inline ray queries in a compute shader. It checks {contextCapabilitiesRayTracingSupported}
+ before initialization.
+samples.rayTracing.details: '{rayTracingRendererCs} · {rayTracingSlang}'
+samples.rayTracing.title: Ray Tracing
+samples.title: Samples
+samples.triangle.description: The triangle associates a vertex buffer with a matching input layout and draws three
+ vertices through a graphics pipeline.
+samples.triangle.details: '{helloTriangleRendererCs} · {helloTriangleSlang} · {link}'
+samples.triangle.details.link: Complete tutorial
+samples.triangle.title: Hello Triangle
+samples.workloads.description: The main repository's {link} contains larger applications that reference the library
+ source.
+samples.workloads.description.link: Experiments directory
+samples.workloads.details: The windowed experiments use the same Windows, macOS, and Linux/X11 presentation paths.
+ UI controls for Avalonia, WinForms, WPF, WinUI/Uno, and MAUI are covered in {link}, including their frame lifecycle
+ and implementation sources.
+samples.workloads.details.link: Platform Integration
+samples.workloads.table.cornellBox.example: '{link}'
+samples.workloads.table.cornellBox.example.link: CornellBox
+samples.workloads.table.cornellBox.whatItDemonstratesAndRequires: Path tracing, denoising, tone mapping, upscaling,
+ and ImGui controls. Requires ray tracing support.
+samples.workloads.table.fluidTank.example: '{link}'
+samples.workloads.table.fluidTank.example.link: FluidTank
+samples.workloads.table.fluidTank.whatItDemonstratesAndRequires: Compute fluid simulation, dependencies between
+ simulation stages, and multipass surface rendering.
+samples.workloads.table.headings.example: Example
+samples.workloads.table.headings.whatItDemonstratesAndRequires: Techniques and requirements
+samples.workloads.table.inkCanvas.example: '{link}'
+samples.workloads.table.inkCanvas.example.link: InkCanvas
+samples.workloads.table.inkCanvas.whatItDemonstratesAndRequires: Skia drawing and input handling in a native window.
+ Requires the backend and Skia native library for the target platform.
+samples.workloads.table.platformDetection.example: '{link}'
+samples.workloads.table.platformDetection.example.link: PlatformDetection
+samples.workloads.table.platformDetection.whatItDemonstratesAndRequires: Attempts to create a context for each backend
+ and reports device capabilities. It reports a failed attempt as unsupported; the failure may be due to the device,
+ driver, or a missing runtime dependency.
+samples.workloads.title: Larger applications
+tutorial.context.description: A {graphicsContext} connects the application to the GPU through a backend and provides
+ the methods used to create rendering resources.
+tutorial.context.details: 'Insert the context initialization {emphasis}:'
+tutorial.context.details.emphasis: after the using directives, before {iWindowWindow}
+tutorial.context.guidance: Each branch selects a backend for the current platform and requests validation. Messages
+ from {validationMessage} are printed in the console.
+tutorial.context.imports: 'Add the following using directives at the top of {programCs}:'
+tutorial.context.title: Graphics context
+tutorial.description: In this tutorial, you will build a .NET console application that opens a window and draws
+ a colored triangle, starting from an empty project.
+tutorial.details: 'The tutorial has three checkpoints: an empty window, a solid background color, and the colored
+ triangle. Run the application at each checkpoint before adding the next part.'
+tutorial.draw.context: 'The arguments to {draw} are:'
+tutorial.draw.description: 'In the {windowRender} callback, replace {addTheTriangleDrawCommandsHereLater} with the
+ following commands, between {beginRenderPass} and {endRenderPass}:'
+tutorial.draw.details: "{setPipeline} selects the shaders and render state. It must precede vertex binding because
+ the backend interprets that binding using the pipeline's input layout."
+tutorial.draw.guidance: '{setVertexBufferVertexBuffer} binds the uploaded vertex data at byte offset 0 to input
+ slot 0, corresponding to the single entry in {inputLayouts}.'
+tutorial.draw.notes: These calls record the draw commands. The existing {submitWait} submits them and waits for
+ the GPU to finish; {present} then requests display of the completed image. Keep the clear operation at the start
+ of the pass so the area outside the triangle has a defined background color.
+tutorial.draw.table.firstInstance.meaning: Start instance numbering at zero.
+tutorial.draw.table.firstVertex.meaning: Start with the first vertex in the buffer.
+tutorial.draw.table.headings.argument: Argument
+tutorial.draw.table.headings.description: Value
+tutorial.draw.table.headings.meaning: Meaning
+tutorial.draw.table.instanceCount.meaning: Draw one copy of the triangle.
+tutorial.draw.table.vertexCount.meaning: Read three vertices.
+tutorial.draw.title: Drawing commands
+tutorial.execution.description: Build and run the {firstTriangle} project.
+tutorial.execution.details: You should see a triangle on a dark background, with red at the top, green at the lower
+ right, and blue at the lower left. Interpolation creates smooth transitions between the vertex colors across the
+ triangle.
+tutorial.execution.guidance: Resizing changes the image's pixel dimensions while the vertex coordinates stay the
+ same. The viewport maps those coordinates to the new width and height, so the triangle stretches when the window's
+ width-to-height ratio changes.
+tutorial.execution.title: Running the application
+tutorial.frame.commands.context: After {endRenderPass}, the second transition prepares the drawable for presentation.
+ {submit} ends recording and submits the command buffer; {wait} blocks until that submission completes. {present}
+ then requests display of the rendered image.
+tutorial.frame.commands.description: A render pass groups drawing operations that write to the same attachments.
+ In this pass, the swap chain's drawable receives the color output. {colorAttachmentClear} describes how to initialize
+ that attachment with an RGBA clear color and preserve its contents when the pass ends. {beginRenderPass} records
+ the start of the pass with those settings.
+tutorial.frame.commands.details: The collection contains one color attachment. {null} specifies that the pass has
+ no depth/stencil attachment.
+tutorial.frame.commands.guidance: '{beginRenderPass} sets the viewport and scissor to cover the whole attachment.
+ The viewport maps rendering coordinates to pixel positions; the scissor limits where pixels can be written.'
+tutorial.frame.commands.title: Clear, submission, and presentation
+tutorial.frame.description: Start by filling the window with a single background color. This operation is called
+ clearing. Once the background appears, the surface, command submission, and presentation are working together,
+ ready for the triangle's geometry and shaders.
+tutorial.frame.details: 'Silk.NET invokes the render callback when a frame is due. This example does not use the
+ elapsed-time argument, so the parameter is named {value}. Register the callback {emphasis}:'
+tutorial.frame.details.emphasis: before {windowRun}
+tutorial.frame.target.description: '{graphicsQueueCommandBuffer} returns a command buffer ready for recording. Recording
+ defines the GPU workload; execution begins only after submission.'
+tutorial.frame.target.details:
+ A texture layout describes the kind of access an operation requires. The first {transition}
+ prepares the drawable for use as a color attachment. Here, {default} selects mip level 0 and array layer 0, the
+ drawable's base image. Specifying {undefined} allows the previous contents to be discarded because this pass clears
+ the entire image.
+tutorial.frame.target.guidance: Use the current drawable on each frame because presentation can advance to a different
+ image.
+tutorial.frame.target.title: Drawable preparation
+tutorial.frame.title: The first frame
+tutorial.geometry.data.description: Add the vertex array {emphasis}. Keep the subsequent resource initialization
+ before this callback as well, in the order presented.
+tutorial.geometry.data.description.emphasis: before {windowRender}
+tutorial.geometry.data.details: 'Each entry constructs a {vertex}; the two inner {new} expressions provide its {vector3}
+ position and {vector4} color. The positions form the upper, lower-right, and lower-left corners. The vertex shader
+ adds {w} as the fourth position coordinate. With w equal to 1, x and y are also normalized device coordinates:
+ -1 and +1 mark the image boundaries, and 0 marks the center. Their interpretation is independent of pixel dimensions.'
+tutorial.geometry.data.guidance: Each color has red, green, blue, and alpha components, with alpha set to 1. All
+ three vertices have z = 0 and lie in the same plane.
+tutorial.geometry.data.title: Vertex positions and colors
+tutorial.geometry.description: Each of the triangle's three vertices has a position and a color. The GPU uses the
+ positions to determine the triangle's shape and interpolates the colors across its surface.
+tutorial.geometry.format.description: 'Append the vertex declaration {emphasis}:'
+tutorial.geometry.format.description.emphasis: at the end of {programCs}, after the cleanup statements
+tutorial.geometry.format.details: '{file} limits the type to {programCs}, and {layoutKindSequential} keeps its fields
+ in declaration order. {position} contains three 32-bit floats and starts at byte offset 0; {color} contains four
+ and starts at byte offset 12. Each vertex therefore occupies 28 bytes. The input layout will describe this arrangement
+ so the GPU can read the fields correctly.'
+tutorial.geometry.format.imports: 'Add these using directives at the top of {programCs} for the vector types and the struct layout attribute:'
+tutorial.geometry.format.title: Vertex representation
+tutorial.geometry.title: Vertex data
+tutorial.geometry.upload.context: '{fixed} prevents the garbage collector from moving the array while its address
+ is in use. For this CPU-writable buffer, {upload} maps the memory, copies the bytes, and unmaps it before returning,
+ so the array only needs to remain pinned during the call. Upload these vertices once during initialization, and
+ keep the buffer alive until rendering has finished.'
+tutorial.geometry.upload.description: 'Store the vertices in a {buffer} so the GPU can read them during drawing.
+ Add the allocation and upload immediately after the array:'
+tutorial.geometry.upload.details: "The allocation size is {sizeofVertexVerticesLength}: three 28-byte vertices,
+ or 84 bytes. {bufferUsagesVertex} declares vertex-input access, while {cpuWriteOnly} permits direct initialization
+ by the CPU. The descriptor's stride records the element size. The input layout defined below specifies how vertex
+ fetching interprets each attribute."
+tutorial.geometry.upload.guidance: Allocation does not initialize the buffer's contents. {upload} copies the array
+ to destination byte offset 0 using the source pointer and byte count supplied in the data description.
+tutorial.geometry.upload.imports: 'In {programCs}, add {bufferAlias} to the using directives to distinguish the Zenith.NET buffer type from {systemBuffer}.'
+tutorial.geometry.upload.notes: Add {vertexBufferDispose} immediately before {swapChainDispose} in the cleanup at
+ the end of {programCs}. For larger static geometry, {link} discusses GPU-only storage and transfer uploads.
+tutorial.geometry.upload.notes.link: Resource Management
+tutorial.geometry.upload.title: Buffer allocation and upload
+tutorial.input.description: 'The GPU needs to know where each vertex attribute, such as position or color, is stored.
+ The input layout supplies its format and byte offset. It also sets the stride: the number of bytes from one vertex
+ to the next. Add this code after creating the shaders:'
+tutorial.input.details: '{inputLayoutAdd} appends each attribute after the previous one and adds its size to the
+ stride. The offsets come from the formats supplied here, rather than from inspection of the C# struct. For these
+ two fields, the resulting layout is:'
+tutorial.input.guidance: The default {semanticIndex} is zero, matching {pOSITION0} and {cOLOR0} in the shader. Position
+ and color are stored together in one vertex buffer, so the pipeline uses one input layout at slot 0. Keep this
+ layout consistent with the C# struct if its field order or padding changes.
+tutorial.input.table.headings.byteOffset: Byte offset
+tutorial.input.table.headings.cField: C# field
+tutorial.input.table.headings.format: Format
+tutorial.input.table.headings.shaderInput: Shader input
+tutorial.input.table.nextVertex.cField: Next vertex
+tutorial.input.table.nextVertex.format: 'Total stride: 28 bytes'
+tutorial.input.table.nextVertex.shaderInput: Next input record
+tutorial.input.table.vertexColor.format: '{float4} (16 bytes)'
+tutorial.input.table.vertexPosition.format: '{float3} (12 bytes)'
+tutorial.input.title: Vertex input layout
+tutorial.lifecycle.description: 'Each frame follows this sequence:'
+tutorial.lifecycle.details: 'When the window closes, {run} returns and execution continues to the cleanup code.
+ Each frame has already waited for its submitted work to finish, so the resources used by those commands can now
+ be released. The complete cleanup after {windowRun} is:'
+tutorial.lifecycle.guidance: Release the swap chain before destroying its native window, and release the context
+ after its resources. Submitted command buffers are recycled by the queue, and the drawable is managed by the swap
+ chain, so neither needs a separate {dispose} call.
+tutorial.lifecycle.title: Frame lifecycle and cleanup
+tutorial.pipeline.behavior: 'After the input layout, create the pipeline:'
+tutorial.pipeline.context: '{cullNone} disables face culling, so the triangle is drawn whether its vertices appear
+ clockwise or counterclockwise.'
+tutorial.pipeline.description: A {graphicsPipeline} brings together the vertex layout, the two shaders, and the
+ settings that determine how primitives are assembled and written to attachments.
+tutorial.pipeline.details: 'The shaders process vertices and fragments. The other pipeline settings control primitive
+ assembly, face culling, depth testing, and blending:'
+tutorial.pipeline.guidance: The pipeline's color format must match the render-pass attachment, so it is taken from
+ {swapChainDescFormat}. {count1} specifies one sample per pixel.
+tutorial.pipeline.item: '{triangleList} assembles each consecutive group of three vertices into a triangle.'
+tutorial.pipeline.notes: '{depthNone} disables depth testing and depth writes, consistent with the absence of a
+ depth attachment.'
+tutorial.pipeline.reference: "{opaque} disables blending. The fragment shader's output therefore replaces the background
+ color inside the triangle."
+tutorial.pipeline.title: Graphics pipeline
+tutorial.pipeline.usage:
+ Shader objects may be released after pipeline creation. Keep the pipeline alive while rendering,
+ and add {pipelineDispose} before {vertexBufferDispose} in the cleanup.
+tutorial.presentation.title: Graphics context and presentation
+tutorial.project.context: Enable {emphasis} ({allowUnsafeBlocks}) in the project's build settings. Native calls
+ in the window helper and the vertex upload require unsafe code.
+tutorial.project.context.emphasis: Allow unsafe code
+tutorial.project.description: Create a C# console project named {firstTriangle} targeting .NET 10.
+tutorial.project.details: 'Add the following NuGet packages to the project:'
+tutorial.project.guidance: '{zenithNET} provides the shared rendering API, and the three backend packages supply
+ its platform implementations. {zenithNETCompiler} compiles the Slang shaders introduced below for the selected
+ backend. Silk.NET provides window creation and event processing.'
+tutorial.project.title: Project setup
+tutorial.requirements.context: The {link} and its {detail} provide reference implementations for this tutorial.
+tutorial.requirements.context.detail: shared host
+tutorial.requirements.context.link: Hello Triangle sample
+tutorial.requirements.description: 'You need the {emphasis} and basic C# knowledge. The application targets {net10}.
+ Your GPU and driver must support the backend selected for your platform:'
+tutorial.requirements.description.emphasis: .NET 10 SDK
+tutorial.requirements.guidance: Zenith.NET renders into a native surface provided by the application. Silk.NET.Windowing
+ creates the window and processes its events. On macOS, presentation uses a Metal layer, represented by {cAMetalLayerCs};
+ the window helper attaches it to the window.
+tutorial.requirements.table.headings.graphicsBackend: Graphics backend
+tutorial.requirements.table.headings.platform: Platform
+tutorial.requirements.table.linux.graphicsBackend: Vulkan 1.4
+tutorial.requirements.table.linux.platform: Linux (X11 or XWayland)
+tutorial.requirements.table.macOS.graphicsBackend: Metal 4
+tutorial.requirements.table.macOS.platform: macOS
+tutorial.requirements.table.windows.graphicsBackend: DirectX 12
+tutorial.requirements.table.windows.platform: Windows
+tutorial.requirements.title: Prerequisites
+tutorial.resize.context: 'Build and run the application again. The background should continue to fill the window
+ when you resize it: each new render pass sets its viewport and scissor from the resized attachment. Keep the render
+ callback in place; the triangle will be drawn inside this pass.'
+tutorial.resize.description:
+ "The swap chain must follow changes to the framebuffer's size. Register a {framebufferResize}
+ callback after the render callback and before {windowRun}:"
+tutorial.resize.details: Listen for {link}, which are measured in pixels, rather than changes to the window's logical
+ size. If minimizing the window sets either framebuffer dimension to zero, the resize callback leaves the swap
+ chain unchanged. The render callback also returns while either dimension is zero, pausing rendering until the
+ framebuffer has a usable size again.
+tutorial.resize.details.link: framebuffer-size changes
+tutorial.resize.guidance: The render callback waits for each submitted frame to finish before calling {present}.
+ The resize callback can therefore replace the swap-chain images without interrupting work from an earlier frame.
+ The {link} article explains these completion and resource-lifetime requirements in more detail.
+tutorial.resize.guidance.link: Synchronization
+tutorial.resize.title: Resizing
+tutorial.shaders.compilation.context: Using {appContextBaseDirectory} loads the copied shader from the application's
+ output directory, so launching the application from another working directory does not change where it looks for
+ the file.
+tutorial.shaders.compilation.description: For {triangleSlang}, set the file property {emphasis} to {detail}. The
+ build will then copy the shader beside the executable.
+tutorial.shaders.compilation.description.detail: Copy if newer
+tutorial.shaders.compilation.description.emphasis: Copy to Output Directory
+tutorial.shaders.compilation.details: 'Return to {programCs} and add shader initialization after the vertex upload,
+ before the render callback:'
+tutorial.shaders.compilation.guidance:
+ '{zenithCompiler} compiles the source for {contextGraphicsApi}, and {createShader}
+ creates a shader object from the compiled description. The entry-point names {vSMain} and {fSMain} select the
+ two functions defined in the Slang file.'
+tutorial.shaders.compilation.title: Compiling the shaders
+tutorial.shaders.context: '{fSInput} connects the two shader stages. The vertex shader writes a clip-space position
+ to {sVPOSITION}; the GPU uses this position to determine which part of the triangle is visible. The {cOLOR0} field
+ carries the vertex color, which is interpolated across the triangle before reaching the fragment shader. Its type
+ and semantic must agree between the two stages.'
+tutorial.shaders.description: Shaders are programs that run on the GPU. The vertex shader determines each vertex's
+ position, and the fragment shader determines the color at each covered image location. Both stages will be written
+ in one Slang file, with a separate entry point—the function where execution begins—for each stage.
+tutorial.shaders.details: 'Create {triangleSlang} with these input and output structures:'
+tutorial.shaders.fragment.description: 'Append the fragment entry point to the same file:'
+tutorial.shaders.fragment.details: Rasterization determines where the triangle covers the image and generates fragments
+ for those locations. Each fragment receives a color interpolated from the three vertex colors. {fSMain} returns
+ this color, and {sVTARGET} directs the result to the first color attachment.
+tutorial.shaders.fragment.title: Fragment shading
+tutorial.shaders.guidance: "{vSInput} defines the vertex shader's inputs. {pOSITION0} and {cOLOR0} identify the
+ attributes by semantic; the C# field names alone do not establish this mapping."
+tutorial.shaders.title: Shader stages
+tutorial.shaders.vertex.description: 'Append the vertex entry point to {triangleSlang}:'
+tutorial.shaders.vertex.details: The GPU invokes this entry point for each input vertex. It adds the homogeneous
+ coordinate {w} and forwards the color. After vertex processing, position is divided by w. Since w is 1 here, x,
+ y, and z are unchanged and may be interpreted directly as normalized device coordinates.
+tutorial.shaders.vertex.title: Vertex shading
+tutorial.surface.description: 'Specify the native presentation target {emphasis}:'
+tutorial.surface.description.emphasis: after {windowCenter} and before {windowRun}
+tutorial.surface.details: The framebuffer is the window's image in pixels. {surface} describes the native presentation
+ target and its dimensions, and {framebufferSize} gives the size in pixels. With display scaling, this can differ
+ from the window's logical size.
+tutorial.surface.guidance: 'The surface supplies the native handles needed by the selected backend: a Win32 window
+ on Windows, an X11 display and window on Linux, or the attached Metal layer on macOS.'
+tutorial.surface.title: Native surface
+tutorial.swapChain.context: 'The swap chain depends on both the context and the window. Replace {windowDispose}
+ at the end of {programCs} with this disposal order:'
+tutorial.swapChain.description: 'Create the swap chain immediately after the surface description:'
+tutorial.swapChain.details: The {swapChain} manages the images presented by the window. {drawable} supplies the
+ current image as a {texture}, the API's image resource. After rendering, {present} requests display of that image
+ and advances to the next drawable.
+tutorial.swapChain.guidance: '{b8G8R8A8UNorm} stores four 8-bit channels in blue, green, red, and alpha order. {uNorm}
+ maps integer values 0–255 to values 0–1. Shader output remains a logical RGBA value; the format specifies its
+ storage representation. The pipeline will use {swapChainDescFormat} to match this attachment.'
+tutorial.swapChain.title: Swap chain
+tutorial.title: First Triangle
+tutorial.troubleshooting.description: 'Use console diagnostics and the three checkpoints to identify the failing
+ stage:'
+tutorial.troubleshooting.details: The {link} and {detail} provide a reference for the vertex data, input layout,
+ and draw operation. That example separates windowing and submission into a shared host; this tutorial retains
+ the application flow in {programCs}. {context} introduces indexed drawing, transformations, and depth testing.
+tutorial.troubleshooting.details.context: Spinning Cube
+tutorial.troubleshooting.details.detail: Slang shader
+tutorial.troubleshooting.details.link: sample renderer
+tutorial.troubleshooting.table.backend.check: Check that the selected backend is supported by the device and installed
+ graphics driver.
+tutorial.troubleshooting.table.backend.symptom: Backend creation fails
+tutorial.troubleshooting.table.drawing.check: Check the upload size, input layout, pipeline selection and the placement
+ of the draw commands inside the render pass.
+tutorial.troubleshooting.table.drawing.symptom: The background appears but the triangle does not
+tutorial.troubleshooting.table.headings.check: Check
+tutorial.troubleshooting.table.headings.symptom: Symptom
+tutorial.troubleshooting.table.input.check: Check the 28-byte stride and the position/color offsets of 0 and 12.
+tutorial.troubleshooting.table.input.symptom: Vertex positions or colors are corrupted
+tutorial.troubleshooting.table.shaders.check: Check the copied {triangleSlang}, entry-point spelling and the compiler's
+ reported diagnostic.
+tutorial.troubleshooting.table.shaders.symptom: The clear-color step works, but shader loading fails
+tutorial.troubleshooting.table.window.check: Confirm the .NET project builds and the window-only step runs in a
+ desktop session. On Linux, check that X11 or XWayland is available.
+tutorial.troubleshooting.table.window.symptom: No window opens
+tutorial.troubleshooting.title: Troubleshooting
+tutorial.window.description: 'Download {cocoaHelperCs} into the project. This helper class attaches the Metal layer to the macOS window.'
+tutorial.window.details: 'Replace the generated {programCs} with the code below. It uses top-level statements, which
+ run in order without an explicit {main} method. Each later section will show where to add its code:'
+tutorial.window.guidance: '{graphicsAPINone} leaves graphics-context creation to Zenith.NET. {initialize} creates
+ the native window and makes its window-system handles available. {center} places the window in the center of the
+ screen. {run} processes events until the window closes, then execution continues to {dispose} to release it.'
+tutorial.window.notes: Build and run the application, then check that the window opens, moves, resizes, and closes.
+ Its contents are not yet defined because no rendering commands have been submitted. Close the window before continuing.
+tutorial.window.title: Window creation
+ui.action.copy: Copy
+ui.action.navigate: Navigate
+ui.action.open: Open
+ui.action.source: View source
+ui.api.browse: Browse namespaces and types.
+ui.api.clearTypeSearch: Clear type search
+ui.api.findType: Find a type
+ui.api.loadError: Could not load types. Reload this page to try again.
+ui.api.loadingNamespaces: Loading namespaces…
+ui.api.loadingTypes: Loading types…
+ui.api.matchingTypes: Matching types
+ui.api.namespace: Namespace
+ui.api.namespaces: Namespaces
+ui.api.noNamespaceTypes: No types in this namespace.
+ui.api.noTypes: No matching types.
+ui.api.types: Types
+ui.content.reference: Reference
+ui.content.source: Source code
+ui.content.tutorial: Tutorial
+ui.contents.close: Close contents
+ui.contents.open: Open contents
+ui.contents.title: Contents
+ui.footer.caption: Modern RHI for .NET
+ui.footer.label: Footer
+ui.language.label: Language
+ui.language.unavailable: Not translated
+ui.members.clear: Clear member search
+ui.members.close: Close member finder
+ui.members.empty: No matching members.
+ui.members.open: Go to member
+ui.members.placeholder: Name or signature…
+ui.members.search: Search members
+ui.members.title: Find a member
+ui.members.undeclared: This type has no declared members.
+ui.navigation.api: API
+ui.navigation.concepts: Concepts
+ui.navigation.home: Home
+ui.navigation.label: Main navigation
+ui.navigation.skip: Skip to content
+ui.navigation.toggle: Toggle navigation
+ui.outline.title: On this page
+ui.reference.assembly: Assembly
+ui.reference.classes: Classes
+ui.reference.constructors: Constructors
+ui.reference.declaration: C# type declaration
+ui.reference.delegates: Delegates
+ui.reference.derived: Derived types
+ui.reference.enums: Enums
+ui.reference.events: Events
+ui.reference.examples: Examples
+ui.reference.exceptions: Exceptions
+ui.reference.extensionMethods: Extension methods
+ui.reference.fields: Fields
+ui.reference.implementations: Explicit interface implementations
+ui.reference.implements: Implements
+ui.reference.inheritance: Inheritance
+ui.reference.inheritedMembers: Inherited members
+ui.reference.interfaces: Interfaces
+ui.reference.metadata: Type metadata
+ui.reference.methods: Methods
+ui.reference.namespace: Namespace
+ui.reference.open: Open reference for {name}
+ui.reference.operators: Operators
+ui.reference.parameters: Parameters
+ui.reference.properties: Properties
+ui.reference.related: See also
+ui.reference.relationships: Inheritance and metadata
+ui.reference.remarks: Remarks
+ui.reference.returns: Returns
+ui.reference.structs: Structs
+ui.reference.typeParameters: Type parameters
+ui.reference.value: Value
+ui.reference.values: Values
+ui.search.close: Close search
+ui.search.empty: No results for “{query}”.
+ui.search.hint: Search tutorials, concepts, samples, and the API reference.
+ui.search.loading: Loading search…
+ui.search.more: Show more results
+ui.search.pending: Searching…
+ui.search.placeholder: What are you looking for?
+ui.search.resultCount: 'Results: {count}'
+ui.search.title: Search documentation
+ui.search.unavailable: Search is unavailable. You can still open the pages below.
diff --git a/documents/locales/es-ES/strings.yml b/documents/locales/es-ES/strings.yml
new file mode 100644
index 00000000..768e023d
--- /dev/null
+++ b/documents/locales/es-ES/strings.yml
@@ -0,0 +1,593 @@
+### YamlMime:Resources
+concepts.execution.context.description: 'Un {graphicsContext} conecta la aplicación con un dispositivo GPU mediante un backend concreto y proporciona sus colas gráfica, de cálculo y de transferencia. Los recursos que se utilizan juntos deben pertenecer al mismo contexto; no se pueden enlazar búferes, texturas, sombreadores, canalizaciones ni sus identificadores entre contextos distintos.'
+concepts.execution.context.details: '{capabilities} indica el nombre del dispositivo y las funciones que admite. Compruebe {rayTracingSupported} y {meshShadingSupported} antes de crear los recursos que necesitan esas funciones. Los ejemplos de {link} y {detail} realizan estas comprobaciones durante la inicialización.'
+concepts.execution.context.details.detail: 'sombreado de mallas'
+concepts.execution.context.details.link: 'trazado de rayos'
+concepts.execution.context.title: 'Contexto gráfico'
+concepts.execution.description: 'El trabajo de la GPU se prepara en la CPU: la aplicación crea recursos, graba comandos que los utilizan y envía esos comandos a una cola. Tras el envío, la CPU puede continuar mientras la GPU ejecuta el trabajo. Los recursos deben seguir disponibles hasta que esa ejecución termine.'
+concepts.execution.ownership.description: 'La propiedad establece quién libera un objeto; la finalización en la GPU determina cuándo puede hacerlo. Una llamada a {dispose} libera los recursos nativos aunque aún existan referencias de C# al objeto. Enviar comandos no transfiere a la cola la propiedad de los recursos que utilizan.'
+concepts.execution.ownership.details: 'Al cerrar la aplicación, deje de enviar trabajo y espere al último uso de los recursos en cada cola implicada. Después, libere los recursos de la aplicación y el contexto. El contexto libera sus colas y auxiliares internos, por lo que este último paso debe realizarse cuando todo el trabajo de la aplicación haya terminado.'
+concepts.execution.ownership.guidance: 'Durante el renderizado, lleve un seguimiento del trabajo que utiliza datos que se van a sobrescribir o recursos que se van a sustituir. Espere a que termine antes de modificar o liberar el almacenamiento afectado. {link} explica cómo se aplica este modelo de vida útil a las vistas y a los recursos que las respaldan.'
+concepts.execution.ownership.guidance.link: 'Administración de recursos'
+concepts.execution.ownership.table.applicationResources.object: 'Recursos creados por la aplicación'
+concepts.execution.ownership.table.applicationResources.ownership: 'La aplicación los libera después de su último uso en la GPU.'
+concepts.execution.ownership.table.commandBuffers.object: 'Búferes de comandos prestados'
+concepts.execution.ownership.table.commandBuffers.ownership: 'La cola los gestiona y los recicla.'
+concepts.execution.ownership.table.headings.object: 'Objeto'
+concepts.execution.ownership.table.headings.ownership: 'Responsabilidad de liberación'
+concepts.execution.ownership.table.queues.object: 'Colas de contexto y sus líneas de tiempo'
+concepts.execution.ownership.table.queues.ownership: 'El contexto y las colas las gestionan.'
+concepts.execution.ownership.table.swapChainDrawable.ownership: 'La cadena de intercambio lo gestiona.'
+concepts.execution.ownership.title: 'Propiedad y tiempo de vida'
+concepts.execution.pipelines.description: 'Una canalización gráfica define los sombreadores, la disposición de entrada de vértices, el ensamblado de primitivas, los formatos de adjunto y el estado de renderizado. Se selecciona antes de enlazar vértices, índices o constantes. Los formatos y el número de muestras deben coincidir con la pasada de renderizado.'
+concepts.execution.pipelines.details: 'Una canalización de cálculo selecciona el punto de entrada del sombreador que ejecutará un despacho. Enlace la canalización y sus constantes antes del despacho, fuera de una pasada de renderizado gráfico.'
+concepts.execution.pipelines.guidance: 'La {link} presenta la secuencia gráfica. El {detail} incorpora un despacho de cálculo antes de la pasada que muestra su resultado.'
+concepts.execution.pipelines.guidance.detail: 'ejemplo de cálculo'
+concepts.execution.pipelines.guidance.link: 'función de renderizado del triángulo'
+concepts.execution.pipelines.title: 'Estado de la canalización'
+concepts.execution.queues.description: '{graphicsQueue} es el punto de partida habitual para un fotograma que combina dibujo, cálculo y copias. Agrupar operaciones dependientes en una cola simplifica su ordenación.'
+concepts.execution.queues.details: 'Las colas permiten enviar cargas de trabajo independientes por separado, aunque no necesariamente corresponden a motores de hardware independientes. Use colas adicionales cuando resulte útil solapar la ejecución. Si una cola utiliza datos producidos por otra, conecte sus envíos mediante una dependencia explícita.'
+concepts.execution.queues.guidance: 'Dentro de una cola, las operaciones dependientes también pueden necesitar barreras o transiciones de textura para que los accesos posteriores vean las escrituras anteriores. Estas dependencias de memoria son distintas del orden de envío; {link} explica cómo expresarlas.'
+concepts.execution.queues.guidance.link: 'Sincronización'
+concepts.execution.queues.table.computeQueue.workToRecord: 'Despachos de cálculo y transferencias compatibles, sin pasadas gráficas.'
+concepts.execution.queues.table.graphicsQueue.workToRecord: 'Pasadas de renderizado, dibujo, cálculo y transferencias.'
+concepts.execution.queues.table.headings.queue: 'Cola'
+concepts.execution.queues.table.headings.workToRecord: 'Operaciones admitidas'
+concepts.execution.queues.table.transferQueue.workToRecord: 'Cargas, descargas y copias, sin dibujo ni despachos de cálculo.'
+concepts.execution.queues.title: 'Selección de colas'
+concepts.execution.recording.context: 'La cola recupera los búferes de comandos completados para reutilizarlos. Obtenga uno con {queueCommandBuffer} para cada grabación, grabe desde un solo hilo a la vez y envíelo una única vez. Después del envío, deje que la cola lo gestione: no lo modifique, vuelva a enviar ni libere.'
+concepts.execution.recording.description: '{queueCommandBuffer} devuelve un {commandBuffer} cuya grabación ya está iniciada. Tras grabar las operaciones y sus dependencias, se llama a {submit} una vez. El envío cierra la grabación y devuelve un {timelineValue} que identifica su finalización en esa cola.'
+concepts.execution.recording.details: 'Tras grabar un fotograma en {commandBuffer}, el tutorial lo envía y espera de este modo:'
+concepts.execution.recording.guidance: 'La grabación construye la secuencia de comandos; {submit} programa su ejecución. La llamada posterior a {wait} bloquea el hilo de la CPU hasta que termina ese envío. Así, el bucle del tutorial resulta fácil de seguir: cada fotograma termina antes de comenzar el siguiente.'
+concepts.execution.recording.title: 'Grabación y envío'
+concepts.execution.title: 'Modelo de ejecución'
+concepts.platforms.description: 'La presentación puede gestionarla la aplicación o un control de interfaz. Esta elección determina quién obtiene la textura de salida del fotograma, registra y envía los comandos y presenta el resultado. Esa textura de salida se denomina drawable.'
+concepts.platforms.frame.context: 'Grabe los enlaces de canalización y los comandos de dibujo dentro de la pasada y termínela antes de que retorne el manejador. El control envía los comandos y presenta el resultado. El manejador no debe enviar el búfer de comandos ni liberar ninguno de los objetos recibidos. Si cambia la disposición de la textura de salida, restáurela a {colorAttachment} antes de retornar para que el control pueda realizar la transición final.'
+concepts.platforms.frame.description: 'Los controles implementan {iZenithView}. La aplicación asigna {graphicsContext}, actualiza la escena en {updateRequested} y graba en {renderRequested} mediante los {renderEventArgs} recibidos.'
+concepts.platforms.frame.details: 'En cada fotograma, el control obtiene una textura de salida y un búfer de comandos, cambia la disposición de la textura a {colorAttachment} y llama a los manejadores de actualización y renderizado. Después envía el trabajo grabado y presenta la imagen mediante el mecanismo del framework.'
+concepts.platforms.frame.guidance: 'Añada {usingZenithNET} al archivo. Para una instancia de {iZenithView} llamada {view}, ya inicializada y con un contexto gráfico asignado, registre este manejador del evento de renderizado para borrar la textura de salida que proporciona el control:'
+concepts.platforms.frame.notes: 'La transición inicial utiliza {undefined}, que permite descartar el contenido anterior. Dibuje o borre la imagen completa en cada fotograma. Guarde el renderizado acumulado o en caché en una textura propia de la aplicación y dibújela después sobre la textura de salida del control.'
+concepts.platforms.frame.reference: '{zenithViewHelperDrawableFormat} proporciona el formato de color predeterminado de estas integraciones.'
+concepts.platforms.frame.title: 'Renderizado mediante controles de interfaz'
+concepts.platforms.integration.context: 'WinForms, WPF y la integración Windows de WinUI se ejecutan en Windows. Para MAUI, instale la carga de trabajo de la plataforma de destino y registre el controlador con {useZenithView}, como se muestra en {extensionsCs}.'
+concepts.platforms.integration.description: 'La interfaz de eventos es común, pero la presentación depende del framework:'
+concepts.platforms.integration.details: 'Estas implementaciones difieren en cómo llevan la imagen de la GPU a la interfaz. La lectura como mapa de bits copia los píxeles a la memoria de la CPU antes de mostrarlos. La interoperabilidad mediante texturas compartidas evita esa copia, pero aún puede necesitar copias en la GPU y sincronización. Al elegir una integración, considere este mecanismo de presentación además del evento común {renderRequested}.'
+concepts.platforms.integration.guidance: 'Añada mediante NuGet el paquete de integración de su framework de interfaz. Para Avalonia, utilice {avaloniaPackage}.'
+concepts.platforms.integration.table.avalonia.integration: 'Avalonia'
+concepts.platforms.integration.table.avalonia.presentation: 'Renderiza en una textura y descarga píxeles en un {writeableBitmap}. Consulte {zenithViewCs} y {surfaceCs}.'
+concepts.platforms.integration.table.headings.integration: 'Integración'
+concepts.platforms.integration.table.headings.presentation: 'Mecanismo de presentación'
+concepts.platforms.integration.table.maui.integration: '.NET MAUI'
+concepts.platforms.integration.table.maui.presentation: 'Utiliza superficies nativas en Android, iOS y Mac Catalyst, y composición de texturas compartidas en Windows. Consulte las {link}.'
+concepts.platforms.integration.table.maui.presentation.link: 'implementaciones de plataforma'
+concepts.platforms.integration.table.uno.integration: 'Ruta de Uno'
+concepts.platforms.integration.table.uno.presentation: 'Descarga píxeles en un {writeableBitmap}. Consulte {zenithViewUnoCs}.'
+concepts.platforms.integration.table.winForms.integration: 'WinForms'
+concepts.platforms.integration.table.winForms.presentation: 'Crea una cadena de intercambio de Zenith.NET para el {hWND} del control. Consulte {zenithViewCs}.'
+concepts.platforms.integration.table.winUI.integration: 'WinUI en Windows'
+concepts.platforms.integration.table.winUI.presentation: 'Renderiza en una textura compartida de Direct3D 11 importada y copia el resultado a una cadena de intercambio de composición conectada a {swapChainPanel}. Consulte {zenithViewWinUICs}.'
+concepts.platforms.integration.table.wpf.integration: 'WPF'
+concepts.platforms.integration.table.wpf.presentation: 'Importa una textura compartida de Direct3D 11 y copia su contenido, mediante interoperabilidad con Direct3D, a una superficie que muestra {d3DImage}. Consulte {surfaceCs}.'
+concepts.platforms.integration.title: 'Framework y presentación'
+concepts.platforms.lifecycle.context: 'Al crear adjuntos compatibles, utilice {descWidth}, {descHeight}, {descFormat} y {descSampleCount} de la textura de salida que proporciona el evento de renderizado. Esa textura indica las dimensiones reales del destino; no deben deducirse del tamaño de diseño del control ni de la escala de pantalla.'
+concepts.platforms.lifecycle.description: 'La cadena de intercambio utiliza el ancho y el alto en píxeles del búfer de fotogramas, que pueden diferir del tamaño lógico de la ventana cuando hay escalado de pantalla. Aplace el renderizado y el redimensionamiento de la cadena mientras alguna dimensión sea cero. Obtenga {swapChainDrawable} en cada fotograma: la cadena de intercambio posee esta textura y puede sustituirla tras presentar o redimensionar.'
+concepts.platforms.lifecycle.details: 'Si cambia el tamaño del búfer de fotogramas, espere a que terminen los usos pendientes de las imágenes anteriores en la GPU antes de llamar a {swapChainResize}. Para sustituir el destino nativo de presentación, espere del mismo modo y pase la nueva descripción de superficie y sus identificadores a {swapChainRefresh}. Al cerrar, libere la cadena de intercambio antes de destruir su destino nativo.'
+concepts.platforms.lifecycle.guidance: 'Si la aplicación tiene texturas de profundidad o fuera de pantalla que siguen el tamaño de la ventana, vuelva a crearlas cuando cambie ese tamaño y actualice las constantes que contengan sus identificadores. La canalización se puede reutilizar mientras sigan coincidiendo los formatos y el número de muestras de los adjuntos. El {link} muestra esta separación entre las texturas que se redimensionan y la canalización reutilizable.'
+concepts.platforms.lifecycle.guidance.link: 'renderizador de cubo giratorio'
+concepts.platforms.lifecycle.title: 'Dimensiones y vida útil de la cadena de intercambio'
+concepts.platforms.ownership.description: 'La aplicación posee el contexto asignado y sus recursos de renderizado; el control posee sus recursos de presentación. Cuando cambia {graphicsContext} en la vista, el planificador vuelve a crear los recursos del control para el nuevo contexto. Vuelva a crear también las canalizaciones, los búferes y las texturas de la aplicación para ese contexto.'
+concepts.platforms.ownership.details: 'Antes de sustituir recursos de renderizado, detenga la actividad de fotogramas de la vista y espere al trabajo de la GPU que los utiliza. Solo entonces libere los recursos o el contexto anteriores. Al retirar la vista, la aplicación sigue siendo responsable de liberar el contexto que tenía asignado.'
+concepts.platforms.ownership.guidance: 'El componente compartido {frameSchedulerCs} ejecuta los manejadores de eventos de cada fotograma mediante el distribuidor de la interfaz de la vista. Limite esos manejadores a actualizar la escena y grabar comandos: un trabajo prolongado en la CPU también retrasa la respuesta de la interfaz.'
+concepts.platforms.ownership.title: 'Responsabilidades de aplicación y control'
+concepts.platforms.surface.description: 'El {link} utiliza Silk.NET para crear la ventana y procesar sus eventos. Zenith.NET recibe los identificadores nativos y las dimensiones mediante una descripción {surface}, con la que crea una {swapChain}. La aplicación sigue siendo responsable de la ventana del sistema operativo.'
+concepts.platforms.surface.description.link: 'tutorial del triángulo'
+concepts.platforms.surface.details: 'Los identificadores nativos deben corresponder al backend:'
+concepts.platforms.surface.guidance: 'El programa anfitrión de {appCs} admite Windows, macOS y X11. En Linux, la ventana proporciona identificadores Xlib, por lo que necesita XWayland para ejecutarse en una sesión Wayland. Una aplicación nativa de Wayland proporciona los identificadores de pantalla y superficie mediante {surfaceWayland}.'
+concepts.platforms.surface.table.android.backend: 'Vulkan en Android.'
+concepts.platforms.surface.table.apple.backend: 'Metal en plataformas Apple.'
+concepts.platforms.surface.table.headings.backend: 'Backend y plataforma'
+concepts.platforms.surface.table.headings.factory: 'Fábrica de superficies'
+concepts.platforms.surface.table.headings.handles: 'Identificadores suministrados por el host'
+concepts.platforms.surface.table.wayland.backend: 'Vulkan con un host Wayland nativo.'
+concepts.platforms.surface.table.wayland.handles: 'Pantalla y superficie Wayland'
+concepts.platforms.surface.table.win32.backend: 'DirectX 12 o Vulkan en Windows.'
+concepts.platforms.surface.table.xlib.backend: 'Vulkan en Linux con X11 o XWayland.'
+concepts.platforms.surface.table.xlib.handles: 'Pantalla y ventana X11'
+concepts.platforms.surface.title: 'Superficies de presentación nativas'
+concepts.platforms.title: 'Integración de plataforma'
+concepts.resources.allocation.context: 'Los recursos colocados en un montículo dependen de su almacenamiento durante toda su vida útil. Después de su último uso en la GPU, libere los recursos antes de liberar el montículo.'
+concepts.resources.allocation.description: 'Use un {heap} cuando la aplicación necesite gestionar las asignaciones por sí misma. Un montículo proporciona almacenamiento donde se colocan los recursos en desplazamientos explícitos en bytes; la aplicación elige esos desplazamientos y lleva el registro de las regiones ocupadas.'
+concepts.resources.allocation.details: 'Para cada recurso, llame a {contextGetSizeAndAlignment} con la descripción de búfer o textura que se utilizará al crearlo. Reserve el tamaño devuelto en un desplazamiento múltiplo de la alineación indicada, dentro del montículo y sin solapar otra asignación en uso. Las texturas pueden necesitar relleno específico del backend; por eso, el número de píxeles y los bytes por píxel no bastan para calcular el tamaño de la asignación.'
+concepts.resources.allocation.guidance: 'La residencia de un búfer colocado en un montículo debe coincidir con la del montículo. Para almacenar texturas, use un montículo exclusivo de la GPU.'
+concepts.resources.allocation.title: 'Asignación en montículos'
+concepts.resources.bufferUsages.table.constant.role: 'Parámetros de sombreador leídos a través de un enlace de búfer de constantes.'
+concepts.resources.bufferUsages.table.headings.role: 'Rol'
+concepts.resources.bufferUsages.table.headings.usage: 'Uso'
+concepts.resources.bufferUsages.table.index.role: 'Índices utilizados por el dibujo indexado.'
+concepts.resources.bufferUsages.table.indirect.role: 'Argumentos leídos por comandos de dibujo o despacho indirectos.'
+concepts.resources.bufferUsages.table.none.role: 'No se declaran indicadores de uso del búfer.'
+concepts.resources.bufferUsages.table.storageReadOnly.role: 'Datos del búfer disponibles mediante un enlace de sombreador de solo lectura.'
+concepts.resources.bufferUsages.table.storageReadWrite.role: 'Datos del búfer disponibles mediante un enlace de sombreador de lectura y escritura.'
+concepts.resources.bufferUsages.table.transferDst.role: 'Almacenamiento de destino para una copia de GPU.'
+concepts.resources.bufferUsages.table.transferSrc.role: 'Datos de origen para una copia de GPU.'
+concepts.resources.bufferUsages.table.vertex.role: 'Atributos de vértice que leen los comandos de dibujo.'
+concepts.resources.bufferUsages.title: 'Usos de búfer'
+concepts.resources.description: 'La descripción de un recurso establece cómo puede utilizar la aplicación su almacenamiento. Por ejemplo, una textura que se usa como destino de renderizado, se muestrea en un sombreador y se copia a la CPU necesita los indicadores de uso de las tres operaciones. La ubicación de memoria determina el acceso de la CPU; la sincronización y la gestión de la vida útil determinan cuándo se pueden usar los datos o liberar el almacenamiento.'
+concepts.resources.memory.context: 'En los búferes accesibles desde la CPU, {map} proporciona la dirección desde la que se leen o escriben los datos. Ni el mapeo ni {unmap} esperan al trabajo de la GPU: antes de leer un resultado, la aplicación debe esperar a que termine la escritura que lo produce; antes de modificar esos mismos bytes, debe esperar a que terminen los accesos pendientes de la GPU.'
+concepts.resources.memory.description: 'El campo {residency} de {bufferDesc} establece el acceso de CPU previsto para la asignación:'
+concepts.resources.memory.details: 'La residencia expresa los requisitos de acceso de la CPU, también en sistemas donde CPU y GPU comparten memoria física. El triángulo utiliza {cpuWriteOnly} para inicializar directamente su pequeño búfer de vértices. Para geometría estática de mayor tamaño, puede ser más adecuado cargar los datos en almacenamiento exclusivo de la GPU.'
+concepts.resources.memory.guidance: 'Las texturas creadas directamente mediante el contexto utilizan almacenamiento de la GPU. Por eso, {textureDesc} no tiene un campo de residencia; la aplicación intercambia sus datos con la memoria de la CPU mediante cargas y descargas.'
+concepts.resources.memory.table.cpuReadOnly.cPUAccess: 'Solo lectura'
+concepts.resources.memory.table.cpuReadOnly.typicalRole: 'Datos producidos por la GPU para su lectura en la CPU'
+concepts.resources.memory.table.cpuWriteOnly.cPUAccess: 'Solo escritura'
+concepts.resources.memory.table.cpuWriteOnly.typicalRole: 'Datos proporcionados o actualizados por la CPU'
+concepts.resources.memory.table.gpuOnly.cPUAccess: 'Ninguno'
+concepts.resources.memory.table.gpuOnly.typicalRole: 'Geometría estática y datos de trabajo de la GPU'
+concepts.resources.memory.table.headings.cPUAccess: 'Acceso previsto de la CPU'
+concepts.resources.memory.table.headings.residency: 'Residencia'
+concepts.resources.memory.table.headings.typicalRole: 'Rol típico'
+concepts.resources.memory.title: 'Acceso de CPU y ubicación de memoria'
+concepts.resources.textureUsages.description: 'Los indicadores de transferencia describen si el recurso actúa como origen o destino de una copia en la GPU. La posibilidad de acceder directamente desde la CPU se decide por separado al elegir la ubicación de memoria.'
+concepts.resources.textureUsages.details: 'Los indicadores de uso declaran las funciones que puede desempeñar una textura. Para pasar de una a otra se deben cumplir los requisitos de {link}; además, el dispositivo elegido debe admitir esos usos para el formato de la textura.'
+concepts.resources.textureUsages.details.link: 'disposición y sincronización'
+concepts.resources.textureUsages.table.colorAttachment.role: 'Un adjunto de color en una pasada de renderizado.'
+concepts.resources.textureUsages.table.depthStencilAttachment.role: 'Un adjunto de profundidad y plantilla en una pasada de renderizado.'
+concepts.resources.textureUsages.table.headings.role: 'Rol'
+concepts.resources.textureUsages.table.headings.usage: 'Uso'
+concepts.resources.textureUsages.table.none.role: 'No se declaran indicadores de uso de la textura.'
+concepts.resources.textureUsages.table.sampled.role: 'Datos de textura leídos mediante un enlace de textura muestreada.'
+concepts.resources.textureUsages.table.storage.role: 'Datos de textura disponibles para lectura o escritura del sombreador mediante un enlace de textura de almacenamiento.'
+concepts.resources.textureUsages.table.transferDst.role: 'Almacenamiento de imagen de destino para una copia de GPU.'
+concepts.resources.textureUsages.table.transferSrc.role: 'Datos de imagen de origen para una copia de GPU.'
+concepts.resources.textureUsages.title: 'Usos de textura'
+concepts.resources.title: 'Administración de recursos'
+concepts.resources.transfers.context: 'Una descarga copia primero los datos de la GPU a un almacenamiento interno de lectura. La biblioteca los copia al destino de CPU indicado cuando recupera el búfer de comandos completado. La llamada a {wait} sobre el valor de envío de la descarga completa ambos pasos. Mantenga el destino asignado y la memoria administrada anclada hasta que esa llamada retorne.'
+concepts.resources.transfers.description: 'Las cargas copian datos de la CPU a un recurso de la GPU; las descargas copian datos del recurso a la memoria de la CPU. Los métodos de búferes y texturas completan la transferencia antes de retornar:'
+concepts.resources.transfers.details: 'Antes de llamar a estos métodos, resuelva los accesos en conflicto de otras colas. La espera interna del método abarca su propia transferencia y no establece una dependencia con otra cola. Cuando se copia directamente a través de memoria mapeada, corresponde al llamador esperar a los accesos de la GPU que entren en conflicto.'
+concepts.resources.transfers.guidance: 'Para enviar transferencias junto con otro trabajo de la GPU, grábelas con {commandBufferUpload} o {commandBufferDownload}. La memoria de origen de una carga y la de destino de una descarga tienen requisitos de vida útil distintos:'
+concepts.resources.transfers.item: 'Durante una llamada de carga, la biblioteca copia los datos de origen a un almacenamiento temporal de transferencia. Después, la GPU lee los datos desde ese almacenamiento, por lo que el puntero de origen de la aplicación solo debe permanecer válido hasta que retorna la llamada.'
+concepts.resources.transfers.notes: 'En las transferencias de texturas, el paso de fila es la distancia en bytes entre filas consecutivas de datos de la CPU; el paso de sección es la distancia entre secciones de profundidad consecutivas. Estos valores permiten copiar los datos entre su organización en la CPU y el almacenamiento de la textura en la GPU. {link} explica por qué terminar la copia en la GPU y entregar los datos al destino de una descarga son pasos distintos.'
+concepts.resources.transfers.notes.link: 'Sincronización'
+concepts.resources.transfers.table.bufferDownload.transfer: 'Para {cpuReadOnly}, copia directamente desde la memoria mapeada. En los demás casos, envía la descarga a la cola de transferencia y espera a que termine.'
+concepts.resources.transfers.table.bufferUpload.transfer: 'Para {cpuWriteOnly}, copia directamente a la memoria mapeada. En los demás casos, envía la carga a la cola de transferencia y espera a que termine.'
+concepts.resources.transfers.table.headings.method: 'Método'
+concepts.resources.transfers.table.headings.transfer: 'Comportamiento de transferencia'
+concepts.resources.transfers.table.textureTransfers.method: '{textureUpload} / {textureDownload}'
+concepts.resources.transfers.table.textureTransfers.transfer: 'Envía la copia y las transiciones de textura a la cola gráfica y espera a que terminen. El subrecurso seleccionado queda en la disposición final solicitada.'
+concepts.resources.transfers.title: 'Transferencias inmediatas y grabadas'
+concepts.resources.usages.description: '{bufferUsages} y {textureUsages} son enumeraciones de indicadores que declaran los usos de un recurso. Se asignan a {bufferDescUsages} y {textureDescUsages}, respectivamente. Para varios usos, se combinan los miembros necesarios mediante {value}.'
+concepts.resources.usages.title: 'Usos de recursos'
+concepts.resources.views.description: 'Una {bufferView} describe una región de un búfer y cómo se interpretan sus datos. Una {textureView} selecciona un intervalo de subrecursos y un tipo y formato de textura compatibles. Ambas se refieren al almacenamiento existente, con los usos declarados al crearlo. Los intervalos de las vistas describen el acceso, pero no garantizan una comprobación de límites uniforme en los sombreadores de todos los backends.'
+concepts.resources.views.details: 'El identificador del propio recurso proporciona su vista predeterminada. Cree otra vista para seleccionar una región o una interpretación compatible diferente. Mantenga vivos tanto la vista como el recurso que la respalda hasta que la GPU termine de utilizarlos; un identificador no conserva ninguno de los dos objetos. Si sustituye alguno, actualice las constantes que contengan el {link} anterior.'
+concepts.resources.views.details.link: 'identificador de recurso'
+concepts.resources.views.title: 'Vistas y recursos subyacentes'
+concepts.shaders.binding.context: 'El ejemplo asigna {inputTextureSampledHandle} a {input} y {outputTextureStorageHandle} a {output}. En Slang, el parámetro de tipo de {descriptorHandle} determina cómo se accede al recurso:'
+concepts.shaders.binding.description: '{setConstantBuffer} selecciona un búfer existente y el desplazamiento de byte desde el que la canalización actual lee sus constantes. Cargue los parámetros en ese almacenamiento antes de que los utilice el sombreador. Si un trabajo anterior de la GPU todavía lee esos bytes, espere a que termine antes de actualizarlos.'
+concepts.shaders.binding.details: 'Con el búfer de comandos, la canalización de cálculo, el búfer de constantes cargado y el número de grupos de hilos preparados, grabe el despacho en este orden. Seleccione primero la canalización, ya que el enlace del búfer de constantes se aplica a ella:'
+concepts.shaders.binding.guidance: 'Aquí, {commandBuffer} graba un despacho con {computePipeline} y los parámetros almacenados en {constantBuffer}. El búfer necesita el uso {bufferUsagesConstant}, y el desplazamiento 0 selecciona su comienzo. Si varios registros de constantes comparten un búfer, el desplazamiento inicial de cada registro debe cumplir también la alineación de enlace del backend, con independencia de la disposición de sus campos internos.'
+concepts.shaders.binding.notes: '{resourceHandle} contiene dos campos de 32 bits cuya interpretación depende del backend. Sus valores solo son válidos en el contexto donde se creó el recurso.'
+concepts.shaders.binding.reference: 'El recurso y su vista deben conservarse hasta el último acceso del sombreador. Sustituir una textura o vista exige actualizar las constantes con el identificador anterior. Las reglas de propiedad se detallan en {link}.'
+concepts.shaders.binding.reference.link: 'Administración de recursos'
+concepts.shaders.binding.table.bufferStorageReadOnlyHandle.access: 'Lectura de elementos estructurados del tipo {t} del sombreador'
+concepts.shaders.binding.table.bufferStorageReadWriteHandle.access: 'Lectura y escritura de elementos estructurados del tipo {t} del sombreador'
+concepts.shaders.binding.table.headings.access: 'Acceso previsto'
+concepts.shaders.binding.table.headings.cHandle: 'Identificador de C#'
+concepts.shaders.binding.table.headings.slangFieldType: 'Tipo de campo Slang'
+concepts.shaders.binding.table.samplerHandle.access: 'Estado de muestreo de texturas'
+concepts.shaders.binding.table.textureSampledHandle.access: 'Lectura de texturas'
+concepts.shaders.binding.table.textureStorageHandle.access: 'Lectura y escritura de texturas de almacenamiento'
+concepts.shaders.binding.title: 'Constantes e identificadores de recursos'
+concepts.shaders.compilation.description: '{zenithCompiler} compila un punto de entrada Slang en un {shaderDesc}. {graphicsApi} selecciona DXIL para DirectX 12, una biblioteca Metal para Metal o SPIR-V para Vulkan.'
+concepts.shaders.compilation.details: 'Con un contexto gráfico ya creado y llamado {context}, compile el sombreador del triángulo a partir de la copia situada junto al ejecutable:'
+concepts.shaders.compilation.guidance: 'El nombre del punto de entrada {vSMain} debe coincidir con la declaración del archivo de sombreadores. Compile los sombreadores y cree las canalizaciones durante la inicialización; después, reutilice las canalizaciones al renderizar. Una vez creadas, los objetos sombreador se pueden liberar, como muestra {link}.'
+concepts.shaders.compilation.guidance.link: 'Primer triángulo'
+concepts.shaders.compilation.title: 'Compilación de sombreadores'
+concepts.shaders.description: 'Las declaraciones de C# y Slang deben coincidir en la ubicación de cada valor y en la forma de acceder a él desde el sombreador. Las disposiciones de entrada describen los atributos de los vértices; las declaraciones de búferes del sombreador describen parámetros y arrays; los identificadores señalan los recursos que utiliza el sombreador.'
+concepts.shaders.dispatch.description: '{dispatch} indica cuántos grupos de hilos se ejecutan. El ejemplo de cálculo declara {numthreads}, por lo que cada grupo contiene 16 × 16 hilos. Para cubrir una imagen, divida el ancho y el alto entre 16 y redondee cada resultado hacia arriba: una imagen de 17 × 19 necesita 2 × 2 grupos. El sombreador comprueba las coordenadas de cada hilo y omite los que quedan fuera de la imagen.'
+concepts.shaders.dispatch.details: 'El tamaño de grupo declarado está disponible en {shaderDescThreadGroupSize} y permite calcular los grupos que se pasan a {dispatch}. {computeShaderRendererCs} muestra el cálculo completo. Después del despacho, el acceso a la imagen resultante debe seguir los requisitos descritos en {link}.'
+concepts.shaders.dispatch.details.link: 'Sincronización'
+concepts.shaders.dispatch.title: 'Grupos de hilos'
+concepts.shaders.layout.context: 'Las {link} muestran esta organización con las dimensiones de una imagen y dos identificadores de textura. Los desplazamientos explícitos indican dónde comienza cada campo. La declaración utiliza {systemRuntimeInteropServices} y {zenithNET}:'
+concepts.shaders.layout.context.link: 'constantes de C# del ejemplo de cálculo'
+concepts.shaders.layout.description: 'Para la entrada de vértices, {inputLayout} describe el formato, el desplazamiento y la semántica de cada atributo, junto con el paso entre vértices. El triángulo coloca una posición {float3} antes de un color {float4}: el color comienza en el desplazamiento de byte 12 y cada vértice ocupa 28 bytes. {inputLayoutAdd} calcula esta disposición compacta a partir de los formatos. Si la estructura de C# contiene relleno, indique desplazamientos y un paso que lo tengan en cuenta.'
+concepts.shaders.layout.details: 'Los búferes de constantes contienen parámetros del sombreador, y los búferes estructurados contienen arrays de elementos tipados. Su disposición sigue las declaraciones de Slang, no {inputLayout}. Ajuste los desplazamientos, tamaños de elemento y relleno de C# a la disposición del sombreador compilado. Un tipo no administrado de C# se puede copiar byte a byte, pero su disposición en memoria no tiene por qué coincidir con la del sombreador; un {float3} empaquetado para un vértice puede necesitar otra alineación en un búfer del sombreador.'
+concepts.shaders.layout.guidance: 'Los parámetros del sombreador pueden incluir identificadores de recursos además de valores ordinarios. Un identificador se refiere a almacenamiento ya existente, por lo que copiarlo a un búfer de constantes no copia el contenido del recurso.'
+concepts.shaders.layout.notes: 'La declaración correspondiente en {computeShaderSlang} es:'
+concepts.shaders.layout.reference: 'Las dos dimensiones ocupan los primeros ocho bytes, seguidas de dos identificadores de ocho bytes cada uno. El ejemplo de C# reserva 256 bytes para el registro completo. El tamaño de otros registros se debe calcular a partir de sus propios campos y requisitos de disposición y alineación.'
+concepts.shaders.layout.title: 'Disposición de los datos'
+concepts.shaders.matrices.description: 'El compilador almacena las matrices por filas contiguas. El {link} sitúa tres valores {matrix4x4} en los desplazamientos 0, 64 y 128. Su {detail} aplica {mulVectorMatrix} en el orden modelo, vista y proyección.'
+concepts.shaders.matrices.description.detail: 'sombreador'
+concepts.shaders.matrices.description.link: 'renderizador de cubo giratorio'
+concepts.shaders.matrices.details: 'El orden de almacenamiento define la disposición de los elementos de la matriz en memoria; el de multiplicación define la transformación. Las convenciones de C# y Slang deben coincidir.'
+concepts.shaders.matrices.title: 'Convenciones matriciales'
+concepts.shaders.title: 'Datos de sombreadores y enlace'
+concepts.synchronization.backends.description: 'DirectX 12 y Vulkan codifican las transiciones de textura como cambios de disposición y acceso. En Metal, la implementación de {transition} de Zenith.NET no realiza una operación equivalente. El backend de Metal agrupa los comandos en codificadores de renderizado y cálculo e inserta barreras de visibilidad cuando termina un codificador. Las operaciones dependientes dentro del mismo codificador, como los despachos de cálculo sucesivos, utilizan llamadas explícitas a {barrier}.'
+concepts.synchronization.backends.details: 'En el {link}, iniciar la pasada gráfica termina el codificador de cálculo y hace visible su salida para el trabajo gráfico. Dos despachos de cálculo consecutivos permanecen en el mismo codificador, de modo que necesitan una barrera aunque se grabe una transición de textura entre ellos. Por eso, una secuencia de comandos portable debe contemplar tanto las dependencias de acceso como las disposiciones de textura.'
+concepts.synchronization.backends.details.link: 'ejemplo de cálculo'
+concepts.synchronization.backends.title: 'Comportamiento de los backends'
+concepts.synchronization.barriers.context: 'Elija etapas que incluyan los accesos implicados y que admita la cola. {barrierStagesAll} incluye todas las etapas admitidas; seleccione un conjunto más reducido si la dependencia solo afecta a esas etapas.'
+concepts.synchronization.barriers.description: 'Considere dos despachos de cálculo grabados en el mismo búfer de comandos: el primero escribe datos de simulación y el segundo los lee. Coloque una barrera entre ambos para que el segundo vea las escrituras completadas. En un búfer de almacenamiento, esto ordena los accesos sin una transición de disposición de textura:'
+concepts.synchronization.barriers.details: 'El primer argumento identifica las etapas que producen los datos; el segundo, las que los consumen. Juntos describen el orden de ejecución y la visibilidad de memoria necesarios entre los dos despachos.'
+concepts.synchronization.barriers.guidance: 'La {link} aplica este patrón entre los pasos de simulación. Cada barrera va después del despacho que produce los datos y antes del que los utiliza.'
+concepts.synchronization.barriers.guidance.link: 'simulación FluidTank'
+concepts.synchronization.barriers.title: 'Barreras de canalización'
+concepts.synchronization.dependencies.description: 'Para cada recurso, identifique los accesos que afectan a los mismos datos. Si alguno escribe, conserve el orden necesario entre ellos. Dos lecturas no entran en conflicto, aunque siguen vigentes los requisitos de disposición de textura y presentación. Las actualizaciones de la CPU y la liberación de recursos también deben esperar a que la GPU termine de utilizar el almacenamiento afectado.'
+concepts.synchronization.dependencies.details: 'El mecanismo se elige según dónde se ejecutan las operaciones:'
+concepts.synchronization.dependencies.guidance: 'Una carga de trabajo puede necesitar varios mecanismos. Por ejemplo, una textura producida en otra cola necesita una dependencia entre envíos y la disposición adecuada para su siguiente uso. Ninguna de estas operaciones exige bloquear la CPU, salvo que esta necesite acceder a los datos o liberarlos.'
+concepts.synchronization.dependencies.table.cpuAccess.mechanism: 'Llamar a {wait} en el valor de finalización correspondiente.'
+concepts.synchronization.dependencies.table.cpuAccess.situation: 'El acceso de la CPU o la liberación de recursos depende de la finalización en la GPU'
+concepts.synchronization.dependencies.table.crossQueue.mechanism: 'Pase el {timelineValue} del productor a {submit} del consumidor.'
+concepts.synchronization.dependencies.table.crossQueue.situation: 'Una cola consume datos producidos en otra'
+concepts.synchronization.dependencies.table.headings.mechanism: 'Mecanismo'
+concepts.synchronization.dependencies.table.headings.situation: 'Situación'
+concepts.synchronization.dependencies.table.layoutChange.mechanism: '{commandBufferTransition} con sus disposiciones anterior y siguiente.'
+concepts.synchronization.dependencies.table.layoutChange.situation: 'Un subrecurso de textura cambia cómo se utiliza'
+concepts.synchronization.dependencies.table.pipelineStages.mechanism: '{commandBufferBarrier} con las etapas productora y consumidora.'
+concepts.synchronization.dependencies.table.pipelineStages.situation: 'Etapas de GPU dependientes en un flujo de comandos'
+concepts.synchronization.dependencies.title: 'Dependencias de acceso'
+concepts.synchronization.description: 'La sincronización establece el orden de acceso a los datos compartidos y permite que las operaciones posteriores vean los resultados que necesitan. En una dependencia de escritura a lectura, quien escribe es el productor y quien lee es el consumidor. Las barreras y las transiciones coordinan los accesos de la GPU; las líneas de tiempo conectan envíos y permiten que la CPU espere a que termine el trabajo de la GPU.'
+concepts.synchronization.layouts.context: 'Use {undefined} como disposición anterior cuando se pueda descartar el contenido, por ejemplo, en una imagen nueva o en un adjunto que se vaya a borrar por completo. Para conservarlo para una carga, un muestreo o una acumulación, indique la disposición del acceso anterior.'
+concepts.synchronization.layouts.description: 'Los indicadores de uso declaran las funciones permitidas de una textura. {textureLayout} describe cómo se accede a un subrecurso, identificado por un nivel de mip y una capa de array, en un punto concreto de la secuencia de comandos. Cuando cambia ese acceso, grabe una transición. En el ejemplo de cálculo, la textura de salida se escribe en disposición {storage} y después se prepara para muestrearla en una pasada gráfica:'
+concepts.synchronization.layouts.details: '{outputTexture} se creó con {sampledStorage}, y el subrecurso seleccionado está en {storage}. {default} elige el nivel mip 0 y la capa 0. Los demás niveles y capas no cambian.'
+concepts.synchronization.layouts.guidance: 'En {transition}, indique la disposición del uso anterior del subrecurso. La aplicación debe mantener actualizado este estado al grabar comandos y pasar texturas entre las etapas de renderizado.'
+concepts.synchronization.layouts.title: 'Disposiciones de textura'
+concepts.synchronization.readback.description: '{isCompleted} informa de la finalización en la GPU. Una descarga grabada tiene un paso adicional: copiar el resultado del almacenamiento interno de lectura a la memoria de destino de la aplicación.'
+concepts.synchronization.readback.details: '{commandBufferDownload} graba la copia de la GPU a ese almacenamiento interno. Una vez terminado el envío, la biblioteca realiza la copia de CPU al recuperar el búfer de comandos completado. Llamar a {wait} sobre el valor de envío de la descarga espera a la GPU y procesa los búferes completados de esa cola, con lo que termina ambos pasos. Mantenga el destino asignado y la memoria administrada anclada hasta que retorne la llamada.'
+concepts.synchronization.readback.guidance: 'Consultar {isCompleted} por sí solo no realiza la copia final de CPU. Esperar en otra cola tampoco recupera los búferes completados de la cola de descarga. {link} describe cuándo las cargas y descargas utilizan acceso directo o almacenamiento temporal de transferencia.'
+concepts.synchronization.readback.guidance.link: 'Administración de recursos'
+concepts.synchronization.readback.title: 'Finalización de la lectura en CPU'
+concepts.synchronization.reuse.description: 'El triángulo espera a que termine cada fotograma antes de reutilizar sus datos. Para solapar fotogramas, reserve almacenamiento modificable para cada uno de los que estén en curso y registre cuándo vuelve a estar disponible. Antes de cambiar datos, espere al trabajo de la GPU que todavía acceda a esos bytes; antes de liberar un recurso, espere a todos sus usos pendientes.'
+concepts.synchronization.reuse.details: 'El redimensionamiento y el cierre siguen la misma regla: impedir nuevos usos, esperar a que terminen los existentes y sustituir o liberar los recursos. Respete ese orden antes de llamar a {swapChainResize} o liberar recursos o el contexto. Como el triángulo ya espera en cada fotograma, puede redimensionar entre las llamadas a sus funciones de respuesta.'
+concepts.synchronization.reuse.guidance: '{swapChainPresent} señala un punto de la cola gráfica y espera a él después de la presentación. Por tanto, el triángulo sigue esperando al presentar aunque se elimine la espera explícita del envío. El trabajo independiente de otras colas necesita su propio seguimiento de finalización.'
+concepts.synchronization.reuse.title: 'Reutilización de recursos'
+concepts.synchronization.timelines.context: 'Si la CPU necesita leer el resultado, sobrescribir los datos compartidos o liberar los recursos cuando termine el consumidor, espere a ese envío:'
+concepts.synchronization.timelines.description: 'Una línea de tiempo identifica la finalización con valores crecientes. {submit} devuelve un {timelineValue} con la línea y el valor del envío. Los campos numéricos {value} de líneas distintas no se comparan directamente.'
+concepts.synchronization.timelines.details: 'Sean {producer} y {consumer} búferes grabados en dos colas del mismo contexto, donde el segundo usa los datos del primero. Su envío establece esta dependencia:'
+concepts.synchronization.timelines.guidance: 'La cola consumidora espera al valor de finalización recibido antes de ejecutar sus comandos. La CPU puede continuar sin llamar a {producedWait}. Conserve los recursos compartidos hasta que se complete {consumed} y grabe las transiciones de textura que necesite el acceso del consumidor.'
+concepts.synchronization.timelines.notes: 'El valor que devuelve {queueTimelineSignal} también permite esperar con {wait} o establecer una dependencia para otra cola. {signal} coloca ese punto de finalización después del trabajo ya enviado; la GPU lo alcanza cuando termina dicho trabajo.'
+concepts.synchronization.timelines.title: 'Líneas de tiempo y dependencias entre colas'
+concepts.synchronization.title: 'Sincronización'
+home.actions.reference: 'Explorar API'
+home.actions.tutorial: 'Comenzar con un triángulo'
+home.entries.description: 'Tutoriales, ejemplos y referencia de la API'
+home.entries.label: 'Secciones de la documentación'
+home.entries.reference.action: 'Explorar la API'
+home.entries.reference.category: 'Referencia'
+home.entries.reference.description: 'Consultar tipos, miembros y declaraciones de la API'
+home.entries.reference.title: 'Referencia de la API'
+home.entries.samples.action: 'Examinar ejemplos'
+home.entries.samples.category: 'Ejemplos'
+home.entries.samples.description: 'Ejemplos ejecutables de técnicas gráficas y de cálculo'
+home.entries.samples.title: 'Ejemplos'
+home.entries.title: 'Documentación'
+home.entries.tutorial.action: 'Iniciar tutorial'
+home.entries.tutorial.category: 'Tutorial'
+home.entries.tutorial.description: 'De un proyecto vacío al primer triángulo en pantalla'
+home.entries.tutorial.title: 'Primer triángulo'
+home.introduction.accent: 'con .NET'
+home.introduction.description: 'Zenith.NET ofrece una API común de gráficos y cálculo para DirectX, Metal y Vulkan'
+home.introduction.title: 'Gráficos multiplataforma{lineBreak}{accent}'
+home.meta.description: 'Desarrollo gráfico y de cálculo multiplataforma en .NET con una API común para DirectX, Metal y Vulkan'
+home.scene.description: 'Geometría interactiva en rotación. Arrastre o utilice las flechas para cambiar la vista.'
+home.scene.label: 'Visualización geométrica'
+home.scene.surface: 'Superficie'
+home.scene.wireframe: 'Estructura alámbrica'
+home.title: 'API gráfica para .NET'
+learning.concepts.description: 'Criterios para elegir usos de recursos, describir datos de sombreadores y coordinar la ejecución en la GPU'
+learning.concepts.execution.description: 'Grabación, envío y tiempo de vida de los recursos'
+learning.concepts.platforms.description: 'Presentación nativa y controles de renderizado'
+learning.concepts.resources.description: 'Usos de recursos, ubicación de memoria y vistas'
+learning.concepts.shaders.description: 'Convenciones de disposición y enlace entre C# y Slang'
+learning.concepts.synchronization.description: 'Dependencias, visibilidad y finalización'
+learning.concepts.title: 'Conceptos básicos'
+learning.description: 'Empiece por el tutorial del triángulo para crear su primera aplicación. Después, consulte los conceptos de gestión de recursos, datos de sombreadores y sincronización, o elija un ejemplo de la técnica que quiera implementar.'
+learning.entries.samples.action: 'Examinar ejemplos'
+learning.entries.samples.description: 'Implementaciones C# y Slang de geometría, cálculo, consultas de rayos y sombreado de mallas'
+learning.entries.tutorial.action: 'Abrir tutorial'
+learning.entries.tutorial.description: 'Una aplicación de consola que abre una ventana y renderiza un triángulo'
+learning.meta.description: 'Tutoriales y conceptos del modelo de programación de Zenith.NET'
+learning.title: 'Aprender'
+reference.description: 'Referencia de recursos, comandos e integración de plataformas'
+reference.entries.commands.description: 'Comandos gráficos, de cálculo y de transferencia'
+reference.entries.commands.label: 'Registrar comandos'
+reference.entries.context.description: 'Creación de recursos GPU para el backend elegido'
+reference.entries.context.label: 'Empezar aquí'
+reference.meta.description: 'Espacios de nombres, tipos y miembros de Zenith.NET'
+reference.navigation.core: 'Referencia de la API principal'
+reference.title: 'Referencia de la API'
+samples.computeShader.description: 'El ejemplo carga la imagen {assetsTexturesShokoPng}, la convierte a escala de grises en un despacho de cálculo y muestra la textura resultante. La conversión se ejecuta una vez; los fotogramas siguientes muestran el mismo resultado. El renderizador ilustra cómo enlazar texturas para muestreo y escritura, redondear hacia arriba el número de grupos de hilos para cubrir la imagen y comprobar sus límites en el sombreador.'
+samples.computeShader.details: '{computeShaderRendererCs} · {computeShaderSlang}'
+samples.computeShader.title: 'Sombreador de cálculo'
+samples.cube.description: 'El cubo añade dibujo indexado, matrices de modelo, vista y proyección en un búfer de constantes y prueba de profundidad. Su textura de profundidad y plantilla {d32FloatS8UInt} se recrea al redimensionar.'
+samples.cube.details: '{spinningCubeRendererCs} · {spinningCubeSlang}'
+samples.cube.title: 'Cubo giratorio'
+samples.description: 'Cada ejemplo reúne un renderizador C# y su sombreador Slang en torno a una técnica. La configuración completa y la secuencia de renderizado se explican en {link}.'
+samples.description.link: 'Primer triángulo'
+samples.execution.description: 'Ejecute el proyecto {link} y elija un ejemplo en el menú de la consola.'
+samples.execution.description.link: 'ZenithTutorials'
+samples.execution.title: 'Ejecución de ejemplos'
+samples.host.description: '{appCs} inicializa contexto, ventana y cadena de intercambio. En cada fotograma obtiene un búfer, llama al renderizador, envía los comandos y presenta el resultado. {iRendererCs} define actualización, renderizado, redimensionamiento y liberación.'
+samples.host.details: 'Los renderizadores de cálculo y trazado de rayos producen texturas fuera de pantalla. {texturePresenterCs} y {presentTextureSlang} las dibujan en la textura de salida actual; después, el programa anfitrión compartido envía los comandos y presenta el fotograma.'
+samples.host.title: 'Host y presentación'
+samples.indirectDrawing.description: 'El dibujo indirecto representa varias instancias de cubo mediante un búfer de argumentos de dibujo indexado y un búfer estructurado con datos por instancia. La CPU proporciona los argumentos y actualiza las transformaciones.'
+samples.indirectDrawing.details: '{indirectDrawingRendererCs} · {indirectDrawingSlang}'
+samples.indirectDrawing.title: 'Dibujo indirecto'
+samples.meshShading.description: 'Un sombreador de tareas descarta las instancias de esferas no visibles y pasa los índices restantes al sombreador de mallas, que genera la geometría. El renderizador comprueba {contextCapabilitiesMeshShadingSupported} antes de crear la canalización.'
+samples.meshShading.details: '{meshShadingRendererCs} · {meshShadingSlang}'
+samples.meshShading.title: 'Sombreado de mallas'
+samples.rayTracing.description: 'El ejemplo construye estructuras de aceleración de nivel inferior y superior para un suelo y esferas procedurales, y realiza consultas de rayos dentro de un sombreador de cálculo. Comprueba {contextCapabilitiesRayTracingSupported} antes de la inicialización.'
+samples.rayTracing.details: '{rayTracingRendererCs} · {rayTracingSlang}'
+samples.rayTracing.title: 'Trazado de rayos'
+samples.title: 'Ejemplos'
+samples.triangle.description: 'El triángulo asocia un búfer de vértices con su disposición de entrada y dibuja tres vértices mediante una canalización gráfica.'
+samples.triangle.details: '{helloTriangleRendererCs} · {helloTriangleSlang} · {link}'
+samples.triangle.details.link: 'Tutorial completo'
+samples.triangle.title: 'Hola triángulo'
+samples.workloads.description: 'El {link} del repositorio principal contiene aplicaciones de mayor escala que utilizan el código fuente de la biblioteca.'
+samples.workloads.description.link: 'directorio Experiments'
+samples.workloads.details: 'Las aplicaciones con ventana usan los mismos caminos de Windows, macOS y Linux/X11. {link} describe los controles Avalonia, WinForms, WPF, WinUI/Uno y MAUI, su ciclo de fotogramas y sus implementaciones.'
+samples.workloads.details.link: 'Integración de plataforma'
+samples.workloads.table.cornellBox.example: '{link}'
+samples.workloads.table.cornellBox.example.link: 'CornellBox'
+samples.workloads.table.cornellBox.whatItDemonstratesAndRequires: 'Trazado de caminos, reducción de ruido, asignación de tonos, aumento de resolución y controles ImGui. Requiere soporte de trazado de rayos.'
+samples.workloads.table.fluidTank.example: '{link}'
+samples.workloads.table.fluidTank.example.link: 'FluidTank'
+samples.workloads.table.fluidTank.whatItDemonstratesAndRequires: 'Simulación de fluidos por cálculo, dependencias entre etapas de simulación y renderizado de superficies multipasada.'
+samples.workloads.table.headings.example: 'Ejemplo'
+samples.workloads.table.headings.whatItDemonstratesAndRequires: 'Técnicas y requisitos'
+samples.workloads.table.inkCanvas.example: '{link}'
+samples.workloads.table.inkCanvas.example.link: 'InkCanvas'
+samples.workloads.table.inkCanvas.whatItDemonstratesAndRequires: 'Dibujo con Skia y control de entrada en una ventana nativa. Requiere el backend y la biblioteca nativa de Skia para la plataforma de destino.'
+samples.workloads.table.platformDetection.example: '{link}'
+samples.workloads.table.platformDetection.example.link: 'PlatformDetection'
+samples.workloads.table.platformDetection.whatItDemonstratesAndRequires: 'Intenta crear un contexto para cada backend e informa de las capacidades del dispositivo. Si un intento falla, lo muestra como no compatible; la causa puede estar en el dispositivo, el controlador o una dependencia de ejecución ausente.'
+samples.workloads.title: 'Aplicaciones de mayor escala'
+tutorial.context.description: 'Un {graphicsContext} conecta la aplicación con la GPU mediante un backend y proporciona los métodos para crear los recursos de renderizado.'
+tutorial.context.details: 'Inserte la inicialización del contexto {emphasis}:'
+tutorial.context.details.emphasis: 'después de las directivas using, antes de {iWindowWindow}'
+tutorial.context.guidance: 'Cada rama selecciona el backend de la plataforma y solicita la validación. Los mensajes de {validationMessage} aparecen en la consola.'
+tutorial.context.imports: 'Añada las siguientes directivas using al principio de {programCs}:'
+tutorial.context.title: 'Contexto gráfico'
+tutorial.description: 'Este tutorial parte de un proyecto de consola .NET vacío para crear una aplicación que abre una ventana y dibuja un triángulo de colores.'
+tutorial.details: 'El tutorial tiene tres puntos de comprobación: una ventana vacía, un fondo de color uniforme y el triángulo de colores. Ejecute la aplicación en cada uno antes de continuar con el siguiente paso.'
+tutorial.draw.context: 'Los argumentos para {draw} son:'
+tutorial.draw.description: 'En {windowRender}, sustituya {addTheTriangleDrawCommandsHereLater} por estos comandos entre {beginRenderPass} y {endRenderPass}:'
+tutorial.draw.details: '{setPipeline} selecciona sombreadores y estado. Debe preceder al enlace de vértices porque el backend lo interpreta según la disposición de entrada de la canalización.'
+tutorial.draw.guidance: '{setVertexBufferVertexBuffer} enlaza los datos de vértices ya cargados desde el desplazamiento de byte 0 a la ranura de entrada 0, que corresponde a la única entrada de {inputLayouts}.'
+tutorial.draw.notes: 'Estas llamadas graban los comandos de dibujo. La llamada existente a {submitWait} los envía y espera a que termine la GPU; después, {present} solicita que se muestre la imagen completada. Conserve el borrado al inicio de la pasada para que la zona exterior al triángulo tenga un color de fondo definido.'
+tutorial.draw.table.firstInstance.meaning: 'Comenzar la numeración de instancias en cero.'
+tutorial.draw.table.firstVertex.meaning: 'Comenzar con el primer vértice en el búfer.'
+tutorial.draw.table.headings.argument: 'Argumento'
+tutorial.draw.table.headings.description: 'Valor'
+tutorial.draw.table.headings.meaning: 'Significado'
+tutorial.draw.table.instanceCount.meaning: 'Dibujar una copia del triángulo.'
+tutorial.draw.table.vertexCount.meaning: 'Leer tres vértices.'
+tutorial.draw.title: 'Comandos de dibujo'
+tutorial.execution.description: 'Compile y ejecute el proyecto {firstTriangle}.'
+tutorial.execution.details: 'Debería aparecer un triángulo sobre un fondo oscuro, rojo arriba, verde abajo a la derecha y azul abajo a la izquierda. La interpolación de los colores de los vértices forma un degradado suave en el interior.'
+tutorial.execution.guidance: 'Al redimensionar, cambian las dimensiones de la imagen en píxeles, pero las coordenadas de los vértices permanecen iguales. El área de visualización adapta esas coordenadas al nuevo ancho y alto, por lo que el triángulo se deforma si cambia la relación de aspecto de la ventana.'
+tutorial.execution.title: 'Ejecución de la aplicación'
+tutorial.frame.commands.context: 'Después de {endRenderPass}, la segunda transición prepara la textura de salida para su presentación. {submit} termina la grabación y envía el búfer de comandos; {wait} bloquea el hilo hasta que termina ese envío. A continuación, {present} solicita que se muestre la imagen renderizada.'
+tutorial.frame.commands.description: 'Una pasada de renderizado agrupa las operaciones de dibujo que escriben en los mismos adjuntos. En esta pasada, la textura de salida de la cadena de intercambio recibe el color. {colorAttachmentClear} indica cómo inicializar ese adjunto con un color RGBA y conservar su contenido al terminar la pasada. {beginRenderPass} graba el inicio de la pasada con estos ajustes.'
+tutorial.frame.commands.details: 'La colección contiene un adjunto de color. {null} indica que la pasada no tiene adjunto de profundidad y plantilla.'
+tutorial.frame.commands.guidance: '{beginRenderPass} configura el área de visualización y el rectángulo de recorte para cubrir todo el adjunto. El área de visualización transforma las coordenadas de renderizado en posiciones de píxeles; el rectángulo de recorte limita dónde se puede escribir.'
+tutorial.frame.commands.title: 'Borrado, envío y presentación'
+tutorial.frame.description: 'Comience por rellenar la ventana con un color de fondo. Esta operación se llama borrado. Cuando el fondo aparece, la superficie, el envío y la presentación ya funcionan juntos y se pueden añadir los vértices y los sombreadores del triángulo.'
+tutorial.frame.details: 'Silk.NET invoca el callback cuando corresponde producir un fotograma. El argumento de tiempo transcurrido no se usa y se denomina {value}. Registre el callback {emphasis}:'
+tutorial.frame.details.emphasis: 'antes de {windowRun}'
+tutorial.frame.target.description: '{graphicsQueueCommandBuffer} devuelve un búfer listo para grabar. La grabación describe trabajo GPU; la ejecución comienza después del envío.'
+tutorial.frame.target.details: 'La disposición de una textura describe el acceso que necesita una operación. La primera llamada a {transition} prepara la textura de salida para usarla como adjunto de color. Aquí, {default} selecciona el nivel de mip 0 y la capa 0 del array, es decir, la imagen base de la textura. Como esta pasada borra toda la imagen, {undefined} permite descartar su contenido anterior.'
+tutorial.frame.target.guidance: 'Obtenga la textura de salida en cada fotograma, ya que la presentación puede seleccionar una imagen distinta.'
+tutorial.frame.target.title: 'Preparación de la salida'
+tutorial.frame.title: 'Primer fotograma'
+tutorial.geometry.data.description: 'Añada el array de vértices {emphasis}. Mantenga también las siguientes inicializaciones de recursos antes de este callback, en el orden indicado.'
+tutorial.geometry.data.description.emphasis: 'antes de {windowRender}'
+tutorial.geometry.data.details: 'Cada entrada construye un {vertex}; las dos expresiones {new} internas aportan posición {vector3} y color {vector4}. Las posiciones forman los vértices superior, inferior derecho e inferior izquierdo. El sombreador añade {w} como cuarta coordenada. Con w = 1, x e y son coordenadas normalizadas del dispositivo: -1 y +1 delimitan la imagen y 0 marca el centro, independientemente de los píxeles.'
+tutorial.geometry.data.guidance: 'Cada color tiene componentes rojo, verde, azul y alfa, con alfa igual a 1. Los tres vértices tienen z = 0 y están en el mismo plano.'
+tutorial.geometry.data.title: 'Posiciones y colores'
+tutorial.geometry.description: 'Cada uno de los tres vértices tiene una posición y un color. La GPU determina la forma del triángulo a partir de las posiciones e interpola los colores por su superficie.'
+tutorial.geometry.format.description: 'Añada la declaración del vértice {emphasis}:'
+tutorial.geometry.format.description.emphasis: 'al final de {programCs}, después de las instrucciones de limpieza'
+tutorial.geometry.format.details: '{file} limita el tipo a {programCs}, y {layoutKindSequential} mantiene los campos en su orden de declaración. {position} contiene tres valores de coma flotante de 32 bits y comienza en el desplazamiento de byte 0; {color} contiene cuatro y comienza en el desplazamiento de byte 12. Cada vértice ocupa, por tanto, 28 bytes. La disposición de entrada describirá esta organización para que la GPU lea los campos correctamente.'
+tutorial.geometry.format.imports: 'Añada estas directivas using al principio de {programCs} para utilizar los tipos vectoriales y el atributo que define la disposición de la estructura:'
+tutorial.geometry.format.title: 'Representación del vértice'
+tutorial.geometry.title: 'Datos de vértices'
+tutorial.geometry.upload.context: '{fixed} impide que el recolector de basura mueva el array mientras se usa su dirección. En este búfer escribible por la CPU, {upload} mapea la memoria, copia los bytes y deshace el mapeo antes de retornar. El array solo debe permanecer anclado durante la llamada. Cargue los vértices una vez durante la inicialización y conserve el búfer hasta que termine el renderizado.'
+tutorial.geometry.upload.description: 'Guarde los vértices en un {buffer} para que la GPU pueda leerlos al dibujar. Añada la asignación y la carga de datos justo después del array:'
+tutorial.geometry.upload.details: '{sizeofVertexVerticesLength} equivale a tres vértices de 28 bytes, 84 bytes en total. {bufferUsagesVertex} declara acceso de entrada, y {cpuWriteOnly} permite inicialización directa por CPU. El paso del descriptor registra el tamaño del elemento; la disposición posterior define cada atributo.'
+tutorial.geometry.upload.guidance: 'La asignación no inicializa el contenido del búfer. {upload} copia el array al desplazamiento de destino 0 utilizando el puntero de origen y el número de bytes de la descripción de datos.'
+tutorial.geometry.upload.imports: 'En {programCs}, añada {bufferAlias} a las directivas using para distinguir el tipo de búfer de Zenith.NET de {systemBuffer}.'
+tutorial.geometry.upload.notes: 'Coloque {vertexBufferDispose} justo antes de {swapChainDispose} al final de {programCs}. {link} trata el almacenamiento exclusivo de GPU y las cargas para geometrías mayores.'
+tutorial.geometry.upload.notes.link: 'Administración de recursos'
+tutorial.geometry.upload.title: 'Asignación y carga del búfer'
+tutorial.input.description: 'La GPU necesita saber dónde se guardan los atributos de cada vértice, como la posición y el color. La disposición de entrada indica su formato y desplazamiento, además del paso: la distancia en bytes hasta el siguiente vértice. Añada este código después de crear los sombreadores:'
+tutorial.input.details: '{inputLayoutAdd} sitúa cada atributo a continuación del anterior y suma su tamaño al paso entre vértices. Calcula los desplazamientos a partir de los formatos indicados, sin inspeccionar la estructura de C#. Para estos dos campos, la disposición resultante es:'
+tutorial.input.guidance: 'El valor predeterminado de {semanticIndex} es cero, que corresponde a {pOSITION0} y {cOLOR0} en el sombreador. La posición y el color se almacenan juntos en un búfer de vértices, por lo que la canalización utiliza una sola disposición de entrada en la ranura 0. Si cambia el orden de los campos o el relleno de la estructura de C#, ajuste también esta disposición.'
+tutorial.input.table.headings.byteOffset: 'Desplazamiento de bytes'
+tutorial.input.table.headings.cField: 'Campo de C#'
+tutorial.input.table.headings.format: 'Formato'
+tutorial.input.table.headings.shaderInput: 'Entrada del sombreador'
+tutorial.input.table.nextVertex.cField: 'Siguiente vértice'
+tutorial.input.table.nextVertex.format: 'Paso total: 28 bytes'
+tutorial.input.table.nextVertex.shaderInput: 'Siguiente registro de entrada'
+tutorial.input.table.vertexColor.format: '{float4} (16 bytes)'
+tutorial.input.table.vertexPosition.format: '{float3} (12 bytes)'
+tutorial.input.title: 'Disposición de entrada de vértices'
+tutorial.lifecycle.description: 'Cada fotograma sigue esta secuencia:'
+tutorial.lifecycle.details: 'Al cerrar la ventana, {run} retorna y la ejecución continúa en el código de limpieza. Como cada fotograma ya ha esperado a que terminen sus comandos, los recursos que utilizaban se pueden liberar. El bloque completo de limpieza después de {windowRun} es:'
+tutorial.lifecycle.guidance: 'Libere la cadena de intercambio antes de destruir su ventana nativa y el contexto después de sus recursos. La cola recicla los búferes de comandos enviados, y la cadena de intercambio gestiona la textura de salida; ninguno necesita una llamada independiente a {dispose}.'
+tutorial.lifecycle.title: 'Ciclo del fotograma y limpieza'
+tutorial.pipeline.behavior: 'Tras la disposición de entrada, cree la canalización:'
+tutorial.pipeline.context: '{cullNone} desactiva el descarte de caras, de modo que el triángulo se dibuja tanto si sus vértices aparecen en sentido horario como antihorario.'
+tutorial.pipeline.description: 'Una {graphicsPipeline} reúne la disposición de los vértices, los dos sombreadores y los ajustes para ensamblar primitivas y escribir en los adjuntos.'
+tutorial.pipeline.details: 'Los sombreadores procesan vértices y fragmentos. Los demás ajustes de la canalización controlan el ensamblado de primitivas, el descarte de caras, la prueba de profundidad y la mezcla de colores:'
+tutorial.pipeline.guidance: 'El formato debe coincidir con el adjunto de la pasada, por lo que procede de {swapChainDescFormat}. {count1} especifica una muestra por píxel.'
+tutorial.pipeline.item: '{triangleList} ensambla cada grupo de tres vértices consecutivos en un triángulo.'
+tutorial.pipeline.notes: '{depthNone} desactiva pruebas y escrituras de profundidad, de acuerdo con la ausencia de ese adjunto.'
+tutorial.pipeline.reference: '{opaque} desactiva la mezcla. El color del fragmento sustituye así el fondo dentro del triángulo.'
+tutorial.pipeline.title: 'Canalización gráfica'
+tutorial.pipeline.usage: 'Los objetos de sombreador pueden liberarse tras crear la canalización. Mantenga la canalización durante el renderizado y añada {pipelineDispose} antes de {vertexBufferDispose} en la limpieza.'
+tutorial.presentation.title: 'Contexto gráfico y presentación'
+tutorial.project.context: 'Active {emphasis} ({allowUnsafeBlocks}) en la configuración de compilación. Las llamadas nativas de ventana y la carga de vértices necesitan código no seguro.'
+tutorial.project.context.emphasis: 'Permitir código no seguro'
+tutorial.project.description: 'Cree un proyecto de consola C# llamado {firstTriangle} que tenga como destino .NET 10.'
+tutorial.project.details: 'Añada los siguientes paquetes NuGet al proyecto:'
+tutorial.project.guidance: '{zenithNET} aporta la API común y los tres paquetes de backend sus implementaciones. {zenithNETCompiler} compila los sombreadores Slang que se presentan a continuación. Silk.NET crea la ventana y procesa eventos.'
+tutorial.project.title: 'Configuración del proyecto'
+tutorial.requirements.context: 'El {link} y su {detail} proporcionan las implementaciones de referencia del tutorial.'
+tutorial.requirements.context.detail: 'host compartido'
+tutorial.requirements.context.link: 'ejemplo Hola triángulo'
+tutorial.requirements.description: 'Necesita el {emphasis} y conocimientos básicos de C#. La aplicación tiene {net10} como framework de destino. La GPU y el controlador deben admitir el backend elegido para su plataforma:'
+tutorial.requirements.description.emphasis: 'SDK de .NET 10'
+tutorial.requirements.guidance: 'Zenith.NET renderiza en una superficie nativa proporcionada por la aplicación. Silk.NET.Windowing crea la ventana y procesa eventos. En macOS se utiliza una capa Metal, representada por {cAMetalLayerCs}, que el código auxiliar conecta a la ventana.'
+tutorial.requirements.table.headings.graphicsBackend: 'Backend gráfico'
+tutorial.requirements.table.headings.platform: 'Plataforma'
+tutorial.requirements.table.linux.graphicsBackend: 'Vulkan 1.4'
+tutorial.requirements.table.linux.platform: 'Linux (X11 o XWayland)'
+tutorial.requirements.table.macOS.graphicsBackend: 'Metal 4'
+tutorial.requirements.table.macOS.platform: 'macOS'
+tutorial.requirements.table.windows.graphicsBackend: 'DirectX 12'
+tutorial.requirements.table.windows.platform: 'Windows'
+tutorial.requirements.title: 'Requisitos previos'
+tutorial.resize.context: 'Compile y ejecute la aplicación de nuevo. El fondo debe seguir ocupando toda la ventana al cambiar su tamaño: cada nueva pasada ajusta el área de visualización y el rectángulo de recorte a las dimensiones del adjunto. Conserve la función de renderizado; el triángulo se dibujará dentro de esta misma pasada.'
+tutorial.resize.description: 'La cadena de intercambio debe adaptarse a los cambios de tamaño del búfer de fotogramas. Registre una función para {framebufferResize} después de la función de renderizado y antes de {windowRun}:'
+tutorial.resize.details: 'Responda a los {link}, que se miden en píxeles, en lugar de a los cambios de tamaño lógico de la ventana. Si al minimizar la ventana alguna dimensión pasa a ser cero, la función de redimensionamiento mantiene la cadena de intercambio tal como está. La función de renderizado también retorna mientras alguna dimensión sea cero, de modo que el renderizado se reanuda cuando el búfer de fotogramas vuelve a tener un tamaño válido.'
+tutorial.resize.details.link: 'cambios de tamaño del búfer de fotogramas'
+tutorial.resize.guidance: 'La función de renderizado espera a que termine cada fotograma enviado antes de llamar a {present}. Por ello, la función de redimensionamiento puede sustituir las imágenes de la cadena de intercambio sin interrumpir trabajo de un fotograma anterior. El artículo {link} explica la relación entre la finalización del trabajo y la vida útil de los recursos.'
+tutorial.resize.guidance.link: 'Sincronización'
+tutorial.resize.title: 'Redimensionamiento'
+tutorial.shaders.compilation.context: '{appContextBaseDirectory} permite cargar la copia del sombreador situada en el directorio de salida de la aplicación. Así, el archivo se busca en el mismo lugar aunque la aplicación se inicie desde otro directorio de trabajo.'
+tutorial.shaders.compilation.description: 'Para {triangleSlang}, establezca {emphasis} en {detail}. La compilación copiará el archivo junto al ejecutable.'
+tutorial.shaders.compilation.description.detail: 'Copiar si es posterior'
+tutorial.shaders.compilation.description.emphasis: 'Copiar en el directorio de salida'
+tutorial.shaders.compilation.details: 'En {programCs}, añada la inicialización de sombreadores después de cargar los vértices y antes del callback:'
+tutorial.shaders.compilation.guidance: '{zenithCompiler} compila el código fuente para {contextGraphicsApi}, y {createShader} crea un objeto sombreador a partir de la descripción compilada. Los nombres de punto de entrada {vSMain} y {fSMain} seleccionan las dos funciones definidas en el archivo Slang.'
+tutorial.shaders.compilation.title: 'Compilación de sombreadores'
+tutorial.shaders.context: '{fSInput} conecta las dos etapas del sombreador. El sombreador de vértices escribe en {sVPOSITION} una posición en el espacio de recorte, que la GPU utiliza para determinar qué parte del triángulo es visible. El campo {cOLOR0} transporta el color del vértice, que se interpola sobre el triángulo antes de llegar al sombreador de fragmentos. Su tipo y su semántica deben coincidir en ambas etapas.'
+tutorial.shaders.description: 'Los sombreadores son programas que se ejecutan en la GPU. El sombreador de vértices determina la posición de cada vértice, y el sombreador de fragmentos determina el color en cada posición cubierta de la imagen. Ambos se escribirán en un mismo archivo Slang, con un punto de entrada propio para cada etapa: la función por la que comienza su ejecución.'
+tutorial.shaders.details: 'Cree {triangleSlang} con estas estructuras de entrada y salida:'
+tutorial.shaders.fragment.description: 'Añada el punto de entrada de fragmentos al mismo archivo:'
+tutorial.shaders.fragment.details: 'La rasterización determina qué zonas de la imagen cubre el triángulo y genera fragmentos en esas posiciones. Cada fragmento recibe un color interpolado a partir de los colores de los tres vértices. {fSMain} devuelve ese color, y {sVTARGET} dirige el resultado al primer adjunto de color.'
+tutorial.shaders.fragment.title: 'Sombreado de fragmentos'
+tutorial.shaders.guidance: '{vSInput} define las entradas del sombreador de vértices. {pOSITION0} y {cOLOR0} identifican atributos por semántica; los nombres C# no establecen esa correspondencia.'
+tutorial.shaders.title: 'Etapas de sombreado'
+tutorial.shaders.vertex.description: 'Añada el punto de entrada de vértices a {triangleSlang}:'
+tutorial.shaders.vertex.details: 'La GPU invoca este punto para cada vértice, incorpora la coordenada homogénea {w} y transmite el color. La posición se divide después por w. Aquí vale 1, por lo que x, y y z no cambian y se interpretan como coordenadas normalizadas del dispositivo.'
+tutorial.shaders.vertex.title: 'Sombreado de vértices'
+tutorial.surface.description: 'Defina el destino de presentación nativo {emphasis}:'
+tutorial.surface.description.emphasis: 'después de {windowCenter} y antes de {windowRun}'
+tutorial.surface.details: 'El búfer de fotogramas es la imagen de la ventana en píxeles. {surface} describe el destino nativo de presentación y sus dimensiones; {framebufferSize} proporciona el tamaño en píxeles. Con el escalado de pantalla, este puede diferir del tamaño lógico de la ventana.'
+tutorial.surface.guidance: 'La superficie proporciona los identificadores nativos del backend: una ventana Win32 en Windows, una pantalla y una ventana X11 en Linux o la capa Metal conectada en macOS.'
+tutorial.surface.title: 'Superficie nativa'
+tutorial.swapChain.context: 'La cadena depende del contexto y de la ventana. Sustituya {windowDispose} al final de {programCs} por este orden de liberación:'
+tutorial.swapChain.description: 'Cree la cadena inmediatamente después de describir la superficie:'
+tutorial.swapChain.details: 'La {swapChain} gestiona las imágenes presentadas. {drawable} suministra la actual como {texture}, el recurso de imagen de la API. Tras renderizar, {present} solicita mostrarla y avanza a la siguiente.'
+tutorial.swapChain.guidance: '{b8G8R8A8UNorm} almacena cuatro canales de 8 bits en orden azul, verde, rojo y alfa. {uNorm} hace corresponder 0–255 con 0–1. La salida sigue siendo RGBA lógico; el formato define su almacenamiento. La canalización usa {swapChainDescFormat} para coincidir con el adjunto.'
+tutorial.swapChain.title: 'Cadena de intercambio'
+tutorial.title: 'Primer triángulo'
+tutorial.troubleshooting.description: 'Utilice los diagnósticos de consola y los tres puntos de control para localizar el fallo:'
+tutorial.troubleshooting.details: 'El {link} y el {detail} permiten contrastar datos, disposición y dibujo. El ejemplo separa ventana y envío en un host común; el tutorial mantiene el flujo en {programCs}. {context} introduce dibujo indexado, transformaciones y profundidad.'
+tutorial.troubleshooting.details.context: 'Cubo giratorio'
+tutorial.troubleshooting.details.detail: 'sombreador Slang'
+tutorial.troubleshooting.details.link: 'renderizador de ejemplo'
+tutorial.troubleshooting.table.backend.check: 'Compruebe que el backend seleccionado sea compatible con el dispositivo y el controlador de gráficos instalado.'
+tutorial.troubleshooting.table.backend.symptom: 'Falla la creación del backend'
+tutorial.troubleshooting.table.drawing.check: 'Compruebe el tamaño de la carga, la disposición de entrada, la selección de la canalización y la ubicación de los comandos de dibujo dentro de la pasada de renderizado.'
+tutorial.troubleshooting.table.drawing.symptom: 'Aparece el fondo pero no el triángulo'
+tutorial.troubleshooting.table.headings.check: 'Comprobación'
+tutorial.troubleshooting.table.headings.symptom: 'Síntoma'
+tutorial.troubleshooting.table.input.check: 'Compruebe el paso de 28 bytes y los desplazamientos de posición/color de 0 y 12.'
+tutorial.troubleshooting.table.input.symptom: 'Las posiciones o colores de los vértices están dañados'
+tutorial.troubleshooting.table.shaders.check: 'Compruebe el archivo {triangleSlang} copiado, la ortografía del punto de entrada y el diagnóstico notificado por el compilador.'
+tutorial.troubleshooting.table.shaders.symptom: 'El fondo se muestra, pero falla la carga de los sombreadores'
+tutorial.troubleshooting.table.window.check: 'Confirme que el proyecto .NET se compila y que el paso de solo ventana se ejecuta en una sesión de escritorio. En Linux, compruebe que X11 o XWayland estén disponibles.'
+tutorial.troubleshooting.table.window.symptom: 'No se abre ninguna ventana'
+tutorial.troubleshooting.title: 'Diagnóstico de problemas'
+tutorial.window.description: 'Descargue {cocoaHelperCs} en el directorio del proyecto. Esta clase auxiliar conecta la capa Metal a la ventana de macOS.'
+tutorial.window.details: 'Sustituya el contenido generado de {programCs} por el código siguiente. Usa instrucciones de nivel superior que se ejecutan en orden, sin un método {main} explícito. Las siguientes secciones indicarán dónde añadir cada fragmento:'
+tutorial.window.guidance: '{graphicsAPINone} deja la creación del contexto gráfico a Zenith.NET. {initialize} crea la ventana nativa y permite acceder a los identificadores del sistema de ventanas. {center} la coloca en el centro de la pantalla. {run} procesa los eventos hasta que se cierra; después se ejecuta {dispose} para liberarla.'
+tutorial.window.notes: 'Compile y ejecute la aplicación. Compruebe que puede abrir, mover, redimensionar y cerrar la ventana. Su contenido aún no está definido porque no se han enviado comandos de renderizado. Cierre la ventana antes de continuar.'
+tutorial.window.title: 'Creación de la ventana'
+ui.action.copy: 'Copiar'
+ui.action.navigate: 'Navegar'
+ui.action.open: 'Abrir'
+ui.action.source: 'Ver código fuente'
+ui.api.browse: 'Examinar espacios de nombres y tipos.'
+ui.api.clearTypeSearch: 'Borrar búsqueda de tipos'
+ui.api.findType: 'Buscar un tipo'
+ui.api.loadError: 'No se pudieron cargar los tipos. Recargue esta página para intentarlo de nuevo.'
+ui.api.loadingNamespaces: 'Cargando espacios de nombres…'
+ui.api.loadingTypes: 'Cargando tipos…'
+ui.api.matchingTypes: 'Tipos coincidentes'
+ui.api.namespace: 'Espacio de nombres'
+ui.api.namespaces: 'Espacios de nombres'
+ui.api.noNamespaceTypes: 'No hay tipos en este espacio de nombres.'
+ui.api.noTypes: 'No hay tipos coincidentes.'
+ui.api.types: 'Tipos'
+ui.content.reference: 'Referencia'
+ui.content.source: 'Código fuente'
+ui.content.tutorial: 'Tutorial'
+ui.contents.close: 'Cerrar el índice'
+ui.contents.open: 'Abrir el índice'
+ui.contents.title: 'Índice'
+ui.footer.caption: 'API gráfica para .NET'
+ui.footer.label: 'Pie de página'
+ui.language.label: 'Idioma'
+ui.language.unavailable: 'No traducido'
+ui.members.clear: 'Borrar búsqueda de miembros'
+ui.members.close: 'Cerrar buscador de miembros'
+ui.members.empty: 'No hay miembros coincidentes.'
+ui.members.open: 'Ir al miembro'
+ui.members.placeholder: 'Nombre o firma…'
+ui.members.search: 'Buscar miembros'
+ui.members.title: 'Buscar un miembro'
+ui.members.undeclared: 'Este tipo no tiene miembros declarados.'
+ui.navigation.api: 'API'
+ui.navigation.concepts: 'Conceptos'
+ui.navigation.home: 'Inicio'
+ui.navigation.label: 'Navegación principal'
+ui.navigation.skip: Ir al contenido
+ui.navigation.toggle: 'Alternar navegación'
+ui.outline.title: 'En esta página'
+ui.reference.assembly: 'Ensamblado'
+ui.reference.classes: 'Clases'
+ui.reference.constructors: 'Constructores'
+ui.reference.declaration: 'Declaración de tipo C#'
+ui.reference.delegates: 'Delegados'
+ui.reference.derived: 'Tipos derivados'
+ui.reference.enums: 'Enumeraciones'
+ui.reference.events: 'Eventos'
+ui.reference.examples: 'Ejemplos'
+ui.reference.exceptions: 'Excepciones'
+ui.reference.extensionMethods: 'Métodos de extensión'
+ui.reference.fields: 'Campos'
+ui.reference.implementations: 'Implementaciones explícitas de interfaz'
+ui.reference.implements: 'Implementa'
+ui.reference.inheritance: 'Herencia'
+ui.reference.inheritedMembers: 'Miembros heredados'
+ui.reference.interfaces: 'Interfaces'
+ui.reference.metadata: 'Metadatos de tipo'
+ui.reference.methods: 'Métodos'
+ui.reference.namespace: 'Espacio de nombres'
+ui.reference.open: 'Abrir referencia de {name}'
+ui.reference.operators: 'Operadores'
+ui.reference.parameters: 'Parámetros'
+ui.reference.properties: 'Propiedades'
+ui.reference.related: 'Vea también'
+ui.reference.relationships: 'Herencia y metadatos'
+ui.reference.remarks: 'Comentarios'
+ui.reference.returns: 'Devuelve'
+ui.reference.structs: 'Estructuras'
+ui.reference.typeParameters: 'Parámetros de tipo'
+ui.reference.value: 'Valor'
+ui.reference.values: 'Valores'
+ui.search.close: 'Cerrar búsqueda'
+ui.search.empty: 'No hay resultados para «{query}».'
+ui.search.hint: 'Busque en tutoriales, conceptos, ejemplos y en la referencia de la API.'
+ui.search.loading: 'Cargando búsqueda…'
+ui.search.more: 'Mostrar más resultados'
+ui.search.pending: 'Buscando…'
+ui.search.placeholder: '¿Qué está buscando?'
+ui.search.resultCount: 'Resultados: {count}'
+ui.search.title: 'Buscar en la documentación'
+ui.search.unavailable: 'La búsqueda no está disponible. Aún puede abrir las páginas siguientes.'
diff --git a/documents/locales/es-ES/translation.md b/documents/locales/es-ES/translation.md
new file mode 100644
index 00000000..ef2e35c6
--- /dev/null
+++ b/documents/locales/es-ES/translation.md
@@ -0,0 +1,85 @@
+# Criterios de traducción al español
+
+## Recursos y estructura
+
+El [diccionario de recursos en inglés](../en-US/strings.yml) es la única fuente del texto. Las normas del proyecto están en la [guía de mantenimiento de la documentación](../../maintenance.md). Para iniciar una traducción, copie el archivo inglés completo como `strings.yml` en este directorio. Conserve la primera línea `### YamlMime:Resources` y la codificación UTF-8. En futuras revisiones, incorpore los cambios del original sin sobrescribir las traducciones existentes. Este archivo `translation.md` se conserva permanentemente y no se publica en el sitio.
+
+La navegación, el orden de los capítulos, las anclas, los enlaces, el formato, el código y las imágenes se mantienen fuera de `locales/` y se comparten entre idiomas. No cree aquí artículos, TOC, plantillas, scripts ni configuraciones.
+
+Modifique únicamente los valores de la propia `strings.yml`. El conjunto de claves debe coincidir exactamente con el inglés. No añada, elimine ni renombre claves por iniciativa propia, aunque parezcan repetidas o correspondan a textos de interfaz que aún no haya visto. Si encuentra un error en el original, comunique la clave y el problema a los responsables del proyecto. Primero se corrigen la fuente inglesa y las páginas compartidas; después, las traducciones.
+
+## Estilo y contexto
+
+Emplear un estilo de manual técnico: preciso, sobrio y comprensible. Explicar la relación entre las operaciones antes de sus restricciones. En los tutoriales, mantener instrucciones concretas y resultados comprobables. El rigor no requiere pasivas ni frases innecesariamente largas.
+
+Revisar cada artículo en su orden de lectura, junto con los párrafos, tablas y código que lo rodean. Las claves son límites de almacenamiento, no unidades de traducción aisladas. Comprobar las concordancias y preposiciones de los enlaces y fragmentos destacados dentro de la frase completa.
+
+Los títulos, etiquetas, textos breves visibles de la portada y leyenda del pie no llevan punto final. Los párrafos, diagnósticos e instrucciones de accesibilidad conservan la puntuación normal. Utilizar un término asentado por concepto, sin añadir de forma selectiva equivalentes ingleses entre paréntesis. Conservar los identificadores de API, archivos y ajustes.
+
+Conserve una frase si explica la operación actual, una decisión de diseño, un resultado comprobable o una condición necesaria. Elimine listas de funciones que no se usan, advertencias genéricas y requisitos repetidos. Traduzca los títulos y las frases divididas por el formato como una unidad, con el orden natural del español. Los marcadores `{accent}` y `{lineBreak}` del título de portada pueden moverse; el diseño no debe imponer la sintaxis inglesa. La eliminación de un pasaje y sus claves se coordina en el original, el marcado común y todos los diccionarios.
+
+## Texto y marcadores
+
+Traduzca frases o párrafos completos con un español técnico claro y natural. Conserve las condiciones, negaciones, unidades, pasos y restricciones de vida útil. No añada garantías técnicas, requisitos de plataforma, versiones de paquetes ni notas de versiones ausentes del original. Mantenga los nombres de productos, paquetes y archivos, así como los identificadores de C# y Slang, incluidas sus mayúsculas y minúsculas.
+
+Cada valor debe ser una cadena no vacía de texto sin HTML ni Markdown. Respete las comillas y los escapes de YAML; utilice comillas cuando sean necesarias para evitar que los dos puntos, las almohadillas o los valores parecidos a números se interpreten incorrectamente. Mantenga el orden de las claves del inglés para facilitar la revisión.
+
+Los marcadores con nombre, como `{context}` y `{link}`, pueden insertar código, enlaces o texto destacado de las páginas compartidas. Los marcadores de interfaz, como `{name}`, `{query}` y `{count}`, también pueden recibir texto durante la ejecución. Compruebe su uso en la página o interfaz correspondiente; no deduzca el contenido solo por el nombre. Traduzca también las etiquetas de enlace y los textos destacados que tengan su propia clave de recurso.
+
+Puede reordenar los marcadores según la sintaxis española. En cada clave, conserve todos los nombres, sus mayúsculas y minúsculas y las llaves ASCII. No añada, elimine ni renombre marcadores, ni los sustituya por nombres de API, etiquetas de enlace o números de ejemplo. No divida las claves para recomponer frases.
+
+## Enlaces de API y significado técnico
+
+Los enlaces se mantienen en las páginas compartidas. Los identificadores que designan una API local en el texto y las tablas enlazan con su tipo o miembro. Las variables, los parámetros, los tipos definidos por el ejemplo, los nombres de paquetes y archivos y los símbolos externos sin referencia local permanecen como texto de código. Una misma grafía puede designar símbolos distintos. Los bloques de código habituales del tutorial no reciben enlaces automáticos. Puede mover un marcador completo, pero no cambiar enlaces, código compartido ni su formato.
+
+Si una API no tiene una entrada de referencia generada, su identificador permanece como texto de código en la página compartida, con un enlace al código fuente claramente identificado cerca. No lo sustituya por un enlace a otro tipo o miembro.
+
+Conserve estas distinciones:
+
+- Grabar comandos, enviarlos, ejecutarlos en la GPU y completarlos son pasos distintos.
+- El uso de un recurso, la residencia de memoria o intención de acceso de la CPU, el layout de una textura y su formato de píxel son conceptos distintos.
+- La propiedad indica quién debe liberar el recurso. Un handle no mantiene el recurso vivo por sí solo; enviar comandos tampoco transfiere su propiedad.
+- La finalización en la GPU no siempre implica que los datos de lectura temporal ya se hayan copiado al destino de CPU de la aplicación. Conserve las indicaciones sobre esperar a la cola responsable.
+- Un drawable es la textura de salida del fotograma actual y no necesariamente una imagen de la cadena de intercambio. Distinga las vistas de recursos de los controles de interfaz.
+
+## Compilación y revisión de entrega
+
+Ejecute los comandos desde la raíz del repositorio, con el SDK de .NET requerido por el proyecto y DocFX instalados. En una copia nueva del repositorio aún no existen los metadatos generados en `documents/api/`. Primero ejecute la compilación completa, incluida la extracción de la API:
+
+```sh
+docfx documents/docfx.json --warningsAsErrors
+```
+
+Si los metadatos de API ya existen y corresponden al código fuente actual, para cambios limitados al diccionario puede usar la compilación normal y la vista previa:
+
+```sh
+docfx build documents/docfx.json --warningsAsErrors
+docfx serve documents/_site --hostname 127.0.0.1 --port 8080
+```
+
+Este idioma ya está registrado. El diccionario completo queda disponible tras una compilación correcta, sin cambiar la configuración. La compilación falla si faltan o sobran claves, hay valores vacíos o de otro tipo, o cambian los nombres de los marcadores. No detecta traducciones inexactas ni texto que siga en inglés; es necesaria una revisión humana.
+
+Todos los idiomas comparten las rutas. `?lang=es-ES` selecciona español; el cambio de idioma conserva la ruta y el ancla. Sin selección explícita, se consultan primero la selección manual guardada, después las preferencias del navegador y, por último, el inglés. Si esta traducción no está publicada, o falla la carga o validación de los recursos de la página, se muestra la página en inglés, sin mezclar idiomas frase a frase. La lista de idiomas se ordena por código.
+
+Antes de entregar, compruebe las claves y los marcadores. Revise la página de inicio, la entrada de Learn, el tutorial del triángulo, los conceptos, los ejemplos y la interfaz de API en escritorio y a 320 píxeles de ancho. Lea el tutorial como principiante y compruebe terminología, botones, búsqueda, copia de código, enlaces de API y la posición del apartado tras cambiar de idioma. Los identificadores, las declaraciones y el código siguen siendo compartidos; solo se traducen los textos del diccionario. Mantenga los diccionarios de prueba y archivos temporales fuera del repositorio y no entregue traducciones provisionales de relleno.
+
+## Terminología
+
+| Término inglés | Forma acordada |
+| ---------------- | ----------------------------------------------------------------------------------------------------------------------- |
+| API / GPU / RHI | Conservar las siglas y explicarlas según el original |
+| barrier | barrera |
+| buffer | búfer; conservar `Buffer` |
+| command buffer | búfer de comandos |
+| command queue | cola de comandos |
+| drawable | textura de salida del fotograma actual; conservar `Drawable`, sin asumir que sea una imagen de la cadena de intercambio |
+| graphics context | contexto gráfico |
+| pipeline | canalización |
+| readback | lectura de datos desde la GPU |
+| render pass | pasada de renderizado |
+| resource | recurso |
+| shader | sombreador |
+| swap chain | cadena de intercambio |
+| texture | textura |
+| timeline | línea de tiempo |
+| upload | carga de datos a la GPU |
diff --git a/documents/locales/fr-FR/strings.yml b/documents/locales/fr-FR/strings.yml
new file mode 100644
index 00000000..8e1a4d1e
--- /dev/null
+++ b/documents/locales/fr-FR/strings.yml
@@ -0,0 +1,593 @@
+### YamlMime:Resources
+concepts.execution.context.description: 'Un {graphicsContext} relie l’application à un périphérique GPU par un backend donné et fournit ses files graphiques, de calcul et de transfert. Les ressources utilisées ensemble doivent appartenir au même contexte : tampons, textures, shaders, pipelines et handles ne peuvent pas être liés d’un contexte à un autre.'
+concepts.execution.context.details: '{capabilities} indique le nom du périphérique et les fonctions prises en charge. Vérifiez {rayTracingSupported} et {meshShadingSupported} avant de créer les ressources nécessaires à ces fonctions. Les exemples de {link} et de {detail} effectuent ces vérifications à l’initialisation.'
+concepts.execution.context.details.detail: mesh shading
+concepts.execution.context.details.link: lancer de rayons
+concepts.execution.context.title: 'Contexte graphique'
+concepts.execution.description: 'Le travail du GPU se prépare sur le CPU : l’application crée des ressources, enregistre les commandes qui les utilisent, puis les soumet à une file. Le CPU peut ensuite poursuivre son exécution pendant que le GPU traite ces commandes. Les ressources nécessaires doivent rester disponibles jusqu’à la fin de ce traitement.'
+concepts.execution.ownership.description: 'La propriété détermine qui libère un objet ; l’achèvement sur le GPU détermine quand il peut être libéré. Un appel à {dispose} libère les ressources natives même si des références C# à l’objet existent encore. Soumettre des commandes ne transfère pas la propriété de leurs ressources à la file.'
+concepts.execution.ownership.details: 'À l’arrêt, cesser les nouvelles soumissions et attendre la dernière utilisation des ressources sur chaque file concernée. Libérer ensuite les ressources de l’application, puis le contexte. Celui-ci libère ses files et ses objets auxiliaires internes ; cette dernière étape doit donc intervenir après la fin du travail de l’application.'
+concepts.execution.ownership.guidance: 'Pendant le rendu, suivre l’achèvement des accès aux données à modifier ou aux ressources à remplacer. Attendre la fin de ces accès avant de modifier ou de libérer la mémoire concernée. L’article {link} explique comment les vues et les ressources sous-jacentes s’inscrivent dans ce modèle de durée de vie.'
+concepts.execution.ownership.guidance.link: Gestion des ressources
+concepts.execution.ownership.table.applicationResources.object: Ressources créées par l’application
+concepts.execution.ownership.table.applicationResources.ownership: L’application les libère après leur dernière utilisation par le GPU.
+concepts.execution.ownership.table.commandBuffers.object: Tampons de commandes empruntés
+concepts.execution.ownership.table.commandBuffers.ownership: La file les gère et les recycle.
+concepts.execution.ownership.table.headings.object: Objet
+concepts.execution.ownership.table.headings.ownership: 'Responsabilité de libération'
+concepts.execution.ownership.table.queues.object: Files de contexte et leurs lignes de temps
+concepts.execution.ownership.table.queues.ownership: Le contexte et les files les gèrent.
+concepts.execution.ownership.table.swapChainDrawable.ownership: La chaîne d’échange la gère.
+concepts.execution.ownership.title: 'Propriété et durée de vie'
+concepts.execution.pipelines.description: 'Un pipeline graphique définit les shaders, la disposition des attributs de sommets, l’assemblage des primitives, les formats d’attachement et l’état de rendu. Il doit être sélectionné avant les liaisons de sommets, d’indices ou de constantes. Ses formats et son nombre d’échantillons doivent correspondre à la passe de rendu.'
+concepts.execution.pipelines.details: 'Un pipeline de calcul sélectionne le point d’entrée du shader pour un lancement de calcul. Lier le pipeline et ses constantes avant ce lancement, en dehors d’une passe de rendu graphique.'
+concepts.execution.pipelines.guidance: 'Le {link} présente la séquence graphique. L’{detail} ajoute un lancement de calcul avant la passe graphique qui en affiche le résultat.'
+concepts.execution.pipelines.guidance.detail: exemple de calcul
+concepts.execution.pipelines.guidance.link: rappel de rendu du triangle
+concepts.execution.pipelines.title: 'État du pipeline'
+concepts.execution.queues.description: '{graphicsQueue} constitue le point de départ habituel lorsqu’une image combine dessin, calcul et copies. Regrouper les opérations dépendantes dans une même file simplifie leur ordonnancement.'
+concepts.execution.queues.details: 'Des files distinctes permettent de soumettre séparément des tâches indépendantes, mais ne correspondent pas nécessairement à des moteurs matériels indépendants. Elles sont utiles lorsque les tâches peuvent bénéficier d’une exécution partiellement simultanée. Lorsqu’une file utilise les données produites par une autre, leurs soumissions doivent être reliées par une dépendance explicite.'
+concepts.execution.queues.guidance: 'Dans une même file, des opérations dépendantes peuvent aussi nécessiter des barrières ou des transitions de texture pour que les accès ultérieurs voient les écritures précédentes. Ces dépendances mémoire sont distinctes de l’ordre de soumission. L’article {link} explique comment les exprimer.'
+concepts.execution.queues.guidance.link: Synchronisation
+concepts.execution.queues.table.computeQueue.workToRecord: 'Lancements de calcul et transferts compatibles, sans passe graphique.'
+concepts.execution.queues.table.graphicsQueue.workToRecord: 'Passes de rendu, dessin, calcul et transferts.'
+concepts.execution.queues.table.headings.queue: File
+concepts.execution.queues.table.headings.workToRecord: 'Opérations prises en charge'
+concepts.execution.queues.table.transferQueue.workToRecord: 'Transferts vers ou depuis le GPU et copies, sans dessin ni lancement de calcul.'
+concepts.execution.queues.title: 'Choix des files'
+concepts.execution.recording.context: 'La file récupère les tampons de commandes terminés pour les réutiliser. Pour chaque enregistrement, obtenir un tampon avec {queueCommandBuffer}, l’enregistrer depuis un seul thread à la fois et le soumettre une seule fois. Après la soumission, le laisser à la file : ne plus le modifier, le soumettre à nouveau ou le libérer.'
+concepts.execution.recording.description: '{queueCommandBuffer} renvoie un {commandBuffer} dont l’enregistrement a commencé. Une fois les opérations et leurs dépendances enregistrées, appeler {submit} une seule fois. La soumission termine l’enregistrement et fournit une {timelineValue} qui identifie le point d’achèvement de cette file.'
+concepts.execution.recording.details: 'Après l’enregistrement d’une image dans {commandBuffer}, le tutoriel soumet le tampon et attend son achèvement :'
+concepts.execution.recording.guidance: 'L’enregistrement construit la séquence de commandes ; {submit} en planifie l’exécution. Un appel à {wait} bloque ensuite le thread CPU jusqu’à la fin de la soumission. Dans le tutoriel, la boucle de rendu reste ainsi facile à suivre : chaque image est terminée avant que la suivante commence.'
+concepts.execution.recording.title: 'Enregistrement et soumission'
+concepts.execution.title: Modèle d’exécution
+concepts.platforms.description: 'La présentation est gérée par l’application ou par un contrôle d’interface. Ce choix détermine qui obtient la texture de sortie de l’image, enregistre et soumet les commandes, puis présente le résultat. Cette texture de sortie est appelée drawable.'
+concepts.platforms.frame.context: 'Enregistrer les liaisons de pipeline et les commandes de dessin dans la passe, puis la terminer avant que le gestionnaire retourne. Le contrôle soumet les commandes et présente le résultat. Le gestionnaire ne doit ni soumettre le tampon de commandes ni libérer l’un ou l’autre des objets fournis. S’il modifie le layout de la texture de sortie, il doit rétablir {colorAttachment} avant de retourner pour permettre au contrôle d’effectuer la transition finale.'
+concepts.platforms.frame.description: 'Les contrôles implémentent {iZenithView}. L’application assigne {graphicsContext}, met à jour la scène dans {updateRequested} et enregistre le rendu dans {renderRequested} à partir de {renderEventArgs}.'
+concepts.platforms.frame.details: 'Pour chaque image, le contrôle obtient une texture de sortie et un tampon de commandes, passe la texture au layout {colorAttachment}, puis appelle les gestionnaires de mise à jour et de rendu. Il soumet ensuite les commandes enregistrées et présente l’image par le mécanisme du framework.'
+concepts.platforms.frame.guidance: 'Ajouter {usingZenithNET} au fichier. Pour un {iZenithView} initialisé, nommé {view} et associé à un contexte graphique, enregistrer ce gestionnaire afin de remplir sa texture de sortie avec une couleur :'
+concepts.platforms.frame.notes: 'La transition initiale utilise {undefined}, ce qui autorise l’abandon du contenu précédent. Dessiner ou réinitialiser l’image entière à chaque rendu. Pour conserver une accumulation ou un résultat en cache, utiliser une texture appartenant à l’application, puis dessiner cette texture dans la sortie du contrôle.'
+concepts.platforms.frame.reference: '{zenithViewHelperDrawableFormat} fournit le format de couleur par défaut de ces intégrations.'
+concepts.platforms.frame.title: 'Rendu par un contrôle UI'
+concepts.platforms.integration.context: 'WinForms, WPF et l’intégration Windows de WinUI s’exécutent sous Windows. Pour MAUI, installer la charge de travail de la plateforme cible et enregistrer le gestionnaire du contrôle avec {useZenithView}, comme dans {extensionsCs}.'
+concepts.platforms.integration.description: 'L’interface d’événements est commune, mais la présentation dépend du framework :'
+concepts.platforms.integration.details: 'Ces implémentations diffèrent par la façon dont l’image GPU rejoint l’interface. La lecture vers un bitmap copie les pixels en mémoire CPU avant l’affichage. L’interopérabilité par texture partagée évite cette copie CPU, mais peut encore nécessiter des copies GPU et de la synchronisation. Pour choisir une intégration, considérer ce chemin de présentation en plus de l’événement commun {renderRequested}.'
+concepts.platforms.integration.guidance: 'Ajoutez le paquet d’intégration correspondant à votre framework d’interface via NuGet. Pour Avalonia, utilisez {avaloniaPackage}.'
+concepts.platforms.integration.table.avalonia.integration: Avalonia
+concepts.platforms.integration.table.avalonia.presentation: 'Effectue le rendu dans une texture, puis copie les pixels en mémoire CPU dans {writeableBitmap}. Implémentation : {zenithViewCs} et {surfaceCs}.'
+concepts.platforms.integration.table.headings.integration: Intégration
+concepts.platforms.integration.table.headings.presentation: 'Mécanisme de présentation'
+concepts.platforms.integration.table.maui.integration: .NET MAUI
+concepts.platforms.integration.table.maui.presentation: Utilise des surfaces natives sur Android, iOS et Mac Catalyst, et la composition par texture partagée sous Windows. Voir les {link}.
+concepts.platforms.integration.table.maui.presentation.link: implémentations de plateforme
+concepts.platforms.integration.table.uno.integration: Voie Uno
+concepts.platforms.integration.table.uno.presentation: 'Copie les pixels en mémoire CPU dans {writeableBitmap}. Implémentation : {zenithViewUnoCs}.'
+concepts.platforms.integration.table.winForms.integration: WinForms
+concepts.platforms.integration.table.winForms.presentation: Crée une chaîne d’échange Zenith.NET pour le {hWND} du contrôle. Voir {zenithViewCs}.
+concepts.platforms.integration.table.winUI.integration: WinUI sous Windows
+concepts.platforms.integration.table.winUI.presentation: 'Effectue le rendu dans une texture partagée Direct3D 11 importée, puis copie le résultat vers une chaîne d’échange de composition attachée à {swapChainPanel}. Voir {zenithViewWinUICs}.'
+concepts.platforms.integration.table.wpf.integration: WPF
+concepts.platforms.integration.table.wpf.presentation: 'Importe une texture partagée Direct3D 11, puis copie son contenu, par interopérabilité Direct3D, vers une surface affichée par {d3DImage}. Voir {surfaceCs}.'
+concepts.platforms.integration.title: 'Framework et présentation'
+concepts.platforms.lifecycle.context: 'Pour créer des attachements compatibles, utiliser {descWidth}, {descHeight}, {descFormat} et {descSampleCount} de la texture de sortie fournie par l’événement de rendu. Cette texture donne les dimensions réelles de la cible ; elles ne doivent pas être déduites de la taille du contrôle dans la mise en page ni de l’échelle d’affichage.'
+concepts.platforms.lifecycle.description: 'La chaîne d’échange utilise la largeur et la hauteur du framebuffer en pixels, qui peuvent différer des dimensions logiques de la fenêtre avec la mise à l’échelle de l’affichage. Différer le rendu et le redimensionnement tant qu’une dimension est nulle. Obtenir {swapChainDrawable} pour chaque image : la chaîne d’échange possède cette texture et peut la remplacer après une présentation ou un redimensionnement.'
+concepts.platforms.lifecycle.details: 'Lors d’un changement de dimensions du framebuffer, attendre les accès GPU en cours aux anciennes images avant d’appeler {swapChainResize}. Pour remplacer la cible native de présentation, transmettre sa nouvelle description et ses handles à {swapChainRefresh}, une fois ces accès terminés. À l’arrêt, libérer la chaîne d’échange avant de détruire sa cible native.'
+concepts.platforms.lifecycle.guidance: 'Si les textures de profondeur ou hors écran de l’application suivent la taille de la fenêtre, les recréer quand celle-ci change et mettre à jour les constantes contenant leurs handles. Le pipeline reste utilisable tant que les formats des attachements et les nombres d’échantillons correspondent. Le {link} montre comment recréer les textures tout en conservant le pipeline.'
+concepts.platforms.lifecycle.guidance.link: moteur de rendu du cube en rotation
+concepts.platforms.lifecycle.title: 'Dimensions et durée de vie de la chaîne d’échange'
+concepts.platforms.ownership.description: 'L’application possède le contexte assigné et ses ressources de rendu ; le contrôle possède ses ressources de présentation. Lorsque la propriété {graphicsContext} de la vue change, l’ordonnanceur recrée les ressources du contrôle pour le nouveau contexte. L’application doit également recréer ses pipelines, tampons et textures pour ce contexte.'
+concepts.platforms.ownership.details: 'Avant de remplacer des ressources de rendu, arrêter le traitement des images de la vue et attendre le travail GPU qui les utilise. Libérer ensuite seulement les anciennes ressources ou le contexte. L’application reste responsable de la libération du contexte assigné lors du retrait de la vue.'
+concepts.platforms.ownership.guidance: 'Le {frameSchedulerCs} commun exécute les gestionnaires d’image via le dispatcher de l’interface de la vue. Limiter leur travail à la mise à jour de la scène et à l’enregistrement des commandes : un traitement CPU prolongé à cet endroit retarde aussi l’interface.'
+concepts.platforms.ownership.title: 'Responsabilités de l’application et du contrôle'
+concepts.platforms.surface.description: 'Le {link} utilise Silk.NET pour créer la fenêtre et traiter ses événements. Zenith.NET reçoit les handles natifs et les dimensions dans une description {surface}, à partir de laquelle il crée une {swapChain}. L’application reste responsable de la fenêtre du système d’exploitation.'
+concepts.platforms.surface.description.link: 'tutoriel du triangle'
+concepts.platforms.surface.details: 'Les handles natifs doivent correspondre au backend :'
+concepts.platforms.surface.guidance: 'L’hôte des exemples dans {appCs} prend en charge Windows, macOS et X11. Sous Linux, sa fenêtre fournit des handles Xlib ; une session Wayland doit donc disposer de XWayland pour l’exécuter. Une application Wayland native transmet ses handles de display et de surface via {surfaceWayland}.'
+concepts.platforms.surface.table.android.backend: Vulkan sous Android.
+concepts.platforms.surface.table.apple.backend: Metal sur les plateformes Apple.
+concepts.platforms.surface.table.headings.backend: 'Backend et plateforme'
+concepts.platforms.surface.table.headings.factory: Fabrique de surface
+concepts.platforms.surface.table.headings.handles: Handles fournis par l’hôte
+concepts.platforms.surface.table.wayland.backend: Vulkan avec un hôte Wayland natif.
+concepts.platforms.surface.table.wayland.handles: Affichage et surface Wayland
+concepts.platforms.surface.table.win32.backend: DirectX 12 ou Vulkan sous Windows.
+concepts.platforms.surface.table.xlib.backend: Vulkan sous Linux avec X11 ou XWayland.
+concepts.platforms.surface.table.xlib.handles: Affichage et fenêtre X11
+concepts.platforms.surface.title: 'Surfaces de présentation natives'
+concepts.platforms.title: Intégration de plateforme
+concepts.resources.allocation.context: 'Les ressources placées dans un tas dépendent de sa mémoire pendant toute leur durée de vie. Après leur dernière utilisation par le GPU, libérer les ressources avant de libérer le tas.'
+concepts.resources.allocation.description: 'Un {heap} permet à l’application de gérer elle-même les allocations. Il fournit une mémoire où placer les ressources à des décalages explicites en octets. L’application choisit ces emplacements et suit les régions occupées.'
+concepts.resources.allocation.details: 'Pour chaque ressource, appeler {contextGetSizeAndAlignment} avec la description de tampon ou de texture qui servira à sa création. Réserver la taille renvoyée à un décalage multiple de l’alignement demandé, dans les limites du tas et sans chevaucher une allocation encore active. La taille d’une texture peut inclure du remplissage propre au backend ; le nombre de pixels et le nombre d’octets par pixel ne suffisent donc pas à calculer la taille de son allocation.'
+concepts.resources.allocation.guidance: 'Un tampon placé dans un tas doit déclarer la même résidence que ce tas. Pour les textures, utiliser un tas destiné exclusivement au GPU.'
+concepts.resources.allocation.title: 'Allocation dans un tas'
+concepts.resources.bufferUsages.table.constant.role: Paramètres de shader lus via une liaison de tampon de constantes.
+concepts.resources.bufferUsages.table.headings.role: 'Rôle'
+concepts.resources.bufferUsages.table.headings.usage: Usage
+concepts.resources.bufferUsages.table.index.role: Indices utilisés par le dessin indexé.
+concepts.resources.bufferUsages.table.indirect.role: 'Arguments lus par les commandes de dessin indirect ou de lancement indirect de calcul.'
+concepts.resources.bufferUsages.table.none.role: 'Aucun indicateur d’usage du tampon n’est déclaré.'
+concepts.resources.bufferUsages.table.storageReadOnly.role: 'Données de tampon accessibles via une liaison de shader en lecture seule.'
+concepts.resources.bufferUsages.table.storageReadWrite.role: 'Données de tampon accessibles via une liaison de shader en lecture et écriture.'
+concepts.resources.bufferUsages.table.transferDst.role: 'Stockage de destination d’une copie GPU.'
+concepts.resources.bufferUsages.table.transferSrc.role: 'Données sources d’une copie GPU.'
+concepts.resources.bufferUsages.table.vertex.role: Attributs de sommets récupérés pour le dessin.
+concepts.resources.bufferUsages.title: Usages de tampon
+concepts.resources.description: 'La description d’une ressource définit les usages autorisés de sa mémoire. Par exemple, une texture servant de cible de rendu, échantillonnée par un shader et recopiée vers le CPU doit déclarer les indicateurs d’usage correspondant aux trois opérations. Le placement mémoire détermine l’accès CPU ; la synchronisation et la gestion de la durée de vie déterminent quand accéder aux données ou libérer la mémoire.'
+concepts.resources.memory.context: 'Pour les tampons accessibles au CPU, {map} fournit l’adresse à laquelle lire ou écrire les données. Ni le mapping ni {unmap} n’attendent le travail du GPU. L’application doit attendre la fin d’une écriture GPU avant d’en lire le résultat, et la fin des accès GPU en cours avant de modifier les mêmes octets.'
+concepts.resources.memory.description: 'Le champ {residency} de {bufferDesc} précise l’accès CPU prévu à l’allocation :'
+concepts.resources.memory.details: 'La résidence exprime les besoins d’accès du CPU, y compris lorsque CPU et GPU partagent la même mémoire physique. Le triangle utilise {cpuWriteOnly} pour initialiser directement son petit tampon de sommets. Pour une géométrie statique plus volumineuse, un transfert vers une mémoire destinée exclusivement au GPU peut être plus adapté.'
+concepts.resources.memory.guidance: 'Les textures créées directement par le contexte utilisent de la mémoire GPU. {textureDesc} ne comporte donc pas de champ de résidence. L’application échange leurs données avec la mémoire CPU au moyen de transferts vers et depuis le GPU.'
+concepts.resources.memory.table.cpuReadOnly.cPUAccess: 'Lecture seule'
+concepts.resources.memory.table.cpuReadOnly.typicalRole: 'Données produites par le GPU pour être relues par le CPU'
+concepts.resources.memory.table.cpuWriteOnly.cPUAccess: 'Écriture seule'
+concepts.resources.memory.table.cpuWriteOnly.typicalRole: 'Données fournies ou mises à jour par le CPU'
+concepts.resources.memory.table.gpuOnly.cPUAccess: 'Aucun'
+concepts.resources.memory.table.gpuOnly.typicalRole: 'Géométrie statique et données de travail du GPU'
+concepts.resources.memory.table.headings.cPUAccess: 'Accès CPU prévu'
+concepts.resources.memory.table.headings.residency: Résidence
+concepts.resources.memory.table.headings.typicalRole: Rôle typique
+concepts.resources.memory.title: 'Accès CPU et placement mémoire'
+concepts.resources.textureUsages.description: 'Les indicateurs de transfert précisent si la ressource sert de source ou de destination d’une copie GPU. La possibilité pour le CPU d’accéder directement à sa mémoire relève d’un choix distinct : le placement mémoire.'
+concepts.resources.textureUsages.details: 'Les indicateurs d’usage déclarent les rôles possibles d’une texture. Le passage d’un rôle à un autre doit respecter les exigences de {link}. Le périphérique doit également prendre en charge les usages demandés pour le format choisi.'
+concepts.resources.textureUsages.details.link: 'layout et synchronisation'
+concepts.resources.textureUsages.table.colorAttachment.role: 'Un attachement de couleur dans une passe de rendu.'
+concepts.resources.textureUsages.table.depthStencilAttachment.role: 'Un attachement de profondeur et de stencil dans une passe de rendu.'
+concepts.resources.textureUsages.table.headings.role: 'Rôle'
+concepts.resources.textureUsages.table.headings.usage: Usage
+concepts.resources.textureUsages.table.none.role: 'Aucun indicateur d’usage de la texture n’est déclaré.'
+concepts.resources.textureUsages.table.sampled.role: 'Données de texture lues via une liaison de texture échantillonnée.'
+concepts.resources.textureUsages.table.storage.role: 'Données de texture accessibles via une liaison de texture de stockage pour les lectures ou écritures des shaders.'
+concepts.resources.textureUsages.table.transferDst.role: 'Stockage de l’image de destination d’une copie GPU.'
+concepts.resources.textureUsages.table.transferSrc.role: 'Données de l’image source d’une copie GPU.'
+concepts.resources.textureUsages.title: Usages de texture
+concepts.resources.title: Gestion des ressources
+concepts.resources.transfers.context: 'Un transfert depuis le GPU copie d’abord les données dans une mémoire de lecture interne. La bibliothèque les copie ensuite vers la destination CPU fournie lorsqu’elle récupère le tampon de commandes terminé. Appeler {wait} sur la valeur de soumission de ce transfert achève les deux étapes. Jusqu’au retour de cet appel, conserver la mémoire de destination et maintenir épinglée toute mémoire managée utilisée.'
+concepts.resources.transfers.description: 'Les transferts vers le GPU copient des données CPU dans une ressource ; les transferts depuis le GPU copient les données d’une ressource en mémoire CPU. Les méthodes des tampons et des textures terminent ce transfert avant de retourner :'
+concepts.resources.transfers.details: 'Avant ces appels, résoudre les conflits d’accès avec les autres files. L’attente effectuée dans une méthode ne couvre que son propre transfert ; elle n’établit pas de dépendance avec une autre file. Pour les chemins qui accèdent directement à la mémoire mappée, il revient entièrement à l’appelant d’attendre les accès GPU en conflit.'
+concepts.resources.transfers.guidance: 'Pour soumettre des transferts avec d’autres opérations GPU, les enregistrer avec {commandBufferUpload} ou {commandBufferDownload}. La mémoire source d’un transfert vers le GPU et la mémoire de destination d’un transfert depuis le GPU ont des exigences de durée de vie différentes :'
+concepts.resources.transfers.item: 'Pendant l’appel de transfert vers le GPU, la bibliothèque copie les données source dans une mémoire de transfert temporaire. Le GPU lit ensuite cette copie intermédiaire. Le pointeur source de l’application n’est donc nécessaire que jusqu’au retour de l’appel.'
+concepts.resources.transfers.notes: 'Pour les transferts de texture, le pas de ligne est la distance en octets entre deux lignes successives des données CPU ; le pas de tranche est la distance entre deux tranches de profondeur. Ces valeurs permettent de copier les données entre la mémoire CPU et la texture GPU. L’article {link} explique pourquoi l’achèvement sur le GPU et la copie vers la destination CPU sont deux étapes distinctes.'
+concepts.resources.transfers.notes.link: Synchronisation
+concepts.resources.transfers.table.bufferDownload.transfer: 'Copie directement depuis la mémoire mappée pour {cpuReadOnly}. Sinon, soumet le transfert à la file de transfert et attend qu’il soit terminé.'
+concepts.resources.transfers.table.bufferUpload.transfer: 'Copie directement dans la mémoire mappée pour {cpuWriteOnly}. Sinon, soumet le transfert à la file de transfert et attend qu’il soit terminé.'
+concepts.resources.transfers.table.headings.method: Méthode
+concepts.resources.transfers.table.headings.transfer: 'Comportement du transfert'
+concepts.resources.transfers.table.textureTransfers.method: '{textureUpload} / {textureDownload}'
+concepts.resources.transfers.table.textureTransfers.transfer: 'Soumet la copie et les transitions de texture à la file graphique, puis attend leur achèvement. La sous-ressource sélectionnée reste dans le layout final demandé.'
+concepts.resources.transfers.title: 'Transferts immédiats et enregistrés'
+concepts.resources.usages.description: '{bufferUsages} et {textureUsages} sont des énumérations d’indicateurs qui déclarent les usages d’une ressource. Leurs valeurs sont affectées à {bufferDescUsages} et {textureDescUsages}, respectivement. Plusieurs membres peuvent être combinés avec {value} pour déclarer plusieurs usages.'
+concepts.resources.usages.title: 'Usages des ressources'
+concepts.resources.views.description: 'Une {bufferView} décrit une région de tampon et l’interprétation de ses données. Une {textureView} sélectionne une plage de sous-ressources, ainsi qu’un type de texture et un format compatibles. Toutes deux désignent une mémoire existante, avec les usages déclarés à sa création. Les plages des vues décrivent l’accès, mais n’offrent pas de contrôle des limites uniforme dans les shaders sur tous les backends.'
+concepts.resources.views.details: 'Le handle propre à une ressource fournit sa vue par défaut. Créer une vue distincte pour sélectionner une autre région ou une autre interprétation compatible. La vue et sa ressource sous-jacente doivent rester disponibles jusqu’à la fin de leur utilisation par le GPU : un handle ne maintient aucun de ces objets en vie. Si l’un d’eux est remplacé, mettre à jour les constantes contenant l’ancien {link}.'
+concepts.resources.views.details.link: 'handle de ressource'
+concepts.resources.views.title: 'Vues et ressources sous-jacentes'
+concepts.shaders.binding.context: 'L’exemple de calcul affecte {inputTextureSampledHandle} à {input} et {outputTextureStorageHandle} à {output}. En Slang, le paramètre de type de {descriptorHandle} définit l’accès à la ressource :'
+concepts.shaders.binding.description: '{setConstantBuffer} sélectionne un tampon existant et le décalage en octets à partir duquel le pipeline courant lit ses constantes. Transférer les paramètres dans cette mémoire avant leur utilisation par le shader. Si un travail GPU antérieur lit encore les mêmes octets, attendre sa fin avant de les mettre à jour.'
+concepts.shaders.binding.details: 'Préparer le tampon de commandes, le pipeline de calcul, le tampon de constantes rempli et le nombre de groupes de threads. Enregistrer ensuite le calcul dans l’ordre ci-dessous. Sélectionner d’abord le pipeline, car la liaison du tampon de constantes s’applique à ce pipeline :'
+concepts.shaders.binding.guidance: 'Ici, {commandBuffer} enregistre un calcul avec {computePipeline} et les paramètres de {constantBuffer}. Le tampon doit déclarer l’usage {bufferUsagesConstant} ; le décalage 0 sélectionne son début. Si plusieurs enregistrements de constantes partagent un tampon, le décalage de chacun doit aussi respecter l’alignement de liaison du backend, indépendamment de la disposition des champs à l’intérieur de l’enregistrement.'
+concepts.shaders.binding.notes: '{resourceHandle} contient deux champs de 32 bits dont l’interprétation dépend du backend. Leurs valeurs ne sont valables que dans le contexte où la ressource a été créée.'
+concepts.shaders.binding.reference: 'Conserver la ressource et toute vue utilisée pour obtenir son handle jusqu’à la fin du dernier accès du shader. Lorsqu’une texture ou une vue est remplacée, mettre à jour les constantes contenant encore son ancien handle. Les règles de propriété sont détaillées dans {link}.'
+concepts.shaders.binding.reference.link: Gestion des ressources
+concepts.shaders.binding.table.bufferStorageReadOnlyHandle.access: 'Lecture d’éléments structurés de type {t} dans le shader'
+concepts.shaders.binding.table.bufferStorageReadWriteHandle.access: 'Lecture et écriture d’éléments structurés de type {t} dans le shader'
+concepts.shaders.binding.table.headings.access: Accès prévu
+concepts.shaders.binding.table.headings.cHandle: 'Handle C#'
+concepts.shaders.binding.table.headings.slangFieldType: Type de champ Slang
+concepts.shaders.binding.table.samplerHandle.access: 'État d’échantillonnage des textures'
+concepts.shaders.binding.table.textureSampledHandle.access: 'Lecture de textures'
+concepts.shaders.binding.table.textureStorageHandle.access: 'Lecture et écriture de textures de stockage'
+concepts.shaders.binding.title: 'Constantes et handles de ressources'
+concepts.shaders.compilation.description: '{zenithCompiler} compile un point d’entrée Slang en {shaderDesc}. {graphicsApi} détermine la cible : DXIL pour DirectX 12, bibliothèque Metal pour Metal, SPIR-V pour Vulkan.'
+concepts.shaders.compilation.details: 'Avec un contexte graphique nommé {context} déjà créé, compiler le shader du triangle à partir de la copie placée à côté de l’exécutable :'
+concepts.shaders.compilation.guidance: 'Le nom de point d’entrée {vSMain} doit correspondre à la déclaration dans le fichier de shader. Compiler les shaders et créer les pipelines à l’initialisation, puis réutiliser les pipelines pendant le rendu. Les objets shader peuvent être libérés une fois le pipeline créé, comme dans le tutoriel {link}.'
+concepts.shaders.compilation.guidance.link: Premier triangle
+concepts.shaders.compilation.title: 'Compilation des shaders'
+concepts.shaders.description: 'Les déclarations C# et Slang doivent s’accorder sur l’emplacement de chaque valeur et sur son accès par le shader. Les dispositions d’entrée décrivent les attributs de sommets ; les déclarations de tampons dans les shaders décrivent les paramètres et les tableaux ; les handles identifient les ressources utilisées par le shader.'
+concepts.shaders.dispatch.description: '{dispatch} indique le nombre de groupes de threads à exécuter. L’exemple de calcul déclare {numthreads} : chaque groupe contient donc 16 × 16 threads. Pour couvrir l’image, diviser sa largeur et sa hauteur par 16, puis arrondir chaque résultat à l’entier supérieur. Une image de 17 × 19 pixels demande ainsi 2 × 2 groupes. Le shader vérifie les coordonnées de chaque thread et ignore ceux qui sont hors de l’image.'
+concepts.shaders.dispatch.details: 'La taille de groupe déclarée est disponible dans {shaderDescThreadGroupSize} et permet de calculer le nombre de groupes à transmettre à {dispatch}. Le calcul complet figure dans {computeShaderRendererCs}. Les accès ultérieurs à l’image produite doivent respecter les conditions décrites dans {link}.'
+concepts.shaders.dispatch.details.link: Synchronisation
+concepts.shaders.dispatch.title: 'Groupes de threads'
+concepts.shaders.layout.context: 'Les {link} illustrent cette organisation avec les dimensions de l’image et deux handles de textures. Des décalages explicites indiquent le début de chaque champ. La déclaration utilise {systemRuntimeInteropServices} et {zenithNET} :'
+concepts.shaders.layout.context.link: 'constantes C# de l’exemple de calcul'
+concepts.shaders.layout.description: 'Pour les sommets, {inputLayout} décrit le format, le décalage et la sémantique de chaque attribut, ainsi que le pas entre sommets. Dans le triangle, une position {float3} précède une couleur {float4} : la couleur commence au décalage de 12 octets et chaque sommet occupe 28 octets. {inputLayoutAdd} calcule cette disposition compacte à partir des formats des éléments. Si la structure C# contient du remplissage, les décalages et le pas doivent en tenir compte.'
+concepts.shaders.layout.details: 'Les tampons de constantes contiennent des paramètres de shader, et les tampons structurés des tableaux d’éléments typés. Leur disposition suit les déclarations Slang, et non {inputLayout}. Les décalages des champs C#, les tailles des éléments et le remplissage doivent correspondre à la disposition mémoire du shader compilé. Un type C# non managé peut être copié sous forme d’octets, mais sa disposition mémoire ne correspond pas nécessairement à celle attendue par le shader : un {float3} compact dans un sommet peut nécessiter un autre alignement dans un tampon de shader.'
+concepts.shaders.layout.guidance: 'Les paramètres de shader peuvent contenir des handles de ressources en plus des valeurs ordinaires. Un handle désigne une mémoire déjà allouée : le copier dans un tampon de constantes ne copie donc pas le contenu de la ressource.'
+concepts.shaders.layout.notes: 'La déclaration correspondante dans {computeShaderSlang} est :'
+concepts.shaders.layout.reference: 'Les deux dimensions occupent les huit premiers octets, suivis de deux handles de huit octets chacun. L’exemple C# réserve 256 octets pour l’ensemble de l’enregistrement. Pour un autre enregistrement, déterminer la taille à partir de ses propres champs et des contraintes de disposition et d’alignement.'
+concepts.shaders.layout.title: 'Disposition des données'
+concepts.shaders.matrices.description: 'Le compilateur choisit un stockage des matrices par lignes contiguës. Le {link} place trois valeurs {matrix4x4} aux décalages de 0, 64 et 128 octets. Son {detail} applique {mulVectorMatrix} dans l’ordre modèle, vue et projection.'
+concepts.shaders.matrices.description.detail: shader
+concepts.shaders.matrices.description.link: moteur de rendu du cube en rotation
+concepts.shaders.matrices.details: 'L’ordre de stockage définit la disposition des éléments de matrice en mémoire ; l’ordre de multiplication définit la transformation. Les conventions C# et Slang doivent correspondre.'
+concepts.shaders.matrices.title: 'Conventions matricielles'
+concepts.shaders.title: Données de shaders et liaison
+concepts.synchronization.backends.description: 'DirectX 12 et Vulkan encodent les transitions de texture sous forme de changements de layout et d’accès. L’implémentation de {transition} dans Zenith.NET n’effectue pas d’opération équivalente sur Metal. Le backend Metal regroupe les commandes dans des encodeurs de rendu et de calcul, puis insère des barrières de visibilité à la fermeture d’un encodeur. Les opérations dépendantes dans un même encodeur, comme deux calculs successifs, utilisent des appels explicites à {barrier}.'
+concepts.synchronization.backends.details: 'Dans l’{link}, l’ouverture de la passe de rendu ferme l’encodeur de calcul et rend sa sortie visible aux commandes graphiques. Deux calculs successifs restent en revanche dans le même encodeur. Leur dépendance exige donc une barrière, même si une transition de texture est enregistrée entre eux. Les séquences de commandes portables doivent ainsi tenir compte des dépendances d’accès autant que des layouts de texture.'
+concepts.synchronization.backends.details.link: exemple de calcul
+concepts.synchronization.backends.title: 'Comportement des backends'
+concepts.synchronization.barriers.context: 'Choisir des étapes qui couvrent les accès concernés et sont prises en charge par la file. {barrierStagesAll} inclut toutes les étapes disponibles ; une sélection plus restreinte convient lorsque la dépendance ne concerne que certaines d’entre elles.'
+concepts.synchronization.barriers.description: 'Considérons deux calculs enregistrés dans le même tampon de commandes : le premier écrit des données de simulation, le second les lit. Une barrière entre les deux garantit que le second voit les écritures achevées. Pour un tampon de stockage, elle ordonne les accès sans transition de layout de texture :'
+concepts.synchronization.barriers.details: 'Le premier argument indique les étapes qui ont produit les données ; le second indique celles qui vont les utiliser. Ensemble, ils décrivent l’ordre d’exécution et la visibilité mémoire nécessaires entre les deux calculs.'
+concepts.synchronization.barriers.guidance: 'La {link} applique ce principe entre les étapes de simulation. Chaque barrière suit le calcul qui produit les données et précède celui qui les utilise.'
+concepts.synchronization.barriers.guidance.link: simulation FluidTank
+concepts.synchronization.barriers.title: 'Barrières de pipeline'
+concepts.synchronization.dependencies.description: 'Pour chaque ressource, repérer les accès qui portent sur les mêmes données. Si l’un d’eux écrit, préserver l’ordre requis entre eux. Deux lectures ne sont pas en conflit, même si les contraintes de layout et de présentation continuent de s’appliquer. Les mises à jour CPU et la libération des ressources doivent aussi attendre que le GPU ait fini d’utiliser la mémoire concernée.'
+concepts.synchronization.dependencies.details: 'Le mécanisme dépend du lieu d’exécution des opérations :'
+concepts.synchronization.dependencies.guidance: 'Un traitement peut nécessiter plusieurs mécanismes. Une texture produite sur une autre file demande par exemple une dépendance entre soumissions et le layout adapté à son prochain usage. Ces opérations ne nécessitent de bloquer le CPU que si celui-ci doit lui-même accéder aux données ou libérer leur mémoire.'
+concepts.synchronization.dependencies.table.cpuAccess.mechanism: Appeler {wait} sur la valeur d’achèvement pertinente.
+concepts.synchronization.dependencies.table.cpuAccess.situation: 'Un accès CPU ou une libération de ressource dépend de la fin du travail GPU'
+concepts.synchronization.dependencies.table.crossQueue.mechanism: 'Transmettre la valeur {timelineValue} du producteur à l’appel {submit} du consommateur.'
+concepts.synchronization.dependencies.table.crossQueue.situation: 'Une file utilise des données produites sur une autre file'
+concepts.synchronization.dependencies.table.headings.mechanism: Mécanisme
+concepts.synchronization.dependencies.table.headings.situation: Situation
+concepts.synchronization.dependencies.table.layoutChange.mechanism: '{commandBufferTransition} avec ses layouts précédent et suivant.'
+concepts.synchronization.dependencies.table.layoutChange.situation: Une sous-ressource de texture modifie sa façon d’être utilisée
+concepts.synchronization.dependencies.table.pipelineStages.mechanism: '{commandBufferBarrier} avec les étapes productrice et consommatrice.'
+concepts.synchronization.dependencies.table.pipelineStages.situation: Étapes GPU dépendantes dans un flux de commandes
+concepts.synchronization.dependencies.title: 'Dépendances d’accès'
+concepts.synchronization.description: 'La synchronisation fixe l’ordre dans lequel les opérations accèdent aux données partagées et garantit que les suivantes voient les résultats dont elles ont besoin. Dans une dépendance entre écriture et lecture, l’opération qui écrit produit les données et celle qui lit les consomme. Les barrières et les transitions de texture coordonnent les accès GPU ; les lignes de temps relient les soumissions et permettent au CPU d’attendre la fin du travail GPU.'
+concepts.synchronization.layouts.context: 'Utiliser {undefined} comme layout précédent lorsque le contenu peut être abandonné, par exemple pour une nouvelle image ou un attachement entièrement réinitialisé. Pour conserver le contenu afin de le charger, de l’échantillonner ou de l’accumuler, utiliser le layout de l’accès précédent.'
+concepts.synchronization.layouts.description: 'Les indicateurs d’usage d’une texture déclarent ses rôles autorisés. {textureLayout} décrit l’accès à une sous-ressource à un moment donné de la séquence de commandes. Cette sous-ressource est identifiée par un niveau de mip et une couche de tableau. Lorsque l’accès change, enregistrer une transition. Par exemple, l’exemple de calcul écrit sa texture de sortie dans le layout {storage}, puis la prépare à être échantillonnée dans une passe graphique :'
+concepts.synchronization.layouts.details: '{outputTexture} a l’usage {sampledStorage}, et la sous-ressource retenue est en {storage}. {default} sélectionne le niveau mip 0 et la couche 0. Les autres niveaux et couches ne sont pas modifiés.'
+concepts.synchronization.layouts.guidance: 'Pour {transition}, fournir le layout de l’utilisation précédente de la sous-ressource. L’application suit cet état lorsqu’elle enregistre les commandes et transmet les textures entre étapes de rendu.'
+concepts.synchronization.layouts.title: 'Layouts de texture'
+concepts.synchronization.readback.description: '{isCompleted} indique la fin du travail GPU. Un transfert enregistré depuis le GPU comporte une étape supplémentaire : copier les données de la mémoire de lecture interne vers la destination de l’application.'
+concepts.synchronization.readback.details: '{commandBufferDownload} enregistre la copie GPU vers cette mémoire interne. Après la fin de la soumission, la bibliothèque effectue la copie CPU lorsqu’elle récupère le tampon de commandes terminé. Appeler {wait} sur la valeur de soumission de ce transfert attend le GPU et traite les tampons terminés de cette file, ce qui achève les deux étapes. Conserver la mémoire de destination et maintenir épinglée toute mémoire managée jusqu’au retour de l’appel.'
+concepts.synchronization.readback.guidance: 'Interroger seulement {isCompleted} n’effectue pas la copie CPU finale. Attendre une autre file ne récupère pas non plus les tampons terminés de la file responsable du transfert. L’article {link} explique quand les transferts utilisent un accès direct à la mémoire ou un stockage intermédiaire.'
+concepts.synchronization.readback.guidance.link: Gestion des ressources
+concepts.synchronization.readback.title: 'Achèvement de la lecture CPU'
+concepts.synchronization.reuse.description: 'Le triangle attend la fin de chaque image avant de réutiliser ses données. Pour faire chevaucher le traitement de plusieurs images, réserver à chacune des données modifiables distinctes et suivre le moment où leur mémoire redevient disponible. Avant de modifier des données, attendre les accès GPU en cours aux mêmes octets ; avant de libérer une ressource, attendre toutes ses utilisations en cours.'
+concepts.synchronization.reuse.details: 'Le redimensionnement et l’arrêt suivent la même règle : empêcher les nouveaux accès, attendre la fin des accès en cours, puis remplacer ou libérer les ressources. Respecter cet ordre avant {swapChainResize} ou avant de libérer les ressources ou le contexte. Le triangle attend déjà la fin de chaque image et peut donc redimensionner entre les rappels.'
+concepts.synchronization.reuse.guidance: 'Après la présentation, {swapChainPresent} signale un point d’achèvement sur la file graphique et l’attend. Le triangle attend donc lors de la présentation même si l’attente explicite de sa soumission est retirée. Le travail soumis indépendamment à d’autres files demande son propre suivi d’achèvement.'
+concepts.synchronization.reuse.title: 'Réutilisation des ressources'
+concepts.synchronization.timelines.context: 'Si le CPU doit lire le résultat, écraser les données partagées ou libérer les ressources après le consommateur, attendre cette soumission :'
+concepts.synchronization.timelines.description: 'Une ligne de temps repère les achèvements par valeurs croissantes. {submit} renvoie une {timelineValue} associant la ligne de temps à la valeur de soumission. Les champs numériques {value} de lignes distinctes ne se comparent pas directement.'
+concepts.synchronization.timelines.details: 'Soient {producer} et {consumer}, deux tampons enregistrés sur des files du même contexte, le second utilisant les données du premier. Leur soumission exprime cette dépendance :'
+concepts.synchronization.timelines.guidance: 'La file consommatrice attend la valeur d’achèvement fournie avant d’exécuter ses commandes. Le CPU peut continuer sans appeler {producedWait}. Conserver les ressources partagées jusqu’à la fin de {consumed}, et enregistrer les transitions de texture requises par l’accès du consommateur.'
+concepts.synchronization.timelines.notes: 'La valeur renvoyée par {queueTimelineSignal} peut elle aussi être attendue avec {wait} ou servir de dépendance à une autre file. {signal} place ce point d’achèvement après le travail déjà soumis ; le GPU l’atteint une fois ce travail terminé.'
+concepts.synchronization.timelines.title: 'Lignes de temps et dépendances entre files'
+concepts.synchronization.title: Synchronisation
+home.actions.reference: Consulter l’API
+home.actions.tutorial: Commencer par un triangle
+home.entries.description: 'Tutoriels, exemples et référence API'
+home.entries.label: 'Rubriques de la documentation'
+home.entries.reference.action: Explorer l’API
+home.entries.reference.category: Référence
+home.entries.reference.description: 'Consulter les types, membres et déclarations de l’API'
+home.entries.reference.title: Référence de l’API
+home.entries.samples.action: Parcourir les exemples
+home.entries.samples.category: Exemples
+home.entries.samples.description: 'Des exemples exécutables de techniques graphiques et de calcul'
+home.entries.samples.title: Exemples
+home.entries.title: 'Documentation'
+home.entries.tutorial.action: Démarrer le tutoriel
+home.entries.tutorial.category: Tutoriel
+home.entries.tutorial.description: 'D’un projet vide au premier triangle à l’écran'
+home.entries.tutorial.title: Premier triangle
+home.introduction.accent: 'avec .NET'
+home.introduction.description: 'Zenith.NET fournit une API commune pour le rendu et le calcul avec DirectX, Metal et Vulkan'
+home.introduction.title: 'Le rendu multiplateforme{lineBreak}{accent}'
+home.meta.description: 'Développez des applications graphiques et de calcul multiplateformes avec .NET et une API commune pour DirectX, Metal et Vulkan'
+home.scene.description: 'Géométrie en rotation interactive. Faire glisser ou utiliser les touches fléchées pour changer le point de vue.'
+home.scene.label: Affichage de la géométrie
+home.scene.surface: Surface
+home.scene.wireframe: Fil de fer
+home.title: RHI moderne pour .NET
+learning.concepts.description: 'Choisir les usages des ressources, décrire les données des shaders et coordonner l’exécution GPU'
+learning.concepts.execution.description: 'Enregistrement, soumission et durée de vie des ressources'
+learning.concepts.platforms.description: 'Présentation native et contrôles de rendu UI'
+learning.concepts.resources.description: 'Usages des ressources, placement mémoire et vues'
+learning.concepts.shaders.description: 'Conventions de disposition et de liaison entre C# et Slang'
+learning.concepts.synchronization.description: 'Dépendances, visibilité et achèvement'
+learning.concepts.title: Concepts fondamentaux
+learning.description: 'Commencez par le tutoriel du triangle pour créer votre première application. Les articles de fond expliquent ensuite la gestion des ressources, les données des shaders et la synchronisation. Les exemples permettent d’aborder séparément chaque technique.'
+learning.entries.samples.action: Parcourir les exemples
+learning.entries.samples.description: 'Implémentations C# et Slang de géométrie, calcul, requêtes de rayons et mesh shading'
+learning.entries.tutorial.action: Ouvrir le tutoriel
+learning.entries.tutorial.description: 'Une application console qui ouvre une fenêtre et rend un triangle'
+learning.meta.description: 'Tutoriels et concepts du modèle de programmation de Zenith.NET'
+learning.title: Apprendre
+reference.description: 'Référence des ressources, commandes et intégrations de plateforme'
+reference.entries.commands.description: 'Commandes graphiques, de calcul et de transfert'
+reference.entries.commands.label: 'Enregistrer des commandes'
+reference.entries.context.description: 'Création de ressources GPU pour le backend sélectionné'
+reference.entries.context.label: Commencer ici
+reference.meta.description: 'Espaces de noms, types et membres de Zenith.NET'
+reference.navigation.core: 'Référence de l’API principale'
+reference.title: Référence de l’API
+samples.computeShader.description: 'L’exemple charge l’image {assetsTexturesShokoPng}, la convertit en niveaux de gris par un calcul GPU et affiche la texture de sortie. La conversion n’a lieu qu’une fois ; les images suivantes affichent le même résultat. Le moteur de rendu montre comment lier des textures échantillonnées et accessibles en écriture, arrondir le nombre de groupes de threads à l’entier supérieur pour couvrir l’image et vérifier ses limites dans le shader.'
+samples.computeShader.details: '{computeShaderRendererCs} · {computeShaderSlang}'
+samples.computeShader.title: Shader de calcul
+samples.cube.description: 'Le cube ajoute le dessin indexé, les matrices de modèle, de vue et de projection dans un tampon de constantes, ainsi que le test de profondeur. Sa texture de profondeur et de stencil {d32FloatS8UInt} est recréée au redimensionnement.'
+samples.cube.details: '{spinningCubeRendererCs} · {spinningCubeSlang}'
+samples.cube.title: Cube en rotation
+samples.description: 'Chaque exemple associe un moteur de rendu C# à son shader Slang et se concentre sur une technique. La configuration complète de l’application et la séquence de rendu sont décrites dans {link}.'
+samples.description.link: Premier triangle
+samples.execution.description: 'Exécutez le projet {link} et choisissez un exemple dans le menu de la console.'
+samples.execution.description.link: ZenithTutorials
+samples.execution.title: 'Exécution des exemples'
+samples.host.description: '{appCs} initialise le contexte, la fenêtre et la chaîne d’échange. À chaque image, l’hôte obtient un tampon de commandes, appelle le moteur choisi, soumet les commandes et présente le résultat. {iRendererCs} définit les opérations de mise à jour, de rendu, de redimensionnement et de libération.'
+samples.host.details: 'Les moteurs de rendu de calcul et de lancer de rayons produisent des textures hors écran. {texturePresenterCs} et {presentTextureSlang} les dessinent dans la texture de sortie courante ; l’hôte commun soumet ensuite les commandes et présente l’image.'
+samples.host.title: 'Hôte et présentation'
+samples.indirectDrawing.description: 'Le dessin indirect affiche plusieurs instances de cube à partir d’un tampon d’arguments de dessin indexé et d’un tampon structuré de données par instance. Le CPU fournit les arguments et actualise les transformations.'
+samples.indirectDrawing.details: '{indirectDrawingRendererCs} · {indirectDrawingSlang}'
+samples.indirectDrawing.title: Dessin indirect
+samples.meshShading.description: 'Un shader de tâches élimine les instances de sphères non visibles, puis transmet les indices restants au shader de maillage qui produit la géométrie. Le moteur vérifie {contextCapabilitiesMeshShadingSupported} avant de créer le pipeline.'
+samples.meshShading.details: '{meshShadingRendererCs} · {meshShadingSlang}'
+samples.meshShading.title: Mesh Shading
+samples.rayTracing.description: 'Cet exemple construit les structures d’accélération de niveau inférieur et supérieur d’un sol et de sphères procédurales, puis effectue des requêtes de rayons directement dans un shader de calcul. Il vérifie {contextCapabilitiesRayTracingSupported} avant l’initialisation.'
+samples.rayTracing.details: '{rayTracingRendererCs} · {rayTracingSlang}'
+samples.rayTracing.title: Lancer de rayons
+samples.title: Exemples
+samples.triangle.description: 'Le triangle associe un tampon de sommets à la disposition d’entrée correspondante et dessine trois sommets au moyen d’un pipeline graphique.'
+samples.triangle.details: '{helloTriangleRendererCs} · {helloTriangleSlang} · {link}'
+samples.triangle.details.link: Tutoriel complet
+samples.triangle.title: Bonjour triangle
+samples.workloads.description: 'Le {link} du dépôt principal contient des applications plus complètes qui utilisent le code source de la bibliothèque.'
+samples.workloads.description.link: répertoire Experiments
+samples.workloads.details: 'Les applications fenêtrées utilisent les mêmes chemins Windows, macOS et Linux/X11. Les contrôles Avalonia, WinForms, WPF, WinUI/Uno et MAUI sont décrits dans {link}, avec leur cycle d’image et leur code d’implémentation.'
+samples.workloads.details.link: Intégration de plateforme
+samples.workloads.table.cornellBox.example: '{link}'
+samples.workloads.table.cornellBox.example.link: CornellBox
+samples.workloads.table.cornellBox.whatItDemonstratesAndRequires: 'Tracé de chemins, débruitage, mappage tonal, agrandissement de l’image et contrôles ImGui. Nécessite la prise en charge du lancer de rayons.'
+samples.workloads.table.fluidTank.example: '{link}'
+samples.workloads.table.fluidTank.example.link: FluidTank
+samples.workloads.table.fluidTank.whatItDemonstratesAndRequires: 'Simulation de fluide par calcul, dépendances entre étapes de simulation et rendu de surface multipasse.'
+samples.workloads.table.headings.example: Exemple
+samples.workloads.table.headings.whatItDemonstratesAndRequires: 'Techniques et prérequis'
+samples.workloads.table.inkCanvas.example: '{link}'
+samples.workloads.table.inkCanvas.example.link: InkCanvas
+samples.workloads.table.inkCanvas.whatItDemonstratesAndRequires: Dessin Skia et gestion des entrées dans une fenêtre native. Nécessite le backend et la bibliothèque native Skia pour la plateforme cible.
+samples.workloads.table.platformDetection.example: '{link}'
+samples.workloads.table.platformDetection.example.link: PlatformDetection
+samples.workloads.table.platformDetection.whatItDemonstratesAndRequires: 'Tente de créer un contexte pour chaque backend et rapporte les capacités du périphérique. Un échec est indiqué comme une absence de prise en charge ; il peut provenir du périphérique, du pilote ou d’une dépendance d’exécution manquante.'
+samples.workloads.title: 'Applications complètes'
+tutorial.context.description: 'Un {graphicsContext} relie l’application au GPU par l’intermédiaire d’un backend et fournit les méthodes de création des ressources de rendu.'
+tutorial.context.details: 'Insérez l’initialisation du contexte {emphasis} :'
+tutorial.context.details.emphasis: après les directives using, avant {iWindowWindow}
+tutorial.context.guidance: 'Chaque branche sélectionne le backend de la plateforme et demande la validation. Les messages de {validationMessage} sont affichés dans la console.'
+tutorial.context.imports: 'Ajoutez les directives using suivantes au début de {programCs} :'
+tutorial.context.title: 'Contexte graphique'
+tutorial.description: 'Ce tutoriel part d’un projet console .NET vide pour créer une application qui ouvre une fenêtre et dessine un triangle coloré.'
+tutorial.details: 'Le tutoriel comporte trois étapes de vérification : une fenêtre vide, un fond uni, puis le triangle coloré. Exécutez l’application à chaque étape avant d’ajouter la partie suivante.'
+tutorial.draw.context: 'Les arguments de {draw} sont :'
+tutorial.draw.description: 'Dans {windowRender}, remplacez {addTheTriangleDrawCommandsHereLater} par ces commandes, entre {beginRenderPass} et {endRenderPass} :'
+tutorial.draw.details: '{setPipeline} sélectionne les shaders et l’état de rendu. Cet appel doit précéder la liaison du tampon de sommets, car le backend l’interprète selon la disposition d’entrée du pipeline.'
+tutorial.draw.guidance: '{setVertexBufferVertexBuffer} lie les données de sommets déjà transférées, à partir du décalage de 0 octet, à l’emplacement d’entrée 0. Celui-ci correspond à l’unique entrée de {inputLayouts}.'
+tutorial.draw.notes: 'Ces appels enregistrent les commandes de dessin. L’appel {submitWait} déjà présent les soumet et attend que le GPU les termine ; {present} demande ensuite l’affichage de l’image achevée. Conservez l’initialisation avec la couleur de fond au début de la passe pour donner un contenu défini à la zone extérieure au triangle.'
+tutorial.draw.table.firstInstance.meaning: Commencer la numérotation des instances à zéro.
+tutorial.draw.table.firstVertex.meaning: Commencer avec le premier sommet dans le tampon.
+tutorial.draw.table.headings.argument: Argument
+tutorial.draw.table.headings.description: Valeur
+tutorial.draw.table.headings.meaning: Signification
+tutorial.draw.table.instanceCount.meaning: Dessiner une copie du triangle.
+tutorial.draw.table.vertexCount.meaning: Lire trois sommets.
+tutorial.draw.title: 'Commandes de dessin'
+tutorial.execution.description: 'Compilez et lancez le projet {firstTriangle}.'
+tutorial.execution.details: 'Un triangle doit apparaître sur fond sombre, rouge en haut, vert en bas à droite et bleu en bas à gauche. L’interpolation des couleurs des sommets forme un dégradé continu à l’intérieur.'
+tutorial.execution.guidance: 'Le redimensionnement modifie les dimensions de l’image en pixels, mais les coordonnées des sommets restent identiques. Le viewport les adapte à la nouvelle largeur et à la nouvelle hauteur. Le triangle s’étire donc lorsque le rapport largeur/hauteur de la fenêtre change.'
+tutorial.execution.title: 'Exécution de l’application'
+tutorial.frame.commands.context: 'Après {endRenderPass}, la seconde transition prépare la sortie pour la présentation. {submit} termine l’enregistrement et soumet le tampon ; {wait} bloque jusqu’à l’achèvement. {present} demande ensuite l’affichage.'
+tutorial.frame.commands.description: 'Une passe de rendu regroupe les opérations de dessin qui écrivent dans les mêmes attachements. Ici, la texture de sortie de la chaîne d’échange reçoit les couleurs produites par le rendu. {colorAttachmentClear} indique que cet attachement doit être initialisé avec une couleur RGBA et que son contenu doit être conservé à la fin de la passe. {beginRenderPass} enregistre le début de la passe avec ces paramètres.'
+tutorial.frame.commands.details: 'La collection contient un attachement de couleur. {null} indique que la passe ne possède pas d’attachement de profondeur et de stencil.'
+tutorial.frame.commands.guidance: '{beginRenderPass} règle le viewport et le rectangle de découpe sur tout l’attachement. Le viewport convertit les coordonnées de rendu en positions de pixels ; le rectangle de découpe limite la zone où l’écriture est autorisée.'
+tutorial.frame.commands.title: 'Effacement, soumission et présentation'
+tutorial.frame.description: 'Commencez par remplir la fenêtre d’une couleur de fond. Cette opération s’appelle l’effacement. Lorsque le fond s’affiche, la surface, la soumission et la présentation fonctionnent ensemble ; vous pouvez ajouter les sommets et les shaders du triangle.'
+tutorial.frame.details: 'Silk.NET appelle le rappel de rendu lorsqu’une image doit être produite. L’argument de temps écoulé reste inutilisé et porte le nom {value}. Enregistrez le rappel {emphasis} :'
+tutorial.frame.details.emphasis: avant {windowRun}
+tutorial.frame.target.description: '{graphicsQueueCommandBuffer} renvoie un tampon prêt à enregistrer. L’enregistrement décrit le travail GPU ; l’exécution ne commence qu’après soumission.'
+tutorial.frame.target.details: 'Le layout d’une texture décrit le type d’accès nécessaire à une opération. Le premier appel à {transition} prépare la texture de sortie à servir d’attachement de couleur. Ici, {default} sélectionne le niveau de mip 0 et la couche de tableau 0, soit l’image de base de cette texture. Comme la passe remplit toute l’image avec la couleur de fond, {undefined} autorise l’abandon de son contenu précédent.'
+tutorial.frame.target.guidance: 'Utilisez la texture de sortie courante à chaque image, car la présentation peut faire passer la chaîne d’échange à une autre texture.'
+tutorial.frame.target.title: 'Préparation de la sortie'
+tutorial.frame.title: 'Première image'
+tutorial.geometry.data.description: 'Ajoutez le tableau de sommets {emphasis}. L’initialisation des ressources suivantes reste aussi avant ce rappel, dans l’ordre indiqué.'
+tutorial.geometry.data.description.emphasis: avant {windowRender}
+tutorial.geometry.data.details: 'Chaque entrée construit un {vertex}. Les deux expressions {new} internes donnent sa position {vector3} et sa couleur {vector4}. Les positions définissent les coins supérieur, inférieur droit et inférieur gauche. Le shader de sommets ajoute {w} comme quatrième coordonnée. Avec w = 1, x et y sont des coordonnées normalisées du périphérique : -1 et +1 marquent les bords, 0 le centre, indépendamment des dimensions en pixels.'
+tutorial.geometry.data.guidance: 'Chaque couleur comporte des composantes rouge, verte, bleue et alpha, avec alpha égal à 1. Les trois sommets sont dans le même plan, à z = 0.'
+tutorial.geometry.data.title: 'Positions et couleurs'
+tutorial.geometry.description: 'Chacun des trois sommets possède une position et une couleur. Le GPU détermine la forme du triangle à partir des positions et interpole les couleurs sur sa surface.'
+tutorial.geometry.format.description: 'Ajoutez la déclaration de sommet {emphasis} :'
+tutorial.geometry.format.description.emphasis: à la fin de {programCs}, après les instructions de nettoyage
+tutorial.geometry.format.details: '{file} limite le type au fichier {programCs}, tandis que {layoutKindSequential} conserve l’ordre de déclaration de ses champs. {position} contient trois nombres flottants de 32 bits à partir du décalage de 0 octet ; {color} en contient quatre à partir du décalage de 12 octets. Chaque sommet occupe donc 28 octets. La disposition d’entrée décrira cette organisation pour que le GPU lise correctement les champs.'
+tutorial.geometry.format.imports: 'Ajoutez ces directives using au début de {programCs} pour les types vectoriels et l’attribut de disposition mémoire des structures :'
+tutorial.geometry.format.title: 'Représentation d’un sommet'
+tutorial.geometry.title: 'Données de sommets'
+tutorial.geometry.upload.context: '{fixed} empêche le ramasse-miettes de déplacer le tableau pendant l’utilisation de son adresse. Pour ce tampon accessible en écriture au CPU, {upload} mappe la mémoire, copie les données, puis termine le mapping avant de retourner. Le tableau ne doit donc rester épinglé que pendant l’appel. Transférez les sommets une seule fois à l’initialisation et conservez le tampon jusqu’à la fin du rendu.'
+tutorial.geometry.upload.description: 'Placez les sommets dans un {buffer} afin que le GPU puisse les lire pendant le dessin. Ajoutez l’allocation et le transfert immédiatement après le tableau :'
+tutorial.geometry.upload.details: '{sizeofVertexVerticesLength} représente trois sommets de 28 octets, soit 84 octets. {bufferUsagesVertex} déclare l’accès en entrée de sommets, {cpuWriteOnly} autorise l’initialisation directe par le CPU. Le pas du descripteur indique la taille d’un élément ; la disposition d’entrée précise ensuite chaque attribut.'
+tutorial.geometry.upload.guidance: 'L’allocation n’initialise pas le contenu. {upload} copie le tableau au décalage de destination 0 à partir du pointeur source et du nombre d’octets de la description.'
+tutorial.geometry.upload.imports: 'Dans {programCs}, ajoutez {bufferAlias} aux directives using pour distinguer le type de tampon de Zenith.NET de {systemBuffer}.'
+tutorial.geometry.upload.notes: 'Ajoutez {vertexBufferDispose} juste avant {swapChainDispose} à la fin de {programCs}. Le stockage réservé au GPU et les transferts pour de plus grandes géométries sont présentés dans {link}.'
+tutorial.geometry.upload.notes.link: Gestion des ressources
+tutorial.geometry.upload.title: 'Allocation et transfert du tampon'
+tutorial.input.description: 'Le GPU doit savoir où se trouvent les attributs de chaque sommet, comme la position et la couleur. La disposition d’entrée indique leur format et leur décalage, ainsi que le pas, la distance en octets entre deux sommets successifs. Ajoutez ce code après la création des shaders :'
+tutorial.input.details: '{inputLayoutAdd} place chaque attribut immédiatement après le précédent et ajoute sa taille au pas entre sommets. Les décalages sont calculés à partir des formats fournis, sans examiner la structure C#. Pour ces deux champs, la disposition obtenue est la suivante :'
+tutorial.input.guidance: 'La valeur par défaut de {semanticIndex} est zéro, ce qui correspond à {pOSITION0} et {cOLOR0} dans le shader. La position et la couleur étant stockées ensemble dans un seul tampon de sommets, le pipeline utilise une disposition d’entrée à l’emplacement 0. Cette disposition doit être adaptée si l’ordre des champs ou le remplissage de la structure C# change.'
+tutorial.input.table.headings.byteOffset: Décalage d’octets
+tutorial.input.table.headings.cField: 'Champ C#'
+tutorial.input.table.headings.format: Format
+tutorial.input.table.headings.shaderInput: Entrée de shader
+tutorial.input.table.nextVertex.cField: Sommet suivant
+tutorial.input.table.nextVertex.format: 'Pas total : 28 octets'
+tutorial.input.table.nextVertex.shaderInput: Enregistrement d’entrée suivant
+tutorial.input.table.vertexColor.format: '{float4} (16 octets)'
+tutorial.input.table.vertexPosition.format: '{float3} (12 octets)'
+tutorial.input.title: 'Disposition des sommets en entrée'
+tutorial.lifecycle.description: 'Chaque image suit cette séquence :'
+tutorial.lifecycle.details: 'À la fermeture de la fenêtre, {run} retourne et l’exécution se poursuit avec le nettoyage. Chaque image ayant déjà attendu la fin de ses commandes, les ressources utilisées par ces commandes peuvent être libérées. Le nettoyage complet après {windowRun} est le suivant :'
+tutorial.lifecycle.guidance: 'Libérez la chaîne d’échange avant sa fenêtre native, et le contexte après ses ressources. La file recycle les tampons de commandes soumis, tandis que la chaîne d’échange gère sa texture de sortie. Aucun de ces objets ne nécessite donc d’appel distinct à {dispose}.'
+tutorial.lifecycle.title: 'Cycle d’image et libération'
+tutorial.pipeline.behavior: 'Après la disposition d’entrée, créez le pipeline :'
+tutorial.pipeline.context: '{cullNone} désactive l’élimination des faces. Le triangle est donc dessiné que ses sommets apparaissent dans le sens horaire ou antihoraire.'
+tutorial.pipeline.description: 'Un {graphicsPipeline} réunit la disposition des sommets, les deux shaders et les réglages d’assemblage des primitives et d’écriture dans les attachements.'
+tutorial.pipeline.details: 'Les shaders traitent les sommets et les fragments. Les autres réglages du pipeline contrôlent l’assemblage des primitives, l’élimination des faces, le test de profondeur et le mélange des couleurs :'
+tutorial.pipeline.guidance: 'Le format du pipeline doit correspondre à l’attachement ; il provient donc de {swapChainDescFormat}. {count1} indique un échantillon par pixel.'
+tutorial.pipeline.item: '{triangleList} assemble chaque groupe de trois sommets consécutifs en un triangle.'
+tutorial.pipeline.notes: '{depthNone} désactive le test et l’écriture de profondeur, puisque la passe ne comporte pas d’attachement de profondeur.'
+tutorial.pipeline.reference: '{opaque} désactive le mélange. La couleur du fragment remplace ainsi le fond à l’intérieur du triangle.'
+tutorial.pipeline.title: 'Pipeline graphique'
+tutorial.pipeline.usage: 'Les objets shader peuvent être libérés après la création du pipeline. Conservez le pipeline pendant le rendu et ajoutez {pipelineDispose} avant {vertexBufferDispose} dans le nettoyage.'
+tutorial.presentation.title: 'Contexte graphique et présentation'
+tutorial.project.context: 'Activez {emphasis} ({allowUnsafeBlocks}) dans les paramètres de compilation. Les appels natifs du code de fenêtre et le transfert des sommets nécessitent du code unsafe.'
+tutorial.project.context.emphasis: Autoriser le code non sécurisé
+tutorial.project.description: 'Créez un projet console C# nommé {firstTriangle} ciblant .NET 10.'
+tutorial.project.details: 'Ajoutez les paquets NuGet suivants au projet :'
+tutorial.project.guidance: '{zenithNET} fournit l’API de rendu commune, et les trois paquets de backend en fournissent les implémentations propres aux plateformes. {zenithNETCompiler} compile les shaders Slang présentés plus bas pour le backend choisi. Silk.NET assure la création de la fenêtre et le traitement des événements.'
+tutorial.project.title: 'Configuration du projet'
+tutorial.requirements.context: 'L’{link} et son {detail} fournissent les implémentations de référence de ce tutoriel.'
+tutorial.requirements.context.detail: hôte partagé
+tutorial.requirements.context.link: exemple Bonjour triangle
+tutorial.requirements.description: 'Vous devez disposer du {emphasis} et connaître les bases de C#. L’application cible {net10}. Le GPU et son pilote doivent prendre en charge le backend choisi pour votre plateforme :'
+tutorial.requirements.description.emphasis: SDK .NET 10
+tutorial.requirements.guidance: 'Zenith.NET rend dans une surface native fournie par l’application. Silk.NET.Windowing crée la fenêtre et gère les événements. Sous macOS, une couche Metal représentée par {cAMetalLayerCs} reçoit la sortie ; le code auxiliaire la rattache à la fenêtre.'
+tutorial.requirements.table.headings.graphicsBackend: Backend graphique
+tutorial.requirements.table.headings.platform: Plateforme
+tutorial.requirements.table.linux.graphicsBackend: Vulkan 1.4
+tutorial.requirements.table.linux.platform: 'Linux (X11 ou XWayland)'
+tutorial.requirements.table.macOS.graphicsBackend: Metal 4
+tutorial.requirements.table.macOS.platform: macOS
+tutorial.requirements.table.windows.graphicsBackend: DirectX 12
+tutorial.requirements.table.windows.platform: Windows
+tutorial.requirements.title: 'Prérequis'
+tutorial.resize.context: 'Compilez et lancez de nouveau l’application. Le fond doit continuer à remplir la fenêtre lorsque vous la redimensionnez : chaque nouvelle passe ajuste son viewport et son rectangle de découpe aux dimensions de l’attachement. Conservez le rappel de rendu ; le triangle sera dessiné dans cette passe.'
+tutorial.resize.description: 'La chaîne d’échange doit suivre les changements de dimensions du framebuffer. Ajoutez un gestionnaire de {framebufferResize} après le rappel de rendu et avant {windowRun} :'
+tutorial.resize.details: 'Réagissez aux {link}, exprimés en pixels, plutôt qu’aux changements de taille logique de la fenêtre. Si la réduction de la fenêtre met l’une des dimensions du framebuffer à zéro, le gestionnaire de redimensionnement laisse la chaîne d’échange inchangée. Le rappel de rendu retourne lui aussi immédiatement tant qu’une dimension est nulle. Le rendu reprend lorsque le framebuffer retrouve des dimensions utilisables.'
+tutorial.resize.details.link: 'changements de taille du framebuffer'
+tutorial.resize.guidance: 'Le rappel de rendu attend la fin des commandes soumises pour chaque image avant d’appeler {present}. Le gestionnaire de redimensionnement peut donc remplacer les images de la chaîne d’échange sans interrompre le travail d’une image précédente. L’article {link} détaille ce lien entre achèvement du travail et durée de vie des ressources.'
+tutorial.resize.guidance.link: Synchronisation
+tutorial.resize.title: 'Redimensionnement'
+tutorial.shaders.compilation.context: '{appContextBaseDirectory} permet de charger la copie du shader depuis le répertoire de sortie de l’application. Le fichier reste ainsi localisable même si l’application est lancée depuis un autre répertoire de travail.'
+tutorial.shaders.compilation.description: 'Pour {triangleSlang}, réglez la propriété {emphasis} sur {detail}. La compilation du projet copiera le shader près de l’exécutable.'
+tutorial.shaders.compilation.description.detail: Copier si plus récent
+tutorial.shaders.compilation.description.emphasis: Copier dans le répertoire de sortie
+tutorial.shaders.compilation.details: 'Dans {programCs}, ajoutez l’initialisation des shaders après le transfert des sommets et avant le rappel de rendu :'
+tutorial.shaders.compilation.guidance: '{zenithCompiler} compile le code source pour {contextGraphicsApi}, puis {createShader} crée un objet shader à partir de la description compilée. Les noms de points d’entrée {vSMain} et {fSMain} sélectionnent les deux fonctions définies dans le fichier Slang.'
+tutorial.shaders.compilation.title: 'Compilation des shaders'
+tutorial.shaders.context: '{fSInput} relie les deux étapes de shader. Le shader de sommets écrit une position dans l’espace de clipping dans {sVPOSITION} ; le GPU s’en sert pour déterminer la partie visible du triangle. Le champ {cOLOR0} transmet la couleur du sommet, qui est interpolée sur le triangle avant de parvenir au shader de fragments. Son type et sa sémantique doivent correspondre entre les deux étapes.'
+tutorial.shaders.description: 'Les shaders sont des programmes exécutés sur le GPU. Le shader de sommets détermine la position de chaque sommet ; le shader de fragments détermine la couleur aux emplacements de l’image couverts par le triangle. Les deux étapes seront écrites dans un même fichier Slang, chacune avec son point d’entrée, c’est-à-dire la fonction où commence son exécution.'
+tutorial.shaders.details: 'Créez {triangleSlang} avec ces structures d’entrée et de sortie :'
+tutorial.shaders.fragment.description: 'Ajoutez le point d’entrée de fragments dans le même fichier :'
+tutorial.shaders.fragment.details: 'La rastérisation détermine les emplacements couverts par le triangle et y génère des fragments. Chaque fragment reçoit une couleur interpolée à partir des couleurs des trois sommets. {fSMain} renvoie cette couleur, et {sVTARGET} dirige le résultat vers le premier attachement de couleur.'
+tutorial.shaders.fragment.title: 'Traitement des fragments'
+tutorial.shaders.guidance: '{vSInput} définit l’entrée du shader de sommets. {pOSITION0} et {cOLOR0} identifient les attributs par leur sémantique ; les noms C# n’établissent pas cette correspondance.'
+tutorial.shaders.title: 'Étapes des shaders'
+tutorial.shaders.vertex.description: 'Ajoutez le point d’entrée de sommets à {triangleSlang} :'
+tutorial.shaders.vertex.details: 'Le GPU appelle ce point d’entrée pour chaque sommet, ajoute la coordonnée homogène {w} et transmet la couleur. La position est ensuite divisée par w. Ici w vaut 1 : x, y et z restent inchangés et correspondent aux coordonnées normalisées du périphérique.'
+tutorial.shaders.vertex.title: 'Traitement des sommets'
+tutorial.surface.description: 'Définissez la cible de présentation native {emphasis} :'
+tutorial.surface.description.emphasis: après {windowCenter} et avant {windowRun}
+tutorial.surface.details: 'Le framebuffer est l’image de la fenêtre en pixels. {surface} décrit la cible de présentation native et ses dimensions ; {framebufferSize} donne la taille en pixels. Avec la mise à l’échelle de l’affichage, cette taille peut différer de la taille logique de la fenêtre.'
+tutorial.surface.guidance: 'La surface fournit les handles natifs du backend choisi : une fenêtre Win32 sous Windows, un affichage et une fenêtre X11 sous Linux, ou la couche Metal attachée sous macOS.'
+tutorial.surface.title: 'Surface native'
+tutorial.swapChain.context: 'La chaîne dépend du contexte et de la fenêtre. Remplacez {windowDispose} à la fin de {programCs} par cet ordre de libération :'
+tutorial.swapChain.description: 'Créez la chaîne d’échange immédiatement après la surface :'
+tutorial.swapChain.details: 'La {swapChain} gère les images présentées. {drawable} fournit l’image courante sous forme de {texture}, la ressource image de l’API. Après le rendu, {present} demande son affichage et passe à la suivante.'
+tutorial.swapChain.guidance: '{b8G8R8A8UNorm} stocke quatre canaux de 8 bits dans l’ordre bleu, vert, rouge, alpha. {uNorm} fait correspondre les entiers de 0 à 255 aux valeurs de 0 à 1. Le shader produit des valeurs RGBA ; le format détermine leur stockage en mémoire. Le pipeline reprend le format de couleur {swapChainDescFormat} pour correspondre à l’attachement.'
+tutorial.swapChain.title: 'Chaîne d’échange'
+tutorial.title: Premier triangle
+tutorial.troubleshooting.description: 'Les diagnostics console et les trois points de contrôle permettent d’isoler l’étape en échec :'
+tutorial.troubleshooting.details: 'Le {link} et le {detail} servent de référence pour les données des sommets, la disposition d’entrée et les commandes de dessin. Dans cet exemple, la fenêtre et la soumission sont gérées par un hôte commun ; ce tutoriel conserve le déroulement de l’application dans {programCs}. L’exemple {context} introduit le dessin indexé, les transformations et le test de profondeur.'
+tutorial.troubleshooting.details.context: Cube en rotation
+tutorial.troubleshooting.details.detail: shader Slang
+tutorial.troubleshooting.details.link: moteur de rendu d’exemple
+tutorial.troubleshooting.table.backend.check: Vérifiez que le backend sélectionné est pris en charge par l’appareil et le pilote graphique installé.
+tutorial.troubleshooting.table.backend.symptom: La création du backend échoue
+tutorial.troubleshooting.table.drawing.check: Vérifiez la taille du transfert, l’agencement d’entrée, la sélection du pipeline et le placement des commandes de dessin à l’intérieur de la passe de rendu.
+tutorial.troubleshooting.table.drawing.symptom: L’arrière-plan apparaît mais pas le triangle
+tutorial.troubleshooting.table.headings.check: Vérification
+tutorial.troubleshooting.table.headings.symptom: Symptôme
+tutorial.troubleshooting.table.input.check: 'Vérifiez le pas de 28 octets et les décalages de la position et de la couleur : respectivement 0 et 12 octets.'
+tutorial.troubleshooting.table.input.symptom: Les positions ou les couleurs des sommets sont corrompues
+tutorial.troubleshooting.table.shaders.check: Vérifiez le fichier {triangleSlang} copié, l’orthographe du point d’entrée et le diagnostic signalé par le compilateur.
+tutorial.troubleshooting.table.shaders.symptom: 'Le fond s’affiche, mais le chargement des shaders échoue'
+tutorial.troubleshooting.table.window.check: Confirmez que le projet .NET se compile et que l’étape de fenêtre seule s’exécute dans une session de bureau. Sous Linux, vérifiez que X11 ou XWayland est disponible.
+tutorial.troubleshooting.table.window.symptom: Aucune fenêtre ne s’ouvre
+tutorial.troubleshooting.title: 'Diagnostic des erreurs'
+tutorial.window.description: 'Téléchargez {cocoaHelperCs} dans le projet. Cette classe auxiliaire attache la couche Metal à la fenêtre macOS.'
+tutorial.window.details: 'Remplacez le contenu généré de {programCs} par le code suivant. Les instructions de niveau supérieur s’exécutent dans l’ordre, sans méthode {main} explicite. Les prochaines sections préciseront où ajouter chaque extrait :'
+tutorial.window.guidance: '{graphicsAPINone} laisse à Zenith.NET la création du contexte graphique. {initialize} crée la fenêtre native et rend ses handles disponibles. {center} la place au centre de l’écran. {run} traite les événements jusqu’à sa fermeture, puis l’exécution passe à {dispose} pour la libérer.'
+tutorial.window.notes: 'Compilez et lancez l’application. Vérifiez que la fenêtre s’ouvre et se ferme correctement, et que vous pouvez la déplacer et la redimensionner. Son contenu n’est pas encore défini, car aucune commande de rendu n’a été soumise. Fermez la fenêtre avant de poursuivre.'
+tutorial.window.title: 'Création de la fenêtre'
+ui.action.copy: Copier
+ui.action.navigate: Naviguer
+ui.action.open: Ouvrir
+ui.action.source: Afficher la source
+ui.api.browse: Parcourir les espaces de noms et les types.
+ui.api.clearTypeSearch: Effacer la recherche de types
+ui.api.findType: Trouver un type
+ui.api.loadError: Impossible de charger les types. Rechargez cette page pour réessayer.
+ui.api.loadingNamespaces: Chargement des espaces de noms…
+ui.api.loadingTypes: Chargement des types…
+ui.api.matchingTypes: Types correspondants
+ui.api.namespace: Espace de noms
+ui.api.namespaces: Espaces de noms
+ui.api.noNamespaceTypes: Aucun type dans cet espace de noms.
+ui.api.noTypes: Aucun type correspondant.
+ui.api.types: Types
+ui.content.reference: Référence
+ui.content.source: Code source
+ui.content.tutorial: Tutoriel
+ui.contents.close: Fermer la table des matières
+ui.contents.open: Ouvrir la table des matières
+ui.contents.title: Table des matières
+ui.footer.caption: 'RHI moderne pour .NET'
+ui.footer.label: Pied de page
+ui.language.label: Langue
+ui.language.unavailable: Non traduit
+ui.members.clear: Effacer la recherche de membres
+ui.members.close: 'Fermer la recherche de membres'
+ui.members.empty: Aucun membre correspondant.
+ui.members.open: Aller au membre
+ui.members.placeholder: Nom ou signature…
+ui.members.search: Rechercher des membres
+ui.members.title: Trouver un membre
+ui.members.undeclared: Ce type ne possède aucun membre déclaré.
+ui.navigation.api: API
+ui.navigation.concepts: Concepts
+ui.navigation.home: Accueil
+ui.navigation.label: Navigation principale
+ui.navigation.skip: Aller au contenu
+ui.navigation.toggle: Basculer la navigation
+ui.outline.title: Sur cette page
+ui.reference.assembly: Assembly
+ui.reference.classes: Classes
+ui.reference.constructors: Constructeurs
+ui.reference.declaration: 'Déclaration de type C#'
+ui.reference.delegates: Délégués
+ui.reference.derived: Types dérivés
+ui.reference.enums: Énumérations
+ui.reference.events: Événements
+ui.reference.examples: Exemples
+ui.reference.exceptions: Exceptions
+ui.reference.extensionMethods: Méthodes d’extension
+ui.reference.fields: Champs
+ui.reference.implementations: Implémentations explicites d’interface
+ui.reference.implements: Implémente
+ui.reference.inheritance: Héritage
+ui.reference.inheritedMembers: Membres hérités
+ui.reference.interfaces: Interfaces
+ui.reference.metadata: Métadonnées du type
+ui.reference.methods: Méthodes
+ui.reference.namespace: Espace de noms
+ui.reference.open: Ouvrir la référence pour {name}
+ui.reference.operators: Opérateurs
+ui.reference.parameters: Paramètres
+ui.reference.properties: Propriétés
+ui.reference.related: Voir aussi
+ui.reference.relationships: Héritage et métadonnées
+ui.reference.remarks: Remarques
+ui.reference.returns: 'Valeur de retour'
+ui.reference.structs: Structures
+ui.reference.typeParameters: Paramètres de type
+ui.reference.value: Valeur
+ui.reference.values: Valeurs
+ui.search.close: Fermer la recherche
+ui.search.empty: Aucun résultat pour « {query} ».
+ui.search.hint: Recherchez dans les tutoriels, concepts, exemples et la référence de l’API.
+ui.search.loading: Chargement de la recherche…
+ui.search.more: Afficher plus de résultats
+ui.search.pending: Recherche en cours…
+ui.search.placeholder: Que recherchez-vous ?
+ui.search.resultCount: 'Résultats : {count}'
+ui.search.title: Rechercher dans la documentation
+ui.search.unavailable: La recherche est indisponible. Vous pouvez toujours ouvrir les pages ci-dessous.
diff --git a/documents/locales/fr-FR/translation.md b/documents/locales/fr-FR/translation.md
new file mode 100644
index 00000000..fa9f17f4
--- /dev/null
+++ b/documents/locales/fr-FR/translation.md
@@ -0,0 +1,85 @@
+# Consignes de traduction en français
+
+## Ressources et structure
+
+Le [dictionnaire de ressources anglais](../en-US/strings.yml) est l’unique source du texte. Les règles du projet figurent dans le [guide de maintenance de la documentation](../../maintenance.md). Pour commencer une traduction, copier l’intégralité du fichier anglais dans `strings.yml`, dans ce répertoire. Conserver la première ligne `### YamlMime:Resources` et l’encodage UTF-8. Pour les mises à jour, reporter les changements de l’original sans écraser les traductions existantes. Ce fichier `translation.md` est conservé durablement et n’est pas publié sur le site.
+
+La navigation, l’ordre des chapitres, les ancres, les liens, la mise en forme, le code et les images sont partagés et restent hors de `locales/`. Ne pas ajouter ici d’articles, de TOC, de modèles, de scripts ou de configuration.
+
+Modifier uniquement les valeurs du fichier `strings.yml` de cette langue. L’ensemble des clés doit être strictement identique à celui de l’anglais. Ne pas ajouter, supprimer ou renommer de clés de sa propre initiative, même si les textes semblent identiques ou ne sont pas encore apparus dans l’interface. Signaler toute erreur de l’original aux responsables du projet en indiquant la clé concernée. Corriger d’abord la source anglaise et les pages communes, puis les traductions.
+
+## Style et contexte
+
+Adopter le ton d’un manuel technique : précis, sobre et accessible. Expliquer les relations entre opérations avant leurs contraintes. Dans les tutoriels, conserver des étapes explicites et des résultats vérifiables. La rigueur ne demande ni tournures passives ni phrases inutilement longues.
+
+Réviser chaque article dans son ordre de lecture, avec les paragraphes voisins, tableaux et extraits de code. Les clés délimitent le stockage, pas les unités de traduction. Vérifier les accords et les prépositions des liens et fragments mis en valeur dans la phrase complète.
+
+Ne pas terminer par un point les titres, libellés, textes courts visibles de l’accueil et légende du pied de page. Conserver la ponctuation normale dans les paragraphes, diagnostics et instructions d’accessibilité. Employer un terme établi par notion, sans ajouter ponctuellement son équivalent anglais entre parenthèses. Préserver les identifiants d’API, fichiers et réglages.
+
+Conserver une phrase si elle explique l’opération en cours, un choix de conception, un résultat vérifiable ou une condition nécessaire. Supprimer les listes de fonctions inutilisées, les réserves générales et les prérequis répétés. Traduire les titres et les phrases découpées par la mise en forme comme un tout, dans l’ordre naturel du français. Les emplacements `{accent}` et `{lineBreak}` du titre d’accueil sont déplaçables : la présentation ne doit pas imposer la syntaxe anglaise. La suppression d’un passage et de ses clés est coordonnée par la maintenance dans le texte source, le balisage commun et tous les dictionnaires.
+
+## Texte et espaces réservés
+
+Traduire des phrases ou paragraphes complets dans un français technique clair et naturel. Préserver les conditions, négations, unités, étapes et contraintes de durée de vie. Ne pas ajouter de garanties techniques, de prérequis de plateforme, de versions de paquets ou de notes de version absents de l’original. Conserver les noms de produits, de paquets et de fichiers, ainsi que les identifiants C# et Slang, en respectant leur casse.
+
+Chaque valeur doit être une chaîne de caractères non vide, en texte brut, sans HTML ni Markdown. Respecter les guillemets et les échappements YAML ; utiliser des guillemets si nécessaire pour éviter une mauvaise interprétation des deux-points, des dièses ou des valeurs ressemblant à des nombres. Conserver l’ordre des clés de l’anglais pour faciliter la relecture.
+
+Les espaces réservés nommés, comme `{context}` et `{link}`, peuvent insérer du code, des liens ou du texte mis en évidence provenant des pages communes. Ceux de l’interface, comme `{name}`, `{query}` et `{count}`, peuvent aussi recevoir du texte à l’exécution. Vérifier leur utilisation dans la page ou l’interface concernée sans déduire leur contenu du seul nom. Traduire également les libellés de liens et les textes mis en évidence qui disposent de leur propre clé.
+
+L’ordre des espaces réservés peut suivre la syntaxe française. Pour chaque clé, conserver tous les noms, leur casse et les accolades ASCII. Ne pas ajouter, supprimer ou renommer d’espaces réservés, ni les remplacer par des noms d’API, des libellés de liens ou des nombres d’exemple. Ne pas scinder les clés pour recomposer une phrase.
+
+## Liens d’API et sens technique
+
+Les liens sont maintenus dans les pages communes. Les identifiants désignant explicitement une API locale dans le texte ou les tableaux renvoient au type ou au membre correspondant. Les variables, noms de paramètres, types propres aux exemples, noms de paquets et de fichiers, ainsi que les symboles externes sans page de référence locale restent du texte de code. Une même orthographe peut désigner des symboles différents. Les blocs de code ordinaires du tutoriel ne reçoivent pas de liens automatiques. Déplacer un espace réservé entier est permis ; modifier les liens, le code commun ou sa mise en forme ne l’est pas.
+
+Si une API ne possède pas d’entrée de référence générée, son identifiant reste du texte de code dans la page commune, accompagné d’un lien explicite vers le code source. Ne pas le remplacer par un lien vers un autre type ou membre.
+
+Préserver les distinctions suivantes :
+
+- L’enregistrement des commandes, leur soumission, leur exécution sur le GPU et leur achèvement sont des étapes distinctes.
+- L’usage d’une ressource, la résidence mémoire ou l’accès CPU prévu, le layout d’une texture et son format de pixel sont des notions distinctes.
+- La propriété désigne la responsabilité de libérer une ressource. Un handle ne maintient pas à lui seul la ressource en vie ; la soumission de commandes ne transfère pas sa propriété.
+- L’achèvement sur le GPU ne signifie pas toujours que les données de lecture intermédiaires ont été copiées vers la destination CPU de l’application. Conserver les indications sur l’attente de la file responsable.
+- Un drawable est la texture de sortie de l’image en cours de rendu, pas nécessairement une image de la chaîne d’échange. Distinguer les vues de ressources des contrôles d’interface.
+
+## Compilation et vérification avant livraison
+
+Exécuter les commandes à la racine du dépôt, avec le SDK .NET requis par le projet et DocFX installés. Dans une nouvelle copie du dépôt, les métadonnées générées dans `documents/api/` sont absentes. Commencer par la compilation complète, qui inclut l’extraction de l’API :
+
+```sh
+docfx documents/docfx.json --warningsAsErrors
+```
+
+Si les métadonnées d’API existent et correspondent au code source actuel, une modification du dictionnaire seul peut utiliser la compilation normale et la prévisualisation :
+
+```sh
+docfx build documents/docfx.json --warningsAsErrors
+docfx serve documents/_site --hostname 127.0.0.1 --port 8080
+```
+
+Cette langue est déjà enregistrée. Le dictionnaire complet devient disponible après une compilation réussie, sans modifier la configuration. Une clé manquante ou supplémentaire, une valeur vide ou d’un autre type, ou un nom d’espace réservé différent fait échouer la compilation. Celle-ci ne détecte ni les traductions inexactes ni les passages restés en anglais ; une relecture humaine est nécessaire.
+
+Les langues partagent les mêmes chemins. `?lang=fr-FR` sélectionne le français ; changer de langue conserve le chemin et l’ancre. Sans choix explicite, la sélection manuelle enregistrée est prioritaire, puis les préférences du navigateur, puis l’anglais. Si cette traduction n’est pas publiée, ou si le chargement ou la validation des ressources de la page échoue, la page reste en anglais sans mélange de langues phrase par phrase. La liste des langues est triée par code.
+
+Avant livraison, vérifier les clés et les espaces réservés. Contrôler l’accueil, l’entrée Learn, le tutoriel du triangle, les concepts, les exemples et l’interface de référence API sur ordinateur et à 320 pixels de largeur. Relire le tutoriel du point de vue d’un débutant ; vérifier la terminologie, les boutons, la recherche, la copie du code, les liens d’API et la position de la section après un changement de langue. Les identifiants, déclarations et extraits de code restent communs ; seuls les textes du dictionnaire sont traduits. Garder les dictionnaires de test et les fichiers temporaires hors du dépôt et ne pas livrer de traductions de remplissage.
+
+## Terminologie
+
+| Terme anglais | Forme retenue |
+| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
+| API / GPU / RHI | Conserver les sigles et les expliquer selon l’original |
+| barrier | barrière |
+| buffer | tampon ; conserver `Buffer` |
+| command buffer | tampon de commandes |
+| command queue | file de commandes |
+| drawable | texture de sortie de l’image en cours de rendu ; conserver `Drawable`, sans supposer qu’elle appartient à une chaîne d’échange |
+| graphics context | contexte graphique |
+| pipeline | pipeline |
+| readback | lecture de données depuis le GPU |
+| render pass | passe de rendu |
+| resource | ressource |
+| shader | shader |
+| swap chain | chaîne d’échange |
+| texture | texture |
+| timeline | ligne de temps |
+| upload | transfert de données vers le GPU |
diff --git a/documents/locales/ja-JP/strings.yml b/documents/locales/ja-JP/strings.yml
new file mode 100644
index 00000000..3ee356f9
--- /dev/null
+++ b/documents/locales/ja-JP/strings.yml
@@ -0,0 +1,593 @@
+### YamlMime:Resources
+concepts.execution.context.description: '{graphicsContext} は特定のバックエンドを介してアプリケーションを GPU デバイスに接続し、描画、計算、転送のキューを提供します。一緒に使うリソースは同じコンテキストに属している必要があります。バッファー、テクスチャ、シェーダー、パイプラインとそのハンドルを、コンテキストをまたいでバインドすることはできません。'
+concepts.execution.context.details: '{capabilities} はデバイス名と対応機能を提供します。レイトレーシングやメッシュシェーディング用のリソースを作成する前に、{rayTracingSupported} と {meshShadingSupported} で対応を確認します。{link}と{detail}のサンプルでは、初期化時にこの確認を行っています。'
+concepts.execution.context.details.detail: 'メッシュシェーディング'
+concepts.execution.context.details.link: 'レイトレーシング'
+concepts.execution.context.title: 'グラフィックスコンテキスト'
+concepts.execution.description: 'GPU の処理は CPU 側で準備します。アプリケーションがリソースを作成し、それらを使うコマンドを記録して、キューに送信します。送信後は GPU がコマンドを実行する間も CPU が処理を続けられます。リソースは GPU の実行が終わるまで保持する必要があります。'
+concepts.execution.ownership.description: '所有権は誰がオブジェクトを解放するかを、GPU の完了はいつ解放できるかを決めます。C# の参照が残っていても、{dispose} を呼ぶとネイティブリソースは解放されます。コマンドを送信しても、そのリソースの所有権はキューへ移りません。'
+concepts.execution.ownership.details: '終了時は新しい送信を止め、関係するすべてのキューでリソースの最後の使用が終わるのを待ちます。その後、アプリケーションのリソースを解放し、最後にコンテキストを解放します。コンテキストは自身のキューと内部の補助オブジェクトを解放するため、この処理はアプリケーションの作業が終わってから行います。'
+concepts.execution.ownership.guidance: '描画中にデータを上書きしたりリソースを置き換えたりする場合は、それを使う処理の完了を追跡します。その使用が終わるのを待ってから、データを変更するか記憶領域を解放します。ビューとその参照先リソースの寿命は、{link}で説明します。'
+concepts.execution.ownership.guidance.link: 'リソース管理'
+concepts.execution.ownership.table.applicationResources.object: 'アプリケーションが作成したリソース'
+concepts.execution.ownership.table.applicationResources.ownership: 'アプリケーションが最後の GPU 使用後に解放します。'
+concepts.execution.ownership.table.commandBuffers.object: '借用したコマンドバッファー'
+concepts.execution.ownership.table.commandBuffers.ownership: 'キューが管理して再利用します。'
+concepts.execution.ownership.table.headings.object: 'オブジェクト'
+concepts.execution.ownership.table.headings.ownership: '解放の責任'
+concepts.execution.ownership.table.queues.object: 'コンテキストキューとそのタイムライン'
+concepts.execution.ownership.table.queues.ownership: 'コンテキストとキューが管理します。'
+concepts.execution.ownership.table.swapChainDrawable.ownership: 'スワップチェーンが管理します。'
+concepts.execution.ownership.title: '所有権と寿命'
+concepts.execution.pipelines.description: 'グラフィックスパイプラインは、シェーダー、頂点入力レイアウト、プリミティブの組み立て、アタッチメントのフォーマット、描画状態を定義します。頂点、インデックス、定数のデータをバインドする前に選択してください。アタッチメントのフォーマットとサンプル数は、レンダーパスと一致させる必要があります。'
+concepts.execution.pipelines.details: 'コンピュートパイプラインは、ディスパッチで実行するシェーダーのエントリーポイントを選びます。グラフィックスのレンダーパス外で、パイプラインと定数をバインドしてからディスパッチします。'
+concepts.execution.pipelines.guidance: '{link}ではグラフィックスの手順を、{detail}では計算結果を表示するパスの前にディスパッチを加える手順を確認できます。'
+concepts.execution.pipelines.guidance.detail: 'コンピュートサンプル'
+concepts.execution.pipelines.guidance.link: '三角形の描画コールバック'
+concepts.execution.pipelines.title: 'パイプラインの状態'
+concepts.execution.queues.description: '描画、計算、コピーを含むフレームでは、通常は {graphicsQueue} から始めます。依存する操作を同じキューにまとめると、順序を扱いやすくなります。'
+concepts.execution.queues.details: '独立した処理を別々のキューに送信できますが、各キューが独立したハードウェア実行ユニットに対応するとは限りません。処理を並行して実行する利点がある場合に、キューを追加します。一方のキューがもう一方の生成データを使う場合は、送信間に明示的な依存関係を設定します。'
+concepts.execution.queues.guidance: '同じキュー内でも、依存する操作にはバリアやテクスチャ遷移が必要になる場合があります。後のアクセスで、それ以前の書き込み結果を読み取れるようにするためです。このメモリ依存関係は送信順序とは別の要件であり、指定方法は{link}で説明します。'
+concepts.execution.queues.guidance.link: '同期'
+concepts.execution.queues.table.computeQueue.workToRecord: 'コンピュートディスパッチと対応する転送操作。グラフィックスのレンダーパスは含みません。'
+concepts.execution.queues.table.graphicsQueue.workToRecord: 'レンダーパス、描画、計算、転送の操作。'
+concepts.execution.queues.table.headings.queue: 'キュー'
+concepts.execution.queues.table.headings.workToRecord: '対応する操作'
+concepts.execution.queues.table.transferQueue.workToRecord: 'アップロード、リードバック、コピー。描画とコンピュートディスパッチは含みません。'
+concepts.execution.queues.title: 'キューの選択'
+concepts.execution.recording.context: 'キューは完了したコマンドバッファーを回収して再利用します。記録ごとに {queueCommandBuffer} で取得し、同時に記録するスレッドを一つに限定して、一度だけ送信します。送信後はキューに管理を任せ、変更、再送信、解放を行わないでください。'
+concepts.execution.recording.description: '{queueCommandBuffer} が返す {commandBuffer} は、既に記録を開始しています。操作と依存関係を記録した後、一度だけ {submit} を呼びます。記録が終了して送信され、そのキューの完了点を示す {timelineValue} が返ります。'
+concepts.execution.recording.details: '三角形のチュートリアルでは、{commandBuffer} にフレームを記録した後、次のように送信して完了を待ちます。'
+concepts.execution.recording.guidance: '記録によってコマンド列を構築し、{submit} でその実行をスケジュールします。続いて {wait} を呼ぶと、送信した処理が完了するまで CPU スレッドが待機します。チュートリアルでは次のフレームを始める前に現在のフレームが終わるため、処理の順序を追いやすくなります。'
+concepts.execution.recording.title: '記録と送信'
+concepts.execution.title: '実行モデル'
+concepts.platforms.description: '画面への提示は、アプリケーションまたは UI コントロールが管理します。この選択によって、フレームの出力テクスチャを取得し、コマンドを記録・送信して結果を提示する担当が決まります。この出力テクスチャを drawable と呼びます。'
+concepts.platforms.frame.context: 'パス内でパイプラインのバインドと描画を記録し、ハンドラーから戻る前にパスを終了します。コントロールがコマンドを送信し、結果を提示します。ハンドラーでは送信を行わず、渡されたコマンドバッファーや drawable を解放しないでください。drawable のレイアウトを変更した場合は、コントロールが最後の遷移を行えるよう、戻る前に {colorAttachment} に戻します。'
+concepts.platforms.frame.description: 'コントロールは {iZenithView} を実装します。アプリケーションは {graphicsContext} を設定し、{updateRequested} でシーンを更新し、{renderRequested} で渡された {renderEventArgs} を使ってコマンドを記録します。'
+concepts.platforms.frame.details: '各フレームで、コントロールは drawable とコマンドバッファーを取得し、drawable を {colorAttachment} に遷移させてから、更新と描画のハンドラーを呼びます。その後、記録した処理を送信し、フレームワークの提示方式に従って画像を表示します。'
+concepts.platforms.frame.guidance: 'ファイルに {usingZenithNET} を追加します。{view} という名前の {iZenithView} が初期化され、グラフィックスコンテキストも設定済みなら、次のハンドラーで drawable をクリアできます。'
+concepts.platforms.frame.notes: '最初の遷移は {undefined} を使うため、以前の画像内容は破棄できます。毎フレーム、画像全体を描画するかクリアしてください。蓄積する描画結果やキャッシュはアプリケーション所有のテクスチャに保存し、そのテクスチャをコントロールの drawable に描画します。'
+concepts.platforms.frame.reference: '{zenithViewHelperDrawableFormat} は、これらの統合で使う既定のカラーフォーマットを提供します。'
+concepts.platforms.frame.title: 'UI コントロールでの描画'
+concepts.platforms.integration.context: 'WinForms、WPF、Windows 向け WinUI の統合は Windows 上で動作します。MAUI では対象プラットフォームのワークロードをインストールし、{extensionsCs} のように {useZenithView} でコントロールハンドラーを登録します。'
+concepts.platforms.integration.description: 'イベントのインターフェースは共通ですが、提示方式はフレームワークごとに異なります。'
+concepts.platforms.integration.details: 'GPU の画像を UI に届ける方法は、実装によって異なります。ビットマップへのリードバックでは、表示前にピクセルを CPU メモリへコピーします。共有テクスチャの相互運用では、この CPU コピーを省けますが、GPU コピーや同期が必要な場合があります。統合を選ぶ際は、共通の {renderRequested} イベントだけでなく、提示方法も検討してください。'
+concepts.platforms.integration.guidance: '使用する UI フレームワークの統合パッケージを NuGet から追加します。Avalonia では {avaloniaPackage} を使います。'
+concepts.platforms.integration.table.avalonia.integration: 'Avalonia'
+concepts.platforms.integration.table.avalonia.presentation: 'テクスチャにレンダリングし、ピクセルを {writeableBitmap} にリードバックします。{zenithViewCs} および {surfaceCs} を参照してください。'
+concepts.platforms.integration.table.headings.integration: '統合'
+concepts.platforms.integration.table.headings.presentation: '提示方式'
+concepts.platforms.integration.table.maui.integration: '.NET MAUI'
+concepts.platforms.integration.table.maui.presentation: 'Android、iOS、Mac Catalyst ではネイティブサーフェスを使用し、Windows では共有テクスチャコンポジションを使用します。{link} を参照してください。'
+concepts.platforms.integration.table.maui.presentation.link: 'プラットフォーム実装'
+concepts.platforms.integration.table.uno.integration: 'Uno パス'
+concepts.platforms.integration.table.uno.presentation: 'ピクセルを {writeableBitmap} にリードバックします。{zenithViewUnoCs} を参照してください。'
+concepts.platforms.integration.table.winForms.integration: 'WinForms'
+concepts.platforms.integration.table.winForms.presentation: 'コントロールの {hWND} 用に Zenith.NET スワップチェーンを作成します。{zenithViewCs} を参照してください。'
+concepts.platforms.integration.table.winUI.integration: 'Windows 上の WinUI'
+concepts.platforms.integration.table.winUI.presentation: 'インポートされた共有 Direct3D 11 テクスチャにレンダリングし、{swapChainPanel} に接続されたコンポジションスワップチェーンにコピーします。{zenithViewWinUICs} を参照してください。'
+concepts.platforms.integration.table.wpf.integration: 'WPF'
+concepts.platforms.integration.table.wpf.presentation: '共有 Direct3D 11 テクスチャをインポートし、Direct3D 相互運用を介して {d3DImage} によって表示されるサーフェスにコピーします。{surfaceCs} を参照してください。'
+concepts.platforms.integration.title: 'フレームワークと提示方式'
+concepts.platforms.lifecycle.context: '対応するアタッチメントを作るときは、描画イベントから渡された drawable の {descWidth}、{descHeight}、{descFormat}、{descSampleCount} を使います。このテクスチャの寸法が、実際の描画先の寸法です。コントロールのレイアウト上のサイズや表示倍率から推測しないでください。'
+concepts.platforms.lifecycle.description: 'スワップチェーンのサイズには、フレームバッファーの幅と高さをピクセル単位で使います。表示倍率によっては、ウィンドウの論理寸法とは異なります。どちらかの寸法がゼロの間は、描画もスワップチェーンのリサイズも行いません。{swapChainDrawable} は毎フレーム取得します。このテクスチャはスワップチェーンが所有し、提示やリサイズの後に置き換わることがあります。'
+concepts.platforms.lifecycle.details: 'フレームバッファーの寸法が変わったら、古い画像への GPU アクセスが終わるのを待ってから {swapChainResize} を呼びます。ネイティブの提示先を置き換える場合も、アクセスの完了後に、新しいサーフェス記述とハンドルを {swapChainRefresh} に渡します。終了時は、ネイティブの提示先を破棄する前にスワップチェーンを解放します。'
+concepts.platforms.lifecycle.guidance: 'アプリケーションの深度テクスチャやオフスクリーンテクスチャがウィンドウと同じ寸法を使う場合は、リサイズ時に再作成し、それらのハンドルを含む定数も更新します。アタッチメントのフォーマットとサンプル数が合っていれば、パイプラインは再利用できます。{link}では、テクスチャをリサイズしながらパイプラインを保持する構成を確認できます。'
+concepts.platforms.lifecycle.guidance.link: '回転する立方体のレンダラー'
+concepts.platforms.lifecycle.title: 'スワップチェーンの寸法と寿命'
+concepts.platforms.ownership.description: '設定したコンテキストと描画リソースはアプリケーションが、提示用リソースはコントロールが所有します。ビューの {graphicsContext} を変えると、スケジューラーは新しいコンテキストでコントロールのリソースを再作成します。アプリケーションのパイプライン、バッファー、テクスチャも、同じコンテキストで作り直してください。'
+concepts.platforms.ownership.details: '描画リソースを置き換える前にビューのフレーム処理を止め、そのリソースを使う GPU 処理の完了を待ちます。その後、古いリソースやコンテキストを解放します。ビューを削除した場合も、設定したコンテキストの解放はアプリケーションが担当します。'
+concepts.platforms.ownership.guidance: '共通の {frameSchedulerCs} は、ビューの UI ディスパッチャーを通してフレームハンドラーを実行します。ハンドラーはシーンデータの更新とコマンドの記録に集中させてください。そこで長い CPU 処理を行うと、UI の応答も遅れます。'
+concepts.platforms.ownership.title: 'アプリケーションとコントロールの所有権'
+concepts.platforms.surface.description: '{link}では、Silk.NET がウィンドウを作成してイベントを処理します。Zenith.NET は {surface} 記述を通じてネイティブウィンドウのハンドルと寸法を受け取り、それを使って {swapChain} を作成します。OS のウィンドウは引き続きアプリケーションが管理します。'
+concepts.platforms.surface.description.link: '三角形のチュートリアル'
+concepts.platforms.surface.details: 'サーフェスのハンドルはバックエンドと対応させます。'
+concepts.platforms.surface.guidance: '{appCs} のサンプルホストは Windows、macOS、X11 に対応しています。Linux のウィンドウは Xlib ハンドルを渡すため、Wayland セッションで実行するには XWayland が必要です。ネイティブ Wayland アプリケーションでは、{surfaceWayland} にディスプレイとサーフェスのハンドルを渡します。'
+concepts.platforms.surface.table.android.backend: 'Android 上の Vulkan。'
+concepts.platforms.surface.table.apple.backend: 'Apple プラットフォーム上の Metal。'
+concepts.platforms.surface.table.headings.backend: 'バックエンドとプラットフォーム'
+concepts.platforms.surface.table.headings.factory: 'サーフェスファクトリ'
+concepts.platforms.surface.table.headings.handles: 'ホストから提供されるハンドル'
+concepts.platforms.surface.table.wayland.backend: 'ネイティブ Wayland ホストを使用した Vulkan。'
+concepts.platforms.surface.table.wayland.handles: 'Wayland ディスプレイおよびサーフェス'
+concepts.platforms.surface.table.win32.backend: 'Windows 上の DirectX 12 または Vulkan。'
+concepts.platforms.surface.table.xlib.backend: 'X11 または XWayland を使用した Linux 上の Vulkan。'
+concepts.platforms.surface.table.xlib.handles: 'X11 ディスプレイおよびウィンドウ'
+concepts.platforms.surface.title: 'ネイティブの提示サーフェス'
+concepts.platforms.title: 'プラットフォーム統合'
+concepts.resources.allocation.context: 'ヒープに配置したリソースは、その寿命全体にわたってヒープの記憶領域を使います。最後の GPU 使用が終わったら、リソースを先に解放し、その後ヒープを解放します。'
+concepts.resources.allocation.description: 'アプリケーションでメモリ割り当てを管理する場合は、{heap} を使います。ヒープは、指定したバイトオフセットにリソースを配置する記憶領域です。オフセットの選択と使用領域の管理はアプリケーションが行います。'
+concepts.resources.allocation.details: '各リソースの作成に使うバッファーまたはテクスチャ記述を渡して、{contextGetSizeAndAlignment} を呼びます。返されたアラインメントの倍数となるオフセットに、返されたサイズ分の領域を確保します。領域はヒープ内に収め、使用中の別の割り当てと重ねてはいけません。テクスチャにはバックエンド固有のパディングが含まれる場合があるため、ピクセル数とピクセル当たりのバイト数だけでは割り当てサイズを算出できません。'
+concepts.resources.allocation.guidance: 'ヒープに配置するバッファーは、そのヒープと同じ常駐区分を指定します。テクスチャの格納には GPU 専用ヒープを使います。'
+concepts.resources.allocation.title: 'ヒープへの配置'
+concepts.resources.bufferUsages.table.constant.role: '定数バッファーバインドを介して読み取られるシェーダーパラメーター。'
+concepts.resources.bufferUsages.table.headings.role: '役割'
+concepts.resources.bufferUsages.table.headings.usage: '用途'
+concepts.resources.bufferUsages.table.index.role: 'インデックス描画で使用されるインデックス。'
+concepts.resources.bufferUsages.table.indirect.role: '間接描画または間接ディスパッチのコマンドが読み取る引数。'
+concepts.resources.bufferUsages.table.none.role: 'バッファーの用途フラグは宣言されていません。'
+concepts.resources.bufferUsages.table.storageReadOnly.role: '読み取り専用のシェーダーバインドを介してアクセスするバッファーデータ。'
+concepts.resources.bufferUsages.table.storageReadWrite.role: '読み書き可能なシェーダーバインドを介してアクセスするバッファーデータ。'
+concepts.resources.bufferUsages.table.transferDst.role: 'GPU コピーの転送先ストレージ。'
+concepts.resources.bufferUsages.table.transferSrc.role: 'GPU コピーの転送元データ。'
+concepts.resources.bufferUsages.table.vertex.role: '描画のためにフェッチされる頂点属性。'
+concepts.resources.bufferUsages.title: 'バッファーの用途'
+concepts.resources.description: 'リソース記述は、アプリケーションが記憶領域をどう使えるかを定めます。例えば、描画先にし、シェーダーでサンプリングし、CPU に読み戻すテクスチャには、三つの操作に対応する用途フラグが必要です。メモリ配置は CPU のアクセス方法を決めます。データにアクセスできる時点や記憶領域を解放できる時点は、同期と寿命の管理によって決まります。'
+concepts.resources.memory.context: 'CPU からアクセスできるバッファーでは、{map} がデータを読み書きするためのアドレスを提供します。マッピングも {unmap} も、GPU の処理を待機しません。GPU が書いた結果を CPU で読む前にはその書き込みを待ち、同じバイトを書き換える前には、その領域への GPU アクセスが終わるのを待ちます。'
+concepts.resources.memory.description: '{bufferDesc} の {residency} は、想定する CPU アクセスを指定します。'
+concepts.resources.memory.details: '常駐区分は CPU アクセスの要件を表します。CPU と GPU が物理メモリを共有するシステムでも、この区分を使います。三角形では {cpuWriteOnly} を使い、小さな頂点バッファーを直接初期化します。大きな静的ジオメトリでは、GPU 専用の記憶領域へアップロードする方が適している場合があります。'
+concepts.resources.memory.guidance: 'コンテキストから直接作成したテクスチャは GPU の記憶領域を使うため、{textureDesc} に常駐区分のフィールドはありません。CPU メモリとのデータ交換には、アップロードとリードバックを使います。'
+concepts.resources.memory.table.cpuReadOnly.cPUAccess: '読み取りのみ'
+concepts.resources.memory.table.cpuReadOnly.typicalRole: 'CPU へ読み戻すために GPU が生成したデータ'
+concepts.resources.memory.table.cpuWriteOnly.cPUAccess: '書き込みのみ'
+concepts.resources.memory.table.cpuWriteOnly.typicalRole: 'CPU が供給または更新するデータ'
+concepts.resources.memory.table.gpuOnly.cPUAccess: 'なし'
+concepts.resources.memory.table.gpuOnly.typicalRole: '静的ジオメトリと GPU の作業用データ'
+concepts.resources.memory.table.headings.cPUAccess: '想定する CPU アクセス'
+concepts.resources.memory.table.headings.residency: '常駐区分'
+concepts.resources.memory.table.headings.typicalRole: '典型的な役割'
+concepts.resources.memory.title: 'CPU アクセスとメモリ配置'
+concepts.resources.textureUsages.description: '転送フラグは、リソースを GPU コピーの転送元または転送先として使うことを宣言します。CPU から直接アクセスできるかどうかは、メモリ配置によって別途決まります。'
+concepts.resources.textureUsages.details: '用途フラグは、テクスチャを何に使えるかを宣言します。用途を切り替える際は、それに応じた{link}が必要です。また、使用するデバイスが、指定したテクスチャフォーマットでその用途をサポートしている必要があります。'
+concepts.resources.textureUsages.details.link: 'レイアウトと同期'
+concepts.resources.textureUsages.table.colorAttachment.role: 'レンダーパスのカラーアタッチメント。'
+concepts.resources.textureUsages.table.depthStencilAttachment.role: 'レンダーパスの深度/ステンシルアタッチメント。'
+concepts.resources.textureUsages.table.headings.role: '役割'
+concepts.resources.textureUsages.table.headings.usage: '用途'
+concepts.resources.textureUsages.table.none.role: 'テクスチャの用途フラグは宣言されていません。'
+concepts.resources.textureUsages.table.sampled.role: 'サンプリング用テクスチャのバインドを介して読み取るテクスチャデータ。'
+concepts.resources.textureUsages.table.storage.role: 'ストレージテクスチャのバインドを介してシェーダーが読み書きするテクスチャデータ。'
+concepts.resources.textureUsages.table.transferDst.role: 'GPU コピーの転送先画像ストレージ。'
+concepts.resources.textureUsages.table.transferSrc.role: 'GPU コピーの転送元画像データ。'
+concepts.resources.textureUsages.title: 'テクスチャの用途'
+concepts.resources.title: 'リソース管理'
+concepts.resources.transfers.context: 'リードバックでは、まず GPU データを内部の読み戻し領域へコピーします。ライブラリは完了したコマンドバッファーを回収するときに、そのデータを指定した CPU の宛先へコピーします。リードバックの送信値に対して {wait} を呼ぶと、両方の処理が完了します。戻るまで宛先を確保したままにし、管理対象メモリは固定してください。'
+concepts.resources.transfers.description: 'アップロードは CPU データを GPU リソースへコピーし、リードバックはリソースのデータを CPU メモリへコピーします。バッファーとテクスチャの次のメソッドは、転送を完了してから戻ります。'
+concepts.resources.transfers.details: '呼び出す前に、他のキューとの競合するアクセスを解消します。メソッド内の待機は自身の転送だけを対象とし、他のキューとの依存関係を設定するものではありません。マッピングしたメモリを直接コピーする場合は、競合する GPU アクセスの完了を呼び出し側で待つ必要があります。'
+concepts.resources.transfers.guidance: '他の GPU 処理とまとめて転送を送信するには、{commandBufferUpload} または {commandBufferDownload} で記録します。アップロードの転送元とリードバックの転送先では、メモリを有効に保つ必要のある期間が異なります。'
+concepts.resources.transfers.item: 'アップロードの呼び出し中に、ライブラリは元データをステージングメモリと呼ばれる内部の一時領域へコピーします。GPU は後でこの領域を読むため、アプリケーションの元ポインターが必要なのは、呼び出しが戻るまでです。'
+concepts.resources.transfers.notes: 'テクスチャ転送の行ストライドは、CPU データの隣り合う行の間隔をバイトで表します。スライスストライドは、深さ方向に隣り合うスライスの間隔です。転送処理はこれらの値を使い、CPU データとテクスチャの GPU 記憶領域との間でコピーします。GPU の完了と宛先へのコピーが別の段階になる理由は、{link}で説明します。'
+concepts.resources.transfers.notes.link: '同期'
+concepts.resources.transfers.table.bufferDownload.transfer: '{cpuReadOnly} では、マッピングしたメモリから直接コピーします。それ以外は転送キューにリードバックを送信し、完了を待ちます。'
+concepts.resources.transfers.table.bufferUpload.transfer: '{cpuWriteOnly} では、マッピングしたメモリへ直接コピーします。それ以外は転送キューにアップロードを送信し、完了を待ちます。'
+concepts.resources.transfers.table.headings.method: '方法'
+concepts.resources.transfers.table.headings.transfer: '転送の動作'
+concepts.resources.transfers.table.textureTransfers.method: '{textureUpload} / {textureDownload}'
+concepts.resources.transfers.table.textureTransfers.transfer: 'コピーとテクスチャ遷移をグラフィックスキューに送信し、完了を待ちます。終了時には、選択したサブリソースが指定した最終レイアウトになります。'
+concepts.resources.transfers.title: '即時転送とコマンドへの記録'
+concepts.resources.usages.description: '{bufferUsages} と {textureUsages} は、リソースの用途を宣言するフラグ列挙型です。それぞれ {bufferDescUsages} と {textureDescUsages} に設定します。複数の用途には、必要なメンバーを {value} で組み合わせます。'
+concepts.resources.usages.title: 'リソースの用途'
+concepts.resources.views.description: '{bufferView} はバッファーの範囲とデータの解釈方法を記述します。{textureView} はサブリソース範囲と、互換性のあるテクスチャ種別やフォーマットを選択します。どちらも既存の記憶領域を参照し、その作成時に宣言した用途に従います。ビューの範囲はアクセス範囲を示すものであり、シェーダーの境界検査が全バックエンドで同じように働くことを保証するものではありません。'
+concepts.resources.views.details: 'リソース自身のハンドルは既定のビューを提供します。別の範囲や互換性のある解釈方法を選ぶ場合は、独立したビューを作成します。ビューと元リソースはどちらも、GPU が使い終わるまで保持してください。ハンドルだけでは、どちらのオブジェクトも保持されません。いずれかを置き換えたら、古い{link}を含む定数も更新します。'
+concepts.resources.views.details.link: 'リソースハンドル'
+concepts.resources.views.title: 'ビューと参照先リソース'
+concepts.shaders.binding.context: 'コンピュートのサンプルでは、{input} に {inputTextureSampledHandle}、{output} に {outputTextureStorageHandle} を設定します。Slang の {descriptorHandle} の型引数が、各リソースへのアクセス方法を定めます。'
+concepts.shaders.binding.description: '{setConstantBuffer} は、現在のパイプラインが定数を読み取るバッファーとバイトオフセットを選択します。シェーダーが使う前に、パラメーターのデータをその領域へアップロードします。以前の GPU 処理が同じ領域をまだ読んでいる場合は、完了を待ってから更新してください。'
+concepts.shaders.binding.details: 'コマンドバッファー、コンピュートパイプライン、データを設定した定数バッファー、スレッドグループ数が用意できたら、次の順にディスパッチを記録します。定数バッファーは選択中のパイプラインにバインドされるため、先にパイプラインを選択します。'
+concepts.shaders.binding.guidance: 'ここでは {commandBuffer} に、{computePipeline} と {constantBuffer} のパラメーターを使うディスパッチを記録しています。定数バッファーには {bufferUsagesConstant} の用途が必要で、オフセット 0 はその先頭を指定します。一つのバッファーを複数の定数レコードで共有する場合、各開始オフセットはバックエンドのバインド時アラインメント要件にも従います。これは、レコード内部のフィールド配置とは別の要件です。'
+concepts.shaders.binding.notes: '{resourceHandle} は二つの 32 ビットフィールドを持ち、解釈はバックエンドに依存します。値はリソースを作成したコンテキスト内でのみ有効です。'
+concepts.shaders.binding.reference: 'ハンドルの取得元リソースとビューは、シェーダーの最後のアクセスが完了するまで保持します。テクスチャやビューの置換後は、古いハンドルを含む定数を更新してください。所有権の規則は{link}で説明します。'
+concepts.shaders.binding.reference.link: 'リソース管理'
+concepts.shaders.binding.table.bufferStorageReadOnlyHandle.access: 'シェーダー型 {t} の構造化要素の読み取り'
+concepts.shaders.binding.table.bufferStorageReadWriteHandle.access: 'シェーダー型 {t} の構造化要素の読み書き'
+concepts.shaders.binding.table.headings.access: '想定されるアクセス'
+concepts.shaders.binding.table.headings.cHandle: 'C# ハンドル'
+concepts.shaders.binding.table.headings.slangFieldType: 'Slang フィールド型'
+concepts.shaders.binding.table.samplerHandle.access: 'テクスチャのサンプリング状態'
+concepts.shaders.binding.table.textureSampledHandle.access: 'テクスチャの読み取り'
+concepts.shaders.binding.table.textureStorageHandle.access: 'ストレージテクスチャの読み書き'
+concepts.shaders.binding.title: '定数バッファーとリソースハンドル'
+concepts.shaders.compilation.description: '{zenithCompiler} は Slang の指定エントリーポイントを {shaderDesc} にコンパイルします。コンテキストの {graphicsApi} に応じ、DirectX 12 には DXIL、Metal には Metal ライブラリ、Vulkan には SPIR-V を生成します。'
+concepts.shaders.compilation.details: '{context} という名前のグラフィックスコンテキストを作成してから、実行ファイルと同じディレクトリにコピーした三角形のシェーダーをコンパイルします。'
+concepts.shaders.compilation.guidance: 'エントリーポイント名 {vSMain} は、シェーダーファイル内の宣言と一致させます。シェーダーのコンパイルとパイプラインの作成は初期化時に行い、描画中はパイプラインを再利用します。作成が終われば、{link}で示すようにシェーダーオブジェクトを解放できます。'
+concepts.shaders.compilation.guidance.link: '最初の三角形'
+concepts.shaders.compilation.title: 'シェーダーのコンパイル'
+concepts.shaders.description: 'C# と Slang の宣言では、各値をどこに格納し、シェーダーがどうアクセスするかを一致させます。頂点入力レイアウトは頂点属性を、シェーダーバッファーの宣言はパラメーターや配列を記述します。リソースハンドルは、シェーダーが使うリソースを識別します。'
+concepts.shaders.dispatch.description: '{dispatch} は、実行するスレッドグループ数を指定します。コンピュートサンプルでは {numthreads} を宣言しているため、一グループは 16 × 16 スレッドです。画像の幅と高さをそれぞれ 16 で割って切り上げると、画像全体を覆えます。17 × 19 の画像には 2 × 2 グループが必要です。シェーダーで各スレッドの座標を調べ、画像の外側なら処理を省きます。'
+concepts.shaders.dispatch.details: '宣言したグループサイズは {shaderDescThreadGroupSize} から取得でき、{dispatch} に渡すグループ数の計算に使えます。計算の全体は {computeShaderRendererCs} で確認できます。ディスパッチ後の画像へのアクセスは、{link}の要件に従います。'
+concepts.shaders.dispatch.details.link: '同期'
+concepts.shaders.dispatch.title: 'スレッドグループ'
+concepts.shaders.layout.context: '{link}では、画像の寸法と二つのテクスチャハンドルを使ってこの配置を示しています。各フィールドの開始位置はオフセットで明示します。次の宣言では {systemRuntimeInteropServices} と {zenithNET} の名前空間を使用します。'
+concepts.shaders.layout.context.link: 'コンピュートサンプルの C# 定数'
+concepts.shaders.layout.description: '頂点入力の {inputLayout} は、各属性のフォーマット、オフセット、セマンティクスと、頂点間のストライドを記述します。三角形では {float3} の位置の後に {float4} の色を置きます。色のバイトオフセットは 12、頂点一つのサイズは 28 バイトです。{inputLayoutAdd} は属性のフォーマットからこの密な配置を計算します。C# の構造体にパディングがある場合は、それを含めたオフセットとストライドを指定してください。'
+concepts.shaders.layout.details: '定数バッファーはシェーダーのパラメーターを、構造化バッファーは型付き要素の配列を格納します。これらの配置は {inputLayout} ではなく、Slang の宣言に従います。C# のフィールドオフセット、要素サイズ、パディングを、コンパイル済みシェーダーの配置に合わせてください。C# のアンマネージ型はバイト列としてコピーできますが、そのメモリ配置がシェーダーの配置と一致するとは限りません。頂点内で密に格納した {float3} も、シェーダーバッファーでは別のアラインメントが必要になる場合があります。'
+concepts.shaders.layout.guidance: 'シェーダーのパラメーターには、数値とともにリソースハンドルを含められます。ハンドルは既存の記憶領域を指すため、定数バッファーにコピーしてもリソースの内容はコピーされません。'
+concepts.shaders.layout.notes: '{computeShaderSlang} の対応する宣言は次のとおりです。'
+concepts.shaders.layout.reference: '二つの寸法値が先頭の 8 バイトを占め、その後に二つの 8 バイトのハンドルが続きます。C# サンプルはレコード全体に 256 バイトを確保します。別のレコードでは、フィールドの内容と必要な配置、アラインメントからサイズを決めます。'
+concepts.shaders.layout.title: 'データ配置'
+concepts.shaders.matrices.description: 'コンパイラーは行ごとに連続する行優先の行列配置を選択します。{link}では三つの {matrix4x4} をオフセット 0、64、128 に格納します。対応する{detail}では、{mulVectorMatrix} をモデル、ビュー、投影の順に適用します。'
+concepts.shaders.matrices.description.detail: 'シェーダー'
+concepts.shaders.matrices.description.link: '回転する立方体のレンダラー'
+concepts.shaders.matrices.details: '格納順序はメモリ内の行列要素の配置を、乗算順序は変換を定めます。C# と Slang で対応する規約を使います。'
+concepts.shaders.matrices.title: '行列の規約'
+concepts.shaders.title: 'シェーダーデータとバインド'
+concepts.synchronization.backends.description: 'DirectX 12 と Vulkan は、テクスチャ遷移をレイアウトとアクセスの変更として記録します。Zenith.NET の Metal バックエンドでは、{transition} に相当する GPU 操作はありません。代わりにコマンドを描画・計算エンコーダーにまとめ、エンコーダー終了時に可視性バリアを挿入します。連続するコンピュートディスパッチなど、同じエンコーダー内の依存には明示的な {barrier} 呼び出しを使います。'
+concepts.synchronization.backends.details: '{link}では、グラフィックスのレンダーパスを開始すると計算エンコーダーが終了し、計算結果を描画処理から読み取れるようになります。連続する二つのコンピュートディスパッチは同じエンコーダー内にとどまるため、間にテクスチャ遷移を記録しても、依存を指定するバリアが必要です。このため、複数のバックエンドで使うコマンド列では、レイアウトとアクセス依存の両方を扱います。'
+concepts.synchronization.backends.details.link: 'コンピュートサンプル'
+concepts.synchronization.backends.title: 'バックエンドごとの動作'
+concepts.synchronization.barriers.context: '関係するアクセスを含み、キューが対応しているステージを選びます。{barrierStagesAll} は対応するすべてのステージを含みます。依存関係が一部のステージだけに関わる場合は、その範囲に限定できます。'
+concepts.synchronization.barriers.description: '同じコマンドバッファーに記録した二つのコンピュートディスパッチで、最初がシミュレーションデータを書き、次がそれを読む場合を考えます。その間にバリアを置くと、後のディスパッチが完了済みの書き込み結果を読み取れます。ストレージバッファーでは、テクスチャレイアウトを遷移させずにアクセスを順序付けられます。'
+concepts.synchronization.barriers.details: '最初の引数はデータを生成したステージ、二つ目はそれを使用するステージを指定します。この二つで、ディスパッチ間に必要な実行順序とメモリの可視性を表します。'
+concepts.synchronization.barriers.guidance: '{link}では、シミュレーションの各段階の間にこの形のバリアを置きます。どのバリアも、データを生成するディスパッチの後、使用するディスパッチの前に記録します。'
+concepts.synchronization.barriers.guidance.link: 'FluidTank シミュレーション'
+concepts.synchronization.barriers.title: 'パイプラインバリア'
+concepts.synchronization.dependencies.description: '各リソースで、同じデータに触れるアクセスを確認します。一方でも書き込みを行う場合は、必要な順序を保ちます。二つの読み取りは競合しませんが、テクスチャレイアウトや提示の要件は満たす必要があります。CPU からの更新やリソースの解放も、対象領域を GPU が使い終わるまで待ちます。'
+concepts.synchronization.dependencies.details: '操作の実行場所に応じて、使う仕組みを選びます。'
+concepts.synchronization.dependencies.guidance: '一つの処理に複数の同期手段が必要な場合もあります。例えば、別のキューで生成したテクスチャには、送信間の依存関係と、次の用途に適したレイアウトの両方が必要です。どちらも、それ自体は CPU の待機を必要としません。CPU がデータにアクセスしたり解放したりする場合に、CPU 側で待機します。'
+concepts.synchronization.dependencies.table.cpuAccess.mechanism: '該当する完了値に対して {wait} を呼び出します。'
+concepts.synchronization.dependencies.table.cpuAccess.situation: 'CPU のアクセスやリソース解放に GPU の完了が必要な場合'
+concepts.synchronization.dependencies.table.crossQueue.mechanism: '生成側の {timelineValue} を使用側の {submit} に渡します。'
+concepts.synchronization.dependencies.table.crossQueue.situation: '別のキューで生成したデータを使用する場合'
+concepts.synchronization.dependencies.table.headings.mechanism: 'メカニズム'
+concepts.synchronization.dependencies.table.headings.situation: '状況'
+concepts.synchronization.dependencies.table.layoutChange.mechanism: '以前のレイアウトと次のレイアウトを指定した {commandBufferTransition}。'
+concepts.synchronization.dependencies.table.layoutChange.situation: 'テクスチャサブリソースの使用方法の変更'
+concepts.synchronization.dependencies.table.pipelineStages.mechanism: '生成側ステージと使用側ステージを指定した {commandBufferBarrier}。'
+concepts.synchronization.dependencies.table.pipelineStages.situation: 'コマンドストリーム内の依存する GPU ステージ'
+concepts.synchronization.dependencies.title: 'アクセス間の依存'
+concepts.synchronization.description: '同期は、共有データへのアクセス順序を定め、後の操作が必要な結果を読み取れるようにします。書き込みから読み取りへの依存では、書き込む側を生成側、読む側を使用側と呼びます。バリアとテクスチャ遷移は GPU のアクセスを調整し、タイムラインは送信間の依存を設定したり、CPU で GPU の完了を待ったりするために使います。'
+concepts.synchronization.layouts.context: '以前の内容を破棄できる場合は、遷移前のレイアウトに {undefined} を指定できます。例えば、新しい画像や全体をクリアするアタッチメントが該当します。ロード、サンプリング、蓄積のために内容を保持する場合は、直前のアクセスのレイアウトを使います。'
+concepts.synchronization.layouts.description: '用途フラグは、テクスチャの許可された用途を宣言します。{textureLayout} は、コマンド列のある時点で、mip レベルと配列レイヤーで特定したサブリソースにどうアクセスするかを表します。アクセス方法が変わるときに遷移を記録します。例えばコンピュートサンプルでは、{storage} レイアウトで出力テクスチャへ書き込んだ後、グラフィックスパスでサンプリングする準備をします。'
+concepts.synchronization.layouts.details: 'ここでは {outputTexture} を {sampledStorage} 用途で作成し、対象サブリソースは {storage} です。{default} は mip レベル 0 と配列レイヤー 0 を選びます。他のレベルとレイヤーには影響しません。'
+concepts.synchronization.layouts.guidance: '{transition} には、サブリソースを前回使用したときのレイアウトを渡します。アプリケーションはコマンドを記録し、テクスチャを描画ステージ間で渡す際に、この状態を追跡します。'
+concepts.synchronization.layouts.title: 'テクスチャレイアウト'
+concepts.synchronization.readback.description: '{isCompleted} は GPU の完了を報告します。記録したリードバックには、その後にもう一つの処理があります。内部の読み戻し領域からアプリケーションの宛先メモリへ結果をコピーする処理です。'
+concepts.synchronization.readback.details: '{commandBufferDownload} は、この内部領域への GPU コピーを記録します。送信完了後、ライブラリはコマンドバッファーの回収時に CPU コピーを行います。リードバックの送信値に対して {wait} を呼ぶと、GPU を待機した後、そのキューの完了済みバッファーを処理し、両方のコピーが完了します。戻るまで宛先を確保し、管理対象メモリは固定したままにしてください。'
+concepts.synchronization.readback.guidance: '{isCompleted} の確認だけでは、最後の CPU コピーは行われません。別のキューで待機しても、リードバックを管理するキューのバッファーは回収されません。アップロードとリードバックが直接アクセスや一時領域を使う条件は、{link}で説明します。'
+concepts.synchronization.readback.guidance.link: 'リソース管理'
+concepts.synchronization.readback.title: 'リードバックの完了'
+concepts.synchronization.reuse.description: '三角形では、各フレームの完了を待ってからデータを再利用します。フレームの処理を並行して進める場合は、処理中の各フレームに個別の変更可能な記憶領域を用意し、再び使える時点を追跡します。データを変更する前にはその領域への GPU アクセスを、リソースを解放する前にはそのすべての使用を待ちます。'
+concepts.synchronization.reuse.details: 'リサイズと終了も、同じ順序で処理します。新しい使用を止め、既存の使用が終わるのを待ってから、リソースを置き換えるか解放します。{swapChainResize} やリソース、コンテキストの解放を呼ぶ前に、この順序を確保してください。三角形では毎フレーム待機しているため、コールバックの間でリサイズできます。'
+concepts.synchronization.reuse.guidance: '{swapChainPresent} は提示後にグラフィックスキューへシグナルを送り、完了を待ちます。そのため、明示的な送信待ちを除いても、三角形は提示時に待機します。別キューへ独立して送信した処理は、完了を個別に追跡する必要があります。'
+concepts.synchronization.reuse.title: 'リソースの再利用'
+concepts.synchronization.timelines.context: '使用側の処理後に CPU で結果を読んだり、共有データを上書きしたり、リソースを解放したりする場合は、その送信を待ちます。'
+concepts.synchronization.timelines.description: 'タイムラインは増加する値でキューの完了を表します。{submit} が返す {timelineValue} はタイムラインと送信の値を含みます。異なるタイムラインの数値 {value} は直接比較できません。'
+concepts.synchronization.timelines.details: '同じコンテキストの二つのキューで記録した {producer} と {consumer} があり、後者が前者の書いたデータを使うとします。次の依存関係で送信します。'
+concepts.synchronization.timelines.guidance: '使用側のキューは、渡された完了値に到達するのを待ってからコマンドを実行します。CPU は {producedWait} を呼ばずに処理を続けられます。共有リソースは {consumed} が完了するまで保持し、使用側のアクセスに必要なテクスチャ遷移も記録します。'
+concepts.synchronization.timelines.notes: '{queueTimelineSignal} の戻り値も、{wait} で待機したり、別キューの依存関係に使ったりできます。{signal} は、それまでに送信した処理の後に完了点を置きます。GPU がその処理を終えると、完了点に到達します。'
+concepts.synchronization.timelines.title: 'タイムラインとキュー間の依存'
+concepts.synchronization.title: '同期'
+home.actions.reference: 'API を参照'
+home.actions.tutorial: '三角形から始める'
+home.entries.description: 'チュートリアル、サンプル、API リファレンス'
+home.entries.label: 'ドキュメントの構成'
+home.entries.reference.action: 'API を探索'
+home.entries.reference.category: 'リファレンス'
+home.entries.reference.description: 'API の型、メンバー、宣言を調べる'
+home.entries.reference.title: 'API リファレンス'
+home.entries.samples.action: 'サンプルを参照'
+home.entries.samples.category: 'サンプル'
+home.entries.samples.description: '描画と計算の手法を実行できるサンプルで学ぶ'
+home.entries.samples.title: 'サンプル'
+home.entries.title: 'ドキュメント'
+home.entries.tutorial.action: 'チュートリアルを開始'
+home.entries.tutorial.category: 'チュートリアル'
+home.entries.tutorial.description: '空のプロジェクトから、最初の三角形を描くまで'
+home.entries.tutorial.title: '最初の三角形'
+home.introduction.accent: 'クロスプラットフォームのグラフィックス'
+home.introduction.description: 'Zenith.NET は、共通の API で DirectX、Metal、Vulkan の描画と計算を扱うライブラリです'
+home.introduction.title: '.NET でつくる{lineBreak}{accent}'
+home.meta.description: 'DirectX、Metal、Vulkan に共通の API を使い、.NET でクロスプラットフォームの描画・計算アプリケーションを開発'
+home.scene.description: '回転する図形を操作できます。ドラッグまたは矢印キーで視点を調整してください。'
+home.scene.label: 'ジオメトリ表示'
+home.scene.surface: 'サーフェス'
+home.scene.wireframe: 'ワイヤーフレーム'
+home.title: '.NET 向けのモダンな RHI'
+learning.concepts.description: 'リソース用途の選択、シェーダーデータの記述、GPU 実行の調整'
+learning.concepts.execution.description: 'コマンドの記録と送信、リソースの寿命'
+learning.concepts.platforms.description: 'ネイティブの提示と UI 描画コントロール'
+learning.concepts.resources.description: 'リソースの用途、メモリ配置、ビュー'
+learning.concepts.shaders.description: 'C# と Slang のデータ配置とバインドの規約'
+learning.concepts.synchronization.description: '依存関係、可視性、完了'
+learning.concepts.title: '基本概念'
+learning.description: 'まずは三角形のチュートリアルで、最初のアプリケーションを作成します。その後は必要に応じて、リソース管理、シェーダーデータ、同期の解説を読むか、実装したい手法のサンプルを選んでください。'
+learning.entries.samples.action: 'サンプルを参照'
+learning.entries.samples.description: 'ジオメトリ、計算、レイクエリ、メッシュシェーディングの C# と Slang による実装'
+learning.entries.tutorial.action: 'チュートリアルを開く'
+learning.entries.tutorial.description: 'ウィンドウを開き、三角形を描くコンソールアプリケーション'
+learning.meta.description: 'Zenith.NET のプログラミングモデルを学ぶチュートリアルと概念解説'
+learning.title: '学習'
+reference.description: 'リソース、コマンド、プラットフォーム統合のリファレンス'
+reference.entries.commands.description: '描画、計算、転送のコマンド'
+reference.entries.commands.label: 'コマンドを記録'
+reference.entries.context.description: '選択したバックエンドの GPU リソース作成'
+reference.entries.context.label: 'ここから開始'
+reference.meta.description: 'Zenith.NET の名前空間、型、メンバー'
+reference.navigation.core: 'コア API リファレンス'
+reference.title: 'API リファレンス'
+samples.computeShader.description: 'サンプルは {assetsTexturesShokoPng} を読み込み、コンピュートディスパッチでグレースケールに変換して、出力テクスチャを表示します。変換は一度だけ行い、以降のフレームでは同じ結果を表示します。レンダラーでは、サンプリング用と書き込み用テクスチャのバインド、画像全体を覆うためのグループ数の切り上げ、シェーダー内の境界検査を確認できます。'
+samples.computeShader.details: '{computeShaderRendererCs} · {computeShaderSlang}'
+samples.computeShader.title: 'コンピュートシェーダー'
+samples.cube.description: '立方体では、インデックス付き描画、定数バッファー内のモデル・ビュー・投影行列、深度テストを加えます。{d32FloatS8UInt} の深度ステンシルテクスチャはリサイズ時に再作成します。'
+samples.cube.details: '{spinningCubeRendererCs} · {spinningCubeSlang}'
+samples.cube.title: '回転する立方体'
+samples.description: '各サンプルは C# のレンダラーと Slang シェーダーを組み合わせ、一つの手法を扱います。アプリケーションの準備から描画までの手順は、{link}で説明します。'
+samples.description.link: '最初の三角形'
+samples.execution.description: '{link} プロジェクトを実行し、コンソールメニューからサンプルを選びます。'
+samples.execution.description.link: 'ZenithTutorials'
+samples.execution.title: 'サンプルの実行'
+samples.host.description: '{appCs} はコンテキスト、ウィンドウ、スワップチェーンを初期化します。各フレームではコマンドバッファーを取得し、選択したレンダラーを呼び、送信と提示を行います。{iRendererCs} は更新、描画、リサイズ、解放の操作を定義します。'
+samples.host.details: 'コンピュートとレイトレーシングのレンダラーは、オフスクリーンテクスチャを生成します。{texturePresenterCs} と {presentTextureSlang} がその内容を現在の drawable に描画し、共通ホストがコマンドを送信してフレームを提示します。'
+samples.host.title: 'ホストと提示'
+samples.indirectDrawing.description: '間接描画では、インデックス付き描画の引数バッファーとインスタンスごとの構造化データを使い、複数の立方体を描きます。CPU が引数を設定し、変換を更新します。'
+samples.indirectDrawing.details: '{indirectDrawingRendererCs} · {indirectDrawingSlang}'
+samples.indirectDrawing.title: '間接描画'
+samples.meshShading.description: 'タスクシェーダーが球のインスタンスをカリングし、残ったインデックスを、ジオメトリを生成するメッシュシェーダーへ渡します。パイプライン作成前に {contextCapabilitiesMeshShadingSupported} を確認します。'
+samples.meshShading.details: '{meshShadingRendererCs} · {meshShadingSlang}'
+samples.meshShading.title: 'メッシュシェーディング'
+samples.rayTracing.description: '床と手続き的な球の下位・上位アクセラレーション構造を構築し、コンピュートシェーダー内でインラインレイクエリを実行します。初期化前に {contextCapabilitiesRayTracingSupported} を確認します。'
+samples.rayTracing.details: '{rayTracingRendererCs} · {rayTracingSlang}'
+samples.rayTracing.title: 'レイトレーシング'
+samples.title: 'サンプル'
+samples.triangle.description: '頂点バッファーと対応する入力レイアウトを関連付け、グラフィックスパイプラインで三つの頂点から三角形を描きます。'
+samples.triangle.details: '{helloTriangleRendererCs} · {helloTriangleSlang} · {link}'
+samples.triangle.details.link: '完全なチュートリアル'
+samples.triangle.title: 'Hello Triangle'
+samples.workloads.description: 'メインリポジトリの {link} にある応用例は、ライブラリのソースを直接参照しています。'
+samples.workloads.description.link: 'Experiments ディレクトリ'
+samples.workloads.details: 'ウィンドウ付きの実験は同じ Windows、macOS、Linux/X11 の提示経路を使います。Avalonia、WinForms、WPF、WinUI/Uno、MAUI のコントロールは、フレーム処理と実装ソースを含めて{link}で紹介します。'
+samples.workloads.details.link: 'プラットフォーム統合'
+samples.workloads.table.cornellBox.example: '{link}'
+samples.workloads.table.cornellBox.example.link: 'CornellBox'
+samples.workloads.table.cornellBox.whatItDemonstratesAndRequires: 'パストレーシング、ノイズ除去、トーンマッピング、アップスケーリング、ImGui による操作を扱います。レイトレーシング対応が必要です。'
+samples.workloads.table.fluidTank.example: '{link}'
+samples.workloads.table.fluidTank.example.link: 'FluidTank'
+samples.workloads.table.fluidTank.whatItDemonstratesAndRequires: '計算による流体シミュレーション、段階間の依存、複数パスの表面描画を扱います。'
+samples.workloads.table.headings.example: '例'
+samples.workloads.table.headings.whatItDemonstratesAndRequires: '手法と要件'
+samples.workloads.table.inkCanvas.example: '{link}'
+samples.workloads.table.inkCanvas.example.link: 'InkCanvas'
+samples.workloads.table.inkCanvas.whatItDemonstratesAndRequires: 'ネイティブウィンドウで Skia の描画と入力処理を行います。対象プラットフォームのバックエンドと Skia ネイティブライブラリが必要です。'
+samples.workloads.table.platformDetection.example: '{link}'
+samples.workloads.table.platformDetection.example.link: 'PlatformDetection'
+samples.workloads.table.platformDetection.whatItDemonstratesAndRequires: '各バックエンドのコンテキスト作成を試み、デバイスの機能を報告します。作成に失敗した場合は非対応と表示しますが、原因にはデバイス、ドライバー、実行時依存の不足が考えられます。'
+samples.workloads.title: 'より大きなアプリケーション'
+tutorial.context.description: '{graphicsContext} はバックエンドを介してアプリケーションを GPU に接続し、描画リソースを作成するためのメソッドを提供します。'
+tutorial.context.details: 'コンテキストの初期化を{emphasis}に追加します。'
+tutorial.context.details.emphasis: 'using ディレクティブの後、{iWindowWindow} の前'
+tutorial.context.guidance: '各分岐でプラットフォームに応じたバックエンドを選び、検証を要求します。{validationMessage} のメッセージはコンソールに出力します。'
+tutorial.context.imports: '{programCs} の先頭に、次の using ディレクティブを追加します。'
+tutorial.context.title: 'グラフィックスコンテキスト'
+tutorial.description: 'このチュートリアルでは、空の .NET コンソールプロジェクトから始め、ウィンドウを開いて色付きの三角形を描くアプリケーションを作成します。'
+tutorial.details: 'このチュートリアルでは、空のウィンドウ、単色の背景、色付きの三角形の三段階で結果を確認します。各段階でアプリケーションを実行し、表示を確かめてから次へ進んでください。'
+tutorial.draw.context: '{draw} の引数は次のとおりです。'
+tutorial.draw.description: '{windowRender} コールバックの {addTheTriangleDrawCommandsHereLater} を、次のコマンドで置き換えます。位置は {beginRenderPass} と {endRenderPass} の間です。'
+tutorial.draw.details: '{setPipeline} はシェーダーと描画状態を選択します。頂点のバインドはパイプラインの入力レイアウトに従って解釈されるため、その前に呼びます。'
+tutorial.draw.guidance: '{setVertexBufferVertexBuffer} は、転送済みの頂点データをバイトオフセット 0 から入力スロット 0 にバインドします。これは {inputLayouts} の唯一の要素に対応します。'
+tutorial.draw.notes: 'これらの呼び出しは描画コマンドを記録します。既存の {submitWait} がコマンドを送信して GPU の完了を待ち、その後 {present} が完成した画像の表示を要求します。三角形の外側にも決まった背景色を表示するため、パスの先頭のクリア処理は残します。'
+tutorial.draw.table.firstInstance.meaning: 'インスタンスの番号付けをゼロから開始します。'
+tutorial.draw.table.firstVertex.meaning: 'バッファー内の最初の頂点から開始します。'
+tutorial.draw.table.headings.argument: '引数'
+tutorial.draw.table.headings.description: '値'
+tutorial.draw.table.headings.meaning: '意味'
+tutorial.draw.table.instanceCount.meaning: '三角形を一つ描きます。'
+tutorial.draw.table.vertexCount.meaning: '3 つの頂点を読み取ります。'
+tutorial.draw.title: '描画コマンド'
+tutorial.execution.description: '{firstTriangle} プロジェクトをビルドして実行します。'
+tutorial.execution.details: '暗い背景の上に、上が赤、右下が緑、左下が青の三角形が表示されます。内部の色は頂点色を補間したもので、滑らかなグラデーションになります。'
+tutorial.execution.guidance: 'リサイズすると画像のピクセル寸法が変わりますが、頂点座標は変わりません。ビューポートは同じ座標を新しい幅と高さに対応付けるため、ウィンドウの縦横比を変えると三角形が伸縮します。'
+tutorial.execution.title: 'アプリケーションの実行'
+tutorial.frame.commands.context: '{endRenderPass} の後、二回目の遷移で出力テクスチャを提示可能な状態にします。{submit} は記録を終えてコマンドを送信し、{wait} はその完了まで待機します。続く {present} が描画済み画像の表示を要求します。'
+tutorial.frame.commands.description: 'レンダーパスは、同じアタッチメントに書き込む描画操作をまとめます。このパスではスワップチェーンの drawable が色の出力先です。{colorAttachmentClear} は、アタッチメントを RGBA 色で初期化し、パス終了時に内容を保持するよう指定します。{beginRenderPass} は、この設定でパスを開始するコマンドを記録します。'
+tutorial.frame.commands.details: 'コレクションにはカラーアタッチメントを一つ入れます。{null} は深度ステンシルアタッチメントを使わない指定です。'
+tutorial.frame.commands.guidance: '{beginRenderPass} は、アタッチメント全体を覆うビューポートとシザーを設定します。ビューポートは描画座標をピクセル位置に変換し、シザーは書き込める範囲を制限します。'
+tutorial.frame.commands.title: 'クリア、送信、提示'
+tutorial.frame.description: 'まず、ウィンドウを単色の背景で埋めます。この操作をクリアと呼びます。背景が表示されれば、サーフェス、コマンド送信、提示の流れが動いています。ここに三角形の頂点とシェーダーを加えていきます。'
+tutorial.frame.details: 'Silk.NET は描画のタイミングでコールバックを呼びます。この例では経過時間の引数を使わないため、パラメーター名を {value} にしています。コールバックを{emphasis}に登録します。'
+tutorial.frame.details.emphasis: '{windowRun} の前'
+tutorial.frame.target.description: '{graphicsQueueCommandBuffer} は記録可能なコマンドバッファーを返します。記録は GPU の処理を定義する段階であり、実行は送信後に始まります。'
+tutorial.frame.target.details: 'テクスチャレイアウトは、操作がテクスチャにどうアクセスするかを表します。最初の {transition} で、出力テクスチャをカラーアタッチメントとして使える状態にします。ここでの {default} は mip レベル 0 と配列レイヤー 0、つまり出力テクスチャの基本画像を選びます。このパスでは画像全体をクリアするため、{undefined} を指定して以前の内容を破棄できます。'
+tutorial.frame.target.guidance: '提示すると別の画像に切り替わることがあるため、毎フレーム、現在の drawable を取得します。'
+tutorial.frame.target.title: 'drawable の準備'
+tutorial.frame.title: '最初のフレーム'
+tutorial.geometry.data.description: '頂点配列を{emphasis}に追加します。以後のリソース初期化も、説明する順序でこのコールバックの前に置きます。'
+tutorial.geometry.data.description.emphasis: '{windowRender} の前'
+tutorial.geometry.data.details: '各要素は {vertex} を作り、内側の二つの {new} が {vector3} の位置と {vector4} の色を渡します。位置は上、右下、左下の頂点です。頂点シェーダーで第四成分 {w} を加えます。w が 1 なので x と y は正規化デバイス座標でもあり、-1 と +1 が画像の端、0 が中央を表します。この意味はピクセル寸法に依存しません。'
+tutorial.geometry.data.guidance: '各色は赤、緑、青、アルファの成分を持ち、アルファは 1 です。三つの頂点はすべて z = 0 で、同じ平面上にあります。'
+tutorial.geometry.data.title: '頂点の位置と色'
+tutorial.geometry.description: '三角形の各頂点には位置と色があります。GPU は三つの位置から形を決め、頂点色を補間して内部の色を求めます。'
+tutorial.geometry.format.description: '頂点の宣言を{emphasis}に追加します。'
+tutorial.geometry.format.description.emphasis: '{programCs} の末尾、解放処理の後'
+tutorial.geometry.format.details: '{file} は型の使用範囲を {programCs} に限定し、{layoutKindSequential} はフィールドの宣言順を保ちます。{position} は三つの 32 ビット浮動小数点数を含み、バイトオフセット 0 から始まります。{color} は四つを含み、バイトオフセット 12 から始まります。したがって、頂点一つの大きさは 28 バイトです。後で入力レイアウトにこの配置を指定し、GPU が各フィールドを正しく読めるようにします。'
+tutorial.geometry.format.imports: 'ベクトル型と構造体のレイアウト属性を使うため、{programCs} の先頭に次の using ディレクティブを追加します。'
+tutorial.geometry.format.title: '頂点の表現'
+tutorial.geometry.title: '頂点データ'
+tutorial.geometry.upload.context: '{fixed} は、配列のアドレスを使用している間、ガベージコレクターが配列を移動しないようにします。この CPU 書き込み可能なバッファーでは、{upload} がメモリをマッピングし、データをコピーして、マッピングを解除してから戻ります。そのため、配列の固定が必要なのは呼び出し中だけです。頂点は初期化時に一度アップロードし、バッファーは描画が終わるまで保持します。'
+tutorial.geometry.upload.description: '描画時に GPU が読み取れるよう、頂点を {buffer} に格納します。配列の直後に、バッファーの確保とアップロードを追加します。'
+tutorial.geometry.upload.details: 'サイズは {sizeofVertexVerticesLength}、つまり 28 バイトの頂点三つで 84 バイトです。{bufferUsagesVertex} は頂点入力用途、{cpuWriteOnly} は CPU による直接初期化を許可します。記述のストライドは要素サイズを表します。各属性の読み方は、後で作る入力レイアウトが定めます。'
+tutorial.geometry.upload.guidance: '確保しただけではデータは入りません。{upload} が、データ記述に渡した元ポインターとバイト数を使い、配列をバッファーのオフセット 0 にコピーします。'
+tutorial.geometry.upload.imports: 'Zenith.NET のバッファー型を {systemBuffer} と区別するため、{programCs} の using ディレクティブに {bufferAlias} を追加します。'
+tutorial.geometry.upload.notes: '{programCs} 末尾の解放処理で、{swapChainDispose} の直前に {vertexBufferDispose} を加えます。大きな静的ジオメトリ向けの GPU 専用メモリと転送は{link}で説明します。'
+tutorial.geometry.upload.notes.link: 'リソース管理'
+tutorial.geometry.upload.title: 'バッファーの確保と転送'
+tutorial.input.description: 'GPU は位置や色などの頂点属性がどこにあるかを知る必要があります。入力レイアウトで各属性のフォーマットとバイトオフセットを指定し、ストライドで次の頂点までのバイト数を示します。シェーダー作成後に次のコードを加えます。'
+tutorial.input.details: '{inputLayoutAdd} は属性を順番に並べ、各属性のサイズをストライドに加算します。オフセットは C# の構造体を調べて得るのではなく、ここで指定したフォーマットから計算されます。この二つのフィールドでは、次の配置になります。'
+tutorial.input.guidance: '{semanticIndex} の既定値は 0 で、シェーダーの {pOSITION0} と {cOLOR0} に対応します。位置と色は一つの頂点バッファーに格納するため、パイプラインにはスロット 0 の入力レイアウトを一つ指定します。C# のフィールド順やパディングを変えた場合は、入力レイアウトも合わせて変更してください。'
+tutorial.input.table.headings.byteOffset: 'バイトオフセット'
+tutorial.input.table.headings.cField: 'C# フィールド'
+tutorial.input.table.headings.format: 'フォーマット'
+tutorial.input.table.headings.shaderInput: 'シェーダー入力'
+tutorial.input.table.nextVertex.cField: '次の頂点'
+tutorial.input.table.nextVertex.format: '合計ストライド:28 バイト'
+tutorial.input.table.nextVertex.shaderInput: '次の入力レコード'
+tutorial.input.table.vertexColor.format: '{float4} (16 バイト)'
+tutorial.input.table.vertexPosition.format: '{float3} (12 バイト)'
+tutorial.input.title: '頂点入力レイアウト'
+tutorial.lifecycle.description: '各フレームは次の順で進みます。'
+tutorial.lifecycle.details: 'ウィンドウを閉じると {run} が戻り、解放処理へ進みます。各フレームで送信した処理の完了を待っているため、その処理で使用したリソースは解放できます。{windowRun} の後に置く解放処理の全体は次のとおりです。'
+tutorial.lifecycle.guidance: 'スワップチェーンはネイティブウィンドウを破棄する前に、コンテキストはそのリソースを解放した後に解放します。送信済みコマンドバッファーはキューが回収し、drawable はスワップチェーンが管理するため、どちらにも個別の {dispose} 呼び出しは不要です。'
+tutorial.lifecycle.title: 'フレームの流れと終了処理'
+tutorial.pipeline.behavior: '入力レイアウトの後にパイプラインを作成します。'
+tutorial.pipeline.context: '{cullNone} は面のカリングを無効にします。頂点が画面上で時計回りに並んでいても、反時計回りに並んでいても、三角形を描画します。'
+tutorial.pipeline.description: '{graphicsPipeline} は、頂点レイアウト、二つのシェーダー、プリミティブの組み立てとアタッチメントへの出力設定をまとめます。'
+tutorial.pipeline.details: '頂点とフラグメントの処理はシェーダーが担当します。パイプラインの残りの設定では、プリミティブの組み立て、面のカリング、深度テスト、ブレンドを指定します。'
+tutorial.pipeline.guidance: 'パイプラインのカラーフォーマットはパスのアタッチメントに合わせ、{swapChainDescFormat} から取得します。{count1} は一ピクセル一サンプルです。'
+tutorial.pipeline.item: '{triangleList} は連続する三つの頂点を一組として、三角形を組み立てます。'
+tutorial.pipeline.notes: '{depthNone} は深度テストと深度書き込みを無効にします。深度アタッチメントを使わないパスと対応します。'
+tutorial.pipeline.reference: '{opaque} はブレンドを無効にします。三角形の内部では、フラグメントシェーダーの色が背景を置き換えます。'
+tutorial.pipeline.title: 'グラフィックスパイプライン'
+tutorial.pipeline.usage: 'シェーダーオブジェクトはパイプライン作成後に解放できます。パイプラインは描画中保持し、終了処理で {vertexBufferDispose} の前に {pipelineDispose} を加えます。'
+tutorial.presentation.title: 'グラフィックスコンテキストと提示'
+tutorial.project.context: 'プロジェクトのビルド設定で {emphasis}({allowUnsafeBlocks})を有効にします。ウィンドウ補助コードのネイティブ呼び出しと頂点の転送に unsafe コードが必要です。'
+tutorial.project.context.emphasis: 'アンセーフコードの許可'
+tutorial.project.description: '.NET 10 を対象とする、{firstTriangle} という名前の C# コンソールプロジェクトを作成します。'
+tutorial.project.details: 'プロジェクトに次の NuGet パッケージを追加します。'
+tutorial.project.guidance: '{zenithNET} が共通の描画 API を、三つのバックエンドパッケージが各実装を提供します。{zenithNETCompiler} は後で作る Slang シェーダーを対象バックエンド向けにコンパイルします。Silk.NET はウィンドウの作成とイベント処理を担当します。'
+tutorial.project.title: 'プロジェクトの準備'
+tutorial.requirements.context: '{link}とその{detail}が、このチュートリアルの参照実装です。'
+tutorial.requirements.context.detail: '共有ホスト'
+tutorial.requirements.context.link: 'Hello Triangle サンプル'
+tutorial.requirements.description: '{emphasis} と C# の基礎知識が必要です。アプリケーションの対象フレームワークは {net10} です。GPU とドライバーは、プラットフォームごとに選ぶ次のバックエンドに対応している必要があります。'
+tutorial.requirements.description.emphasis: '.NET 10 SDK'
+tutorial.requirements.guidance: 'Zenith.NET はアプリケーションから渡されたネイティブサーフェスへ描画します。Silk.NET.Windowing がウィンドウとイベントを管理します。macOS の提示には {cAMetalLayerCs} で表される Metal レイヤーを使い、補助コードがウィンドウへ取り付けます。'
+tutorial.requirements.table.headings.graphicsBackend: 'グラフィックスバックエンド'
+tutorial.requirements.table.headings.platform: 'プラットフォーム'
+tutorial.requirements.table.linux.graphicsBackend: 'Vulkan 1.4'
+tutorial.requirements.table.linux.platform: 'Linux(X11 または XWayland)'
+tutorial.requirements.table.macOS.graphicsBackend: 'Metal 4'
+tutorial.requirements.table.macOS.platform: 'macOS'
+tutorial.requirements.table.windows.graphicsBackend: 'DirectX 12'
+tutorial.requirements.table.windows.platform: 'Windows'
+tutorial.requirements.title: '前提条件'
+tutorial.resize.context: 'アプリケーションをもう一度ビルドして実行します。リサイズしても背景がウィンドウ全体を覆うことを確認してください。新しいレンダーパスでは、変更後のアタッチメントの寸法からビューポートとシザーが設定されます。描画コールバックはそのまま残し、後でこのパスに三角形の描画を加えます。'
+tutorial.resize.description: 'フレームバッファーの寸法が変わったら、スワップチェーンも合わせて変更します。描画コールバックの後、{windowRun} の前に {framebufferResize} のコールバックを登録します。'
+tutorial.resize.details: 'ウィンドウの論理寸法ではなく、ピクセル単位の{link}を受け取ります。最小化によって幅か高さがゼロになった場合、リサイズコールバックはスワップチェーンを変更しません。描画コールバックも、どちらかの寸法がゼロの間はそのまま戻ります。有効な寸法に戻るまで描画を中断するためです。'
+tutorial.resize.details.link: 'フレームバッファーサイズの変化'
+tutorial.resize.guidance: '描画コールバックは、送信したフレームの完了を待ってから {present} を呼びます。そのため、リサイズコールバックがスワップチェーンの画像を置き換える時点では、以前のフレームによる処理は終わっています。完了とリソースの寿命に関する条件は、{link}で詳しく説明します。'
+tutorial.resize.guidance.link: '同期'
+tutorial.resize.title: 'リサイズ'
+tutorial.shaders.compilation.context: '{appContextBaseDirectory} を使うと、コピーしたシェーダーをアプリケーションの出力ディレクトリから読み込みます。別の作業ディレクトリから起動しても、ファイルを探す場所は変わりません。'
+tutorial.shaders.compilation.description: '{triangleSlang} のファイルプロパティで {emphasis} を {detail} に設定します。ビルド時にシェーダーが実行ファイルの隣へコピーされます。'
+tutorial.shaders.compilation.description.detail: '新しい場合はコピーする'
+tutorial.shaders.compilation.description.emphasis: '出力ディレクトリにコピー'
+tutorial.shaders.compilation.details: '{programCs} に戻り、頂点のアップロード後、描画コールバック前にシェーダーの初期化を追加します。'
+tutorial.shaders.compilation.guidance: '{zenithCompiler} は {contextGraphicsApi} のバックエンド向けにソースをコンパイルします。{createShader} は、コンパイル済みの記述からシェーダーオブジェクトを作成します。エントリーポイント名 {vSMain} と {fSMain} は、Slang ファイルに定義した二つの関数を選択します。'
+tutorial.shaders.compilation.title: 'シェーダーのコンパイル'
+tutorial.shaders.context: '{fSInput} は二つのシェーダーステージをつなぎます。頂点シェーダーはクリップ空間の位置を {sVPOSITION} に書き込み、GPU はその位置から三角形の見える範囲を判断します。{cOLOR0} は頂点色を渡すフィールドです。色は三角形の内部で補間されてから、フラグメントシェーダーに届きます。両ステージで色の型とセマンティクスを一致させる必要があります。'
+tutorial.shaders.description: 'シェーダーは GPU 上で実行するプログラムです。頂点シェーダーは各頂点の位置を決め、フラグメントシェーダーは画像の覆われた位置の色を決めます。両ステージを一つの Slang ファイルに書き、それぞれにエントリーポイントを定義します。エントリーポイントは、そのステージの実行が始まる関数です。'
+tutorial.shaders.details: '{triangleSlang} を作成し、次の入出力構造体を書きます。'
+tutorial.shaders.fragment.description: '同じファイルにフラグメントのエントリーポイントを追加します。'
+tutorial.shaders.fragment.details: 'ラスタライズは三角形が画像を覆う範囲を調べ、その位置にフラグメントを生成します。各フラグメントの色は、三つの頂点色を補間して求めます。{fSMain} がその色を返し、{sVTARGET} が最初のカラーアタッチメントへの出力を指定します。'
+tutorial.shaders.fragment.title: 'フラグメントシェーダー'
+tutorial.shaders.guidance: '{vSInput} は頂点シェーダーの入力です。{pOSITION0} と {cOLOR0} はセマンティクスによって属性を対応付けます。C# のフィールド名だけでは、この対応は決まりません。'
+tutorial.shaders.title: 'シェーダーステージ'
+tutorial.shaders.vertex.description: '{triangleSlang} に頂点のエントリーポイントを追加します。'
+tutorial.shaders.vertex.details: 'GPU は入力頂点ごとにこの関数を呼びます。同次座標 {w} を追加し、色をそのまま渡します。頂点処理後に位置を w で割りますが、ここでは 1 なので x、y、z は変わらず、正規化デバイス座標として直接解釈できます。'
+tutorial.shaders.vertex.title: '頂点シェーダー'
+tutorial.surface.description: 'ネイティブの提示先を{emphasis}に指定します。'
+tutorial.surface.description.emphasis: '{windowCenter} の後、{windowRun} の前'
+tutorial.surface.details: 'フレームバッファーは、ウィンドウが表示するピクセル画像です。{surface} はネイティブの提示先と寸法を記述し、{framebufferSize} はピクセル単位のサイズを返します。表示倍率によっては、ウィンドウの論理サイズと異なります。'
+tutorial.surface.guidance: 'サーフェスにはバックエンドが必要とするネイティブハンドルを渡します。Windows は Win32 ウィンドウ、Linux は X11 のディスプレイとウィンドウ、macOS は取り付けた Metal レイヤーを使います。'
+tutorial.surface.title: 'ネイティブサーフェス'
+tutorial.swapChain.context: 'スワップチェーンはコンテキストとウィンドウの両方に依存します。{programCs} 末尾の {windowDispose} を次の解放順序に置き換えます。'
+tutorial.swapChain.description: 'サーフェス記述の直後にスワップチェーンを作成します。'
+tutorial.swapChain.details: '{swapChain} はウィンドウが提示する画像を管理します。{drawable} は現在の画像を、API の画像リソースである {texture} として返します。描画後に {present} が表示を要求し、次の drawable へ進みます。'
+tutorial.swapChain.guidance: '{b8G8R8A8UNorm} は青、緑、赤、アルファの順に各 8 ビットを格納します。{uNorm} は整数 0~255 を 0~1 に対応付けます。シェーダー出力は論理的な RGBA のままで、フォーマットは保存表現を定めます。パイプラインは {swapChainDescFormat} でこのアタッチメントに合わせます。'
+tutorial.swapChain.title: 'スワップチェーン'
+tutorial.title: '最初の三角形'
+tutorial.troubleshooting.description: 'コンソールの診断と三段階の結果を使い、問題のある箇所を特定します。'
+tutorial.troubleshooting.details: '{link}と{detail}で頂点データ、入力レイアウト、描画操作を照合できます。サンプルはウィンドウと送信を共通ホストへ分離していますが、このチュートリアルの処理は {programCs} にまとめています。{context}ではインデックス付き描画、変換、深度テストへ進みます。'
+tutorial.troubleshooting.details.context: '回転する立方体'
+tutorial.troubleshooting.details.detail: 'Slang シェーダー'
+tutorial.troubleshooting.details.link: 'サンプルレンダラー'
+tutorial.troubleshooting.table.backend.check: '選択したバックエンドがデバイスおよびインストールされているグラフィックスドライバーでサポートされていることを確認してください。'
+tutorial.troubleshooting.table.backend.symptom: 'バックエンドの作成に失敗する'
+tutorial.troubleshooting.table.drawing.check: 'アップロードサイズ、入力レイアウト、パイプラインの選択、およびレンダーパス内の描画コマンドの配置を確認してください。'
+tutorial.troubleshooting.table.drawing.symptom: '背景は表示されるが三角形が表示されない'
+tutorial.troubleshooting.table.headings.check: '確認事項'
+tutorial.troubleshooting.table.headings.symptom: '症状'
+tutorial.troubleshooting.table.input.check: 'ストライドが 28 バイト、位置と色のオフセットがそれぞれ 0 と 12 バイトであることを確認します。'
+tutorial.troubleshooting.table.input.symptom: '頂点の位置や色が破損している'
+tutorial.troubleshooting.table.shaders.check: 'コピーされた {triangleSlang}、エントリーポイントのスペル、およびコンパイラーが報告した診断情報を確認してください。'
+tutorial.troubleshooting.table.shaders.symptom: '背景色は表示されるがシェーダーを読み込めない'
+tutorial.troubleshooting.table.window.check: '.NET プロジェクトがビルドされ、ウィンドウ単体のステップがデスクトップセッションで実行されることを確認してください。Linux では、X11 または XWayland が利用可能であることを確認してください。'
+tutorial.troubleshooting.table.window.symptom: 'ウィンドウが開かない'
+tutorial.troubleshooting.title: '問題の切り分け'
+tutorial.window.description: '{cocoaHelperCs} をダウンロードしてプロジェクトに追加します。この補助クラスが macOS のウィンドウに Metal レイヤーを取り付けます。'
+tutorial.window.details: '自動生成された {programCs} の内容を、次のコードに置き換えます。トップレベルステートメントを使うため、明示的な {main} メソッドを書かずに、上から順に実行できます。続く各節でコードを追加する位置を示します。'
+tutorial.window.guidance: '{graphicsAPINone} で、グラフィックスコンテキストの作成を Zenith.NET に任せます。{initialize} はネイティブウィンドウを作り、ウィンドウシステムのハンドルを利用可能にします。{center} で画面中央に置き、{run} で閉じるまでイベントを処理します。その後、{dispose} でウィンドウを解放します。'
+tutorial.window.notes: 'アプリケーションをビルドして実行し、ウィンドウを開く、移動する、リサイズする、閉じるという操作を確認します。描画コマンドはまだ送信していないため、表示内容は未定義です。次へ進む前にウィンドウを閉じてください。'
+tutorial.window.title: 'ウィンドウの作成'
+ui.action.copy: 'コピー'
+ui.action.navigate: '移動'
+ui.action.open: '開く'
+ui.action.source: 'ソースを表示'
+ui.api.browse: '名前空間と型を参照します。'
+ui.api.clearTypeSearch: '型の検索をクリア'
+ui.api.findType: '型を検索'
+ui.api.loadError: '型を読み込めませんでした。このページを再読み込みしてやり直してください。'
+ui.api.loadingNamespaces: '名前空間を読み込み中…'
+ui.api.loadingTypes: '型を読み込み中…'
+ui.api.matchingTypes: '一致する型'
+ui.api.namespace: '名前空間'
+ui.api.namespaces: '名前空間'
+ui.api.noNamespaceTypes: 'この名前空間には型がありません。'
+ui.api.noTypes: '一致する型はありません。'
+ui.api.types: '型'
+ui.content.reference: 'リファレンス'
+ui.content.source: 'ソースコード'
+ui.content.tutorial: 'チュートリアル'
+ui.contents.close: '目次を閉じる'
+ui.contents.open: '目次を開く'
+ui.contents.title: '目次'
+ui.footer.caption: '.NET 向けのモダンな RHI'
+ui.footer.label: 'フッター'
+ui.language.label: '言語'
+ui.language.unavailable: '未翻訳'
+ui.members.clear: 'メンバー検索をクリア'
+ui.members.close: 'メンバー検索を閉じる'
+ui.members.empty: '一致するメンバーはありません。'
+ui.members.open: 'メンバーへ移動'
+ui.members.placeholder: '名前またはシグネチャ…'
+ui.members.search: 'メンバーを検索'
+ui.members.title: 'メンバーを検索'
+ui.members.undeclared: 'この型には宣言されたメンバーがありません。'
+ui.navigation.api: 'API'
+ui.navigation.concepts: '概念'
+ui.navigation.home: 'ホーム'
+ui.navigation.label: 'メインナビゲーション'
+ui.navigation.skip: 本文へ移動
+ui.navigation.toggle: 'ナビゲーションの切り替え'
+ui.outline.title: 'このページの内容'
+ui.reference.assembly: 'アセンブリ'
+ui.reference.classes: 'クラス'
+ui.reference.constructors: 'コンストラクター'
+ui.reference.declaration: 'C# 型宣言'
+ui.reference.delegates: 'デリゲート'
+ui.reference.derived: '派生型'
+ui.reference.enums: '列挙型'
+ui.reference.events: 'イベント'
+ui.reference.examples: '例'
+ui.reference.exceptions: '例外'
+ui.reference.extensionMethods: '拡張メソッド'
+ui.reference.fields: 'フィールド'
+ui.reference.implementations: '明示的なインターフェイスの実装'
+ui.reference.implements: '実装'
+ui.reference.inheritance: '継承'
+ui.reference.inheritedMembers: '継承されたメンバー'
+ui.reference.interfaces: 'インターフェイス'
+ui.reference.metadata: '型のメタデータ'
+ui.reference.methods: 'メソッド'
+ui.reference.namespace: '名前空間'
+ui.reference.open: '{name} のリファレンスを開く'
+ui.reference.operators: '演算子'
+ui.reference.parameters: 'パラメーター'
+ui.reference.properties: 'プロパティ'
+ui.reference.related: '関連項目'
+ui.reference.relationships: '継承とメタデータ'
+ui.reference.remarks: '注記'
+ui.reference.returns: '戻り値'
+ui.reference.structs: '構造体'
+ui.reference.typeParameters: '型パラメーター'
+ui.reference.value: '値'
+ui.reference.values: '値'
+ui.search.close: '検索を閉じる'
+ui.search.empty: '「{query}」に一致する結果は見つかりませんでした。'
+ui.search.hint: 'チュートリアル、概念、サンプル、API リファレンスを検索します。'
+ui.search.loading: '検索を読み込み中…'
+ui.search.more: 'さらに結果を表示'
+ui.search.pending: '検索中…'
+ui.search.placeholder: '何をお探しですか?'
+ui.search.resultCount: '検索結果: {count} 件'
+ui.search.title: 'ドキュメントを検索'
+ui.search.unavailable: '検索は利用できません。以下のページを直接開くことができます。'
diff --git a/documents/locales/ja-JP/translation.md b/documents/locales/ja-JP/translation.md
new file mode 100644
index 00000000..cfc3ef31
--- /dev/null
+++ b/documents/locales/ja-JP/translation.md
@@ -0,0 +1,85 @@
+# 日本語翻訳ガイド
+
+## リソースと構成
+
+[英語のリソース辞書](../en-US/strings.yml)を唯一の原文とします。プロジェクトの規約は[ドキュメント保守ガイド](../../maintenance.md)を参照してください。新しい翻訳を始めるときは、英語のファイル全体をこのディレクトリの `strings.yml` にコピーします。先頭行の `### YamlMime:Resources` と UTF-8 エンコーディングを維持してください。更新時は英語の変更点を既存の訳文に反映し、ファイル全体を上書きしないでください。この `translation.md` は翻訳後も保持し、サイトには公開しません。
+
+ナビゲーション、章の順序、アンカー、リンク、書式、コード、画像は `locales/` の外で共通管理します。このディレクトリに記事、TOC、テンプレート、スクリプト、設定を追加しないでください。
+
+翻訳では、この言語の `strings.yml` の値だけを変更します。キーの集合は英語と完全に一致させます。同じ文言に見えるキーや、まだ画面で見ていないキーも含め、独自の追加・削除・改名はしません。原文に問題があれば、該当キーと問題点を保守担当者に伝えてください。英語と共通ページを先に修正し、その後で訳文を更新します。
+
+## 文体と文脈
+
+技術書として正確で落ち着いた、理解しやすい文章にします。操作同士の関係を説明してから、必要な条件や制約を示します。チュートリアルでは具体的な手順と確認できる結果を残します。専門的な文章にするために、受け身や長い文を増やす必要はありません。
+
+記事は前後の段落、表、コードと一緒に、読む順序で見直します。リソースキーは保存の区切りであり、翻訳を一文ずつ切り離すための単位ではありません。リンクラベルや強調部分を本文に戻し、助詞と文のつながりを確認してください。
+
+見出し、操作ラベル、ホームの短い紹介文とフッター文言には句点を付けません。本文、診断、アクセシビリティの操作説明では通常の句読点を使います。一つの概念には定着した訳語を一貫して使い、一部の用語だけ英語を括弧で併記しないでください。API、ファイル、実際の設定項目の識別子は保持します。
+
+文を残すかどうかは、現在の操作、設計上の選択、確認できる結果、正しく使うための条件を説明しているかで判断します。使わない機能の列挙、漠然とした環境上の注意、前提条件の繰り返しは省きます。見出しや装飾で分かれた句も全体を翻訳し、日本語の語順にします。ホームの `{accent}` と `{lineBreak}` は移動でき、装飾や改行で英語の語順を固定しません。文章の削除に伴う原文・共通マークアップ・全言語のキー変更は、保守担当者がまとめて行います。
+
+## テキストとプレースホルダー
+
+文または段落全体を、簡潔で自然な「です・ます」調の日本語に翻訳します。前提条件、否定、単位、操作順序、リソースの有効期間に関する制約を維持してください。原文にない技術的な保証、プラットフォーム要件、パッケージのバージョン、リリース情報を追加しません。製品名、パッケージ名、ファイル名、C# と Slang の識別子は、大文字と小文字も含めて変更しません。
+
+値はすべて、空でない文字列のプレーンテキストにします。HTML や Markdown は記述しません。YAML の引用符とエスケープに注意し、コロン、シャープ記号、数値に見える値などが誤って解析されないよう、必要に応じて引用符で囲みます。照合しやすいよう、キーの順序も英語に合わせてください。
+
+`{context}` や `{link}` などの名前付きプレースホルダーには、共通ページのコード、リンク、強調表示が入る場合があります。UI の `{name}`、`{query}`、`{count}` などには、実行時に通常のテキストが入る場合もあります。名前だけで内容を判断せず、対応するページや UI での使われ方を確認してください。リンクのラベルや強調される文言に独立したリソースキーがあれば、その値も翻訳します。
+
+日本語の語順に合わせてプレースホルダーを移動できます。ただし、各キーのすべての名前、大文字と小文字、半角の波括弧を維持してください。追加・削除・改名や、表示された API 名、リンクのラベル、例示の数値への置き換えはしません。文を組み直すためにリソースキーを分割しないでください。
+
+## API リンクと技術的な意味
+
+リンクは共通ページで管理します。本文と表でサイト内の API を明示する識別子は、対応する型またはメンバーにリンクします。変数、パラメーター名、サンプル独自の型、パッケージ名、ファイル名、サイト内に参照ページのない外部シンボルは通常のコード表記のままです。同じつづりでも同じシンボルとは限りません。通常のチュートリアルのコードブロックには、自動でリンクを追加しません。プレースホルダー全体は移動できますが、リンク先、共通コード、その書式は変更しないでください。
+
+API の参照項目が生成されていない場合、共通ページでは識別子を通常のコード表記にし、その近くにソースコードへのリンクを明示します。別の型やメンバーへのリンクに置き換えないでください。
+
+特に次の区別を維持してください。
+
+- コマンドの記録、サブミット、GPU での実行、完了は別の段階です。
+- リソースの用途、メモリの常駐特性や想定する CPU アクセス、テクスチャレイアウト、ピクセル形式は異なる概念です。
+- 所有権はリソースを解放する責任を表します。ハンドルだけではリソースの存続は保証されず、コマンドのサブミットで所有権が移るわけでもありません。
+- GPU の処理が完了しても、一時領域のリードバックデータがアプリケーションの CPU 側の転送先にコピー済みとは限りません。処理を管理するキューでの待機に関する説明を維持してください。
+- drawable は現在のフレームの出力テクスチャであり、必ずしもスワップチェーンの画像ではありません。リソースビューと UI コントロールも文脈に応じて区別してください。
+
+## ビルドと提出前の確認
+
+プロジェクトに必要な .NET SDK と DocFX がある環境で、リポジトリのルートから実行します。新しくチェックアウトしたリポジトリには、`documents/api/` の生成メタデータがまだありません。最初に API の抽出を含む完全なビルドを実行してください。
+
+```sh
+docfx documents/docfx.json --warningsAsErrors
+```
+
+API メタデータが生成済みで現在のソースコードと一致している場合、辞書だけの変更には通常のビルドとプレビューを使えます。
+
+```sh
+docfx build documents/docfx.json --warningsAsErrors
+docfx serve documents/_site --hostname 127.0.0.1 --port 8080
+```
+
+この言語は登録済みです。完全な辞書でビルドが成功すると、設定を変更せずに利用できます。キーの不足・余剰、空の値、文字列以外の値、プレースホルダー名の不一致はビルドエラーになります。翻訳の正確さや英語の訳し残しはビルドでは判定できないため、人による通読が必要です。
+
+全言語でページのパスを共有し、`?lang=ja-JP` で日本語を選びます。言語を切り替えてもパスとアンカーは維持されます。明示的な指定がない場合は、保存済みの手動選択、ブラウザーの言語設定、英語の順に選ばれます。この訳文が未公開の場合や、辞書の読み込み・ページのリソース検証に失敗した場合は、文ごとに言語を混在させず、英語でページを表示します。言語一覧は言語コード順です。
+
+提出前にキーとプレースホルダーを照合し、ホーム、Learn の入口、三角形チュートリアル、概念、サンプル、API の UI をデスクトップと幅 320 ピクセルで確認してください。初心者の順序でチュートリアルを読み、用語、ボタン、検索、コードのコピー、API リンク、言語切り替え後の節の位置を確認します。識別子、宣言、コードは共通のまま、辞書が提供する本文と UI の文言だけを翻訳します。テスト用辞書や一時的な確認ファイルはリポジトリの外に置き、仮の訳文を提出しないでください。
+
+## 用語
+
+| 英語 | 使用する表現 |
+| ---------------- | ----------------------------------------------------------------------------------------------- |
+| API / GPU / RHI | 略語を維持し、原文に従って説明する |
+| barrier | バリア |
+| buffer | バッファー。`Buffer` は変更しない |
+| command buffer | コマンドバッファー |
+| command queue | コマンドキュー |
+| drawable | 現在のフレームの出力テクスチャ。`Drawable` は変更せず、常にスワップチェーンの画像とは解釈しない |
+| graphics context | グラフィックスコンテキスト |
+| pipeline | パイプライン |
+| readback | リードバック |
+| render pass | レンダーパス |
+| resource | リソース |
+| shader | シェーダー |
+| swap chain | スワップチェーン |
+| texture | テクスチャ |
+| timeline | タイムライン |
+| upload | アップロード |
diff --git a/documents/locales/ko-KR/strings.yml b/documents/locales/ko-KR/strings.yml
new file mode 100644
index 00000000..5396aadf
--- /dev/null
+++ b/documents/locales/ko-KR/strings.yml
@@ -0,0 +1,593 @@
+### YamlMime:Resources
+concepts.execution.context.description: '{graphicsContext}는 특정 백엔드를 통해 애플리케이션을 GPU 장치에 연결하고 그래픽스, 컴퓨트, 전송 큐를 제공합니다. 함께 사용하는 리소스는 같은 컨텍스트에 속해야 합니다. 버퍼, 텍스처, 셰이더, 파이프라인과 그 핸들은 서로 다른 컨텍스트 사이에서 바인딩할 수 없습니다.'
+concepts.execution.context.details: '{capabilities}는 장치 이름과 지원 기능을 알려 줍니다. 해당 기능의 리소스를 만들기 전에 {rayTracingSupported}와 {meshShadingSupported}로 지원 여부를 확인합니다. {link} 및 {detail} 예제는 초기화 과정에서 이 검사를 수행합니다.'
+concepts.execution.context.details.detail: 메시 셰이딩
+concepts.execution.context.details.link: 레이 트레이싱
+concepts.execution.context.title: '그래픽스 컨텍스트'
+concepts.execution.description: 'GPU 작업은 CPU에서 준비합니다. 애플리케이션이 리소스를 만들고 이를 사용하는 명령을 기록한 뒤 큐에 제출하면, GPU가 명령을 실행하는 동안 CPU는 다른 작업을 계속할 수 있습니다. 사용 중인 리소스는 GPU 실행이 끝날 때까지 유효하게 유지해야 합니다.'
+concepts.execution.ownership.description: '소유권은 객체를 해제할 주체를 정하고, GPU 작업의 완료 여부는 해제할 수 있는 시점을 정합니다. C# 참조가 남아 있어도 {dispose}를 호출하면 네이티브 리소스가 해제됩니다. 명령을 제출한다고 해당 리소스의 소유권이 큐로 넘어가지는 않습니다.'
+concepts.execution.ownership.details: '애플리케이션을 종료할 때는 새 작업 제출을 중단하고, 관련된 모든 큐에서 리소스의 마지막 사용이 끝날 때까지 기다립니다. 그런 다음 애플리케이션 리소스를 해제하고 컨텍스트를 정리합니다. 컨텍스트는 자체 큐와 내부 보조 객체를 해제하므로 애플리케이션 작업이 모두 끝난 뒤 마지막에 정리해야 합니다.'
+concepts.execution.ownership.guidance: '렌더링 중에는 덮어쓸 데이터나 교체할 리소스를 사용하는 작업의 완료 시점을 추적합니다. 해당 작업이 끝날 때까지 기다린 뒤 저장 공간을 수정하거나 해제합니다. 뷰와 원본 리소스에도 이 수명 관리 원칙을 적용하는 방법은 {link} 문서에서 설명합니다.'
+concepts.execution.ownership.guidance.link: 리소스 관리
+concepts.execution.ownership.table.applicationResources.object: 애플리케이션이 생성한 리소스
+concepts.execution.ownership.table.applicationResources.ownership: 애플리케이션이 마지막 GPU 사용 후 해제합니다.
+concepts.execution.ownership.table.commandBuffers.object: 대여한 명령 버퍼
+concepts.execution.ownership.table.commandBuffers.ownership: 큐가 관리하고 재활용합니다.
+concepts.execution.ownership.table.headings.object: '객체'
+concepts.execution.ownership.table.headings.ownership: '해제 책임'
+concepts.execution.ownership.table.queues.object: 컨텍스트 큐 및 해당 타임라인
+concepts.execution.ownership.table.queues.ownership: 컨텍스트와 큐가 관리합니다.
+concepts.execution.ownership.table.swapChainDrawable.ownership: 스왑 체인이 관리합니다.
+concepts.execution.ownership.title: '소유권과 수명'
+concepts.execution.pipelines.description: '그래픽스 파이프라인은 셰이더, 정점 입력 레이아웃, 프리미티브 조립, 어태치먼트의 포맷, 그리기 상태를 정의합니다. 정점, 인덱스, 상수 데이터를 바인딩하기 전에 파이프라인을 선택합니다. 어태치먼트의 포맷과 샘플 수는 렌더 패스와 일치해야 합니다.'
+concepts.execution.pipelines.details: '컴퓨트 파이프라인은 디스패치가 실행할 셰이더 진입점을 선택합니다. 그래픽스 렌더 패스 밖에서 파이프라인과 상수를 바인딩한 뒤 디스패치합니다.'
+concepts.execution.pipelines.guidance: '{link}에서는 그래픽스 명령 순서를, {detail}에서는 계산 결과를 표시하는 패스 앞에 컴퓨트 디스패치를 추가하는 구성을 볼 수 있습니다.'
+concepts.execution.pipelines.guidance.detail: '컴퓨트 예제'
+concepts.execution.pipelines.guidance.link: 삼각형의 렌더링 콜백
+concepts.execution.pipelines.title: '파이프라인 상태'
+concepts.execution.queues.description: '그리기, 계산, 복사를 한 프레임에서 수행한다면 일반적으로 {graphicsQueue}부터 사용합니다. 서로 의존하는 작업을 한 큐에 두면 순서를 관리하기 쉽습니다.'
+concepts.execution.queues.details: '큐를 나누면 독립적인 작업을 각각 제출할 수 있지만, 각 큐가 반드시 독립된 하드웨어 엔진에 대응하는 것은 아닙니다. 작업을 겹쳐 실행할 때 이점이 있다면 추가 큐를 사용합니다. 한 큐가 다른 큐에서 만든 데이터를 사용한다면 두 제출 사이에 의존성을 명시합니다.'
+concepts.execution.queues.guidance: '한 큐 안에서도 뒤의 접근이 앞서 쓴 데이터를 볼 수 있도록 배리어나 텍스처 전환이 필요할 수 있습니다. 이러한 메모리 의존성은 제출 순서와 구별됩니다. 구체적인 표현 방법은 {link} 문서에서 설명합니다.'
+concepts.execution.queues.guidance.link: 동기화
+concepts.execution.queues.table.computeQueue.workToRecord: '그래픽 렌더 패스를 제외한 컴퓨트 디스패치 및 호환 가능한 전송 작업.'
+concepts.execution.queues.table.graphicsQueue.workToRecord: '렌더 패스, 그리기, 컴퓨트 및 전송 작업.'
+concepts.execution.queues.table.headings.queue: 큐
+concepts.execution.queues.table.headings.workToRecord: '지원 작업'
+concepts.execution.queues.table.transferQueue.workToRecord: '업로드, 다운로드 및 복사 작업. 그리기나 컴퓨트 디스패치는 지원하지 않습니다.'
+concepts.execution.queues.title: '큐 선택'
+concepts.execution.recording.context: '큐는 완료된 명령 버퍼를 회수해 재사용합니다. 명령을 기록할 때마다 {queueCommandBuffer}로 버퍼를 가져오고, 한 번에 한 스레드에서 기록한 뒤 한 번만 제출합니다. 제출 후에는 큐가 버퍼를 관리하므로 수정하거나 다시 제출하거나 해제하지 않습니다.'
+concepts.execution.recording.description: '{queueCommandBuffer}가 반환하는 {commandBuffer}는 이미 기록을 시작한 상태입니다. 작업과 의존 관계를 기록한 후 {submit}을 한 번 호출합니다. 기록이 종료되고 명령이 제출되며, 해당 큐의 완료 지점을 나타내는 {timelineValue}가 반환됩니다.'
+concepts.execution.recording.details: '삼각형 튜토리얼은 {commandBuffer}에 프레임을 기록한 뒤 다음과 같이 제출하고 완료를 기다립니다.'
+concepts.execution.recording.guidance: '기록은 명령 시퀀스를 구성하고, {submit}은 그 시퀀스의 실행을 예약합니다. 이어서 {wait}를 호출하면 해당 제출이 끝날 때까지 CPU 스레드가 대기합니다. 튜토리얼에서는 각 프레임이 끝난 뒤 다음 프레임을 시작하므로 실행 흐름을 쉽게 따라갈 수 있습니다.'
+concepts.execution.recording.title: '명령 기록과 제출'
+concepts.execution.title: 실행 모델
+concepts.platforms.description: '화면 표시는 애플리케이션이나 UI 컨트롤이 관리합니다. 이 선택에 따라 프레임의 출력 텍스처를 얻고 명령을 기록·제출하며 결과를 표시할 주체가 정해집니다. 이 출력 텍스처를 drawable이라고 합니다.'
+concepts.platforms.frame.context: '패스 안에서 파이프라인 바인딩과 그리기 명령을 기록하고, 핸들러가 반환하기 전에 패스를 끝냅니다. 명령 제출과 결과 표시는 컨트롤이 담당합니다. 핸들러는 명령 버퍼를 직접 제출하거나 제공받은 두 객체를 해제해서는 안 됩니다. 출력 텍스처의 레이아웃을 바꿨다면 반환 전에 {colorAttachment}로 복원해 컨트롤이 마지막 전환을 수행할 수 있도록 합니다.'
+concepts.platforms.frame.description: '컨트롤은 {iZenithView}를 구현합니다. 애플리케이션은 {graphicsContext}를 지정하고 {updateRequested}에서 장면을 갱신하며, {renderRequested}에서 전달받은 {renderEventArgs}로 명령을 기록합니다.'
+concepts.platforms.frame.details: '매 프레임 컨트롤은 출력 텍스처와 명령 버퍼를 가져오고, 텍스처를 {colorAttachment}로 전환한 뒤 업데이트와 렌더링 핸들러를 호출합니다. 이어서 기록된 작업을 제출하고 프레임워크의 표시 방식으로 이미지를 화면에 표시합니다.'
+concepts.platforms.frame.guidance: '파일에 {usingZenithNET} 구문을 추가합니다. {view}라는 {iZenithView} 인스턴스가 초기화되어 있고 그래픽스 컨텍스트가 할당되어 있다면, 다음 핸들러를 등록해 출력 텍스처를 지울 수 있습니다.'
+concepts.platforms.frame.notes: '초기 전환은 {undefined}를 사용하므로 이전 이미지의 내용은 버릴 수 있습니다. 매 프레임 이미지 전체를 그리거나 지웁니다. 누적 결과나 캐시한 렌더링 결과는 애플리케이션이 소유한 텍스처에 보관한 뒤 컨트롤의 출력 텍스처에 그립니다.'
+concepts.platforms.frame.reference: '{zenithViewHelperDrawableFormat}은 이 통합들이 사용하는 기본 색상 형식을 제공합니다.'
+concepts.platforms.frame.title: 'UI 컨트롤에서 렌더링'
+concepts.platforms.integration.context: 'WinForms, WPF, Windows용 WinUI 통합은 Windows에서 실행됩니다. MAUI는 대상 플랫폼의 워크로드를 설치하고, {extensionsCs}에서처럼 {useZenithView}로 컨트롤 핸들러를 등록합니다.'
+concepts.platforms.integration.description: '이벤트 인터페이스는 공통이지만 표시 방식은 프레임워크마다 다릅니다.'
+concepts.platforms.integration.details: '각 구현은 GPU 이미지를 UI에 전달하는 방식이 다릅니다. 비트맵 리드백은 표시 전에 픽셀을 CPU 메모리로 복사합니다. 공유 텍스처 상호 운용은 이 CPU 복사를 피하지만 GPU 복사와 동기화는 여전히 필요할 수 있습니다. 통합을 선택할 때는 공통 {renderRequested} 이벤트뿐 아니라 이 표시 경로도 고려합니다.'
+concepts.platforms.integration.guidance: 'NuGet으로 사용하는 UI 프레임워크의 통합 패키지를 추가합니다. Avalonia에는 {avaloniaPackage}를 사용합니다.'
+concepts.platforms.integration.table.avalonia.integration: Avalonia
+concepts.platforms.integration.table.avalonia.presentation: 텍스처에 렌더링하고 픽셀을 {writeableBitmap}으로 다운로드합니다. {zenithViewCs} 및 {surfaceCs}를 참조하세요.
+concepts.platforms.integration.table.headings.integration: 통합
+concepts.platforms.integration.table.headings.presentation: '표시 방식'
+concepts.platforms.integration.table.maui.integration: .NET MAUI
+concepts.platforms.integration.table.maui.presentation: 'Android, iOS 및 Mac Catalyst에서는 네이티브 서피스를 사용하고 Windows에서는 공유 텍스처를 이용한 합성 방식을 사용합니다. {link}을 참조하세요.'
+concepts.platforms.integration.table.maui.presentation.link: 플랫폼 구현
+concepts.platforms.integration.table.uno.integration: Uno 경로
+concepts.platforms.integration.table.uno.presentation: 픽셀을 {writeableBitmap}으로 다운로드합니다. {zenithViewUnoCs}를 참조하세요.
+concepts.platforms.integration.table.winForms.integration: WinForms
+concepts.platforms.integration.table.winForms.presentation: 컨트롤의 {hWND}에 대한 Zenith.NET 스왑 체인을 생성합니다. {zenithViewCs}를 참조하세요.
+concepts.platforms.integration.table.winUI.integration: Windows의 WinUI
+concepts.platforms.integration.table.winUI.presentation: '가져온 공유 Direct3D 11 텍스처에 렌더링한 다음 {swapChainPanel}에 연결된 합성용 스왑 체인에 복사합니다. {zenithViewWinUICs}를 참조하세요.'
+concepts.platforms.integration.table.wpf.integration: WPF
+concepts.platforms.integration.table.wpf.presentation: 공유 Direct3D 11 텍스처를 가져온 다음 Direct3D 상호 운용성을 통해 {d3DImage}로 표시되는 서피스에 복사합니다. {surfaceCs}를 참조하세요.
+concepts.platforms.integration.title: '프레임워크와 표시 방식'
+concepts.platforms.lifecycle.context: '호환되는 어태치먼트를 만들 때는 렌더 이벤트가 제공한 출력 텍스처의 {descWidth}, {descHeight}, {descFormat}, {descSampleCount}를 사용합니다. 이 텍스처의 크기가 렌더 타깃의 실제 크기입니다. 컨트롤의 레이아웃 크기나 화면 배율로 추정하지 않습니다.'
+concepts.platforms.lifecycle.description: '스왑 체인은 프레임버퍼의 픽셀 너비와 높이를 사용합니다. 화면 배율이 적용되면 이 크기는 창의 논리적 크기와 다를 수 있습니다. 너비나 높이가 0인 동안에는 렌더링과 스왑 체인 크기 변경을 미룹니다. {swapChainDrawable}은 매 프레임 가져옵니다. 이 텍스처는 스왑 체인이 소유하며 화면 표시나 크기 변경 후 교체될 수 있습니다.'
+concepts.platforms.lifecycle.details: '프레임버퍼 크기가 바뀌면 GPU가 이전 이미지의 사용을 마칠 때까지 기다린 뒤 {swapChainResize}를 호출합니다. 네이티브 표시 대상을 교체할 때도 사용이 끝난 뒤 새 서피스 설명과 핸들을 {swapChainRefresh}에 전달합니다. 종료할 때는 네이티브 표시 대상을 파괴하기 전에 스왑 체인을 해제합니다.'
+concepts.platforms.lifecycle.guidance: '애플리케이션이 소유한 깊이 텍스처나 오프스크린 텍스처가 창 크기를 따른다면, 크기가 바뀔 때 다시 만들고 해당 핸들을 담은 상수도 갱신합니다. 어태치먼트 형식과 샘플 수가 계속 일치하면 파이프라인은 재사용할 수 있습니다. {link}는 크기가 바뀌는 텍스처와 재사용하는 파이프라인을 분리하는 예를 보여 줍니다.'
+concepts.platforms.lifecycle.guidance.link: 회전하는 큐브 렌더러
+concepts.platforms.lifecycle.title: '스왑 체인의 크기와 수명'
+concepts.platforms.ownership.description: '애플리케이션은 할당한 컨텍스트와 자체 렌더링 리소스를 소유하고, 컨트롤은 화면 표시 리소스를 소유합니다. 뷰의 {graphicsContext}가 바뀌면 스케줄러는 새 컨텍스트에 맞게 컨트롤 리소스를 다시 만듭니다. 애플리케이션의 파이프라인, 버퍼, 텍스처도 새 컨텍스트에서 다시 만들어야 합니다.'
+concepts.platforms.ownership.details: '렌더링 리소스를 교체하기 전에 뷰의 프레임 처리를 중단하고 해당 리소스를 사용하는 GPU 작업이 끝날 때까지 기다립니다. 그런 다음 이전 리소스나 컨텍스트를 해제합니다. 뷰를 제거할 때도 할당한 컨텍스트를 해제할 책임은 애플리케이션에 있습니다.'
+concepts.platforms.ownership.guidance: '공통 {frameSchedulerCs}는 뷰의 UI 디스패처를 통해 프레임 핸들러를 실행합니다. 핸들러에서는 장면 데이터 갱신과 명령 기록에 집중합니다. 여기서 CPU 작업이 오래 걸리면 인터페이스의 응답도 지연됩니다.'
+concepts.platforms.ownership.title: '애플리케이션과 컨트롤의 소유권'
+concepts.platforms.surface.description: '{link}은 Silk.NET으로 창을 만들고 이벤트를 처리합니다. Zenith.NET은 네이티브 창 핸들과 크기를 {surface} 설명으로 전달받아 {swapChain}을 만듭니다. 운영체제 창의 관리는 애플리케이션이 계속 맡습니다.'
+concepts.platforms.surface.description.link: '삼각형 튜토리얼'
+concepts.platforms.surface.details: '서피스 핸들은 선택한 백엔드와 일치해야 합니다.'
+concepts.platforms.surface.guidance: '{appCs}의 예제 호스트는 Windows, macOS, X11을 지원합니다. Linux 창에서 Xlib 핸들을 제공하므로 Wayland 세션에서 실행하려면 XWayland가 필요합니다. 네이티브 Wayland 애플리케이션은 {surfaceWayland}로 디스플레이와 서피스 핸들을 전달합니다.'
+concepts.platforms.surface.table.android.backend: Android의 Vulkan.
+concepts.platforms.surface.table.apple.backend: Apple 플랫폼의 Metal.
+concepts.platforms.surface.table.headings.backend: '백엔드와 플랫폼'
+concepts.platforms.surface.table.headings.factory: 서피스 팩토리
+concepts.platforms.surface.table.headings.handles: 호스트에서 제공하는 핸들
+concepts.platforms.surface.table.wayland.backend: 네이티브 Wayland 호스트를 사용하는 Vulkan.
+concepts.platforms.surface.table.wayland.handles: Wayland 디스플레이 및 서피스
+concepts.platforms.surface.table.win32.backend: Windows의 DirectX 12 또는 Vulkan.
+concepts.platforms.surface.table.xlib.backend: X11 또는 XWayland를 사용하는 Linux의 Vulkan.
+concepts.platforms.surface.table.xlib.handles: 'X11 디스플레이 및 창'
+concepts.platforms.surface.title: '네이티브 표시 서피스'
+concepts.platforms.title: 플랫폼 통합
+concepts.resources.allocation.context: '힙에 배치한 리소스는 수명 내내 그 힙의 저장 공간에 의존합니다. GPU의 마지막 사용이 끝난 뒤 리소스를 먼저 해제하고 힙을 해제합니다.'
+concepts.resources.allocation.description: '애플리케이션이 메모리 할당을 직접 관리해야 한다면 {heap}을 사용합니다. 힙은 리소스를 지정한 바이트 오프셋에 배치할 저장 공간을 제공합니다. 오프셋을 선택하고 어느 영역이 사용 중인지 추적하는 일은 애플리케이션이 맡습니다.'
+concepts.resources.allocation.details: '리소스를 만들 때 사용할 버퍼 또는 텍스처 설명으로 {contextGetSizeAndAlignment}를 호출합니다. 반환된 정렬 값의 배수인 오프셋에 반환된 크기만큼 공간을 확보하되, 힙의 범위를 벗어나거나 아직 사용 중인 다른 할당과 겹치지 않도록 합니다. 텍스처에는 백엔드별 패딩이 포함될 수 있으므로 픽셀 수와 픽셀당 바이트 수만으로 할당 크기를 계산할 수 없습니다.'
+concepts.resources.allocation.guidance: '힙에 배치하는 버퍼의 상주 특성은 힙과 같아야 합니다. 텍스처를 저장할 때는 GPU 전용 힙을 사용합니다.'
+concepts.resources.allocation.title: '힙에 리소스 배치'
+concepts.resources.bufferUsages.table.constant.role: 상수 버퍼 바인딩을 통해 읽는 셰이더 매개 변수.
+concepts.resources.bufferUsages.table.headings.role: '역할'
+concepts.resources.bufferUsages.table.headings.usage: 용도
+concepts.resources.bufferUsages.table.index.role: '인덱스 그리기에 사용되는 인덱스.'
+concepts.resources.bufferUsages.table.indirect.role: '간접 그리기 또는 디스패치 명령이 읽는 인수.'
+concepts.resources.bufferUsages.table.none.role: '버퍼 용도 플래그를 선언하지 않음.'
+concepts.resources.bufferUsages.table.storageReadOnly.role: '읽기 전용 셰이더 바인딩으로 접근하는 버퍼 데이터.'
+concepts.resources.bufferUsages.table.storageReadWrite.role: '읽기 및 쓰기 셰이더 바인딩으로 접근하는 버퍼 데이터.'
+concepts.resources.bufferUsages.table.transferDst.role: 'GPU 복사의 대상 저장 공간.'
+concepts.resources.bufferUsages.table.transferSrc.role: 'GPU 복사의 원본 데이터.'
+concepts.resources.bufferUsages.table.vertex.role: '그리기에 사용하는 정점 속성.'
+concepts.resources.bufferUsages.title: 버퍼 용도
+concepts.resources.description: '리소스 설명은 애플리케이션이 저장 공간을 어떤 용도로 사용할지 정합니다. 예를 들어 렌더 타깃으로 쓰고, 셰이더에서 샘플링하고, CPU로 다시 복사하는 텍스처에는 세 작업의 용도 플래그가 모두 필요합니다. 메모리 배치는 CPU 접근 방식을 결정하고, 동기화와 수명 관리는 데이터에 접근하거나 저장 공간을 해제할 수 있는 시점을 결정합니다.'
+concepts.resources.memory.context: 'CPU에서 접근할 수 있는 버퍼의 경우 {map}은 데이터를 읽거나 쓸 주소를 제공합니다. 매핑과 {unmap}은 GPU 작업을 기다리지 않습니다. 따라서 GPU가 쓴 결과를 읽으려면 쓰기가 끝날 때까지 기다려야 하고, 같은 바이트를 수정하려면 진행 중인 GPU 접근이 끝날 때까지 기다려야 합니다.'
+concepts.resources.memory.description: '{bufferDesc}의 {residency} 필드는 할당 영역에 대한 CPU 접근 방식을 지정합니다.'
+concepts.resources.memory.details: '상주 특성은 CPU와 GPU가 물리 메모리를 공유하는 시스템에서도 CPU 접근 요구 사항을 나타냅니다. 삼각형은 작은 정점 버퍼를 직접 초기화하기 위해 {cpuWriteOnly}를 사용합니다. 더 큰 정적 지오메트리에는 GPU 전용 저장 공간을 만들고 데이터를 업로드하는 방식이 적합할 수 있습니다.'
+concepts.resources.memory.guidance: '컨텍스트에서 직접 만든 텍스처는 GPU 저장 공간을 사용합니다. 따라서 {textureDesc}에는 상주 특성 필드가 없으며, 애플리케이션은 업로드와 다운로드로 텍스처 데이터를 CPU 메모리와 주고받습니다.'
+concepts.resources.memory.table.cpuReadOnly.cPUAccess: '읽기 전용'
+concepts.resources.memory.table.cpuReadOnly.typicalRole: 'GPU가 생성하고 CPU가 읽는 데이터'
+concepts.resources.memory.table.cpuWriteOnly.cPUAccess: '쓰기 전용'
+concepts.resources.memory.table.cpuWriteOnly.typicalRole: 'CPU가 제공하거나 갱신하는 데이터'
+concepts.resources.memory.table.gpuOnly.cPUAccess: '없음'
+concepts.resources.memory.table.gpuOnly.typicalRole: '정적 지오메트리와 GPU 작업 데이터'
+concepts.resources.memory.table.headings.cPUAccess: '의도한 CPU 접근'
+concepts.resources.memory.table.headings.residency: '상주 방식'
+concepts.resources.memory.table.headings.typicalRole: 일반적인 역할
+concepts.resources.memory.title: 'CPU 접근과 메모리 배치'
+concepts.resources.textureUsages.description: '전송 플래그는 리소스가 GPU 복사의 원본인지 대상인지를 나타냅니다. CPU가 저장 공간에 직접 접근할 수 있는지는 별도로 메모리 배치를 선택할 때 정합니다.'
+concepts.resources.textureUsages.details: '용도 플래그는 텍스처가 수행할 수 있는 역할을 선언합니다. 역할을 바꿔 사용할 때는 해당 {link} 요구 사항을 따라야 하며, 선택한 장치가 그 텍스처 형식의 용도를 지원해야 합니다.'
+concepts.resources.textureUsages.details.link: '레이아웃과 동기화'
+concepts.resources.textureUsages.table.colorAttachment.role: '렌더 패스의 색상 어태치먼트.'
+concepts.resources.textureUsages.table.depthStencilAttachment.role: '렌더 패스의 깊이/스텐실 어태치먼트.'
+concepts.resources.textureUsages.table.headings.role: '역할'
+concepts.resources.textureUsages.table.headings.usage: 용도
+concepts.resources.textureUsages.table.none.role: '텍스처 용도 플래그를 선언하지 않음.'
+concepts.resources.textureUsages.table.sampled.role: '샘플링 텍스처 바인딩을 통해 읽는 텍스처 데이터.'
+concepts.resources.textureUsages.table.storage.role: '스토리지 텍스처 바인딩을 통해 셰이더가 읽거나 쓰는 텍스처 데이터.'
+concepts.resources.textureUsages.table.transferDst.role: 'GPU 복사의 대상 이미지 저장 공간.'
+concepts.resources.textureUsages.table.transferSrc.role: 'GPU 복사의 원본 이미지 데이터.'
+concepts.resources.textureUsages.title: 텍스처 용도
+concepts.resources.title: 리소스 관리
+concepts.resources.transfers.context: '다운로드는 먼저 GPU 데이터를 내부 리드백 공간으로 복사합니다. 라이브러리는 완료된 명령 버퍼를 회수할 때 이 데이터를 지정한 CPU 대상으로 복사합니다. 다운로드를 제출해 받은 값에서 {wait}를 호출하면 두 단계가 모두 완료됩니다. 이 호출이 반환할 때까지 대상 메모리를 할당된 상태로 유지하고, 관리형 메모리라면 고정 상태도 유지합니다.'
+concepts.resources.transfers.description: '업로드는 CPU 데이터를 GPU 리소스로 복사하고, 다운로드는 리소스 데이터를 CPU 메모리로 복사합니다. 버퍼와 텍스처의 메서드는 이 전송을 완료한 뒤 반환합니다.'
+concepts.resources.transfers.details: '이 메서드들을 호출하기 전에 다른 큐의 충돌하는 접근을 정리해야 합니다. 메서드 내부의 대기는 자체 전송만 대상으로 하며 다른 큐와의 의존성을 설정하지 않습니다. 매핑된 메모리로 직접 복사하는 경로에서는 충돌하는 GPU 접근을 기다리는 일도 전적으로 호출자가 담당합니다.'
+concepts.resources.transfers.guidance: '전송을 다른 GPU 작업과 함께 제출하려면 {commandBufferUpload} 또는 {commandBufferDownload}로 기록합니다. 업로드 원본과 다운로드 대상은 메모리를 유효하게 유지해야 하는 기간이 서로 다릅니다.'
+concepts.resources.transfers.item: '업로드를 호출하면 라이브러리가 원본 데이터를 임시 전송 공간인 스테이징 메모리에 복사합니다. 이후 GPU는 이 공간에서 읽으므로 애플리케이션의 원본 포인터는 호출이 반환할 때까지만 필요합니다.'
+concepts.resources.transfers.notes: '텍스처 전송에서 행 스트라이드는 CPU 데이터의 인접한 행 사이의 바이트 거리이고, 슬라이스 스트라이드는 인접한 깊이 슬라이스 사이의 거리입니다. 이 값으로 CPU에 배치된 데이터와 GPU의 텍스처 저장 공간 사이를 복사할 수 있습니다. GPU 복사 완료와 다운로드 대상에 데이터가 전달되는 시점이 다른 이유는 {link} 문서에서 설명합니다.'
+concepts.resources.transfers.notes.link: 동기화
+concepts.resources.transfers.table.bufferDownload.transfer: '{cpuReadOnly}이면 매핑된 메모리에서 직접 복사합니다. 그 외에는 전송 큐에 다운로드를 제출하고 완료를 기다립니다.'
+concepts.resources.transfers.table.bufferUpload.transfer: '{cpuWriteOnly}이면 매핑된 메모리에 직접 복사합니다. 그 외에는 전송 큐에 업로드를 제출하고 완료를 기다립니다.'
+concepts.resources.transfers.table.headings.method: '메서드'
+concepts.resources.transfers.table.headings.transfer: '전송 동작'
+concepts.resources.transfers.table.textureTransfers.method: '{textureUpload} / {textureDownload}'
+concepts.resources.transfers.table.textureTransfers.transfer: '복사와 텍스처 전환을 그래픽스 큐에 제출한 뒤 완료를 기다립니다. 선택한 하위 리소스는 요청한 최종 레이아웃으로 남습니다.'
+concepts.resources.transfers.title: '즉시 전송과 기록된 전송'
+concepts.resources.usages.description: '{bufferUsages}와 {textureUsages}는 리소스 용도를 선언하는 플래그 열거형입니다. 각각 {bufferDescUsages}와 {textureDescUsages}에 지정합니다. 여러 용도가 필요하면 {value}로 해당 멤버를 결합합니다.'
+concepts.resources.usages.title: '리소스 용도'
+concepts.resources.views.description: '{bufferView}는 버퍼의 영역과 그 데이터를 해석하는 방식을 기술합니다. {textureView}는 하위 리소스 범위를 선택하고, 호환되는 텍스처 유형과 포맷을 지정합니다. 둘 다 기존 저장 공간을 참조하며, 생성 시 선언한 용도를 따릅니다. 뷰 범위는 접근 대상을 기술하지만 모든 백엔드에서 동일한 셰이더 범위 검사를 보장하지는 않습니다.'
+concepts.resources.views.details: '리소스 자체의 핸들은 기본 뷰를 제공합니다. 다른 영역이나 호환되는 다른 해석 방식이 필요하면 별도 뷰를 만듭니다. GPU가 사용을 마칠 때까지 뷰와 원본 리소스를 모두 유지해야 합니다. 핸들만으로는 어느 객체의 수명도 유지되지 않습니다. 둘 중 하나를 교체하면 이전 {link}이 들어 있는 상수도 갱신합니다.'
+concepts.resources.views.details.link: '리소스 핸들'
+concepts.resources.views.title: '뷰와 원본 리소스'
+concepts.shaders.binding.context: '컴퓨트 예제는 {input}에 {inputTextureSampledHandle}을, {output}에 {outputTextureStorageHandle}을 지정합니다. Slang의 {descriptorHandle} 형식 인수가 각 리소스의 셰이더 접근 방식을 정합니다.'
+concepts.shaders.binding.description: '{setConstantBuffer}는 현재 파이프라인이 상수를 읽을 버퍼와 시작 바이트 오프셋을 선택합니다. 셰이더가 사용하기 전에 해당 공간에 매개변수를 업로드합니다. 이전 GPU 작업이 같은 바이트를 아직 읽고 있다면 그 작업이 끝난 뒤 데이터를 갱신합니다.'
+concepts.shaders.binding.details: '명령 버퍼, 컴퓨트 파이프라인, 데이터가 채워진 상수 버퍼, 스레드 그룹 수를 준비한 뒤 다음 순서로 디스패치를 기록합니다. 상수 버퍼 바인딩은 현재 파이프라인에 적용되므로 파이프라인을 먼저 선택합니다.'
+concepts.shaders.binding.guidance: '여기서 {commandBuffer}는 {computePipeline}을 사용하는 디스패치를 기록합니다. 셰이더 매개변수는 {constantBuffer}에 저장되어 있습니다. 버퍼에는 {bufferUsagesConstant} 용도가 필요하고, 오프셋 0은 버퍼의 시작을 선택합니다. 여러 상수 레코드가 한 버퍼를 공유한다면 각 레코드의 시작 오프셋도 백엔드의 바인딩 정렬을 따라야 합니다. 이는 레코드 내부의 필드 배치와 별개의 조건입니다.'
+concepts.shaders.binding.notes: '{resourceHandle}은 32비트 필드 두 개를 포함하며 해석은 백엔드에 따라 다릅니다. 이 값은 리소스를 생성한 컨텍스트에서만 유효합니다.'
+concepts.shaders.binding.reference: '핸들을 얻은 리소스와 뷰는 셰이더의 마지막 접근이 끝날 때까지 유지합니다. 텍스처나 뷰를 교체하면 이전 핸들을 담은 상수를 갱신합니다. 소유권 규칙은 {link}에서 설명합니다.'
+concepts.shaders.binding.reference.link: 리소스 관리
+concepts.shaders.binding.table.bufferStorageReadOnlyHandle.access: '셰이더 형식 {t}의 구조화 요소 읽기'
+concepts.shaders.binding.table.bufferStorageReadWriteHandle.access: '셰이더 형식 {t}의 구조화 요소 읽기 및 쓰기'
+concepts.shaders.binding.table.headings.access: 의도된 접근
+concepts.shaders.binding.table.headings.cHandle: 'C# 핸들'
+concepts.shaders.binding.table.headings.slangFieldType: Slang 필드 형식
+concepts.shaders.binding.table.samplerHandle.access: '텍스처 샘플링 상태'
+concepts.shaders.binding.table.textureSampledHandle.access: '텍스처 읽기'
+concepts.shaders.binding.table.textureStorageHandle.access: '스토리지 텍스처 읽기 및 쓰기'
+concepts.shaders.binding.title: '상수 버퍼와 리소스 핸들'
+concepts.shaders.compilation.description: '{zenithCompiler}는 지정한 Slang 진입점을 {shaderDesc}로 컴파일합니다. 컨텍스트의 {graphicsApi}에 따라 DirectX 12용 DXIL, Metal 라이브러리, Vulkan용 SPIR-V를 생성합니다.'
+concepts.shaders.compilation.details: '{context}라는 그래픽스 컨텍스트를 이미 만든 상태에서, 실행 파일 옆에 복사해 둔 삼각형 셰이더를 다음과 같이 컴파일합니다.'
+concepts.shaders.compilation.guidance: '진입점 이름 {vSMain}은 셰이더 파일의 선언과 일치해야 합니다. 초기화 중에 셰이더를 컴파일하고 파이프라인을 만든 뒤, 렌더링 중에는 파이프라인을 재사용합니다. 파이프라인 생성이 끝나면 셰이더 객체를 해제할 수 있습니다. {link} 튜토리얼에서 이 순서를 확인할 수 있습니다.'
+concepts.shaders.compilation.guidance.link: 첫 번째 삼각형
+concepts.shaders.compilation.title: '셰이더 컴파일'
+concepts.shaders.description: 'C#과 Slang의 선언은 각 값의 위치와 셰이더의 접근 방식이 일치해야 합니다. 정점 입력 레이아웃은 정점 속성을 기술하고, 셰이더의 버퍼 선언은 매개변수와 배열을 기술하며, 리소스 핸들은 셰이더가 사용할 리소스를 가리킵니다.'
+concepts.shaders.dispatch.description: '{dispatch}는 실행할 스레드 그룹 수를 지정합니다. 컴퓨트 예제는 {numthreads}를 선언하므로 한 그룹은 16 × 16개 스레드로 구성됩니다. 이미지 전체를 처리하려면 너비와 높이를 각각 16으로 나누고 올림합니다. 예를 들어 17 × 19 이미지는 2 × 2개 그룹이 필요합니다. 셰이더는 각 스레드의 좌표를 확인하고 이미지 범위 밖이면 처리를 건너뜁니다.'
+concepts.shaders.dispatch.details: '선언한 그룹 크기는 {shaderDescThreadGroupSize}에서 확인할 수 있으며, 이 값으로 {dispatch}에 전달할 그룹 수를 계산합니다. 전체 계산은 {computeShaderRendererCs}에 있습니다. 디스패치 이후 결과 이미지에 접근할 때는 {link} 문서의 요구 사항을 따릅니다.'
+concepts.shaders.dispatch.details.link: 동기화
+concepts.shaders.dispatch.title: '스레드 그룹'
+concepts.shaders.layout.context: '{link}는 이미지 크기와 텍스처 핸들 두 개로 이 구성을 보여 줍니다. 명시적 오프셋으로 각 필드의 시작 위치를 지정합니다. 다음 선언에는 {systemRuntimeInteropServices}와 {zenithNET} 네임스페이스를 사용합니다.'
+concepts.shaders.layout.context.link: '컴퓨트 예제의 C# 상수'
+concepts.shaders.layout.description: '정점 입력에서 {inputLayout}은 각 속성의 형식, 오프셋, 시맨틱과 정점 간 스트라이드를 기술합니다. 삼각형은 {float3} 위치 뒤에 {float4} 색상을 배치합니다. 색상은 바이트 오프셋 12에서 시작하고 정점 하나는 28바이트를 차지합니다. {inputLayoutAdd}는 요소 형식으로 이 조밀한 배치를 계산합니다. C# 구조체에 패딩이 있다면 이를 반영한 오프셋과 스트라이드를 지정해야 합니다.'
+concepts.shaders.layout.details: '상수 버퍼에는 셰이더 매개변수를, 구조화 버퍼에는 형식이 지정된 요소의 배열을 저장합니다. 이들의 배치는 {inputLayout}이 아니라 Slang 선언을 따릅니다. C#의 필드 오프셋, 요소 크기, 패딩을 컴파일된 셰이더 레이아웃에 맞춥니다. 관리되지 않는 C# 형식은 바이트로 복사할 수 있지만, 그 메모리 배치가 셰이더의 배치와 반드시 일치하는 것은 아닙니다. 정점에서 촘촘히 배치한 {float3}도 셰이더 버퍼에서는 다른 정렬이 필요할 수 있습니다.'
+concepts.shaders.layout.guidance: '셰이더 매개변수에는 일반 값뿐 아니라 리소스 핸들도 포함할 수 있습니다. 핸들은 이미 존재하는 저장 공간을 가리키므로 상수 버퍼에 핸들을 복사해도 리소스의 내용까지 복사되지는 않습니다.'
+concepts.shaders.layout.notes: '{computeShaderSlang}의 대응 선언은 다음과 같습니다.'
+concepts.shaders.layout.reference: '두 크기 값이 처음 8바이트를 차지하고, 그 뒤에 각각 8바이트인 핸들 두 개가 이어집니다. C# 예제는 전체 레코드에 256바이트를 확보합니다. 다른 레코드의 크기는 그 필드와 레이아웃 및 정렬 요구 사항에 맞춰 정합니다.'
+concepts.shaders.layout.title: '데이터 배치'
+concepts.shaders.matrices.description: '컴파일러는 각 행을 연속으로 저장하는 행 우선 행렬 배치를 사용합니다. {link}에서는 {matrix4x4} 세 개를 오프셋 0, 64, 128에 저장합니다. 해당 {detail}는 {mulVectorMatrix}를 모델, 뷰, 투영 순으로 적용합니다.'
+concepts.shaders.matrices.description.detail: 셰이더
+concepts.shaders.matrices.description.link: 회전하는 큐브 렌더러
+concepts.shaders.matrices.details: '저장 순서는 메모리의 행렬 요소 배치를, 곱셈 순서는 변환을 정합니다. C#과 Slang에서 서로 맞는 규약을 사용해야 합니다.'
+concepts.shaders.matrices.title: '행렬 규약'
+concepts.shaders.title: 셰이더 데이터 및 바인딩
+concepts.synchronization.backends.description: 'DirectX 12와 Vulkan은 텍스처 전환을 레이아웃 및 접근 변경 명령으로 기록합니다. Metal에서는 Zenith.NET의 {transition} 구현이 이에 해당하는 GPU 작업을 수행하지 않습니다. 대신 Metal 백엔드는 명령을 렌더 인코더와 컴퓨트 인코더로 묶고 인코더가 끝날 때 가시성 배리어를 삽입합니다. 연속된 컴퓨트 디스패치처럼 같은 인코더 안에서 의존하는 작업에는 {barrier}를 명시적으로 호출합니다.'
+concepts.synchronization.backends.details: '{link}에서는 그래픽스 렌더 패스를 시작하면서 컴퓨트 인코더를 끝내므로 그래픽스 작업이 컴퓨트 결과를 볼 수 있습니다. 연속된 두 컴퓨트 디스패치는 같은 인코더에 남아 있으므로 중간에 텍스처 전환을 기록하더라도 의존성을 위한 배리어가 필요합니다. 여러 백엔드에서 사용할 명령 시퀀스는 이처럼 텍스처 레이아웃과 접근 의존성을 함께 고려해야 합니다.'
+concepts.synchronization.backends.details.link: '컴퓨트 예제'
+concepts.synchronization.backends.title: '백엔드별 동작'
+concepts.synchronization.barriers.context: '관련 접근을 포함하고 큐에서 지원하는 단계를 선택합니다. {barrierStagesAll}은 지원되는 모든 단계를 포함합니다. 의존성이 일부 단계에만 관련된다면 해당 단계만 선택합니다.'
+concepts.synchronization.barriers.description: '같은 명령 버퍼에 두 컴퓨트 디스패치를 기록한다고 가정합니다. 첫 번째는 시뮬레이션 데이터를 쓰고 두 번째는 이를 읽습니다. 두 번째가 완료된 쓰기를 볼 수 있도록 디스패치 사이에 배리어를 둡니다. 스토리지 버퍼라면 텍스처 레이아웃을 전환하지 않고 다음과 같이 접근 순서를 정할 수 있습니다.'
+concepts.synchronization.barriers.details: '첫 번째 인수는 데이터를 만든 단계를, 두 번째는 데이터를 사용할 단계를 지정합니다. 두 인수는 디스패치 사이에 필요한 실행 순서와 메모리 가시성을 기술합니다.'
+concepts.synchronization.barriers.guidance: '{link}은 시뮬레이션 단계 사이에 이 패턴을 적용합니다. 각 배리어는 데이터를 만드는 디스패치 뒤, 이를 사용하는 디스패치 앞에 놓입니다.'
+concepts.synchronization.barriers.guidance.link: FluidTank 시뮬레이션
+concepts.synchronization.barriers.title: '파이프라인 배리어'
+concepts.synchronization.dependencies.description: '각 리소스에서 같은 데이터를 다루는 접근을 확인합니다. 어느 한쪽이라도 쓰기라면 필요한 순서를 지켜야 합니다. 읽기끼리는 충돌하지 않지만 텍스처 레이아웃과 프레젠테이션 요구 사항은 여전히 적용됩니다. CPU가 데이터를 갱신하거나 리소스를 해제할 때도 GPU가 해당 저장 공간을 다 쓸 때까지 기다려야 합니다.'
+concepts.synchronization.dependencies.details: '작업이 실행되는 위치에 따라 적절한 수단을 선택합니다.'
+concepts.synchronization.dependencies.guidance: '한 작업에 여러 동기화 수단이 필요할 수 있습니다. 예를 들어 다른 큐에서 만든 텍스처를 사용하려면 제출 간 의존성과 다음 용도에 맞는 레이아웃이 모두 필요합니다. CPU가 직접 데이터에 접근하거나 메모리를 해제해야 하는 경우가 아니라면 이 두 작업 때문에 CPU를 대기시킬 필요는 없습니다.'
+concepts.synchronization.dependencies.table.cpuAccess.mechanism: 해당 완료 값에 대해 {wait}를 호출합니다.
+concepts.synchronization.dependencies.table.cpuAccess.situation: 'CPU 접근이나 리소스 해제에 GPU 완료가 필요'
+concepts.synchronization.dependencies.table.crossQueue.mechanism: '생산자의 {timelineValue}를 소비자의 {submit}에 전달합니다.'
+concepts.synchronization.dependencies.table.crossQueue.situation: '한 큐에서 다른 큐가 만든 데이터를 사용'
+concepts.synchronization.dependencies.table.headings.mechanism: 메커니즘
+concepts.synchronization.dependencies.table.headings.situation: 상황
+concepts.synchronization.dependencies.table.layoutChange.mechanism: '이전·다음 레이아웃을 지정한 {commandBufferTransition}.'
+concepts.synchronization.dependencies.table.layoutChange.situation: 텍스처 하위 리소스 사용 방식 변경
+concepts.synchronization.dependencies.table.pipelineStages.mechanism: '생성 단계와 소비 단계를 지정한 {commandBufferBarrier}.'
+concepts.synchronization.dependencies.table.pipelineStages.situation: 명령 스트림의 종속 GPU 단계
+concepts.synchronization.dependencies.title: '접근 의존 관계'
+concepts.synchronization.description: '동기화는 공유 데이터에 접근하는 순서를 정하고, 뒤의 작업이 필요한 결과를 볼 수 있게 합니다. 쓰기에서 읽기로 이어지는 의존성에서 쓰는 작업을 생산자, 읽는 작업을 소비자라고 합니다. 배리어와 텍스처 전환은 GPU 접근을 조율하며, 타임라인은 제출 간 의존성을 연결하고 CPU가 GPU 완료를 기다릴 수 있게 합니다.'
+concepts.synchronization.layouts.context: '새 이미지나 전체를 지울 어태치먼트처럼 기존 내용을 버려도 되는 경우 이전 레이아웃에 {undefined}를 사용합니다. 로드, 샘플링, 누적에 기존 내용이 필요하다면 직전 접근의 레이아웃을 지정합니다.'
+concepts.synchronization.layouts.description: '용도 플래그는 텍스처에 허용된 역할을 선언합니다. {textureLayout}은 밉 레벨과 배열 레이어로 식별한 하위 리소스를 명령 시퀀스의 특정 지점에서 어떻게 접근하는지 나타냅니다. 접근 방식이 바뀌면 전환을 기록합니다. 예를 들어 컴퓨트 예제는 출력 텍스처를 {storage} 레이아웃에서 쓴 뒤 그래픽스 패스에서 샘플링할 수 있도록 준비합니다.'
+concepts.synchronization.layouts.details: '여기서는 {outputTexture}를 {sampledStorage} 용도로 만들었고 선택한 하위 리소스는 {storage} 상태입니다. {default}는 mip 레벨 0과 배열 레이어 0을 선택합니다. 다른 레벨이나 레이어에는 영향을 주지 않습니다.'
+concepts.synchronization.layouts.guidance: '{transition}에는 하위 리소스의 직전 사용 레이아웃을 전달합니다. 애플리케이션은 명령을 기록하고 렌더링 단계 사이에 텍스처를 전달하면서 이 상태를 추적합니다.'
+concepts.synchronization.layouts.title: '텍스처 레이아웃'
+concepts.synchronization.readback.description: '{isCompleted}는 GPU 완료 여부를 알려 줍니다. 기록된 다운로드에는 내부 리드백 공간의 결과를 애플리케이션의 대상 메모리로 복사하는 단계가 하나 더 있습니다.'
+concepts.synchronization.readback.details: '{commandBufferDownload}는 내부 리드백 공간으로의 GPU 복사를 기록합니다. 제출이 끝나면 라이브러리는 완료된 명령 버퍼를 회수하면서 CPU 복사를 수행합니다. 다운로드를 제출해 받은 값에서 {wait}를 호출하면 GPU 완료를 기다리고 해당 큐의 완료된 버퍼도 처리하므로 두 단계가 모두 끝납니다. 호출이 반환할 때까지 대상 메모리의 할당을 유지하고, 관리형 메모리라면 고정 상태도 유지합니다.'
+concepts.synchronization.readback.guidance: '{isCompleted}만 조회해서는 마지막 CPU 복사가 수행되지 않습니다. 다른 큐를 기다리는 것 역시 다운로드 큐의 완료된 버퍼를 회수하지 않습니다. 업로드와 다운로드가 직접 메모리 접근이나 스테이징 공간을 사용하는 조건은 {link} 문서에서 설명합니다.'
+concepts.synchronization.readback.guidance.link: 리소스 관리
+concepts.synchronization.readback.title: '리드백 완료'
+concepts.synchronization.reuse.description: '삼각형은 각 프레임이 끝난 뒤 데이터를 재사용합니다. 프레임을 겹쳐 실행하려면 진행 중인 프레임마다 별도의 수정 가능한 저장 공간을 두고 언제 다시 사용할 수 있는지 추적합니다. 데이터를 변경하기 전에는 해당 바이트에 아직 접근하는 GPU 작업을 기다리고, 리소스를 해제하기 전에는 그 리소스의 모든 사용이 끝날 때까지 기다립니다.'
+concepts.synchronization.reuse.details: '크기 변경과 종료에도 같은 원칙이 적용됩니다. 새 사용을 막고 기존 사용이 끝날 때까지 기다린 뒤 리소스를 교체하거나 해제합니다. {swapChainResize}를 호출하거나 리소스 또는 컨텍스트를 해제하기 전에 이 순서를 지켜야 합니다. 삼각형은 매 프레임 기다리므로 콜백 호출 사이에서 크기를 바꿀 수 있습니다.'
+concepts.synchronization.reuse.guidance: '{swapChainPresent}는 이미지를 표시한 뒤 그래픽스 큐에 신호를 보내고 완료를 기다립니다. 따라서 명시적인 제출 대기를 제거해도 삼각형은 화면 표시 단계에서 여전히 기다립니다. 다른 큐에 독립적으로 제출한 작업은 별도로 완료를 추적해야 합니다.'
+concepts.synchronization.reuse.title: '리소스 재사용'
+concepts.synchronization.timelines.context: '소비자가 끝난 뒤 CPU에서 결과를 읽거나 공유 데이터를 덮어쓰거나 리소스를 해제하려면 해당 제출을 기다립니다.'
+concepts.synchronization.timelines.description: '타임라인은 증가하는 값으로 큐의 완료를 나타냅니다. {submit}은 타임라인과 제출 값을 담은 {timelineValue}를 반환합니다. 서로 다른 타임라인의 숫자 {value}를 직접 비교할 수는 없습니다.'
+concepts.synchronization.timelines.details: '같은 컨텍스트의 두 큐에서 기록한 {producer}와 {consumer}가 있고, 소비자가 생산자가 쓴 데이터를 사용한다고 하겠습니다. 다음 의존 관계로 제출합니다.'
+concepts.synchronization.timelines.guidance: '소비자 큐는 전달받은 완료 값에 도달할 때까지 기다린 뒤 명령을 실행합니다. CPU는 {producedWait}를 호출하지 않고 계속 진행할 수 있습니다. {consumed}가 완료될 때까지 공유 리소스를 유지하고, 소비자의 접근에 필요한 텍스처 전환을 기록합니다.'
+concepts.synchronization.timelines.notes: '{queueTimelineSignal}이 반환한 값도 {wait}로 기다리거나 다른 큐의 의존성에 사용할 수 있습니다. {signal}은 이미 제출된 작업 뒤에 완료 지점을 넣으며, GPU는 그 작업을 끝낸 뒤 해당 지점에 도달합니다.'
+concepts.synchronization.timelines.title: '타임라인과 큐 간 의존 관계'
+concepts.synchronization.title: 동기화
+home.actions.reference: API 탐색
+home.actions.tutorial: 삼각형으로 시작하기
+home.entries.description: '튜토리얼, 예제, API 참조'
+home.entries.label: '문서 구성'
+home.entries.reference.action: API 탐색
+home.entries.reference.category: 참조
+home.entries.reference.description: 'API 형식, 멤버, 선언 찾아보기'
+home.entries.reference.title: API 참조
+home.entries.samples.action: 예제 탐색
+home.entries.samples.category: 예제
+home.entries.samples.description: '직접 실행하며 살펴보는 그래픽스와 컴퓨트 예제'
+home.entries.samples.title: 예제
+home.entries.title: '문서'
+home.entries.tutorial.action: 튜토리얼 시작
+home.entries.tutorial.category: 튜토리얼
+home.entries.tutorial.description: '빈 프로젝트에서 첫 삼각형을 그리기까지'
+home.entries.tutorial.title: 첫 번째 삼각형
+home.introduction.accent: '크로스 플랫폼 그래픽스'
+home.introduction.description: 'Zenith.NET은 DirectX, Metal, Vulkan의 그래픽스와 컴퓨트 기능을 하나의 API로 제공합니다'
+home.introduction.title: '.NET으로 만드는{lineBreak}{accent}'
+home.meta.description: 'DirectX, Metal, Vulkan의 공통 API로 개발하는 .NET 크로스 플랫폼 그래픽스·컴퓨트 애플리케이션'
+home.scene.description: '회전하는 도형을 조작할 수 있습니다. 드래그하거나 방향키를 눌러 시점을 바꾸세요.'
+home.scene.label: 지오메트리 표시
+home.scene.surface: 서피스
+home.scene.wireframe: 와이어프레임
+home.title: '.NET을 위한 현대적인 RHI'
+learning.concepts.description: '리소스 용도를 선택하고 셰이더 데이터를 기술하며 GPU 실행을 조율하는 방법'
+learning.concepts.execution.description: '명령 기록과 제출, 리소스 수명'
+learning.concepts.platforms.description: '네이티브 화면 표시와 UI 렌더링 컨트롤'
+learning.concepts.resources.description: '리소스 용도, 메모리 배치, 뷰'
+learning.concepts.shaders.description: 'C#과 Slang의 데이터 배치 및 바인딩 규약'
+learning.concepts.synchronization.description: '의존 관계, 가시성, 완료'
+learning.concepts.title: 핵심 개념
+learning.description: '삼각형 튜토리얼로 첫 애플리케이션을 만들어 보세요. 이후 리소스 관리, 셰이더 데이터, 동기화 개념을 필요한 순서대로 읽거나 구현하려는 기법의 예제를 살펴볼 수 있습니다.'
+learning.entries.samples.action: 예제 탐색
+learning.entries.samples.description: '지오메트리, 컴퓨트, 레이 쿼리, 메시 셰이딩의 C# 및 Slang 구현'
+learning.entries.tutorial.action: 튜토리얼 열기
+learning.entries.tutorial.description: '창을 열고 삼각형을 그리는 콘솔 애플리케이션'
+learning.meta.description: 'Zenith.NET 프로그래밍 모델을 위한 튜토리얼과 개념 설명'
+learning.title: 학습
+reference.description: '리소스, 명령, 플랫폼 통합 참조'
+reference.entries.commands.description: '그래픽스, 컴퓨트, 전송 명령'
+reference.entries.commands.label: '명령 기록'
+reference.entries.context.description: '선택한 백엔드의 GPU 리소스 생성'
+reference.entries.context.label: 여기서 시작
+reference.meta.description: 'Zenith.NET 네임스페이스, 형식, 멤버'
+reference.navigation.core: '핵심 API 참조'
+reference.title: API 참조
+samples.computeShader.description: '이 예제는 {assetsTexturesShokoPng}를 읽고 컴퓨트 디스패치에서 회색조로 변환한 뒤 결과 텍스처를 표시합니다. 변환은 한 번만 실행하고 이후 프레임은 같은 결과를 표시합니다. 렌더러는 샘플링용 텍스처와 쓰기 가능한 텍스처를 바인딩하고, 스레드 그룹 수를 올림해 이미지 전체를 처리하며, 셰이더에서 이미지 범위를 확인하는 방법을 보여 줍니다.'
+samples.computeShader.details: '{computeShaderRendererCs} · {computeShaderSlang}'
+samples.computeShader.title: '컴퓨트 셰이더'
+samples.cube.description: '큐브는 인덱스 그리기, 상수 버퍼의 모델·뷰·투영 행렬, 깊이 테스트를 추가합니다. {d32FloatS8UInt} 깊이·스텐실 텍스처는 크기를 바꿀 때 다시 생성합니다.'
+samples.cube.details: '{spinningCubeRendererCs} · {spinningCubeSlang}'
+samples.cube.title: 회전하는 큐브
+samples.description: '각 예제는 C# 렌더러와 Slang 셰이더를 함께 제공하며 한 가지 기법을 다룹니다. 애플리케이션 구성부터 렌더링까지의 전체 과정은 {link}에서 설명합니다.'
+samples.description.link: 첫 번째 삼각형
+samples.execution.description: '{link} 프로젝트를 실행하고 콘솔 메뉴에서 예제를 선택합니다.'
+samples.execution.description.link: ZenithTutorials
+samples.execution.title: '예제 실행'
+samples.host.description: '{appCs}는 컨텍스트, 창, 스왑 체인을 초기화합니다. 매 프레임 명령 버퍼를 얻고 선택한 렌더러를 호출한 다음 명령을 제출하고 결과를 표시합니다. {iRendererCs}는 갱신, 렌더링, 크기 변경, 해제 작업을 정의합니다.'
+samples.host.details: '컴퓨트 렌더러와 레이 트레이싱 렌더러는 오프스크린 텍스처를 만듭니다. {texturePresenterCs}와 {presentTextureSlang}이 이 텍스처를 현재 출력 텍스처에 그리면, 공통 호스트가 명령을 제출하고 프레임을 화면에 표시합니다.'
+samples.host.title: '호스트와 화면 표시'
+samples.indirectDrawing.description: '간접 그리기는 인덱스 그리기 인수 버퍼와 인스턴스별 구조화 데이터 버퍼로 여러 큐브를 그립니다. CPU가 인수를 제공하고 변환을 갱신합니다.'
+samples.indirectDrawing.details: '{indirectDrawingRendererCs} · {indirectDrawingSlang}'
+samples.indirectDrawing.title: '간접 그리기'
+samples.meshShading.description: '태스크 셰이더가 구 인스턴스를 컬링하고 남은 인덱스를 메시 셰이더에 전달하면 메시 셰이더가 지오메트리를 생성합니다. 파이프라인 생성 전에 {contextCapabilitiesMeshShadingSupported}를 확인합니다.'
+samples.meshShading.details: '{meshShadingRendererCs} · {meshShadingSlang}'
+samples.meshShading.title: 메시 셰이딩
+samples.rayTracing.description: '바닥과 절차적 구의 하위·상위 가속 구조를 만들고 컴퓨트 셰이더에서 인라인 레이 쿼리를 수행합니다. 초기화 전에 {contextCapabilitiesRayTracingSupported}를 확인합니다.'
+samples.rayTracing.details: '{rayTracingRendererCs} · {rayTracingSlang}'
+samples.rayTracing.title: 레이 트레이싱
+samples.title: 예제
+samples.triangle.description: '정점 버퍼와 대응하는 입력 레이아웃을 연결하고 그래픽스 파이프라인으로 정점 세 개를 그립니다.'
+samples.triangle.details: '{helloTriangleRendererCs} · {helloTriangleSlang} · {link}'
+samples.triangle.details.link: 전체 튜토리얼
+samples.triangle.title: Hello Triangle
+samples.workloads.description: '주 저장소의 {link}에는 라이브러리 소스를 참조하는 더 큰 규모의 애플리케이션이 있습니다.'
+samples.workloads.description.link: Experiments 디렉터리
+samples.workloads.details: '창을 사용하는 실험은 동일한 Windows, macOS, Linux/X11 표시 경로를 사용합니다. Avalonia, WinForms, WPF, WinUI/Uno, MAUI 컨트롤의 프레임 흐름과 구현 소스는 {link}에서 설명합니다.'
+samples.workloads.details.link: 플랫폼 통합
+samples.workloads.table.cornellBox.example: '{link}'
+samples.workloads.table.cornellBox.example.link: CornellBox
+samples.workloads.table.cornellBox.whatItDemonstratesAndRequires: '패스 트레이싱, 디노이징, 톤 매핑, 업스케일링, ImGui 컨트롤을 다룹니다. 레이 트레이싱 지원이 필요합니다.'
+samples.workloads.table.fluidTank.example: '{link}'
+samples.workloads.table.fluidTank.example.link: FluidTank
+samples.workloads.table.fluidTank.whatItDemonstratesAndRequires: '컴퓨트 유체 시뮬레이션, 시뮬레이션 단계 사이의 의존 관계, 다중 패스 표면 렌더링을 다룹니다.'
+samples.workloads.table.headings.example: 예제
+samples.workloads.table.headings.whatItDemonstratesAndRequires: '기법과 요구 사항'
+samples.workloads.table.inkCanvas.example: '{link}'
+samples.workloads.table.inkCanvas.example.link: InkCanvas
+samples.workloads.table.inkCanvas.whatItDemonstratesAndRequires: '네이티브 창에서의 Skia 그리기 및 입력 처리를 보여줍니다. 대상 플랫폼용 백엔드 및 네이티브 Skia 라이브러리가 필요합니다.'
+samples.workloads.table.platformDetection.example: '{link}'
+samples.workloads.table.platformDetection.example.link: PlatformDetection
+samples.workloads.table.platformDetection.whatItDemonstratesAndRequires: '각 백엔드의 컨텍스트 생성을 시도하고 장치 기능을 보고합니다. 생성에 실패하면 지원되지 않는 것으로 표시하지만, 원인은 장치나 드라이버 또는 누락된 런타임 의존성일 수 있습니다.'
+samples.workloads.title: '복합 렌더링 애플리케이션'
+tutorial.context.description: '{graphicsContext}는 백엔드를 통해 애플리케이션을 GPU에 연결하고 렌더링 리소스를 만드는 메서드를 제공합니다.'
+tutorial.context.details: '컨텍스트 초기화를 {emphasis}에 추가합니다.'
+tutorial.context.details.emphasis: using 지시문 뒤, {iWindowWindow} 앞
+tutorial.context.guidance: '각 분기에서 플랫폼에 맞는 백엔드를 선택하고 검증을 요청합니다. {validationMessage} 메시지는 콘솔에 출력합니다.'
+tutorial.context.imports: '{programCs} 맨 위에 다음 using 지시문을 추가합니다.'
+tutorial.context.title: '그래픽스 컨텍스트'
+tutorial.description: '이 튜토리얼에서는 빈 .NET 콘솔 프로젝트에서 시작해 창을 열고 색상이 있는 삼각형을 그리는 애플리케이션을 만듭니다.'
+tutorial.details: '튜토리얼에는 세 번의 확인 단계가 있습니다. 먼저 빈 창을 열고, 단색 배경을 표시한 다음, 색상이 있는 삼각형을 그립니다. 각 단계에서 애플리케이션을 실행해 결과를 확인한 뒤 다음 부분을 추가합니다.'
+tutorial.draw.context: '{draw}의 인수는 다음과 같습니다.'
+tutorial.draw.description: '{windowRender} 콜백의 {addTheTriangleDrawCommandsHereLater}를 다음 명령으로 바꿉니다. 위치는 {beginRenderPass}와 {endRenderPass} 사이입니다.'
+tutorial.draw.details: '{setPipeline}은 셰이더와 렌더링 상태를 선택합니다. 정점 바인딩은 파이프라인의 입력 레이아웃으로 해석되므로, 그 전에 호출해야 합니다.'
+tutorial.draw.guidance: '{setVertexBufferVertexBuffer}는 업로드한 정점 데이터를 바이트 오프셋 0부터 입력 슬롯 0에 바인딩합니다. 이 슬롯은 {inputLayouts}의 유일한 항목과 대응합니다.'
+tutorial.draw.notes: '이 호출들은 그리기 명령을 기록합니다. 기존의 {submitWait}가 명령을 제출하고 GPU 완료를 기다리면, {present}가 완성된 이미지의 표시를 요청합니다. 삼각형 바깥 영역에도 배경색이 정해지도록 패스 시작 부분의 지우기 작업을 유지합니다.'
+tutorial.draw.table.firstInstance.meaning: 인스턴스 번호 매기기를 0부터 시작합니다.
+tutorial.draw.table.firstVertex.meaning: 버퍼의 첫 번째 정점부터 시작합니다.
+tutorial.draw.table.headings.argument: 인수
+tutorial.draw.table.headings.description: 값
+tutorial.draw.table.headings.meaning: 의미
+tutorial.draw.table.instanceCount.meaning: 삼각형 한 개를 그립니다.
+tutorial.draw.table.vertexCount.meaning: 세 개의 정점을 읽습니다.
+tutorial.draw.title: '그리기 명령'
+tutorial.execution.description: '{firstTriangle} 프로젝트를 빌드하고 실행합니다.'
+tutorial.execution.details: '어두운 배경 위에 위쪽은 빨강, 오른쪽 아래는 초록, 왼쪽 아래는 파랑인 삼각형이 나타나야 합니다. 내부 색상은 정점 색상을 보간한 결과로, 부드럽게 이어집니다.'
+tutorial.execution.guidance: '창 크기를 바꾸면 이미지의 픽셀 크기가 달라지지만 정점 좌표는 그대로입니다. 뷰포트는 이 좌표를 새로운 너비와 높이에 맞추므로 창의 가로세로 비율이 바뀌면 삼각형도 늘어나거나 줄어듭니다.'
+tutorial.execution.title: '애플리케이션 실행'
+tutorial.frame.commands.context: '{endRenderPass} 뒤의 두 번째 전환은 출력 텍스처를 화면에 표시할 수 있도록 준비합니다. {submit}은 기록을 끝내고 명령 버퍼를 제출하며, {wait}는 해당 제출이 완료될 때까지 CPU 스레드를 대기시킵니다. 이어서 {present}가 렌더링한 이미지의 표시를 요청합니다.'
+tutorial.frame.commands.description: '렌더 패스는 같은 어태치먼트에 쓰는 그리기 작업을 묶습니다. 이 패스에서는 스왑 체인의 출력 텍스처가 색상 결과를 받습니다. {colorAttachmentClear}는 해당 어태치먼트를 RGBA 색상으로 초기화하고 패스가 끝난 뒤에도 내용을 보존하도록 지정합니다. {beginRenderPass}는 이 설정으로 패스를 시작하는 명령을 기록합니다.'
+tutorial.frame.commands.details: '컬렉션에는 색상 어태치먼트 하나가 있습니다. {null}은 이 패스에 깊이·스텐실 어태치먼트가 없다는 뜻입니다.'
+tutorial.frame.commands.guidance: '{beginRenderPass}는 어태치먼트 전체를 덮도록 뷰포트와 시저를 설정합니다. 뷰포트는 렌더링 좌표를 픽셀 위치로 바꾸고 시저는 쓸 수 있는 영역을 제한합니다.'
+tutorial.frame.commands.title: '지우기, 제출, 표시'
+tutorial.frame.description: '먼저 창을 하나의 배경색으로 채웁니다. 이 작업을 지우기라고 합니다. 배경색이 보이면 서피스, 명령 제출, 표시 과정이 연결된 것입니다. 이제 삼각형의 정점과 셰이더를 추가할 수 있습니다.'
+tutorial.frame.details: 'Silk.NET은 프레임을 그릴 시점에 콜백을 호출합니다. 이 예제는 경과 시간 인수를 쓰지 않으므로 매개변수 이름을 {value}로 둡니다. 콜백을 {emphasis}에 등록합니다.'
+tutorial.frame.details.emphasis: '{windowRun} 앞'
+tutorial.frame.target.description: '{graphicsQueueCommandBuffer}는 기록할 준비가 된 명령 버퍼를 반환합니다. 기록은 GPU 작업을 정의하는 단계이며 실행은 제출 후 시작됩니다.'
+tutorial.frame.target.details: '텍스처 레이아웃은 작업에 필요한 접근 방식을 나타냅니다. 첫 번째 {transition} 호출은 출력 텍스처를 색상 어태치먼트로 사용할 수 있도록 준비합니다. 여기서 {default}는 출력 텍스처의 기본 이미지인 밉 레벨 0과 배열 레이어 0을 선택합니다. 이 패스는 이미지 전체를 지우므로 {undefined}를 지정해 이전 내용을 버릴 수 있습니다.'
+tutorial.frame.target.guidance: '화면에 표시한 뒤에는 다른 이미지로 넘어갈 수 있으므로 매 프레임 현재 출력 텍스처를 가져옵니다.'
+tutorial.frame.target.title: '출력 텍스처 준비'
+tutorial.frame.title: '첫 프레임'
+tutorial.geometry.data.description: '정점 배열을 {emphasis}에 추가합니다. 이후 리소스 초기화도 설명한 순서대로 이 콜백 앞에 둡니다.'
+tutorial.geometry.data.description.emphasis: '{windowRender} 앞'
+tutorial.geometry.data.details: '각 항목은 {vertex}를 생성하고, 안쪽의 두 {new} 표현식은 {vector3} 위치와 {vector4} 색상을 제공합니다. 위치는 위, 오른쪽 아래, 왼쪽 아래 꼭짓점입니다. 정점 셰이더는 네 번째 좌표를 {w}로 설정합니다. w가 1이므로 x와 y는 정규화 장치 좌표이기도 하며, -1과 +1은 이미지 경계, 0은 중앙을 뜻합니다. 이 해석은 픽셀 크기와 무관합니다.'
+tutorial.geometry.data.guidance: '각 색상은 빨강, 초록, 파랑, 알파 성분을 가지며 알파는 1입니다. 세 정점 모두 z = 0인 같은 평면에 있습니다.'
+tutorial.geometry.data.title: '정점 위치와 색상'
+tutorial.geometry.description: '세 정점은 각각 위치와 색상을 가집니다. GPU는 위치로 삼각형 모양을 정하고 정점 색상을 보간해 내부의 색상을 계산합니다.'
+tutorial.geometry.format.description: '정점 선언을 {emphasis}에 추가합니다.'
+tutorial.geometry.format.description.emphasis: '{programCs} 끝부분, 정리 문 뒤'
+tutorial.geometry.format.details: '{file}은 형식의 사용 범위를 {programCs}로 제한하고, {layoutKindSequential}은 필드를 선언 순서대로 배치합니다. {position}은 32비트 부동소수점 값 세 개로 구성되며 바이트 오프셋 0에서 시작합니다. {color}는 네 개로 구성되며 바이트 오프셋 12에서 시작합니다. 따라서 정점 하나는 28바이트입니다. GPU가 각 필드를 올바르게 읽을 수 있도록 입력 레이아웃에 이 배치를 기술합니다.'
+tutorial.geometry.format.imports: '벡터 형식과 구조체 레이아웃 특성을 사용하도록 {programCs} 맨 위에 다음 using 지시문을 추가합니다.'
+tutorial.geometry.format.title: '정점 표현'
+tutorial.geometry.title: '정점 데이터'
+tutorial.geometry.upload.context: '{fixed}는 배열의 주소를 사용하는 동안 가비지 수집기가 배열을 옮기지 못하게 고정합니다. 이 버퍼는 CPU 쓰기가 가능하므로 {upload}는 메모리를 매핑하고 바이트를 복사한 뒤 매핑을 해제하고 반환합니다. 배열은 이 호출이 끝날 때까지만 고정하면 됩니다. 정점은 초기화 중에 한 번 업로드하고, 버퍼는 렌더링이 끝날 때까지 유지합니다.'
+tutorial.geometry.upload.description: 'GPU가 그리기 중에 읽을 수 있도록 정점 데이터를 {buffer}에 저장합니다. 배열 바로 뒤에 버퍼 할당과 업로드 코드를 추가합니다.'
+tutorial.geometry.upload.details: '할당 크기는 {sizeofVertexVerticesLength}로, 28바이트 정점 세 개인 84바이트입니다. {bufferUsagesVertex}는 정점 입력 접근을 선언하고 {cpuWriteOnly}는 CPU 직접 초기화를 허용합니다. 설명의 스트라이드는 요소 크기이며, 뒤에서 정의할 입력 레이아웃이 각 속성을 읽는 방법을 정합니다.'
+tutorial.geometry.upload.guidance: '할당만으로 버퍼 내용이 초기화되지는 않습니다. {upload}는 데이터 설명의 원본 포인터와 바이트 수를 사용해 배열을 목적지 오프셋 0에 복사합니다.'
+tutorial.geometry.upload.imports: 'Zenith.NET 버퍼 형식을 {systemBuffer}와 구분하기 위해 {programCs}의 using 지시문에 {bufferAlias} 구문을 추가합니다.'
+tutorial.geometry.upload.notes: '{programCs} 끝의 해제 코드에서 {swapChainDispose} 바로 앞에 {vertexBufferDispose}를 추가합니다. 큰 정적 지오메트리를 위한 GPU 전용 저장과 전송은 {link}에서 설명합니다.'
+tutorial.geometry.upload.notes.link: 리소스 관리
+tutorial.geometry.upload.title: '버퍼 할당과 업로드'
+tutorial.input.description: 'GPU는 위치와 색상 같은 정점 속성이 어디에 있는지 알아야 합니다. 입력 레이아웃은 각 속성의 포맷과 바이트 오프셋을 지정합니다. 스트라이드는 다음 정점까지의 바이트 거리입니다. 셰이더를 생성한 뒤 다음 코드를 추가합니다.'
+tutorial.input.details: '{inputLayoutAdd}는 각 속성을 이전 속성 바로 뒤에 배치하고 그 크기를 정점 간 스트라이드에 더합니다. 오프셋은 C# 구조체를 조사하는 대신 여기서 지정한 형식으로 계산합니다. 두 필드의 배치는 다음과 같습니다.'
+tutorial.input.guidance: '{semanticIndex}의 기본값은 0이며 셰이더의 {pOSITION0}, {cOLOR0}과 일치합니다. 위치와 색상이 하나의 정점 버퍼에 함께 저장되므로 파이프라인은 슬롯 0에 입력 레이아웃 하나를 사용합니다. C# 구조체의 필드 순서나 패딩을 바꾸면 입력 레이아웃도 맞춰 수정합니다.'
+tutorial.input.table.headings.byteOffset: 바이트 오프셋
+tutorial.input.table.headings.cField: 'C# 필드'
+tutorial.input.table.headings.format: 형식
+tutorial.input.table.headings.shaderInput: 셰이더 입력
+tutorial.input.table.nextVertex.cField: 다음 정점
+tutorial.input.table.nextVertex.format: '총 스트라이드: 28바이트'
+tutorial.input.table.nextVertex.shaderInput: 다음 입력 레코드
+tutorial.input.table.vertexColor.format: '{float4} (16바이트)'
+tutorial.input.table.vertexPosition.format: '{float3} (12바이트)'
+tutorial.input.title: '정점 입력 레이아웃'
+tutorial.lifecycle.description: '각 프레임은 다음 순서를 따릅니다.'
+tutorial.lifecycle.details: '창을 닫으면 {run}이 반환하고 정리 코드가 이어서 실행됩니다. 각 프레임에서 제출한 작업이 끝날 때까지 이미 기다렸으므로 명령에 사용한 리소스를 해제할 수 있습니다. {windowRun} 뒤에 들어갈 전체 정리 코드는 다음과 같습니다.'
+tutorial.lifecycle.guidance: '네이티브 창을 파괴하기 전에 스왑 체인을 해제하고, 컨텍스트는 그 리소스들을 해제한 뒤 정리합니다. 제출한 명령 버퍼는 큐가 재사용하고 출력 텍스처는 스왑 체인이 관리하므로 각각 {dispose}를 호출할 필요가 없습니다.'
+tutorial.lifecycle.title: '프레임 흐름과 해제'
+tutorial.pipeline.behavior: '입력 레이아웃 다음에 파이프라인을 생성합니다.'
+tutorial.pipeline.context: '{cullNone}은 면 컬링을 끕니다. 따라서 정점이 시계 방향으로 보이든 반시계 방향으로 보이든 삼각형을 그립니다.'
+tutorial.pipeline.description: '{graphicsPipeline}은 정점 레이아웃, 두 셰이더, 프리미티브 조립과 어태치먼트 출력 설정을 하나로 묶습니다.'
+tutorial.pipeline.details: '셰이더는 정점과 프래그먼트를 처리합니다. 나머지 파이프라인 설정은 프리미티브 조립, 면 컬링, 깊이 테스트, 색상 블렌딩을 제어합니다.'
+tutorial.pipeline.guidance: '파이프라인의 색상 포맷은 렌더 패스의 어태치먼트와 같아야 하므로 {swapChainDescFormat}에서 가져옵니다. {count1}은 픽셀당 샘플 하나를 지정합니다.'
+tutorial.pipeline.item: '{triangleList}는 연속된 정점 세 개를 한 삼각형으로 조립합니다.'
+tutorial.pipeline.notes: '{depthNone}은 깊이 테스트와 쓰기를 끕니다. 깊이 어태치먼트가 없는 패스와 일치하는 설정입니다.'
+tutorial.pipeline.reference: '{opaque}는 블렌딩을 끕니다. 따라서 프래그먼트 셰이더 출력이 삼각형 내부의 배경색을 대체합니다.'
+tutorial.pipeline.title: '그래픽스 파이프라인'
+tutorial.pipeline.usage: '셰이더 객체는 파이프라인을 생성한 뒤 해제할 수 있습니다. 파이프라인은 렌더링 동안 유지하고, 해제 코드에서 {vertexBufferDispose} 앞에 {pipelineDispose}를 추가합니다.'
+tutorial.presentation.title: '그래픽스 컨텍스트와 표시'
+tutorial.project.context: '프로젝트 빌드 설정에서 {emphasis}({allowUnsafeBlocks})를 켭니다. 창 보조 코드의 네이티브 호출과 정점 업로드에는 unsafe 코드가 필요합니다.'
+tutorial.project.context.emphasis: 안전하지 않은 코드 허용
+tutorial.project.description: '.NET 10을 대상으로 하는 {firstTriangle} C# 콘솔 프로젝트를 만듭니다.'
+tutorial.project.details: '다음 NuGet 패키지를 프로젝트에 추가합니다.'
+tutorial.project.guidance: '{zenithNET}은 공통 렌더링 API를, 백엔드 패키지 세 개는 플랫폼 구현을 제공합니다. {zenithNETCompiler}는 뒤에서 작성할 Slang 셰이더를 선택한 백엔드에 맞게 컴파일합니다. Silk.NET은 창 생성과 이벤트 처리를 담당합니다.'
+tutorial.project.title: '프로젝트 준비'
+tutorial.requirements.context: '{link}와 해당 {detail}에서 이 튜토리얼의 참조 구현을 확인할 수 있습니다.'
+tutorial.requirements.context.detail: 공유 호스트
+tutorial.requirements.context.link: 'Hello Triangle 예제'
+tutorial.requirements.description: '{emphasis}와 기본 C# 지식이 필요합니다. 애플리케이션의 대상 프레임워크는 {net10}입니다. GPU와 드라이버는 사용 중인 플랫폼에 맞춰 선택한 다음 백엔드를 지원해야 합니다.'
+tutorial.requirements.description.emphasis: .NET 10 SDK
+tutorial.requirements.guidance: 'Zenith.NET은 애플리케이션이 제공하는 네이티브 서피스에 렌더링합니다. Silk.NET.Windowing은 창을 만들고 이벤트를 처리합니다. macOS에서는 {cAMetalLayerCs}로 표현되는 Metal 레이어로 표시하며, 창 보조 코드가 이 레이어를 창에 연결합니다.'
+tutorial.requirements.table.headings.graphicsBackend: 그래픽 백엔드
+tutorial.requirements.table.headings.platform: 플랫폼
+tutorial.requirements.table.linux.graphicsBackend: Vulkan 1.4
+tutorial.requirements.table.linux.platform: 'Linux(X11 또는 XWayland)'
+tutorial.requirements.table.macOS.graphicsBackend: Metal 4
+tutorial.requirements.table.macOS.platform: macOS
+tutorial.requirements.table.windows.graphicsBackend: DirectX 12
+tutorial.requirements.table.windows.platform: Windows
+tutorial.requirements.title: '준비 사항'
+tutorial.resize.context: '애플리케이션을 다시 빌드하고 실행합니다. 창 크기를 바꿔도 배경이 창 전체를 채워야 합니다. 새 렌더 패스를 시작할 때마다 변경된 어태치먼트 크기로 뷰포트와 시저가 설정되기 때문입니다. 렌더링 콜백은 그대로 둡니다. 이후 이 패스 안에서 삼각형을 그립니다.'
+tutorial.resize.description: '프레임버퍼 크기가 바뀌면 스왑 체인도 그에 맞춰야 합니다. 렌더링 콜백 뒤, {windowRun} 앞에 {framebufferResize} 콜백을 등록합니다.'
+tutorial.resize.details: '창의 논리적 크기 대신 픽셀 단위의 {link}을 처리합니다. 창을 최소화하면서 너비나 높이가 0이 되면 크기 변경 콜백은 스왑 체인을 그대로 둡니다. 렌더링 콜백도 어느 한쪽이 0인 동안에는 바로 반환하므로, 프레임버퍼가 다시 유효한 크기가 될 때까지 렌더링이 중단됩니다.'
+tutorial.resize.details.link: 프레임버퍼 크기 변경
+tutorial.resize.guidance: '렌더링 콜백은 제출한 프레임이 끝날 때까지 기다린 뒤 {present}를 호출합니다. 따라서 크기 변경 콜백은 이전 프레임의 작업을 방해하지 않고 스왑 체인 이미지를 교체할 수 있습니다. 작업 완료와 리소스 수명의 관계는 {link} 문서에서 자세히 설명합니다.'
+tutorial.resize.guidance.link: 동기화
+tutorial.resize.title: '크기 변경'
+tutorial.shaders.compilation.context: '{appContextBaseDirectory}를 사용하면 애플리케이션 출력 디렉터리에 복사한 셰이더를 불러옵니다. 다른 작업 디렉터리에서 애플리케이션을 실행하더라도 셰이더를 찾는 위치는 바뀌지 않습니다.'
+tutorial.shaders.compilation.description: '{triangleSlang}의 파일 속성에서 {emphasis}를 {detail}로 설정합니다. 빌드하면 셰이더가 실행 파일 옆으로 복사됩니다.'
+tutorial.shaders.compilation.description.detail: 변경된 내용만 복사
+tutorial.shaders.compilation.description.emphasis: 출력 디렉터리에 복사
+tutorial.shaders.compilation.details: '{programCs}로 돌아와 정점 업로드 뒤, 렌더 콜백 앞에 셰이더 초기화를 추가합니다.'
+tutorial.shaders.compilation.guidance: '{zenithCompiler}는 {contextGraphicsApi}에 맞게 소스를 컴파일하고, {createShader}는 컴파일 결과의 설명으로 셰이더 객체를 만듭니다. 진입점 이름 {vSMain}과 {fSMain}은 Slang 파일에 정의한 두 함수를 선택합니다.'
+tutorial.shaders.compilation.title: '셰이더 컴파일'
+tutorial.shaders.context: '{fSInput}은 두 셰이더 단계를 연결합니다. 정점 셰이더는 {sVPOSITION}에 클립 공간 위치를 기록하며, GPU는 이 위치를 이용해 삼각형에서 보이는 부분을 결정합니다. {cOLOR0} 필드는 정점 색상을 전달합니다. 이 색상은 삼각형 내부에서 보간된 뒤 프래그먼트 셰이더에 전달되므로 두 단계의 필드 형식과 시맨틱이 일치해야 합니다.'
+tutorial.shaders.description: '셰이더는 GPU에서 실행하는 프로그램입니다. 정점 셰이더는 각 정점의 위치를 정하고, 프래그먼트 셰이더는 이미지에서 도형이 덮는 각 위치의 색상을 정합니다. 두 단계를 하나의 Slang 파일에 작성하되, 실행을 시작하는 함수인 진입점은 단계별로 따로 둡니다.'
+tutorial.shaders.details: '{triangleSlang}을 만들고 다음 입력·출력 구조체를 작성합니다.'
+tutorial.shaders.fragment.description: '같은 파일에 프래그먼트 진입점을 추가합니다.'
+tutorial.shaders.fragment.details: '래스터화는 삼각형이 이미지의 어느 위치를 덮는지 결정하고 해당 위치에 프래그먼트를 생성합니다. 각 프래그먼트는 세 정점의 색상으로부터 보간된 색상을 받습니다. {fSMain}은 이 색상을 반환하고, {sVTARGET}은 결과를 첫 번째 색상 어태치먼트로 보냅니다.'
+tutorial.shaders.fragment.title: '프래그먼트 셰이더'
+tutorial.shaders.guidance: '{vSInput}은 정점 셰이더의 입력을 정의합니다. {pOSITION0}과 {cOLOR0}이 시맨틱으로 속성을 식별하며, C# 필드 이름만으로는 이 대응 관계가 정해지지 않습니다.'
+tutorial.shaders.title: '셰이더 단계'
+tutorial.shaders.vertex.description: '{triangleSlang}에 정점 진입점을 추가합니다.'
+tutorial.shaders.vertex.details: 'GPU는 입력 정점마다 이 함수를 실행합니다. 함수는 네 번째 동차 좌표를 {w}로 설정하고 색상을 그대로 전달합니다. 정점 처리 후 위치를 w로 나누는데, 여기서는 1이므로 x, y, z가 그대로 유지되어 정규화 장치 좌표로 해석됩니다.'
+tutorial.shaders.vertex.title: '정점 셰이더'
+tutorial.surface.description: '네이티브 표시 대상을 {emphasis}에 지정합니다.'
+tutorial.surface.description.emphasis: '{windowCenter} 뒤, {windowRun} 앞'
+tutorial.surface.details: '프레임버퍼는 창에 표시할 픽셀 이미지입니다. {surface}는 네이티브 표시 대상과 크기를 설명하고 {framebufferSize}는 픽셀 단위 크기를 제공합니다. 화면 배율에 따라 창의 논리 크기와 다를 수 있습니다.'
+tutorial.surface.guidance: '서피스는 백엔드가 요구하는 네이티브 핸들을 제공합니다. Windows에서는 Win32 창, Linux에서는 X11 디스플레이와 창, macOS에서는 연결한 Metal 레이어를 사용합니다.'
+tutorial.surface.title: '네이티브 서피스'
+tutorial.swapChain.context: '스왑 체인은 컨텍스트와 창 모두에 의존합니다. {programCs} 끝의 {windowDispose}를 다음 해제 순서로 바꿉니다.'
+tutorial.swapChain.description: '서피스 설명 바로 다음에 스왑 체인을 생성합니다.'
+tutorial.swapChain.details: '{swapChain}은 창에 표시할 이미지를 관리합니다. {drawable}은 현재 이미지를 API의 이미지 리소스인 {texture}로 제공합니다. 렌더링 후 {present}가 이미지 표시를 요청하고 다음 출력 텍스처로 넘어갑니다.'
+tutorial.swapChain.guidance: '{b8G8R8A8UNorm}은 파랑, 초록, 빨강, 알파 순서로 8비트 채널 네 개를 저장합니다. {uNorm}은 정수 0–255를 0–1에 대응시킵니다. 셰이더 출력은 논리적인 RGBA 값이며, 포맷은 메모리에 저장하는 방식을 정합니다. 파이프라인의 색상 포맷은 {swapChainDescFormat}을 사용해 어태치먼트와 맞춥니다.'
+tutorial.swapChain.title: '스왑 체인'
+tutorial.title: 첫 번째 삼각형
+tutorial.troubleshooting.description: '콘솔 진단과 세 단계의 결과를 이용해 문제가 생긴 지점을 찾습니다.'
+tutorial.troubleshooting.details: '{link}와 {detail}에서 정점 데이터, 입력 레이아웃, 그리기를 확인할 수 있습니다. 예제는 창과 제출을 공통 호스트로 분리하지만, 이 튜토리얼은 전체 흐름을 {programCs}에 둡니다. {context}에서는 인덱스 그리기, 변환, 깊이 테스트를 소개합니다.'
+tutorial.troubleshooting.details.context: 회전하는 큐브
+tutorial.troubleshooting.details.detail: Slang 셰이더
+tutorial.troubleshooting.details.link: '예제 렌더러'
+tutorial.troubleshooting.table.backend.check: 선택한 백엔드가 장치 및 설치된 그래픽 드라이버에서 지원되는지 확인합니다.
+tutorial.troubleshooting.table.backend.symptom: 백엔드 생성 실패
+tutorial.troubleshooting.table.drawing.check: '업로드 크기, 입력 레이아웃, 파이프라인 선택 및 렌더 패스 내부의 그리기 명령 배치를 확인합니다.'
+tutorial.troubleshooting.table.drawing.symptom: 배경은 나타나지만 삼각형은 나타나지 않음
+tutorial.troubleshooting.table.headings.check: 확인 사항
+tutorial.troubleshooting.table.headings.symptom: 증상
+tutorial.troubleshooting.table.input.check: '스트라이드가 28바이트이고 위치와 색상의 오프셋이 각각 0과 12바이트인지 확인합니다.'
+tutorial.troubleshooting.table.input.symptom: 정점 위치 또는 색상이 손상됨
+tutorial.troubleshooting.table.shaders.check: 복사된 {triangleSlang}, 진입점 철자 및 컴파일러에서 보고된 진단을 확인합니다.
+tutorial.troubleshooting.table.shaders.symptom: '배경색은 표시되지만 셰이더를 불러오지 못함'
+tutorial.troubleshooting.table.window.check: '.NET 프로젝트가 빌드되고 창 전용 단계가 데스크톱 세션에서 실행되는지 확인합니다. Linux의 경우 X11 또는 XWayland를 사용할 수 있는지 확인합니다.'
+tutorial.troubleshooting.table.window.symptom: '창이 열리지 않음'
+tutorial.troubleshooting.title: '문제 진단'
+tutorial.window.description: '{cocoaHelperCs}를 다운로드해 프로젝트에 추가합니다. 이 보조 클래스는 macOS 창에 Metal 레이어를 연결합니다.'
+tutorial.window.details: '자동 생성된 {programCs} 내용을 다음 코드로 바꿉니다. 최상위 문을 사용하므로 {main} 메서드를 직접 작성하지 않아도 위에서 아래로 실행됩니다. 이후 각 절에서 새 코드를 넣을 위치를 안내합니다.'
+tutorial.window.guidance: '{graphicsAPINone}은 그래픽스 컨텍스트 생성을 Zenith.NET에 맡깁니다. {initialize}는 네이티브 창을 만들고 창 시스템 핸들을 사용할 수 있게 합니다. {center}는 창을 화면 가운데 놓습니다. {run}이 창을 닫을 때까지 이벤트를 처리한 뒤 {dispose}가 실행되어 창을 해제합니다.'
+tutorial.window.notes: '애플리케이션을 빌드하고 실행합니다. 창을 열고 닫을 수 있는지, 이동하거나 크기를 바꿀 수 있는지 확인합니다. 아직 렌더링 명령을 제출하지 않아 창 내부의 내용은 정의되지 않았습니다. 다음 단계로 넘어가기 전에 창을 닫습니다.'
+tutorial.window.title: '창 생성'
+ui.action.copy: 복사
+ui.action.navigate: 탐색
+ui.action.open: 열기
+ui.action.source: 소스 보기
+ui.api.browse: 네임스페이스 및 형식을 탐색합니다.
+ui.api.clearTypeSearch: 형식 검색 지우기
+ui.api.findType: 형식 찾기
+ui.api.loadError: 형식을 로드할 수 없습니다. 이 페이지를 새로 고쳐 다시 시도하세요.
+ui.api.loadingNamespaces: 네임스페이스 로드 중…
+ui.api.loadingTypes: 형식 로드 중…
+ui.api.matchingTypes: 일치하는 형식
+ui.api.namespace: 네임스페이스
+ui.api.namespaces: 네임스페이스
+ui.api.noNamespaceTypes: 이 네임스페이스에는 형식이 없습니다.
+ui.api.noTypes: 일치하는 형식이 없습니다.
+ui.api.types: 형식
+ui.content.reference: 참조
+ui.content.source: 소스 코드
+ui.content.tutorial: 튜토리얼
+ui.contents.close: 목차 닫기
+ui.contents.open: 목차 열기
+ui.contents.title: 목차
+ui.footer.caption: '.NET을 위한 현대적인 RHI'
+ui.footer.label: 바닥글
+ui.language.label: 언어
+ui.language.unavailable: 번역되지 않음
+ui.members.clear: 멤버 검색 지우기
+ui.members.close: 멤버 검색기 닫기
+ui.members.empty: 일치하는 멤버가 없습니다.
+ui.members.open: 멤버로 이동
+ui.members.placeholder: 이름 또는 시그니처…
+ui.members.search: 멤버 검색
+ui.members.title: 멤버 찾기
+ui.members.undeclared: 이 형식에는 선언된 멤버가 없습니다.
+ui.navigation.api: API
+ui.navigation.concepts: 개념
+ui.navigation.home: 홈
+ui.navigation.label: 기본 탐색
+ui.navigation.skip: 본문으로 건너뛰기
+ui.navigation.toggle: 탐색 전환
+ui.outline.title: 이 페이지의 내용
+ui.reference.assembly: 어셈블리
+ui.reference.classes: 클래스
+ui.reference.constructors: 생성자
+ui.reference.declaration: 'C# 형식 선언'
+ui.reference.delegates: 대리자
+ui.reference.derived: 파생 형식
+ui.reference.enums: 열거형
+ui.reference.events: 이벤트
+ui.reference.examples: 예제
+ui.reference.exceptions: 예외
+ui.reference.extensionMethods: 확장 메서드
+ui.reference.fields: 필드
+ui.reference.implementations: 명시적 인터페이스 구현
+ui.reference.implements: 구현
+ui.reference.inheritance: 상속
+ui.reference.inheritedMembers: 상속된 멤버
+ui.reference.interfaces: 인터페이스
+ui.reference.metadata: 형식 메타데이터
+ui.reference.methods: 메서드
+ui.reference.namespace: 네임스페이스
+ui.reference.open: '{name} 참조 열기'
+ui.reference.operators: 연산자
+ui.reference.parameters: 매개 변수
+ui.reference.properties: 속성
+ui.reference.related: 참고 항목
+ui.reference.relationships: 상속 및 메타데이터
+ui.reference.remarks: 설명
+ui.reference.returns: 반환 값
+ui.reference.structs: 구조체
+ui.reference.typeParameters: 형식 매개 변수
+ui.reference.value: 값
+ui.reference.values: 값
+ui.search.close: 검색 닫기
+ui.search.empty: “{query}”에 대한 결과가 없습니다.
+ui.search.hint: 튜토리얼, 개념, 예제 및 API 참조를 검색합니다.
+ui.search.loading: 검색 로드 중…
+ui.search.more: 결과 더 보기
+ui.search.pending: 검색 중…
+ui.search.placeholder: 무엇을 찾으시나요?
+ui.search.resultCount: '결과: {count}개'
+ui.search.title: 문서 검색
+ui.search.unavailable: 검색을 사용할 수 없습니다. 아래 페이지를 계속 열 수 있습니다.
diff --git a/documents/locales/ko-KR/translation.md b/documents/locales/ko-KR/translation.md
new file mode 100644
index 00000000..d1fd58e3
--- /dev/null
+++ b/documents/locales/ko-KR/translation.md
@@ -0,0 +1,85 @@
+# 한국어 번역 지침
+
+## 리소스와 구조
+
+[영문 리소스 사전](../en-US/strings.yml)을 유일한 원문으로 사용합니다. 프로젝트 규칙은 [문서 유지 관리 지침](../../maintenance.md)을 참고합니다. 새 번역을 시작할 때는 영문 파일 전체를 이 디렉터리의 `strings.yml`로 복사하고, 첫 줄의 `### YamlMime:Resources`와 UTF-8 인코딩을 유지합니다. 이후에는 영문 변경 사항을 기존 번역에 반영하며, 번역 파일 전체를 원문으로 덮어쓰지 않습니다. 이 `translation.md`는 번역 후에도 보존하며 사이트에는 게시하지 않습니다.
+
+탐색 구조, 장 순서, 앵커, 링크, 서식, 코드 및 이미지는 `locales/` 밖에서 공통으로 관리합니다. 이 디렉터리에 문서, TOC, 템플릿, 스크립트 또는 설정을 추가하지 않습니다.
+
+번역 시에는 이 언어의 `strings.yml` 값만 수정합니다. 키 집합은 영문과 정확히 같아야 합니다. 문구가 같아 보이거나 아직 화면에서 보지 못한 키도 임의로 추가, 삭제하거나 이름을 바꾸지 않습니다. 원문에 문제가 있으면 해당 키와 문제를 유지 관리자에게 알립니다. 영문과 공유 페이지를 먼저 수정한 다음 번역에 반영합니다.
+
+## 문체와 문맥
+
+기술 교재에 맞게 정확하고 차분하며 이해하기 쉬운 문장으로 씁니다. 작업 사이의 관계를 먼저 설명하고 조건과 제약을 이어서 제시합니다. 튜토리얼은 구체적인 절차와 확인 가능한 결과를 유지합니다. 학술적 정확성을 위해 수동태나 긴 문장을 늘릴 필요는 없습니다.
+
+기사는 앞뒤 문단, 표, 코드와 함께 읽는 순서대로 검토합니다. 리소스 키는 저장 경계일 뿐 독립된 번역 단위가 아닙니다. 링크와 강조 구절을 완성된 문장에 넣고 조사와 문장 연결을 확인합니다.
+
+제목, UI 레이블, 홈페이지의 짧은 소개와 바닥글 문구에는 마침표를 붙이지 않습니다. 본문, 진단, 접근성 안내는 정상적인 문장 부호를 유지합니다. 개념마다 통용되는 용어 하나를 일관되게 쓰고 일부 용어에만 영어를 괄호로 덧붙이지 않습니다. API, 파일, 실제 설정 항목의 식별자는 유지합니다.
+
+문장을 남길지는 현재 작업, 설계 선택, 확인 가능한 결과, 올바른 사용 조건을 설명하는지로 판단합니다. 사용하지 않는 기능 목록, 막연한 환경 주의사항, 반복된 전제는 덜어 냅니다. 제목과 스타일로 나뉜 구절도 하나로 번역해 한국어 어순을 따릅니다. 홈페이지 제목의 `{accent}`와 `{lineBreak}`는 옮길 수 있으며 강조와 줄바꿈이 영어 어순을 강제해서는 안 됩니다. 문단을 삭제할 때는 유지 관리자가 영어 원문, 공통 바인딩, 모든 언어의 키를 함께 정리합니다.
+
+## 텍스트와 자리 표시자
+
+문장이나 문단 전체를 명확하고 자연스러운 한국어로 번역합니다. 전제 조건, 부정 표현, 단위, 작업 순서 및 수명 제약을 유지합니다. 원문에 없는 기술적 보장, 플랫폼 요구 사항, 패키지 버전 또는 릴리스 설명을 추가하지 않습니다. 제품명, 패키지명, 파일명과 C# 및 Slang 식별자는 대소문자까지 그대로 유지합니다.
+
+모든 값은 비어 있지 않은 일반 텍스트 문자열이어야 하며 HTML이나 Markdown을 넣지 않습니다. YAML 따옴표와 이스케이프 규칙을 지키고, 콜론, 샵 기호 또는 숫자처럼 보이는 값이 잘못 해석되지 않도록 필요할 때 따옴표를 사용합니다. 검토하기 쉽도록 영문의 키 순서도 유지합니다.
+
+`{context}`, `{link}` 같은 이름 있는 자리 표시자는 공유 페이지의 코드, 링크 또는 강조된 내용을 삽입할 수 있습니다. UI의 `{name}`, `{query}`, `{count}` 등에는 실행 중 일반 텍스트가 들어갈 수도 있습니다. 이름만으로 내용을 추측하지 말고 해당 페이지나 UI에서 쓰이는 문맥을 확인합니다. 링크 레이블이나 강조 문구에 별도의 리소스 키가 있으면 그 값도 번역합니다.
+
+한국어 어순에 맞게 자리 표시자를 옮길 수 있습니다. 단, 각 키의 모든 이름과 대소문자, ASCII 중괄호를 유지해야 합니다. 자리 표시자를 추가, 삭제하거나 이름을 바꾸지 않으며, 화면에 보이는 API 이름, 링크 문구 또는 예시 숫자로 대체하지 않습니다. 문장을 재구성하려고 리소스 키를 나누지 않습니다.
+
+## API 링크와 기술적 의미
+
+링크는 공유 페이지에서 관리합니다. 본문과 표에서 사이트 내 API를 명확히 가리키는 식별자는 해당 형식이나 멤버로 연결됩니다. 변수, 매개 변수 이름, 예제에서 정의한 형식, 패키지명, 파일명 및 사이트 내 참조 페이지가 없는 외부 기호는 일반 코드 텍스트로 유지합니다. 철자가 같아도 같은 기호는 아닐 수 있습니다. 일반적인 튜토리얼 코드 블록에는 링크를 자동으로 추가하지 않습니다. 자리 표시자 전체를 옮길 수는 있지만 링크 대상, 공유 코드 및 서식은 바꾸지 않습니다.
+
+API의 참조 항목이 생성되지 않은 경우, 공유 페이지에서 식별자를 일반 코드 텍스트로 두고 주변에 소스 코드 링크를 명확히 제공합니다. 다른 형식이나 멤버의 링크로 대체하지 않습니다.
+
+특히 다음 차이를 유지합니다.
+
+- 명령 기록, 제출, GPU 실행, 완료는 서로 다른 단계입니다.
+- 리소스 용도, 메모리 상주 특성 또는 의도한 CPU 접근, 텍스처 레이아웃, 픽셀 형식은 서로 다른 개념입니다.
+- 소유권은 리소스를 해제할 책임을 뜻합니다. 핸들만 보유한다고 리소스의 수명이 유지되는 것은 아니며, 명령 제출로 소유권이 이전되지도 않습니다.
+- GPU 작업이 완료되어도 임시 리드백 데이터가 애플리케이션의 CPU 대상에 복사되었다고 단정할 수 없습니다. 작업을 관리하는 큐에서 기다려야 한다는 원문의 설명을 유지합니다.
+- drawable은 현재 프레임의 출력 텍스처이며 항상 스왑 체인 이미지인 것은 아닙니다. 리소스 뷰와 UI 컨트롤도 문맥에 맞게 구분합니다.
+
+## 빌드와 제출 전 검토
+
+프로젝트에 필요한 .NET SDK와 DocFX가 설치된 환경에서 저장소 루트에서 명령을 실행합니다. 새로 체크아웃한 저장소에는 `documents/api/`의 생성된 메타데이터가 아직 없습니다. 먼저 API 추출을 포함한 전체 빌드를 실행합니다.
+
+```sh
+docfx documents/docfx.json --warningsAsErrors
+```
+
+API 메타데이터가 이미 생성되어 있고 현재 소스 코드와 일치하면, 사전만 수정할 때 일반 빌드와 미리 보기를 사용할 수 있습니다.
+
+```sh
+docfx build documents/docfx.json --warningsAsErrors
+docfx serve documents/_site --hostname 127.0.0.1 --port 8080
+```
+
+이 언어는 이미 등록되어 있습니다. 완전한 사전으로 빌드가 성공하면 설정 변경 없이 사용할 수 있습니다. 누락되거나 추가된 키, 빈 값, 문자열이 아닌 값, 일치하지 않는 자리 표시자 이름은 빌드 오류를 일으킵니다. 빌드가 부정확한 번역이나 번역되지 않은 영어를 판단하지는 못하므로 사람이 전체 내용을 읽고 검토해야 합니다.
+
+모든 언어는 같은 페이지 경로를 사용하며 `?lang=ko-KR`로 한국어를 선택합니다. 언어를 바꿔도 경로와 앵커가 유지됩니다. 명시적으로 지정하지 않으면 저장된 수동 선택, 브라우저 언어 설정, 영어 순서로 선택합니다. 이 번역이 아직 게시되지 않았거나 사전 로드 또는 페이지 리소스 검증이 실패하면 문장별로 언어를 섞지 않고 페이지 전체를 영어로 표시합니다. 언어 목록은 언어 코드순으로 정렬합니다.
+
+제출 전에 키와 자리 표시자를 대조하고, 데스크톱 및 320픽셀 너비에서 홈, Learn 시작 페이지, 삼각형 튜토리얼, 개념, 예제, API UI를 확인합니다. 초보자의 순서로 튜토리얼을 읽으며 용어, 버튼, 검색, 코드 복사, API 링크, 언어 전환 후의 절 위치를 점검합니다. 식별자, 선언 및 코드는 공통으로 유지하고 사전이 제공하는 본문과 UI 문구만 번역합니다. 테스트 사전과 임시 검증 파일은 저장소 밖에 두고 임시 문구를 채운 번역을 제출하지 않습니다.
+
+## 용어
+
+| 영어 | 사용할 표현 |
+| ---------------- | -------------------------------------------------------------------------------------- |
+| API / GPU / RHI | 약어를 유지하고 원문에 따라 설명 |
+| barrier | 배리어 |
+| buffer | 버퍼. `Buffer`는 유지 |
+| command buffer | 명령 버퍼 |
+| command queue | 명령 큐 |
+| drawable | 현재 프레임의 출력 텍스처. `Drawable`은 유지하며 항상 스왑 체인 이미지로 해석하지 않음 |
+| graphics context | 그래픽스 컨텍스트 |
+| pipeline | 파이프라인 |
+| readback | 리드백 |
+| render pass | 렌더 패스 |
+| resource | 리소스 |
+| shader | 셰이더 |
+| swap chain | 스왑 체인 |
+| texture | 텍스처 |
+| timeline | 타임라인 |
+| upload | 업로드 |
diff --git a/documents/locales/pt-BR/strings.yml b/documents/locales/pt-BR/strings.yml
new file mode 100644
index 00000000..065cd8fa
--- /dev/null
+++ b/documents/locales/pt-BR/strings.yml
@@ -0,0 +1,593 @@
+### YamlMime:Resources
+concepts.execution.context.description: 'Um {graphicsContext} conecta a aplicação a um dispositivo GPU por meio de um backend específico e fornece suas filas gráfica, de computação e de transferência. Recursos usados em conjunto devem pertencer ao mesmo contexto; buffers, texturas, shaders, pipelines e seus identificadores não podem ser vinculados entre contextos diferentes.'
+concepts.execution.context.details: '{capabilities} informa o nome do dispositivo e as funcionalidades disponíveis. Consulte {rayTracingSupported} e {meshShadingSupported} antes de criar os recursos necessários a essas operações. Os exemplos de {link} e {detail} fazem essas verificações durante a inicialização.'
+concepts.execution.context.details.detail: 'sombreamento de malha'
+concepts.execution.context.details.link: 'traçado de raios'
+concepts.execution.context.title: 'Contexto gráfico'
+concepts.execution.description: 'O trabalho da GPU é preparado na CPU: a aplicação cria recursos, registra comandos que os utilizam e envia esses comandos a uma fila. Após o envio, a CPU pode continuar enquanto a GPU executa o trabalho. Os recursos precisam permanecer disponíveis até o fim dessa execução.'
+concepts.execution.ownership.description: 'A propriedade define quem libera um objeto; a conclusão do trabalho na GPU determina quando isso pode ocorrer. Chamar {dispose} libera os recursos nativos mesmo que ainda existam referências C# ao objeto. Enviar comandos não transfere à fila a propriedade dos recursos utilizados.'
+concepts.execution.ownership.details: 'Ao encerrar a aplicação, pare de enviar trabalho e espere o último uso dos recursos em cada fila envolvida. Depois, libere os recursos da aplicação e o contexto. O contexto libera suas filas e estruturas auxiliares internas, portanto essa última etapa deve ocorrer após a conclusão de todo o trabalho da aplicação.'
+concepts.execution.ownership.guidance: 'Durante a renderização, acompanhe a conclusão do trabalho que utiliza dados a serem sobrescritos ou recursos a serem substituídos. Espere esses usos terminarem antes de modificar ou liberar o armazenamento afetado. {link} explica como esse modelo de tempo de vida se aplica às visualizações e aos recursos que as sustentam.'
+concepts.execution.ownership.guidance.link: 'Gerenciamento de recursos'
+concepts.execution.ownership.table.applicationResources.object: 'Recursos criados pela aplicação'
+concepts.execution.ownership.table.applicationResources.ownership: 'A aplicação os libera após o último uso na GPU.'
+concepts.execution.ownership.table.commandBuffers.object: 'Buffers de comandos emprestados'
+concepts.execution.ownership.table.commandBuffers.ownership: 'A fila os gerencia e recicla.'
+concepts.execution.ownership.table.headings.object: 'Objeto'
+concepts.execution.ownership.table.headings.ownership: 'Responsabilidade pela liberação'
+concepts.execution.ownership.table.queues.object: 'Filas do contexto e suas linhas do tempo'
+concepts.execution.ownership.table.queues.ownership: 'O contexto e as filas as gerenciam.'
+concepts.execution.ownership.table.swapChainDrawable.ownership: 'A cadeia de troca o gerencia.'
+concepts.execution.ownership.title: 'Responsabilidade e tempo de vida'
+concepts.execution.pipelines.description: 'Um pipeline gráfico define os shaders, a disposição dos atributos de vértice, a montagem de primitivas, os formatos dos anexos e o estado usado nas operações de desenho. Selecione o pipeline antes de vincular dados de vértices, índices ou constantes. Os formatos dos anexos e a quantidade de amostras devem corresponder aos da passagem de renderização.'
+concepts.execution.pipelines.details: 'Um pipeline de computação seleciona o ponto de entrada do shader executado por um despacho. Vincule o pipeline e suas constantes antes do despacho, fora de uma passagem de renderização gráfica.'
+concepts.execution.pipelines.guidance: 'O {link} apresenta a sequência gráfica. O {detail} acrescenta um despacho de computação antes da passagem que exibe seu resultado.'
+concepts.execution.pipelines.guidance.detail: 'exemplo de computação'
+concepts.execution.pipelines.guidance.link: 'callback de renderização do triângulo'
+concepts.execution.pipelines.title: 'Estado do pipeline'
+concepts.execution.queues.description: 'A {graphicsQueue} costuma ser o ponto de partida para um quadro com desenho, computação e cópias. Manter operações dependentes na mesma fila simplifica a ordenação.'
+concepts.execution.queues.details: 'Filas distintas permitem enviar cargas de trabalho independentes separadamente, mas nem sempre correspondem a mecanismos de hardware independentes. Use filas adicionais quando houver benefício em sobrepor a execução. Se uma fila utilizar dados produzidos por outra, estabeleça uma dependência explícita entre seus envios.'
+concepts.execution.queues.guidance: 'Dentro de uma fila, operações dependentes também podem precisar de barreiras ou transições de textura para que os acessos posteriores vejam as escritas anteriores. Essas dependências de memória são diferentes da ordem de envio; {link} explica como expressá-las.'
+concepts.execution.queues.guidance.link: 'Sincronização'
+concepts.execution.queues.table.computeQueue.workToRecord: 'Despachos de computação e trabalho de transferência compatível, sem passagens de renderização gráfica.'
+concepts.execution.queues.table.graphicsQueue.workToRecord: 'Passagens de renderização, desenho, computação e transferências.'
+concepts.execution.queues.table.headings.queue: 'Fila'
+concepts.execution.queues.table.headings.workToRecord: 'Operações aceitas'
+concepts.execution.queues.table.transferQueue.workToRecord: 'Uploads, downloads e cópias, sem desenho ou despachos de computação.'
+concepts.execution.queues.title: 'Seleção de filas'
+concepts.execution.recording.context: 'A fila recupera os buffers de comandos concluídos para reutilizá-los. Obtenha um com {queueCommandBuffer} para cada registro de comandos, use uma thread por vez e envie-o uma única vez. Após o envio, deixe seu gerenciamento com a fila: não o modifique, envie novamente ou libere.'
+concepts.execution.recording.description: '{queueCommandBuffer} retorna um {commandBuffer} já em gravação. Registre as operações e suas dependências e chame {submit} uma vez. O envio encerra a gravação e retorna um {timelineValue}, que identifica o ponto de conclusão naquela fila.'
+concepts.execution.recording.details: 'Após registrar o quadro em {commandBuffer}, o tutorial do triângulo envia os comandos e aguarda a conclusão:'
+concepts.execution.recording.guidance: 'O registro monta a sequência de comandos; {submit} agenda sua execução. A chamada seguinte a {wait} bloqueia a thread da CPU até esse envio terminar. Isso facilita a compreensão do loop do tutorial, pois cada quadro termina antes do início do próximo.'
+concepts.execution.recording.title: 'Gravação e envio'
+concepts.execution.title: 'Modelo de execução'
+concepts.platforms.description: 'A apresentação pode ser gerenciada pela aplicação ou por um controle de interface. Essa escolha define quem obtém a textura de saída do quadro, registra e envia os comandos e apresenta o resultado. Essa textura de saída é o drawable.'
+concepts.platforms.frame.context: 'Registre as vinculações de pipeline e os comandos de desenho dentro da passagem e encerre-a antes de o manipulador retornar. O controle envia os comandos e apresenta o resultado. O manipulador não deve enviar o buffer de comandos nem liberar qualquer um dos objetos recebidos. Se alterar o layout da textura de saída, restaure {colorAttachment} antes de retornar para que o controle realize a transição final.'
+concepts.platforms.frame.description: 'Os controles implementam {iZenithView}. A aplicação atribui {graphicsContext}, atualiza a cena em {updateRequested} e registra comandos em {renderRequested} usando o {renderEventArgs} recebido.'
+concepts.platforms.frame.details: 'A cada quadro, o controle obtém uma textura de saída e um buffer de comandos, faz a transição da textura para {colorAttachment} e chama os manipuladores de atualização e renderização. Depois, envia o trabalho registrado e apresenta a imagem pelo mecanismo do framework.'
+concepts.platforms.frame.guidance: 'Adicione {usingZenithNET} ao arquivo. Para uma instância de {iZenithView} chamada {view}, já inicializada e com um contexto gráfico atribuído, registre este manipulador para limpar sua textura de saída:'
+concepts.platforms.frame.notes: 'A transição inicial usa {undefined}, que permite descartar o conteúdo anterior da imagem. Desenhe ou limpe a imagem inteira a cada quadro. Mantenha resultados acumulados ou em cache em uma textura da aplicação e depois a desenhe na textura de saída do controle.'
+concepts.platforms.frame.reference: '{zenithViewHelperDrawableFormat} fornece o formato de cor padrão dessas integrações.'
+concepts.platforms.frame.title: 'Renderização em controles de interface'
+concepts.platforms.integration.context: 'WinForms, WPF e a integração Windows do WinUI executam no Windows. Para MAUI, instale a carga de trabalho da plataforma de destino e registre o manipulador do controle com {useZenithView}, como mostra {extensionsCs}.'
+concepts.platforms.integration.description: 'A interface de eventos é comum, mas o mecanismo de apresentação depende do framework:'
+concepts.platforms.integration.details: 'Essas implementações diferem na forma de levar a imagem da GPU à interface. A leitura como bitmap copia os pixels para a memória da CPU antes da exibição. A interoperabilidade por texturas compartilhadas evita essa cópia, mas ainda pode exigir cópias na GPU e sincronização. Ao escolher uma integração, considere esse caminho de apresentação além do evento comum {renderRequested}.'
+concepts.platforms.integration.guidance: 'Adicione pelo NuGet o pacote de integração correspondente ao seu framework de interface. Para Avalonia, use {avaloniaPackage}.'
+concepts.platforms.integration.table.avalonia.integration: 'Avalonia'
+concepts.platforms.integration.table.avalonia.presentation: 'Renderiza em uma textura e copia os pixels para a memória da CPU em {writeableBitmap}. Implementação: {zenithViewCs} e {surfaceCs}.'
+concepts.platforms.integration.table.headings.integration: 'Integração'
+concepts.platforms.integration.table.headings.presentation: 'Mecanismo de apresentação'
+concepts.platforms.integration.table.maui.integration: '.NET MAUI'
+concepts.platforms.integration.table.maui.presentation: 'Usa superfícies nativas no Android, iOS e Mac Catalyst, e composição de textura compartilhada no Windows. Consulte as {link}.'
+concepts.platforms.integration.table.maui.presentation.link: 'implementações de plataforma'
+concepts.platforms.integration.table.uno.integration: 'Caminho Uno'
+concepts.platforms.integration.table.uno.presentation: 'Copia os pixels para a memória da CPU em {writeableBitmap}. Implementação: {zenithViewUnoCs}.'
+concepts.platforms.integration.table.winForms.integration: 'WinForms'
+concepts.platforms.integration.table.winForms.presentation: 'Cria uma cadeia de troca do Zenith.NET para o {hWND} do controle. Consulte {zenithViewCs}.'
+concepts.platforms.integration.table.winUI.integration: 'WinUI no Windows'
+concepts.platforms.integration.table.winUI.presentation: 'Renderiza em uma textura Direct3D 11 compartilhada importada e copia o resultado para uma cadeia de troca de composição conectada a {swapChainPanel}. Consulte {zenithViewWinUICs}.'
+concepts.platforms.integration.table.wpf.integration: 'WPF'
+concepts.platforms.integration.table.wpf.presentation: 'Importa uma textura Direct3D 11 compartilhada e copia seu conteúdo, por meio da interoperabilidade com Direct3D, para uma superfície exibida por {d3DImage}. Consulte {surfaceCs}.'
+concepts.platforms.integration.title: 'Framework e apresentação'
+concepts.platforms.lifecycle.context: 'Ao criar anexos compatíveis, use {descWidth}, {descHeight}, {descFormat} e {descSampleCount} da textura de saída fornecida pelo evento de renderização. Essa textura informa as dimensões reais do destino; elas não devem ser deduzidas do tamanho de layout do controle nem da escala de exibição.'
+concepts.platforms.lifecycle.description: 'A cadeia de troca usa a largura e a altura do framebuffer em pixels, que podem diferir das dimensões lógicas da janela quando há escala de exibição. Adie a renderização e o redimensionamento da cadeia enquanto alguma dimensão for zero. Obtenha {swapChainDrawable} a cada quadro: a textura pertence à cadeia de troca, que pode substituí-la após apresentar ou redimensionar.'
+concepts.platforms.lifecycle.details: 'Quando o tamanho do framebuffer mudar, espere os usos pendentes das imagens antigas na GPU antes de chamar {swapChainResize}. Para substituir o destino nativo de apresentação, aguarde esses usos da mesma forma e passe a nova descrição da superfície e seus identificadores a {swapChainRefresh}. No encerramento, libere a cadeia de troca antes de destruir seu destino nativo.'
+concepts.platforms.lifecycle.guidance: 'Se a aplicação tiver texturas de profundidade ou fora da tela que acompanham o tamanho da janela, recrie-as quando esse tamanho mudar e atualize as constantes que contêm seus identificadores. O pipeline pode ser reutilizado enquanto os formatos e as quantidades de amostras dos anexos continuarem correspondentes. O {link} mostra essa separação entre as texturas redimensionadas e o pipeline reutilizável.'
+concepts.platforms.lifecycle.guidance.link: 'renderizador de cubo giratório'
+concepts.platforms.lifecycle.title: 'Dimensões e tempo de vida da cadeia de troca'
+concepts.platforms.ownership.description: 'O contexto atribuído e os recursos de renderização pertencem à aplicação; os recursos de apresentação pertencem ao controle. Quando {graphicsContext} muda na visualização, o agendador recria os recursos do controle para o novo contexto. Recrie também os pipelines, buffers e texturas da aplicação para esse contexto.'
+concepts.platforms.ownership.details: 'Antes de substituir recursos de renderização, interrompa o processamento de quadros da visualização e espere o trabalho da GPU que os utiliza. Só então libere os recursos ou o contexto antigos. Ao remover a visualização, a aplicação continua responsável por liberar o contexto atribuído a ela.'
+concepts.platforms.ownership.guidance: 'O componente compartilhado {frameSchedulerCs} executa os manipuladores de quadro pelo dispatcher da interface. Concentre esses manipuladores na atualização da cena e no registro de comandos: um processamento prolongado na CPU também atrasa a resposta da interface.'
+concepts.platforms.ownership.title: 'Responsabilidades da aplicação e do controle'
+concepts.platforms.surface.description: 'O {link} usa Silk.NET para criar a janela e processar seus eventos. O Zenith.NET recebe os identificadores nativos e as dimensões em uma descrição {surface}, usada para criar uma {swapChain}. A aplicação continua responsável pela janela do sistema operacional.'
+concepts.platforms.surface.description.link: 'tutorial do triângulo'
+concepts.platforms.surface.details: 'Os identificadores da superfície devem corresponder ao backend:'
+concepts.platforms.surface.guidance: 'O programa hospedeiro em {appCs} oferece suporte a Windows, macOS e X11. Sua janela Linux fornece identificadores Xlib, portanto uma sessão Wayland precisa de XWayland para executá-lo. Uma aplicação nativa de Wayland fornece os identificadores de display e superfície por meio de {surfaceWayland}.'
+concepts.platforms.surface.table.android.backend: 'Vulkan no Android.'
+concepts.platforms.surface.table.apple.backend: 'Metal nas plataformas Apple.'
+concepts.platforms.surface.table.headings.backend: 'Backend e plataforma'
+concepts.platforms.surface.table.headings.factory: 'Fábrica de superfícies'
+concepts.platforms.surface.table.headings.handles: 'Identificadores fornecidos pelo programa hospedeiro'
+concepts.platforms.surface.table.wayland.backend: 'Vulkan com um host nativo Wayland.'
+concepts.platforms.surface.table.wayland.handles: 'Display e superfície Wayland'
+concepts.platforms.surface.table.win32.backend: 'DirectX 12 ou Vulkan no Windows.'
+concepts.platforms.surface.table.xlib.backend: 'Vulkan no Linux com X11 ou XWayland.'
+concepts.platforms.surface.table.xlib.handles: 'Display e janela X11'
+concepts.platforms.surface.title: 'Superfícies nativas de apresentação'
+concepts.platforms.title: 'Integração de plataforma'
+concepts.resources.allocation.context: 'Os recursos colocados em um heap dependem de seu armazenamento durante todo o tempo de vida. Depois do último uso na GPU, libere os recursos antes de liberar o heap.'
+concepts.resources.allocation.description: 'Use um {heap} quando a aplicação precisar gerenciar as alocações por conta própria. Um heap fornece armazenamento no qual os recursos são colocados em deslocamentos explícitos em bytes; a aplicação escolhe esses deslocamentos e acompanha as regiões ocupadas.'
+concepts.resources.allocation.details: 'Para cada recurso, chame {contextGetSizeAndAlignment} com a descrição de buffer ou textura que será usada na criação. Reserve o tamanho retornado em um deslocamento múltiplo do alinhamento informado, dentro dos limites do heap e sem sobrepor outra alocação ainda em uso. Texturas podem incluir preenchimento específico do backend; por isso, a quantidade de pixels e os bytes por pixel não bastam para calcular o tamanho da alocação.'
+concepts.resources.allocation.guidance: 'Um buffer colocado em um heap deve declarar a mesma residência desse heap. Para armazenar texturas, use um heap exclusivo da GPU.'
+concepts.resources.allocation.title: 'Alocação em heaps'
+concepts.resources.bufferUsages.table.constant.role: 'Parâmetros de shader lidos por meio de uma vinculação de buffer de constantes.'
+concepts.resources.bufferUsages.table.headings.role: 'Função'
+concepts.resources.bufferUsages.table.headings.usage: 'Uso'
+concepts.resources.bufferUsages.table.index.role: 'Índices usados pelo desenho indexado.'
+concepts.resources.bufferUsages.table.indirect.role: 'Argumentos lidos por comandos de desenho ou despacho indiretos.'
+concepts.resources.bufferUsages.table.none.role: 'Nenhum indicador de uso de buffer é declarado.'
+concepts.resources.bufferUsages.table.storageReadOnly.role: 'Dados de buffer acessados por uma vinculação de shader somente leitura.'
+concepts.resources.bufferUsages.table.storageReadWrite.role: 'Dados de buffer acessados por uma vinculação de shader de leitura e gravação.'
+concepts.resources.bufferUsages.table.transferDst.role: 'Armazenamento de destino para uma cópia de GPU.'
+concepts.resources.bufferUsages.table.transferSrc.role: 'Dados de origem para uma cópia de GPU.'
+concepts.resources.bufferUsages.table.vertex.role: 'Atributos de vértice buscados para desenho.'
+concepts.resources.bufferUsages.title: 'Usos de buffer'
+concepts.resources.description: 'A descrição de um recurso define como a aplicação pode usar seu armazenamento. Por exemplo, uma textura usada como destino de renderização, amostrada por um shader e copiada de volta à CPU precisa dos indicadores de uso das três operações. A localização da memória determina o acesso da CPU; a sincronização e o gerenciamento do tempo de vida determinam quando os dados podem ser acessados ou o armazenamento liberado.'
+concepts.resources.memory.context: 'Nos buffers acessíveis pela CPU, {map} fornece o endereço pelo qual os dados são lidos ou escritos. Nem o mapeamento nem {unmap} esperam o trabalho da GPU: antes de ler um resultado, a aplicação precisa esperar a escrita que o produz; antes de modificar os mesmos bytes, precisa esperar os acessos pendentes da GPU.'
+concepts.resources.memory.description: 'O campo {residency} de {bufferDesc} especifica o acesso pretendido pela CPU:'
+concepts.resources.memory.details: 'A residência expressa os requisitos de acesso da CPU, inclusive em sistemas em que CPU e GPU compartilham memória física. O triângulo utiliza {cpuWriteOnly} para inicializar diretamente seu pequeno buffer de vértices. Para geometrias estáticas maiores, pode ser mais adequado enviar os dados para armazenamento exclusivo da GPU.'
+concepts.resources.memory.guidance: 'Texturas criadas diretamente pelo contexto usam armazenamento da GPU. Por isso, {textureDesc} não possui um campo de residência; a aplicação troca seus dados com a memória da CPU por meio de uploads e downloads.'
+concepts.resources.memory.table.cpuReadOnly.cPUAccess: 'Somente leitura'
+concepts.resources.memory.table.cpuReadOnly.typicalRole: 'Dados produzidos pela GPU para leitura na CPU'
+concepts.resources.memory.table.cpuWriteOnly.cPUAccess: 'Somente escrita'
+concepts.resources.memory.table.cpuWriteOnly.typicalRole: 'Dados fornecidos ou atualizados pela CPU'
+concepts.resources.memory.table.gpuOnly.cPUAccess: 'Nenhum'
+concepts.resources.memory.table.gpuOnly.typicalRole: 'Geometria estática e dados de trabalho da GPU'
+concepts.resources.memory.table.headings.cPUAccess: 'Acesso previsto da CPU'
+concepts.resources.memory.table.headings.residency: 'Residência'
+concepts.resources.memory.table.headings.typicalRole: 'Função típica'
+concepts.resources.memory.title: 'Acesso pela CPU e localização na memória'
+concepts.resources.textureUsages.description: 'Os indicadores de transferência descrevem se o recurso é a origem ou o destino de uma cópia na GPU. A possibilidade de acesso direto pela CPU é uma decisão separada, tomada ao escolher a localização da memória.'
+concepts.resources.textureUsages.details: 'Os indicadores de uso declaram as funções que uma textura pode desempenhar. Alternar entre elas exige os cuidados de {link} correspondentes, e o dispositivo escolhido deve oferecer suporte a esses usos para o formato da textura.'
+concepts.resources.textureUsages.details.link: 'layout e sincronização'
+concepts.resources.textureUsages.table.colorAttachment.role: 'Um anexo de cor em uma passagem de renderização.'
+concepts.resources.textureUsages.table.depthStencilAttachment.role: 'Um anexo de profundidade e estêncil em uma passagem de renderização.'
+concepts.resources.textureUsages.table.headings.role: 'Função'
+concepts.resources.textureUsages.table.headings.usage: 'Uso'
+concepts.resources.textureUsages.table.none.role: 'Nenhum indicador de uso de textura é declarado.'
+concepts.resources.textureUsages.table.sampled.role: 'Dados de textura lidos por uma vinculação de textura amostrada.'
+concepts.resources.textureUsages.table.storage.role: 'Dados de textura acessados por uma vinculação de textura de armazenamento para leituras ou gravações de shaders.'
+concepts.resources.textureUsages.table.transferDst.role: 'Armazenamento da imagem de destino para uma cópia de GPU.'
+concepts.resources.textureUsages.table.transferSrc.role: 'Dados da imagem de origem para uma cópia de GPU.'
+concepts.resources.textureUsages.title: 'Usos de textura'
+concepts.resources.title: 'Gerenciamento de recursos'
+concepts.resources.transfers.context: 'Um download copia primeiro os dados da GPU para uma área interna de leitura. A biblioteca os copia para o destino de CPU fornecido quando recupera o buffer de comandos concluído. Chamar {wait} no valor de envio do download completa as duas etapas. Mantenha o destino alocado e a memória gerenciada fixada até o retorno dessa chamada.'
+concepts.resources.transfers.description: 'Uploads copiam dados da CPU para um recurso da GPU; downloads copiam dados do recurso para a memória da CPU. Os métodos de buffers e texturas concluem essa transferência antes de retornar:'
+concepts.resources.transfers.details: 'Antes de chamar esses métodos, resolva os acessos conflitantes em outras filas. A espera interna do método abrange sua própria transferência; ela não estabelece uma dependência de outra fila. Nas cópias diretas por memória mapeada, cabe inteiramente a quem chama esperar os acessos conflitantes da GPU.'
+concepts.resources.transfers.guidance: 'Para enviar transferências junto com outro trabalho da GPU, registre-as com {commandBufferUpload} ou {commandBufferDownload}. A memória de origem de um upload e a de destino de um download precisam permanecer válidas por períodos diferentes:'
+concepts.resources.transfers.item: 'Durante uma chamada de upload, a biblioteca copia os dados de origem para uma área temporária de transferência. A GPU lê dessa área posteriormente, portanto o ponteiro de origem da aplicação só é necessário até o retorno da chamada.'
+concepts.resources.transfers.notes: 'Nas transferências de textura, o passo de linha é a distância em bytes entre linhas consecutivas dos dados da CPU; o passo de fatia é a distância entre fatias de profundidade consecutivas. Esses valores permitem copiar os dados entre sua organização na CPU e o armazenamento da textura na GPU. {link} explica por que concluir a cópia na GPU e entregar os dados ao destino de um download são etapas distintas.'
+concepts.resources.transfers.notes.link: 'Sincronização'
+concepts.resources.transfers.table.bufferDownload.transfer: 'Para {cpuReadOnly}, copia diretamente da memória mapeada. Nos demais casos, envia o download à fila de transferência e espera sua conclusão.'
+concepts.resources.transfers.table.bufferUpload.transfer: 'Para {cpuWriteOnly}, copia diretamente para a memória mapeada. Nos demais casos, envia o upload à fila de transferência e espera sua conclusão.'
+concepts.resources.transfers.table.headings.method: 'Método'
+concepts.resources.transfers.table.headings.transfer: 'Comportamento da transferência'
+concepts.resources.transfers.table.textureTransfers.method: '{textureUpload} / {textureDownload}'
+concepts.resources.transfers.table.textureTransfers.transfer: 'Envia a cópia e as transições de textura à fila gráfica e espera sua conclusão. O sub-recurso selecionado fica no layout final solicitado.'
+concepts.resources.transfers.title: 'Transferências imediatas e registradas'
+concepts.resources.usages.description: '{bufferUsages} e {textureUsages} são enumerações de indicadores que declaram os usos de um recurso. Seus valores são atribuídos a {bufferDescUsages} e {textureDescUsages}, respectivamente. Para vários usos, combine os membros necessários com {value}.'
+concepts.resources.usages.title: 'Usos de recursos'
+concepts.resources.views.description: 'Uma {bufferView} descreve uma região de um buffer e a interpretação de seus dados. Uma {textureView} seleciona um intervalo de sub-recursos, com um tipo e formato de textura compatíveis. Ambas se referem ao armazenamento existente, com os usos declarados na criação. Os intervalos das visualizações descrevem o acesso, mas não garantem uma verificação de limites uniforme nos shaders de todos os backends.'
+concepts.resources.views.details: 'O identificador do próprio recurso fornece sua visualização padrão. Crie uma visualização separada para escolher outra região ou uma interpretação compatível diferente. Mantenha vivos tanto a visualização quanto o recurso que a sustenta até a GPU terminar de utilizá-los; um identificador não mantém nenhum desses objetos vivo. Ao substituir qualquer um deles, atualize as constantes que contêm o {link} anterior.'
+concepts.resources.views.details.link: 'identificador de recurso'
+concepts.resources.views.title: 'Visualizações e recursos subjacentes'
+concepts.shaders.binding.context: 'O exemplo de computação atribui {inputTextureSampledHandle} a {input} e {outputTextureStorageHandle} a {output}. Em Slang, o parâmetro de tipo de {descriptorHandle} define como o shader acessa cada recurso:'
+concepts.shaders.binding.description: '{setConstantBuffer} seleciona um buffer existente e o deslocamento de byte a partir do qual o pipeline atual lê suas constantes. Envie os parâmetros para esse armazenamento antes de serem usados pelo shader. Se um trabalho anterior da GPU ainda estiver lendo os mesmos bytes, espere sua conclusão antes de atualizá-los.'
+concepts.shaders.binding.details: 'Com o buffer de comandos, o pipeline de computação, o buffer de constantes preenchido e as quantidades de grupos de threads prontos, registre o despacho nesta ordem. Selecione primeiro o pipeline, pois a vinculação do buffer de constantes se aplica a ele:'
+concepts.shaders.binding.guidance: 'Aqui, {commandBuffer} registra um despacho com {computePipeline} e os parâmetros armazenados em {constantBuffer}. O buffer precisa do uso {bufferUsagesConstant}, e o deslocamento 0 seleciona seu início. Se vários registros de constantes compartilharem um buffer, o deslocamento inicial de cada registro também deve atender ao alinhamento de vinculação do backend, independentemente do layout de seus campos internos.'
+concepts.shaders.binding.notes: '{resourceHandle} contém dois campos de 32 bits cuja interpretação depende do backend. Seus valores são válidos apenas no contexto em que o recurso foi criado.'
+concepts.shaders.binding.reference: 'Mantenha o recurso e a visualização usada para obter seu identificador até o último acesso do shader. A substituição exige atualizar constantes que ainda contenham o identificador antigo. {link} apresenta as regras de responsabilidade.'
+concepts.shaders.binding.reference.link: 'Gerenciamento de recursos'
+concepts.shaders.binding.table.bufferStorageReadOnlyHandle.access: 'Leitura de elementos estruturados do tipo {t} do shader'
+concepts.shaders.binding.table.bufferStorageReadWriteHandle.access: 'Leitura e escrita de elementos estruturados do tipo {t} do shader'
+concepts.shaders.binding.table.headings.access: 'Acesso pretendido'
+concepts.shaders.binding.table.headings.cHandle: 'Identificador C#'
+concepts.shaders.binding.table.headings.slangFieldType: 'Tipo de campo Slang'
+concepts.shaders.binding.table.samplerHandle.access: 'Estado de amostragem de texturas'
+concepts.shaders.binding.table.textureSampledHandle.access: 'Leitura de texturas'
+concepts.shaders.binding.table.textureStorageHandle.access: 'Leitura e escrita de texturas de armazenamento'
+concepts.shaders.binding.title: 'Buffers de constantes e identificadores de recursos'
+concepts.shaders.compilation.description: '{zenithCompiler} compila um ponto de entrada Slang em uma {shaderDesc}. O {graphicsApi} do contexto define o destino: DXIL para DirectX 12, biblioteca Metal para Metal ou SPIR-V para Vulkan.'
+concepts.shaders.compilation.details: 'Com um contexto gráfico já criado e chamado {context}, compile o shader do triângulo a partir da cópia ao lado do executável:'
+concepts.shaders.compilation.guidance: 'O nome do ponto de entrada {vSMain} deve corresponder à declaração no arquivo de shader. Compile os shaders e crie os pipelines na inicialização; depois, reutilize os pipelines durante a renderização. Após a criação dos pipelines, os objetos shader podem ser liberados, como mostra {link}.'
+concepts.shaders.compilation.guidance.link: 'Primeiro triângulo'
+concepts.shaders.compilation.title: 'Compilação de shaders'
+concepts.shaders.description: 'As declarações em C# e Slang precisam concordar quanto à posição de cada valor e ao acesso feito pelo shader. Layouts de entrada descrevem os atributos dos vértices; declarações de buffers do shader descrevem parâmetros e arrays; identificadores apontam para os recursos usados pelo shader.'
+concepts.shaders.dispatch.description: '{dispatch} especifica quantos grupos de threads serão executados. O exemplo de computação declara {numthreads}, portanto cada grupo contém 16 × 16 threads. Para cobrir uma imagem, divida a largura e a altura por 16 e arredonde cada resultado para cima: uma imagem de 17 × 19 precisa de 2 × 2 grupos. O shader verifica as coordenadas de cada thread e ignora as que ficam fora da imagem.'
+concepts.shaders.dispatch.details: 'O tamanho de grupo declarado está disponível em {shaderDescThreadGroupSize} e permite calcular as quantidades de grupos passadas a {dispatch}. {computeShaderRendererCs} mostra o cálculo completo. Depois do despacho, o acesso à imagem resultante deve seguir os requisitos descritos em {link}.'
+concepts.shaders.dispatch.details.link: 'Sincronização'
+concepts.shaders.dispatch.title: 'Grupos de threads'
+concepts.shaders.layout.context: 'As {link} ilustram essa organização com as dimensões de uma imagem e dois identificadores de textura. Deslocamentos explícitos indicam onde cada campo começa. A declaração usa {systemRuntimeInteropServices} e {zenithNET}:'
+concepts.shaders.layout.context.link: 'constantes C# do exemplo de computação'
+concepts.shaders.layout.description: 'Para a entrada de vértices, {inputLayout} descreve o formato, o deslocamento e a semântica de cada atributo, além do passo entre vértices. O triângulo coloca uma posição {float3} antes de uma cor {float4}: a cor começa no deslocamento de byte 12 e cada vértice ocupa 28 bytes. {inputLayoutAdd} calcula essa organização compacta a partir dos formatos. Se a estrutura C# contiver preenchimento, informe deslocamentos e um passo que o considerem.'
+concepts.shaders.layout.details: 'Buffers de constantes contêm parâmetros do shader, e buffers estruturados contêm arrays de elementos tipados. Seus layouts seguem as declarações de Slang, e não {inputLayout}. Ajuste os deslocamentos dos campos, tamanhos dos elementos e preenchimento em C# ao layout do shader compilado. Um tipo não gerenciado de C# pode ser copiado byte a byte, mas seu layout na memória não corresponde necessariamente ao do shader; um {float3} armazenado sem preenchimento em um vértice pode exigir outro alinhamento em um buffer do shader.'
+concepts.shaders.layout.guidance: 'Os parâmetros do shader podem incluir identificadores de recursos além de valores comuns. Um identificador aponta para armazenamento já existente, portanto copiá-lo para um buffer de constantes não copia o conteúdo do recurso.'
+concepts.shaders.layout.notes: 'A declaração correspondente em {computeShaderSlang} é:'
+concepts.shaders.layout.reference: 'As duas dimensões ocupam os primeiros oito bytes, seguidas de dois identificadores de oito bytes cada. O exemplo C# reserva 256 bytes para o registro completo. Para outros registros, determine o tamanho a partir de seus próprios campos e requisitos de layout e alinhamento.'
+concepts.shaders.layout.title: 'Layout de dados'
+concepts.shaders.matrices.description: 'O compilador usa armazenamento de matrizes por linhas, mantendo cada linha contígua. O {link} armazena três valores {matrix4x4} nos deslocamentos 0, 64 e 128. Seu {detail} aplica {mulVectorMatrix} na ordem modelo, visão e projeção.'
+concepts.shaders.matrices.description.detail: 'shader'
+concepts.shaders.matrices.description.link: 'renderizador de cubo giratório'
+concepts.shaders.matrices.details: 'A ordem de armazenamento determina a disposição dos elementos da matriz na memória; a ordem de multiplicação determina a transformação. C# e Slang devem adotar convenções compatíveis.'
+concepts.shaders.matrices.title: 'Convenções de matrizes'
+concepts.shaders.title: 'Dados de shaders e vinculação'
+concepts.synchronization.backends.description: 'DirectX 12 e Vulkan codificam as transições de textura como mudanças de layout e acesso. Em Metal, a implementação de {transition} do Zenith.NET não executa uma operação equivalente. O backend de Metal reúne os comandos em codificadores de renderização e computação e insere barreiras de visibilidade ao encerrar cada codificador. Operações dependentes no mesmo codificador, como despachos de computação sucessivos, usam chamadas explícitas a {barrier}.'
+concepts.synchronization.backends.details: 'No {link}, iniciar a passagem gráfica encerra o codificador de computação e torna sua saída visível ao trabalho gráfico. Dois despachos consecutivos permanecem no mesmo codificador, portanto sua dependência exige uma barreira mesmo que haja uma transição de textura entre eles. Por isso, uma sequência de comandos portável deve considerar as dependências de acesso além dos layouts de textura.'
+concepts.synchronization.backends.details.link: 'exemplo de computação'
+concepts.synchronization.backends.title: 'Comportamento dos backends'
+concepts.synchronization.barriers.context: 'Escolha etapas que incluam os acessos envolvidos e sejam compatíveis com a fila. {barrierStagesAll} inclui todas as etapas compatíveis; selecione um conjunto menor quando a dependência envolver apenas essas etapas.'
+concepts.synchronization.barriers.description: 'Considere dois despachos de computação registrados no mesmo buffer de comandos: o primeiro escreve dados de simulação e o segundo os lê. Coloque uma barreira entre eles para que o segundo veja as escritas concluídas. Em um buffer de armazenamento, isso ordena os acessos sem uma transição de layout de textura:'
+concepts.synchronization.barriers.details: 'O primeiro argumento identifica as etapas que produzem os dados; o segundo, as etapas que os consomem. Juntos, eles descrevem a ordem de execução e a visibilidade de memória necessárias entre os dois despachos.'
+concepts.synchronization.barriers.guidance: 'A {link} usa esse padrão entre etapas da simulação. Cada barreira fica depois do despacho que produz os dados e antes daquele que os utiliza.'
+concepts.synchronization.barriers.guidance.link: 'simulação FluidTank'
+concepts.synchronization.barriers.title: 'Barreiras do pipeline'
+concepts.synchronization.dependencies.description: 'Para cada recurso, identifique os acessos aos mesmos dados. Se algum deles escrever, preserve a ordem necessária entre as operações. Duas leituras não entram em conflito, embora os requisitos de layout de textura e apresentação continuem válidos. Atualizações pela CPU e a liberação de recursos também precisam esperar a GPU terminar de usar o armazenamento afetado.'
+concepts.synchronization.dependencies.details: 'O mecanismo adequado depende de onde as operações são executadas:'
+concepts.synchronization.dependencies.guidance: 'Uma carga de trabalho pode precisar de mais de um mecanismo. Por exemplo, uma textura produzida em outra fila precisa de uma dependência entre envios e do layout adequado para o próximo uso. Nenhuma dessas operações exige bloquear a CPU, a menos que ela própria precise acessar ou liberar os dados.'
+concepts.synchronization.dependencies.table.cpuAccess.mechanism: 'Chamar {wait} no valor de conclusão relevante.'
+concepts.synchronization.dependencies.table.cpuAccess.situation: 'O acesso da CPU ou a liberação de recursos depende da conclusão na GPU'
+concepts.synchronization.dependencies.table.crossQueue.mechanism: 'Passe o {timelineValue} do produtor para o {submit} do consumidor.'
+concepts.synchronization.dependencies.table.crossQueue.situation: 'Uma fila consome dados produzidos em outra'
+concepts.synchronization.dependencies.table.headings.mechanism: 'Mecanismo'
+concepts.synchronization.dependencies.table.headings.situation: 'Situação'
+concepts.synchronization.dependencies.table.layoutChange.mechanism: '{commandBufferTransition} com seus layouts anterior e seguinte.'
+concepts.synchronization.dependencies.table.layoutChange.situation: 'Um sub-recurso de textura altera como é usado'
+concepts.synchronization.dependencies.table.pipelineStages.mechanism: '{commandBufferBarrier} com os estágios produtor e consumidor.'
+concepts.synchronization.dependencies.table.pipelineStages.situation: 'Estágios dependentes da GPU em um fluxo de comandos'
+concepts.synchronization.dependencies.title: 'Dependências de acesso'
+concepts.synchronization.description: 'A sincronização estabelece a ordem de acesso aos dados compartilhados e garante que operações posteriores vejam os resultados necessários. Em uma dependência de escrita para leitura, quem escreve é o produtor e quem lê é o consumidor. Barreiras e transições de textura coordenam os acessos da GPU; linhas do tempo conectam envios e permitem que a CPU espere a conclusão do trabalho na GPU.'
+concepts.synchronization.layouts.context: 'Use {undefined} como layout anterior quando o conteúdo existente puder ser descartado, como em uma imagem nova ou em um anexo que será limpo por completo. Para preservar o conteúdo para carregamento, amostragem ou acumulação, informe o layout do acesso anterior.'
+concepts.synchronization.layouts.description: 'Os indicadores de uso declaram as funções permitidas da textura. {textureLayout} descreve o acesso a um sub-recurso, identificado pelo nível de mip e pela camada do array de texturas, em determinado ponto da sequência de comandos. Quando esse acesso mudar, registre uma transição. No exemplo de computação, a textura de saída é escrita em layout {storage} e depois preparada para amostragem em uma passagem gráfica:'
+concepts.synchronization.layouts.details: 'Aqui, {outputTexture} foi criada com uso {sampledStorage}, e o sub-recurso selecionado está em {storage}. {default} seleciona o nível de mip 0 e a camada 0. A transição não afeta outros níveis ou camadas.'
+concepts.synchronization.layouts.guidance: 'Em {transition}, informe o layout do uso anterior do sub-recurso. A aplicação acompanha esse estado ao registrar comandos e passar texturas entre as etapas de renderização.'
+concepts.synchronization.layouts.title: 'Layouts de textura'
+concepts.synchronization.readback.description: '{isCompleted} informa a conclusão na GPU. Um download registrado tem uma etapa adicional: copiar o resultado da área interna de leitura para a memória de destino da aplicação.'
+concepts.synchronization.readback.details: '{commandBufferDownload} registra a cópia da GPU para essa área interna. Quando o envio termina, a biblioteca realiza a cópia na CPU ao recuperar o buffer de comandos concluído. Chamar {wait} no valor de envio do download espera a GPU e processa os buffers concluídos daquela fila, completando as duas etapas. Mantenha o destino alocado e a memória gerenciada fixada até o retorno da chamada.'
+concepts.synchronization.readback.guidance: 'Consultar apenas {isCompleted} não executa a cópia final na CPU. Esperar em outra fila também não recupera os buffers concluídos da fila do download. {link} descreve quando uploads e downloads usam acesso direto à memória ou armazenamento temporário de transferência.'
+concepts.synchronization.readback.guidance.link: 'Gerenciamento de recursos'
+concepts.synchronization.readback.title: 'Conclusão da leitura de dados'
+concepts.synchronization.reuse.description: 'O triângulo espera cada quadro terminar antes de reutilizar seus dados. Para sobrepor quadros, reserve armazenamento mutável próprio para cada quadro em andamento e acompanhe quando ele volta a estar disponível. Antes de modificar dados, espere o trabalho da GPU que ainda acessa os bytes afetados; antes de liberar um recurso, espere todos os seus usos pendentes.'
+concepts.synchronization.reuse.details: 'Redimensionamento e encerramento seguem a mesma regra: impedir novos usos, esperar os existentes terminarem e então substituir ou liberar os recursos. Respeite essa ordem antes de chamar {swapChainResize} ou liberar recursos ou o contexto. Como o triângulo já espera a cada quadro, ele pode redimensionar entre as chamadas dos callbacks.'
+concepts.synchronization.reuse.guidance: '{swapChainPresent} sinaliza um ponto da fila gráfica e espera por ele após a apresentação. Assim, o triângulo continua esperando ao apresentar, mesmo sem a espera explícita do envio. O trabalho independente em outras filas precisa de seu próprio acompanhamento de conclusão.'
+concepts.synchronization.reuse.title: 'Reutilização de recursos'
+concepts.synchronization.timelines.context: 'Se a CPU precisar ler o resultado, sobrescrever os dados compartilhados ou liberar os recursos após o consumidor terminar, espere esse envio:'
+concepts.synchronization.timelines.description: 'Uma linha do tempo identifica a conclusão na fila por valores crescentes. {submit} retorna um {timelineValue} contendo a linha do tempo e o valor do envio. Campos {value} de linhas do tempo distintas não são diretamente comparáveis.'
+concepts.synchronization.timelines.details: 'Considere {producer} e {consumer}, buffers já gravados em duas filas do mesmo contexto, com o consumidor usando dados escritos pelo produtor. Envie-os com esta dependência:'
+concepts.synchronization.timelines.guidance: 'A fila consumidora espera o valor de conclusão recebido antes de executar seus comandos. A CPU pode continuar sem chamar {producedWait}. Mantenha os recursos compartilhados vivos até a conclusão de {consumed} e registre as transições de textura necessárias ao acesso do consumidor.'
+concepts.synchronization.timelines.notes: 'O valor retornado por {queueTimelineSignal} também pode ser usado para esperar com {wait} ou estabelecer uma dependência para outra fila. {signal} coloca esse ponto de conclusão depois do trabalho já enviado; a GPU o alcança quando esse trabalho termina.'
+concepts.synchronization.timelines.title: 'Linhas do tempo e dependências entre filas'
+concepts.synchronization.title: 'Sincronização'
+home.actions.reference: 'Navegar na API'
+home.actions.tutorial: 'Começar com um triângulo'
+home.entries.description: 'Tutoriais, exemplos e referência da API'
+home.entries.label: 'Seções da documentação'
+home.entries.reference.action: 'Explorar a API'
+home.entries.reference.category: 'Referência'
+home.entries.reference.description: 'Consulte tipos, membros e declarações da API'
+home.entries.reference.title: 'Referência da API'
+home.entries.samples.action: 'Procurar exemplos'
+home.entries.samples.category: 'Exemplos'
+home.entries.samples.description: 'Exemplos executáveis de técnicas gráficas e de computação'
+home.entries.samples.title: 'Exemplos'
+home.entries.title: 'Documentação'
+home.entries.tutorial.action: 'Iniciar tutorial'
+home.entries.tutorial.category: 'Tutorial'
+home.entries.tutorial.description: 'De um projeto vazio ao primeiro triângulo na tela'
+home.entries.tutorial.title: 'Primeiro triângulo'
+home.introduction.accent: 'com .NET'
+home.introduction.description: 'Zenith.NET oferece uma API comum de gráficos e computação para DirectX, Metal e Vulkan'
+home.introduction.title: 'Gráficos multiplataforma{lineBreak}{accent}'
+home.meta.description: 'Gráficos e computação multiplataforma em .NET com uma API comum para DirectX, Metal e Vulkan'
+home.scene.description: 'Geometria interativa em rotação. Arraste ou use as teclas de direção para ajustar a visualização.'
+home.scene.label: 'Exibição de geometria'
+home.scene.surface: 'Superfície'
+home.scene.wireframe: 'Wireframe'
+home.title: 'RHI moderna para .NET'
+learning.concepts.description: 'Critérios para escolher usos de recursos, descrever dados de shaders e coordenar a execução na GPU'
+learning.concepts.execution.description: 'Gravação de comandos, envio e tempo de vida dos recursos'
+learning.concepts.platforms.description: 'Apresentação nativa e controles de renderização'
+learning.concepts.resources.description: 'Usos de recursos, localização na memória e visualizações'
+learning.concepts.shaders.description: 'Convenções de layout e vinculação entre C# e Slang'
+learning.concepts.synchronization.description: 'Dependências, visibilidade e conclusão'
+learning.concepts.title: 'Conceitos fundamentais'
+learning.description: 'Comece pelo tutorial do triângulo para criar sua primeira aplicação. Depois, consulte os artigos sobre recursos, dados de shaders e sincronização, ou escolha um exemplo da técnica que deseja implementar.'
+learning.entries.samples.action: 'Procurar exemplos'
+learning.entries.samples.description: 'Implementações em C# e Slang de geometria, computação, consultas de raios e sombreamento de malha'
+learning.entries.tutorial.action: 'Abrir tutorial'
+learning.entries.tutorial.description: 'Uma aplicação de console que abre uma janela e renderiza um triângulo'
+learning.meta.description: 'Tutoriais e fundamentos do modelo de programação de Zenith.NET'
+learning.title: 'Aprender'
+reference.description: 'Referência de recursos, comandos e integrações de plataforma'
+reference.entries.commands.description: 'Comandos gráficos, de computação e de transferência'
+reference.entries.commands.label: 'Registrar comandos'
+reference.entries.context.description: 'Criação de recursos da GPU no backend selecionado'
+reference.entries.context.label: 'Comece aqui'
+reference.meta.description: 'Namespaces, tipos e membros de Zenith.NET'
+reference.navigation.core: 'Referência da API principal'
+reference.title: 'Referência da API'
+samples.computeShader.description: 'O exemplo carrega {assetsTexturesShokoPng}, converte a imagem para tons de cinza em um despacho de computação e exibe a textura resultante. A conversão ocorre uma vez; os quadros seguintes exibem o mesmo resultado. O renderizador mostra como vincular texturas para amostragem e escrita, arredondar para cima as quantidades de grupos de threads para cobrir a imagem e verificar seus limites no shader.'
+samples.computeShader.details: '{computeShaderRendererCs} · {computeShaderSlang}'
+samples.computeShader.title: 'Shader de computação'
+samples.cube.description: 'O cubo acrescenta desenho indexado, matrizes de modelo, visão e projeção em um buffer de constantes e teste de profundidade. Sua textura de profundidade e estêncil {d32FloatS8UInt} é recriada no redimensionamento.'
+samples.cube.details: '{spinningCubeRendererCs} · {spinningCubeSlang}'
+samples.cube.title: 'Cubo giratório'
+samples.description: 'Cada exemplo reúne um renderizador C# e seu shader Slang em torno de uma técnica. Para a configuração completa da aplicação e a sequência de renderização, comece por {link}.'
+samples.description.link: 'Primeiro triângulo'
+samples.execution.description: 'Execute o projeto {link} e escolha um exemplo no menu do console.'
+samples.execution.description.link: 'ZenithTutorials'
+samples.execution.title: 'Execução dos exemplos'
+samples.host.description: '{appCs} inicializa contexto, janela e cadeia de troca. A cada quadro, obtém um buffer de comandos, chama o renderizador selecionado, envia os comandos e apresenta o resultado. {iRendererCs} define as operações de atualização, renderização, redimensionamento e liberação.'
+samples.host.details: 'Os renderizadores de computação e traçado de raios produzem texturas fora da tela. {texturePresenterCs} e {presentTextureSlang} desenham essas texturas na saída do quadro atual; em seguida, o programa hospedeiro compartilhado envia os comandos e apresenta o quadro.'
+samples.host.title: 'Programa hospedeiro e apresentação'
+samples.indirectDrawing.description: 'O desenho indireto renderiza várias instâncias de cubos a partir de um buffer de argumentos de desenho indexado e de um buffer estruturado de dados por instância. A CPU fornece os argumentos e atualiza as transformações.'
+samples.indirectDrawing.details: '{indirectDrawingRendererCs} · {indirectDrawingSlang}'
+samples.indirectDrawing.title: 'Desenho indireto'
+samples.meshShading.description: 'Um shader de tarefa descarta as instâncias de esferas não visíveis e passa os índices restantes ao shader de malha, que gera a geometria. O renderizador verifica {contextCapabilitiesMeshShadingSupported} antes de criar o pipeline.'
+samples.meshShading.details: '{meshShadingRendererCs} · {meshShadingSlang}'
+samples.meshShading.title: 'Sombreamento de malha'
+samples.rayTracing.description: 'O exemplo cria estruturas de aceleração de nível inferior e superior para um piso e esferas procedurais e realiza consultas de raios em um shader de computação. Verifica {contextCapabilitiesRayTracingSupported} antes da inicialização.'
+samples.rayTracing.details: '{rayTracingRendererCs} · {rayTracingSlang}'
+samples.rayTracing.title: 'Traçado de raios'
+samples.title: 'Exemplos'
+samples.triangle.description: 'O triângulo associa um buffer de vértices ao layout de entrada correspondente e desenha três vértices por um pipeline gráfico.'
+samples.triangle.details: '{helloTriangleRendererCs} · {helloTriangleSlang} · {link}'
+samples.triangle.details.link: 'Tutorial completo'
+samples.triangle.title: 'Olá triângulo'
+samples.workloads.description: 'O {link} do repositório principal contém aplicações de maior porte que referenciam o código-fonte da biblioteca.'
+samples.workloads.description.link: 'diretório Experiments'
+samples.workloads.details: 'Os experimentos com janela usam os mesmos mecanismos de apresentação em Windows, macOS e Linux/X11. {link} trata dos controles para Avalonia, WinForms, WPF, WinUI/Uno e MAUI, incluindo o ciclo dos quadros e as implementações.'
+samples.workloads.details.link: 'Integração de plataforma'
+samples.workloads.table.cornellBox.example: '{link}'
+samples.workloads.table.cornellBox.example.link: 'CornellBox'
+samples.workloads.table.cornellBox.whatItDemonstratesAndRequires: 'Traçado de caminhos, redução de ruído, mapeamento de tons, ampliação de resolução e controles ImGui. Requer suporte a traçado de raios.'
+samples.workloads.table.fluidTank.example: '{link}'
+samples.workloads.table.fluidTank.example.link: 'FluidTank'
+samples.workloads.table.fluidTank.whatItDemonstratesAndRequires: 'Simulação de fluidos por computação, dependências entre estágios de simulação e renderização de superfície em várias passagens.'
+samples.workloads.table.headings.example: 'Exemplo'
+samples.workloads.table.headings.whatItDemonstratesAndRequires: 'Técnicas e requisitos'
+samples.workloads.table.inkCanvas.example: '{link}'
+samples.workloads.table.inkCanvas.example.link: 'InkCanvas'
+samples.workloads.table.inkCanvas.whatItDemonstratesAndRequires: 'Desenho com Skia e tratamento de entrada em uma janela nativa. Requer o backend e a biblioteca nativa do Skia para a plataforma de destino.'
+samples.workloads.table.platformDetection.example: '{link}'
+samples.workloads.table.platformDetection.example.link: 'PlatformDetection'
+samples.workloads.table.platformDetection.whatItDemonstratesAndRequires: 'Tenta criar um contexto para cada backend e informa as capacidades do dispositivo. Uma tentativa que falha é indicada como não compatível; a causa pode estar no dispositivo, no driver ou em uma dependência de execução ausente.'
+samples.workloads.title: 'Aplicações de maior porte'
+tutorial.context.description: 'Um {graphicsContext} conecta a aplicação à GPU por meio de um backend e fornece os métodos para criar os recursos de renderização.'
+tutorial.context.details: 'Insira a inicialização do contexto {emphasis}:'
+tutorial.context.details.emphasis: 'após as diretivas using, antes de {iWindowWindow}'
+tutorial.context.guidance: 'Cada ramo seleciona o backend da plataforma e solicita a validação. As mensagens de {validationMessage} são exibidas no console.'
+tutorial.context.imports: 'Adicione as seguintes diretivas using no início de {programCs}:'
+tutorial.context.title: 'Contexto gráfico'
+tutorial.description: 'Neste tutorial, um projeto de console .NET vazio se torna uma aplicação que abre uma janela e desenha um triângulo colorido.'
+tutorial.details: 'O tutorial tem três pontos de verificação: uma janela vazia, um fundo de cor uniforme e o triângulo colorido. Execute a aplicação em cada ponto antes de acrescentar a próxima parte.'
+tutorial.draw.context: 'Os argumentos para {draw} são:'
+tutorial.draw.description: 'No callback {windowRender}, substitua {addTheTriangleDrawCommandsHereLater} pelos comandos abaixo, entre {beginRenderPass} e {endRenderPass}:'
+tutorial.draw.details: '{setPipeline} seleciona shaders e estado de renderização. Deve preceder o vínculo dos vértices, pois o backend interpreta esse vínculo conforme o layout de entrada do pipeline.'
+tutorial.draw.guidance: '{setVertexBufferVertexBuffer} vincula os dados de vértices já transferidos, a partir do deslocamento de byte 0, ao slot de entrada 0, correspondente à única entrada de {inputLayouts}.'
+tutorial.draw.notes: 'Essas chamadas registram os comandos de desenho. A chamada existente a {submitWait} os envia e espera a GPU terminar; em seguida, {present} solicita a exibição da imagem concluída. Mantenha a limpeza no início da passagem para definir a cor de fundo na área externa ao triângulo.'
+tutorial.draw.table.firstInstance.meaning: 'Iniciar a numeração de instâncias em zero.'
+tutorial.draw.table.firstVertex.meaning: 'Começar com o primeiro vértice no buffer.'
+tutorial.draw.table.headings.argument: 'Argumento'
+tutorial.draw.table.headings.description: 'Valor'
+tutorial.draw.table.headings.meaning: 'Significado'
+tutorial.draw.table.instanceCount.meaning: 'Desenhar uma cópia do triângulo.'
+tutorial.draw.table.vertexCount.meaning: 'Ler três vértices.'
+tutorial.draw.title: 'Comandos de desenho'
+tutorial.execution.description: 'Compile e execute o projeto {firstTriangle}.'
+tutorial.execution.details: 'A janela deve exibir um triângulo sobre fundo escuro, vermelho no alto, verde embaixo à direita e azul embaixo à esquerda. A interpolação das cores dos vértices forma um gradiente suave em seu interior.'
+tutorial.execution.guidance: 'O redimensionamento muda as dimensões da imagem em pixels, mas mantém as coordenadas dos vértices. O viewport ajusta essas coordenadas à nova largura e altura, por isso o triângulo se deforma quando a proporção da janela muda.'
+tutorial.execution.title: 'Execução da aplicação'
+tutorial.frame.commands.context: 'Após {endRenderPass}, a segunda transição prepara a textura de saída para apresentação. {submit} encerra o registro e envia o buffer de comandos; {wait} bloqueia a thread até a conclusão desse envio. Em seguida, {present} solicita a exibição da imagem renderizada.'
+tutorial.frame.commands.description: 'Uma passagem de renderização reúne operações de desenho que escrevem nos mesmos anexos. Nesta passagem, a textura de saída da cadeia de troca recebe a cor. {colorAttachmentClear} descreve como inicializar esse anexo com uma cor RGBA e preservar seu conteúdo ao encerrar a passagem. {beginRenderPass} registra o início da passagem com essas configurações.'
+tutorial.frame.commands.details: 'A coleção contém um anexo de cor. {null} indica que a passagem não possui anexo de profundidade e estêncil.'
+tutorial.frame.commands.guidance: '{beginRenderPass} ajusta o viewport e a região de recorte para todo o anexo. O viewport converte coordenadas de renderização em posições de pixels; o recorte limita a região em que se pode escrever.'
+tutorial.frame.commands.title: 'Limpeza, envio e apresentação'
+tutorial.frame.description: 'Comece preenchendo a janela com uma cor de fundo. Essa operação é chamada de limpeza. Quando o fundo aparece, a superfície, o envio de comandos e a apresentação já funcionam juntos. A partir daí, acrescente os vértices e os shaders do triângulo.'
+tutorial.frame.details: 'Silk.NET chama o callback quando há um quadro a renderizar. Como o exemplo não usa o argumento de tempo decorrido, o parâmetro se chama {value}. Registre o callback {emphasis}:'
+tutorial.frame.details.emphasis: 'antes de {windowRun}'
+tutorial.frame.target.description: '{graphicsQueueCommandBuffer} retorna um buffer pronto para gravação. Gravar define o trabalho da GPU; sua execução só começa após o envio.'
+tutorial.frame.target.details: 'O layout de uma textura descreve o acesso necessário a uma operação. A primeira chamada a {transition} prepara a textura de saída para uso como anexo de cor. Aqui, {default} seleciona o nível de mip 0 e a camada 0 do array, ou seja, a imagem base da textura. Como a passagem limpa a imagem inteira, {undefined} permite descartar o conteúdo anterior.'
+tutorial.frame.target.guidance: 'Obtenha a textura de saída atual a cada quadro, pois a apresentação pode passar a usar outra imagem.'
+tutorial.frame.target.title: 'Preparação da textura de saída'
+tutorial.frame.title: 'O primeiro quadro'
+tutorial.geometry.data.description: 'Adicione o array de vértices {emphasis}. Mantenha também as próximas inicializações de recursos antes desse callback, na ordem apresentada.'
+tutorial.geometry.data.description.emphasis: 'antes de {windowRender}'
+tutorial.geometry.data.details: 'Cada entrada constrói um {vertex}; os dois {new} internos fornecem a posição {vector3} e a cor {vector4}. As posições formam os cantos superior, inferior direito e inferior esquerdo. O shader de vértices acrescenta {w} como quarta coordenada. Com w igual a 1, x e y também são coordenadas normalizadas de dispositivo: -1 e +1 marcam os limites da imagem, e 0, o centro. Sua interpretação independe da quantidade de pixels.'
+tutorial.geometry.data.guidance: 'Cada cor tem componentes vermelho, verde, azul e alfa, com alfa igual a 1. Os três vértices têm z = 0 e estão no mesmo plano.'
+tutorial.geometry.data.title: 'Posições e cores dos vértices'
+tutorial.geometry.description: 'Cada um dos três vértices tem posição e cor. A GPU determina a forma do triângulo a partir das posições e interpola as cores em sua superfície.'
+tutorial.geometry.format.description: 'Acrescente a declaração do vértice {emphasis}:'
+tutorial.geometry.format.description.emphasis: 'no final de {programCs}, após as instruções de limpeza'
+tutorial.geometry.format.details: '{file} restringe o tipo a {programCs}, e {layoutKindSequential} mantém os campos na ordem de declaração. {position} contém três valores de ponto flutuante de 32 bits e começa no deslocamento de byte 0; {color} contém quatro e começa no deslocamento de byte 12. Cada vértice ocupa, portanto, 28 bytes. O layout de entrada descreverá essa organização para que a GPU leia os campos corretamente.'
+tutorial.geometry.format.imports: 'Adicione estas diretivas using no início de {programCs} para usar os tipos vetoriais e o atributo de layout da estrutura:'
+tutorial.geometry.format.title: 'Representação dos vértices'
+tutorial.geometry.title: 'Dados de vértices'
+tutorial.geometry.upload.context: '{fixed} impede que o coletor de lixo mova o array enquanto seu endereço estiver em uso. Neste buffer gravável pela CPU, {upload} mapeia a memória, copia os bytes e desfaz o mapeamento antes de retornar. O array só precisa permanecer fixado durante a chamada. Envie os vértices uma vez na inicialização e mantenha o buffer vivo até o fim da renderização.'
+tutorial.geometry.upload.description: 'Armazene os vértices em um {buffer} para que a GPU possa lê-los durante o desenho. Acrescente a alocação e o envio dos dados logo após o array:'
+tutorial.geometry.upload.details: 'O tamanho é {sizeofVertexVerticesLength}: três vértices de 28 bytes, ou 84 bytes. {bufferUsagesVertex} declara o uso como entrada de vértices, e {cpuWriteOnly} permite a inicialização direta pela CPU. O passo do descritor registra o tamanho do elemento. O layout definido adiante especifica como cada atributo será lido.'
+tutorial.geometry.upload.guidance: 'A alocação não inicializa o conteúdo do buffer. {upload} copia o array para o deslocamento 0 do destino, usando o ponteiro de origem e a quantidade de bytes da descrição de dados.'
+tutorial.geometry.upload.imports: 'Em {programCs}, adicione {bufferAlias} às diretivas using para distinguir o tipo de buffer do Zenith.NET de {systemBuffer}.'
+tutorial.geometry.upload.notes: 'Na limpeza ao final de {programCs}, insira {vertexBufferDispose} imediatamente antes de {swapChainDispose}. {link} trata do armazenamento exclusivo da GPU e de transferências para geometria estática maior.'
+tutorial.geometry.upload.notes.link: 'Gerenciamento de recursos'
+tutorial.geometry.upload.title: 'Alocação e transferência do buffer'
+tutorial.input.description: 'A GPU precisa saber onde estão os atributos de cada vértice, como posição e cor. O layout de entrada fornece o formato e o deslocamento de cada atributo, além do passo: a distância em bytes até o próximo vértice. Adicione este código após criar os shaders:'
+tutorial.input.details: '{inputLayoutAdd} coloca cada atributo após o anterior e soma seu tamanho ao passo entre vértices. Os deslocamentos são calculados a partir dos formatos informados, sem inspecionar a estrutura C#. Para esses dois campos, o resultado é:'
+tutorial.input.guidance: 'O valor padrão de {semanticIndex} é zero, correspondente a {pOSITION0} e {cOLOR0} no shader. Posição e cor ficam juntas em um buffer de vértices, portanto o pipeline utiliza um único layout de entrada no slot 0. Se a ordem dos campos ou o preenchimento da estrutura C# mudar, ajuste também esse layout.'
+tutorial.input.table.headings.byteOffset: 'Deslocamento de bytes'
+tutorial.input.table.headings.cField: 'Campo C#'
+tutorial.input.table.headings.format: 'Formato'
+tutorial.input.table.headings.shaderInput: 'Entrada do shader'
+tutorial.input.table.nextVertex.cField: 'Próximo vértice'
+tutorial.input.table.nextVertex.format: 'Passo total: 28 bytes'
+tutorial.input.table.nextVertex.shaderInput: 'Próximo registro de entrada'
+tutorial.input.table.vertexColor.format: '{float4} (16 bytes)'
+tutorial.input.table.vertexPosition.format: '{float3} (12 bytes)'
+tutorial.input.title: 'Layout de entrada dos vértices'
+tutorial.lifecycle.description: 'Cada quadro segue esta sequência:'
+tutorial.lifecycle.details: 'Quando a janela fecha, {run} retorna e a execução continua no código de limpeza. Como cada quadro já esperou seus comandos terminarem, os recursos usados por eles podem ser liberados. O bloco completo de limpeza após {windowRun} é:'
+tutorial.lifecycle.guidance: 'Libere a cadeia de troca antes de destruir sua janela nativa e o contexto depois de seus recursos. A fila recicla os buffers de comandos enviados, e a cadeia de troca gerencia a textura de saída; nenhum deles precisa de uma chamada separada a {dispose}.'
+tutorial.lifecycle.title: 'Ciclo dos quadros e liberação'
+tutorial.pipeline.behavior: 'Após o layout de entrada, crie o pipeline:'
+tutorial.pipeline.context: '{cullNone} desativa o descarte de faces, de modo que o triângulo seja desenhado tanto com os vértices em sentido horário quanto em sentido anti-horário.'
+tutorial.pipeline.description: 'Um {graphicsPipeline} reúne o layout dos vértices, os dois shaders e as configurações de montagem de primitivas e escrita nos anexos.'
+tutorial.pipeline.details: 'Os shaders processam vértices e fragmentos. As demais configurações do pipeline controlam a montagem de primitivas, o descarte de faces, o teste de profundidade e a mesclagem de cores:'
+tutorial.pipeline.guidance: 'O formato de cor do pipeline deve corresponder ao anexo da passagem, por isso é obtido de {swapChainDescFormat}. {count1} especifica uma amostra por pixel.'
+tutorial.pipeline.item: '{triangleList} monta um triângulo a cada grupo de três vértices consecutivos.'
+tutorial.pipeline.notes: '{depthNone} desativa teste e escrita de profundidade, de acordo com a ausência desse anexo.'
+tutorial.pipeline.reference: '{opaque} desativa a mesclagem. A saída do shader de fragmentos substitui, portanto, a cor de fundo dentro do triângulo.'
+tutorial.pipeline.title: 'Pipeline gráfico'
+tutorial.pipeline.usage: 'Os objetos de shader podem ser liberados após a criação do pipeline. Mantenha o pipeline durante a renderização e adicione {pipelineDispose} antes de {vertexBufferDispose} na limpeza.'
+tutorial.presentation.title: 'Contexto gráfico e apresentação'
+tutorial.project.context: 'Ative {emphasis} ({allowUnsafeBlocks}) nas opções de compilação do projeto. As chamadas nativas do auxiliar de janela e a transferência de vértices exigem código não seguro.'
+tutorial.project.context.emphasis: 'Permitir código não seguro'
+tutorial.project.description: 'Crie um projeto de console C# chamado {firstTriangle}, com .NET 10 como framework de destino.'
+tutorial.project.details: 'Adicione os seguintes pacotes NuGet ao projeto:'
+tutorial.project.guidance: '{zenithNET} fornece a API comum de renderização; os três pacotes de backend, suas implementações. {zenithNETCompiler} compila os shaders Slang apresentados adiante para o backend selecionado. Silk.NET cria a janela e processa eventos.'
+tutorial.project.title: 'Configuração do projeto'
+tutorial.requirements.context: 'O {link} e seu {detail} fornecem as implementações de referência deste tutorial.'
+tutorial.requirements.context.detail: 'programa hospedeiro'
+tutorial.requirements.context.link: 'exemplo Olá triângulo'
+tutorial.requirements.description: 'São necessários o {emphasis} e conhecimentos básicos de C#. A aplicação tem {net10} como destino. A GPU e o driver devem oferecer suporte ao backend escolhido para sua plataforma:'
+tutorial.requirements.description.emphasis: 'SDK do .NET 10'
+tutorial.requirements.guidance: 'Zenith.NET renderiza em uma superfície nativa fornecida pela aplicação. Silk.NET.Windowing cria a janela e processa seus eventos. No macOS, a apresentação usa uma camada Metal, representada por {cAMetalLayerCs}, que o auxiliar associa à janela.'
+tutorial.requirements.table.headings.graphicsBackend: 'Backend gráfico'
+tutorial.requirements.table.headings.platform: 'Plataforma'
+tutorial.requirements.table.linux.graphicsBackend: 'Vulkan 1.4'
+tutorial.requirements.table.linux.platform: 'Linux (X11 ou XWayland)'
+tutorial.requirements.table.macOS.graphicsBackend: 'Metal 4'
+tutorial.requirements.table.macOS.platform: 'macOS'
+tutorial.requirements.table.windows.graphicsBackend: 'DirectX 12'
+tutorial.requirements.table.windows.platform: 'Windows'
+tutorial.requirements.title: 'Pré-requisitos'
+tutorial.resize.context: 'Compile e execute a aplicação novamente. O fundo deve continuar preenchendo a janela quando ela for redimensionada: cada nova passagem ajusta o viewport e o retângulo de recorte às dimensões do anexo. Mantenha o callback de renderização; o triângulo será desenhado dentro dessa mesma passagem.'
+tutorial.resize.description: 'A cadeia de troca precisa acompanhar as mudanças de tamanho do framebuffer. Registre um callback de {framebufferResize} depois do callback de renderização e antes de {windowRun}:'
+tutorial.resize.details: 'Responda às {link}, medidas em pixels, em vez de às mudanças no tamanho lógico da janela. Se minimizar a janela fizer alguma dimensão chegar a zero, o callback de redimensionamento mantém a cadeia de troca como está. O callback de renderização também retorna enquanto alguma dimensão for zero, interrompendo a renderização até que o framebuffer volte a ter um tamanho utilizável.'
+tutorial.resize.details.link: 'alterações no tamanho do framebuffer'
+tutorial.resize.guidance: 'O callback de renderização espera cada quadro enviado terminar antes de chamar {present}. Assim, o callback de redimensionamento pode substituir as imagens da cadeia de troca sem interromper o trabalho de um quadro anterior. O artigo {link} detalha essa relação entre a conclusão do trabalho e o tempo de vida dos recursos.'
+tutorial.resize.guidance.link: 'Sincronização'
+tutorial.resize.title: 'Redimensionamento'
+tutorial.shaders.compilation.context: '{appContextBaseDirectory} permite carregar a cópia do shader no diretório de saída da aplicação. Assim, iniciar a aplicação a partir de outro diretório de trabalho não altera o local em que ela procura o arquivo.'
+tutorial.shaders.compilation.description: 'Nas propriedades de {triangleSlang}, defina {emphasis} como {detail}. A compilação copiará o shader para o diretório do executável.'
+tutorial.shaders.compilation.description.detail: 'Copiar se mais recente'
+tutorial.shaders.compilation.description.emphasis: 'Copiar para o Diretório de Saída'
+tutorial.shaders.compilation.details: 'Em {programCs}, acrescente a inicialização dos shaders após a transferência dos vértices e antes do callback de renderização:'
+tutorial.shaders.compilation.guidance: '{zenithCompiler} compila o código-fonte para {contextGraphicsApi}, e {createShader} cria um objeto shader a partir da descrição compilada. Os nomes de ponto de entrada {vSMain} e {fSMain} selecionam as duas funções definidas no arquivo Slang.'
+tutorial.shaders.compilation.title: 'Compilação dos shaders'
+tutorial.shaders.context: '{fSInput} conecta as duas etapas do shader. O shader de vértices escreve em {sVPOSITION} uma posição no espaço de recorte, que a GPU usa para determinar a parte visível do triângulo. O campo {cOLOR0} leva a cor do vértice, interpolada ao longo do triângulo antes de chegar ao shader de fragmentos. Seu tipo e sua semântica devem corresponder nas duas etapas.'
+tutorial.shaders.description: 'Shaders são programas executados na GPU. O shader de vértices determina a posição de cada vértice, e o shader de fragmentos determina a cor em cada posição coberta da imagem. As duas etapas serão escritas em um único arquivo Slang, cada uma com seu ponto de entrada: a função em que a execução começa.'
+tutorial.shaders.details: 'Crie {triangleSlang} com estas estruturas de entrada e saída:'
+tutorial.shaders.fragment.description: 'Acrescente o ponto de entrada de fragmentos ao mesmo arquivo:'
+tutorial.shaders.fragment.details: 'A rasterização identifica as posições da imagem cobertas pelo triângulo e gera fragmentos para elas. Cada fragmento recebe uma cor interpolada a partir das cores dos três vértices. {fSMain} retorna essa cor, e {sVTARGET} direciona o resultado ao primeiro anexo de cor.'
+tutorial.shaders.fragment.title: 'Sombreamento de fragmentos'
+tutorial.shaders.guidance: '{vSInput} define as entradas do shader de vértices. {pOSITION0} e {cOLOR0} identificam os atributos por semântica; nomes de campos em C# não estabelecem sozinhos essa correspondência.'
+tutorial.shaders.title: 'Estágios dos shaders'
+tutorial.shaders.vertex.description: 'Acrescente o ponto de entrada de vértices a {triangleSlang}:'
+tutorial.shaders.vertex.details: 'A GPU executa esse ponto de entrada para cada vértice. Ele acrescenta a coordenada homogênea {w} e repassa a cor. Após o processamento dos vértices, a posição é dividida por w. Como w vale 1, x, y e z permanecem iguais e podem ser interpretados como coordenadas normalizadas de dispositivo.'
+tutorial.shaders.vertex.title: 'Sombreamento de vértices'
+tutorial.surface.description: 'Especifique o destino nativo de apresentação {emphasis}:'
+tutorial.surface.description.emphasis: 'após {windowCenter} e antes de {windowRun}'
+tutorial.surface.details: 'O framebuffer é a imagem da janela em pixels. {surface} descreve o destino nativo de apresentação e suas dimensões; {framebufferSize} fornece o tamanho em pixels. Com a escala da tela, esse tamanho pode diferir das dimensões lógicas da janela.'
+tutorial.surface.guidance: 'A superfície fornece os identificadores nativos do backend: uma janela Win32 no Windows, display e janela X11 no Linux ou a camada Metal associada no macOS.'
+tutorial.surface.title: 'Superfície nativa'
+tutorial.swapChain.context: 'A cadeia de troca depende do contexto e da janela. Substitua {windowDispose} no final de {programCs} por esta ordem de liberação:'
+tutorial.swapChain.description: 'Crie a cadeia de troca logo após a descrição da superfície:'
+tutorial.swapChain.details: 'A {swapChain} gerencia as imagens apresentadas pela janela. {drawable} fornece a imagem atual como uma {texture}, o recurso de imagem da API. Após a renderização, {present} solicita sua exibição e avança para a próxima textura de saída.'
+tutorial.swapChain.guidance: '{b8G8R8A8UNorm} armazena quatro canais de 8 bits na ordem azul, verde, vermelho e alfa. {uNorm} mapeia os inteiros de 0–255 para 0–1. A saída do shader continua sendo um valor RGBA lógico; o formato define sua representação no armazenamento. O pipeline usará {swapChainDescFormat} para corresponder a esse anexo.'
+tutorial.swapChain.title: 'Cadeia de troca'
+tutorial.title: 'Primeiro triângulo'
+tutorial.troubleshooting.description: 'Use os diagnósticos do console e os três pontos de verificação para localizar a etapa com falha:'
+tutorial.troubleshooting.details: 'O {link} e o {detail} servem de referência para dados de vértices, layout e desenho. O exemplo separa janela e envio em um programa hospedeiro comum; este tutorial mantém o fluxo em {programCs}. O {context} introduz desenho indexado, transformações e teste de profundidade.'
+tutorial.troubleshooting.details.context: 'Cubo giratório'
+tutorial.troubleshooting.details.detail: 'shader Slang'
+tutorial.troubleshooting.details.link: 'renderizador de exemplo'
+tutorial.troubleshooting.table.backend.check: 'Verifique se o backend selecionado é suportado pelo dispositivo e pelo driver gráfico instalado.'
+tutorial.troubleshooting.table.backend.symptom: 'Falha na criação do backend'
+tutorial.troubleshooting.table.drawing.check: 'Verifique o tamanho do upload, o layout de entrada, a seleção de pipeline e o posicionamento dos comandos de desenho dentro da passagem de renderização.'
+tutorial.troubleshooting.table.drawing.symptom: 'O fundo aparece, mas o triângulo não'
+tutorial.troubleshooting.table.headings.check: 'Verificação'
+tutorial.troubleshooting.table.headings.symptom: 'Sintoma'
+tutorial.troubleshooting.table.input.check: 'Verifique o passo de 28 bytes e os deslocamentos de posição/cor de 0 e 12.'
+tutorial.troubleshooting.table.input.symptom: 'Posições ou cores dos vértices estão corrompidas'
+tutorial.troubleshooting.table.shaders.check: 'Verifique o {triangleSlang} copiado, a ortografia do ponto de entrada e o diagnóstico relatado pelo compilador.'
+tutorial.troubleshooting.table.shaders.symptom: 'O fundo aparece, mas o carregamento dos shaders falha'
+tutorial.troubleshooting.table.window.check: 'Confirme se o projeto .NET compila e se a etapa somente de janela é executada em uma sessão de desktop. No Linux, verifique se o X11 ou XWayland está disponível.'
+tutorial.troubleshooting.table.window.symptom: 'Nenhuma janela se abre'
+tutorial.troubleshooting.title: 'Diagnóstico de problemas'
+tutorial.window.description: 'Baixe {cocoaHelperCs} para o diretório do projeto. Essa classe auxiliar conecta a camada Metal à janela do macOS.'
+tutorial.window.details: 'Substitua o conteúdo gerado de {programCs} pelo código abaixo. Ele usa instruções de nível superior, executadas em ordem sem um método {main} explícito. As próximas seções indicarão onde acrescentar cada trecho:'
+tutorial.window.guidance: '{graphicsAPINone} deixa a criação do contexto gráfico a cargo de Zenith.NET. {initialize} cria a janela nativa e disponibiliza os identificadores do sistema de janelas. {center} a coloca no centro da tela. {run} processa eventos até seu fechamento; depois, {dispose} libera a janela.'
+tutorial.window.notes: 'Compile e execute a aplicação. Verifique se a janela abre e fecha normalmente e se é possível movê-la e redimensioná-la. O conteúdo ainda não está definido, pois nenhum comando de renderização foi enviado. Feche a janela antes de continuar.'
+tutorial.window.title: 'Criação da janela'
+ui.action.copy: 'Copiar'
+ui.action.navigate: 'Navegar'
+ui.action.open: 'Abrir'
+ui.action.source: 'Ver código-fonte'
+ui.api.browse: 'Navegar por namespaces e tipos.'
+ui.api.clearTypeSearch: 'Limpar busca de tipos'
+ui.api.findType: 'Localizar um tipo'
+ui.api.loadError: 'Não foi possível carregar os tipos. Recarregue esta página para tentar novamente.'
+ui.api.loadingNamespaces: 'Carregando namespaces…'
+ui.api.loadingTypes: 'Carregando tipos…'
+ui.api.matchingTypes: 'Tipos correspondentes'
+ui.api.namespace: 'Namespace'
+ui.api.namespaces: 'Namespaces'
+ui.api.noNamespaceTypes: 'Nenhum tipo neste namespace.'
+ui.api.noTypes: 'Nenhum tipo correspondente.'
+ui.api.types: 'Tipos'
+ui.content.reference: 'Referência'
+ui.content.source: 'Código-fonte'
+ui.content.tutorial: 'Tutorial'
+ui.contents.close: 'Fechar sumário'
+ui.contents.open: 'Abrir sumário'
+ui.contents.title: 'Sumário'
+ui.footer.caption: 'RHI moderna para .NET'
+ui.footer.label: 'Rodapé'
+ui.language.label: 'Idioma'
+ui.language.unavailable: 'Não traduzido'
+ui.members.clear: 'Limpar busca de membros'
+ui.members.close: 'Fechar localizador de membros'
+ui.members.empty: 'Nenhum membro correspondente.'
+ui.members.open: 'Ir para o membro'
+ui.members.placeholder: 'Nome ou assinatura…'
+ui.members.search: 'Buscar membros'
+ui.members.title: 'Localizar um membro'
+ui.members.undeclared: 'Este tipo não possui membros declarados.'
+ui.navigation.api: 'API'
+ui.navigation.concepts: 'Conceitos'
+ui.navigation.home: 'Início'
+ui.navigation.label: 'Navegação principal'
+ui.navigation.skip: Ir para o conteúdo
+ui.navigation.toggle: 'Alternar navegação'
+ui.outline.title: 'Nesta página'
+ui.reference.assembly: 'Assembly'
+ui.reference.classes: 'Classes'
+ui.reference.constructors: 'Construtores'
+ui.reference.declaration: 'Declaração de tipo C#'
+ui.reference.delegates: 'Delegados'
+ui.reference.derived: 'Tipos derivados'
+ui.reference.enums: 'Enumerações'
+ui.reference.events: 'Eventos'
+ui.reference.examples: 'Exemplos'
+ui.reference.exceptions: 'Exceções'
+ui.reference.extensionMethods: 'Métodos de extensão'
+ui.reference.fields: 'Campos'
+ui.reference.implementations: 'Implementações explícitas de interface'
+ui.reference.implements: 'Implementa'
+ui.reference.inheritance: 'Herança'
+ui.reference.inheritedMembers: 'Membros herdados'
+ui.reference.interfaces: 'Interfaces'
+ui.reference.metadata: 'Metadados do tipo'
+ui.reference.methods: 'Métodos'
+ui.reference.namespace: 'Namespace'
+ui.reference.open: 'Abrir referência para {name}'
+ui.reference.operators: 'Operadores'
+ui.reference.parameters: 'Parâmetros'
+ui.reference.properties: 'Propriedades'
+ui.reference.related: 'Consulte também'
+ui.reference.relationships: 'Herança e metadados'
+ui.reference.remarks: 'Comentários'
+ui.reference.returns: 'Retornos'
+ui.reference.structs: 'Estruturas'
+ui.reference.typeParameters: 'Parâmetros de tipo'
+ui.reference.value: 'Valor'
+ui.reference.values: 'Valores'
+ui.search.close: 'Fechar busca'
+ui.search.empty: 'Nenhum resultado para “{query}”.'
+ui.search.hint: 'Pesquise tutoriais, conceitos, exemplos e a referência da API.'
+ui.search.loading: 'Carregando busca…'
+ui.search.more: 'Mostrar mais resultados'
+ui.search.pending: 'Buscando…'
+ui.search.placeholder: 'O que você está procurando?'
+ui.search.resultCount: 'Resultados: {count}'
+ui.search.title: 'Pesquisar documentação'
+ui.search.unavailable: 'A busca não está disponível. Você ainda pode abrir as páginas abaixo.'
diff --git a/documents/locales/pt-BR/translation.md b/documents/locales/pt-BR/translation.md
new file mode 100644
index 00000000..8d55ed34
--- /dev/null
+++ b/documents/locales/pt-BR/translation.md
@@ -0,0 +1,85 @@
+# Diretrizes de tradução para português brasileiro
+
+## Recursos e estrutura
+
+O [dicionário de recursos em inglês](../en-US/strings.yml) é a única fonte do texto. Consulte as regras do projeto no [guia de manutenção da documentação](../../maintenance.md). Para iniciar uma tradução, copie o arquivo inglês completo para `strings.yml` neste diretório. Preserve a primeira linha `### YamlMime:Resources` e a codificação UTF-8. Nas atualizações, incorpore as mudanças do original sem sobrescrever as traduções existentes. Este arquivo `translation.md` permanece no diretório e não é publicado no site.
+
+Navegação, ordem dos capítulos, âncoras, links, formatação, código e imagens são mantidos fora de `locales/` e compartilhados entre idiomas. Não adicione artigos, TOC, templates, scripts ou configurações neste diretório.
+
+Altere apenas os valores da `strings.yml` deste idioma. O conjunto de chaves deve corresponder exatamente ao inglês. Não adicione, remova ou renomeie chaves por conta própria, mesmo que os textos pareçam iguais ou ainda não tenham aparecido na interface. Se encontrar um problema no original, informe a chave e o problema aos responsáveis pelo projeto. Corrija primeiro a fonte inglesa e as páginas compartilhadas; depois, atualize as traduções.
+
+## Estilo e contexto
+
+Adote o tom de um manual técnico: preciso, sóbrio e acessível. Explique a relação entre as operações antes de apresentar suas restrições. Nos tutoriais, mantenha instruções claras e resultados verificáveis. Rigor não exige voz passiva nem frases longas.
+
+Revise cada artigo na ordem de leitura, considerando os parágrafos, tabelas e código ao redor. As chaves delimitam o armazenamento, não unidades isoladas de tradução. Confira concordância, artigos e preposições dos links e trechos destacados na frase completa.
+
+Títulos, rótulos, textos curtos visíveis da página inicial e legenda do rodapé não levam ponto final. Parágrafos, diagnósticos e instruções de acessibilidade mantêm a pontuação normal. Use um termo consagrado por conceito, sem acrescentar equivalentes ingleses entre parênteses de forma seletiva. Preserve identificadores de API, arquivos e configurações. Use pipeline no masculino.
+
+Mantenha uma frase quando ela explicar a operação atual, uma escolha de projeto, um resultado verificável ou uma condição necessária. Elimine listas de funções não utilizadas, ressalvas genéricas e requisitos repetidos. Traduza títulos e frases separados pela formatação como um todo, na ordem natural do português. Os marcadores `{accent}` e `{lineBreak}` do título inicial podem ser movidos; o estilo visual não deve impor a ordem inglesa. A manutenção coordena a retirada de um trecho e de suas chaves no original, na marcação compartilhada e em todos os dicionários.
+
+## Texto e marcadores
+
+Traduza frases ou parágrafos completos para um português brasileiro técnico, claro e natural. Preserve condições, negações, unidades, etapas e restrições de tempo de vida. Não acrescente garantias técnicas, requisitos de plataforma, versões de pacotes ou notas de versão ausentes no original. Mantenha nomes de produtos, pacotes e arquivos, além dos identificadores C# e Slang, inclusive maiúsculas e minúsculas.
+
+Cada valor deve ser uma string não vazia de texto simples, sem HTML ou Markdown. Respeite as aspas e os escapes do YAML; use aspas quando necessário para evitar que dois-pontos, cerquilhas ou valores parecidos com números sejam interpretados incorretamente. Preserve a ordem das chaves do inglês para facilitar a revisão.
+
+Marcadores nomeados como `{context}` e `{link}` podem inserir código, links ou texto destacado das páginas compartilhadas. Marcadores de interface como `{name}`, `{query}` e `{count}` também podem receber texto em tempo de execução. Confira o uso na página ou interface correspondente, sem deduzir o conteúdo apenas pelo nome. Traduza também os rótulos de links e os textos destacados que tenham sua própria chave de recurso.
+
+A ordem dos marcadores pode seguir a sintaxe do português. Para cada chave, preserve todos os nomes, suas maiúsculas e minúsculas e as chaves ASCII que delimitam o marcador. Não adicione, remova ou renomeie marcadores, nem os substitua por nomes de API, rótulos de links ou números de exemplo. Não divida chaves de recurso para recompor frases.
+
+## Links de API e significado técnico
+
+Os links são mantidos nas páginas compartilhadas. Identificadores que designam uma API local no texto e nas tabelas levam ao tipo ou membro correspondente. Variáveis, nomes de parâmetros, tipos definidos pelo exemplo, nomes de pacotes e arquivos e símbolos externos sem uma página de referência local permanecem como texto de código. A mesma grafia pode designar símbolos diferentes. Blocos de código comuns do tutorial não recebem links automáticos. É permitido mover um marcador inteiro, mas não alterar links, código compartilhado ou sua formatação.
+
+Se uma API não tiver uma entrada de referência gerada, o identificador permanece como texto de código na página compartilhada, com um link para o código-fonte claramente identificado por perto. Não o substitua por um link para outro tipo ou membro.
+
+Preserve estas distinções:
+
+- Gravar comandos, enviá-los, executá-los na GPU e concluí-los são etapas distintas.
+- Uso de recursos, residência de memória ou intenção de acesso da CPU, layout de textura e formato de pixel são conceitos distintos.
+- A propriedade indica a responsabilidade de liberar um recurso. Um handle não mantém o recurso vivo por si só; o envio de comandos também não transfere a propriedade.
+- A conclusão na GPU nem sempre significa que os dados de leitura temporários já foram copiados para o destino de CPU da aplicação. Preserve as instruções sobre aguardar a fila responsável.
+- Um drawable é a textura de saída do quadro atual, não necessariamente uma imagem da cadeia de troca. Distinga as visualizações de recursos dos controles de interface.
+
+## Build e revisão antes da entrega
+
+Execute os comandos na raiz do repositório, com o SDK do .NET exigido pelo projeto e o DocFX instalados. Em uma cópia nova do repositório, os metadados gerados em `documents/api/` ainda não existem. Primeiro execute o build completo, que inclui a extração da API:
+
+```sh
+docfx documents/docfx.json --warningsAsErrors
+```
+
+Se os metadados de API já existem e correspondem ao código-fonte atual, alterações apenas no dicionário podem usar o build normal e a prévia:
+
+```sh
+docfx build documents/docfx.json --warningsAsErrors
+docfx serve documents/_site --hostname 127.0.0.1 --port 8080
+```
+
+Este idioma já está registrado. O dicionário completo fica disponível após um build bem-sucedido, sem alterar a configuração. Chaves ausentes ou extras, valores vazios ou de outro tipo e nomes de marcadores diferentes fazem o build falhar. O build não detecta traduções imprecisas nem trechos que continuem em inglês; a leitura e revisão humanas são necessárias.
+
+Todos os idiomas compartilham os caminhos das páginas. `?lang=pt-BR` seleciona português brasileiro; a troca de idioma mantém o caminho e a âncora. Sem uma escolha explícita, são usadas, nesta ordem, a escolha manual salva, as preferências do navegador e o inglês. Se esta tradução ainda não estiver publicada, ou se o carregamento ou a validação dos recursos da página falhar, a página fica em inglês, sem misturar idiomas frase a frase. A lista de idiomas é ordenada por código.
+
+Antes de entregar, confira as chaves e os marcadores. Revise a página inicial, a entrada de Learn, o tutorial do triângulo, os conceitos, os exemplos e a interface de API no desktop e com 320 pixels de largura. Leia o tutorial como iniciante e confira terminologia, botões, busca, cópia de código, links de API e a posição da seção após trocar de idioma. Identificadores, declarações e código continuam compartilhados; traduza apenas os textos fornecidos pelo dicionário. Mantenha dicionários de teste e arquivos temporários fora do repositório e não entregue traduções com texto de preenchimento.
+
+## Terminologia
+
+| Termo em inglês | Forma adotada |
+| ---------------- | ----------------------------------------------------------------------------------------------------------- |
+| API / GPU / RHI | Preservar as siglas e explicá-las conforme o original |
+| barrier | barreira |
+| buffer | buffer; preservar `Buffer` |
+| command buffer | buffer de comandos |
+| command queue | fila de comandos |
+| drawable | textura de saída do quadro atual; preservar `Drawable`, sem presumir que seja uma imagem da cadeia de troca |
+| graphics context | contexto gráfico |
+| pipeline | pipeline |
+| readback | leitura de dados da GPU |
+| render pass | passagem de renderização |
+| resource | recurso |
+| shader | shader |
+| swap chain | cadeia de troca |
+| texture | textura |
+| timeline | linha do tempo |
+| upload | envio de dados à GPU |
diff --git a/documents/locales/ru-RU/strings.yml b/documents/locales/ru-RU/strings.yml
new file mode 100644
index 00000000..9ccc4f3b
--- /dev/null
+++ b/documents/locales/ru-RU/strings.yml
@@ -0,0 +1,593 @@
+### YamlMime:Resources
+concepts.execution.context.description: '{graphicsContext} связывает приложение с устройством GPU через определённый бэкенд и предоставляет его графическую, вычислительную и передающую очереди. Совместно используемые ресурсы должны принадлежать одному контексту: буферы, текстуры, шейдеры, конвейеры и их дескрипторы нельзя привязывать между разными контекстами.'
+concepts.execution.context.details: '{capabilities} сообщает имя устройства и поддерживаемые возможности. Перед созданием ресурсов для трассировки лучей или меш-шейдинга проверяйте соответственно {rayTracingSupported} и {meshShadingSupported}. Такие проверки при инициализации показаны в примерах {link} и {detail}.'
+concepts.execution.context.details.detail: 'меш-шейдинга'
+concepts.execution.context.details.link: трассировки лучей
+concepts.execution.context.title: 'Графический контекст'
+concepts.execution.description: 'Подготовка работы GPU начинается на CPU: приложение создаёт ресурсы, записывает команды, которые их используют, и отправляет команды в очередь. После отправки CPU может продолжать работу, пока GPU выполняет команды. Нужные им ресурсы должны оставаться доступными до завершения выполнения.'
+concepts.execution.ownership.description: 'Владение определяет, кто освобождает объект, а завершение работы GPU — когда его можно освободить. Вызов {dispose} освобождает нативные ресурсы, даже если ссылки C# на объект ещё существуют. Отправка команд не передаёт очереди владение их ресурсами.'
+concepts.execution.ownership.details: 'При завершении приложения прекратите новые отправки и дождитесь последнего использования ресурсов во всех задействованных очередях. Затем освободите ресурсы приложения и контекст. Контекст освобождает свои очереди и внутренние вспомогательные объекты, поэтому этот последний шаг должен выполняться после завершения работы приложения с GPU.'
+concepts.execution.ownership.guidance: 'Во время рендеринга отслеживайте завершение обращений к данным, которые предстоит перезаписать, или к ресурсам, которые предстоит заменить. Прежде чем изменять или освобождать память, дождитесь окончания этих обращений. Время жизни представлений и лежащих в их основе ресурсов описано в статье «{link}».'
+concepts.execution.ownership.guidance.link: Управление ресурсами
+concepts.execution.ownership.table.applicationResources.object: Ресурсы, созданные приложением
+concepts.execution.ownership.table.applicationResources.ownership: Приложение освобождает их после последнего использования на GPU.
+concepts.execution.ownership.table.commandBuffers.object: 'Заимствованные буферы команд'
+concepts.execution.ownership.table.commandBuffers.ownership: Очередь управляет ими и повторно использует их.
+concepts.execution.ownership.table.headings.object: Объект
+concepts.execution.ownership.table.headings.ownership: 'Ответственность за освобождение'
+concepts.execution.ownership.table.queues.object: Очереди контекста и их временные шкалы
+concepts.execution.ownership.table.queues.ownership: Контекст и очереди управляют ими.
+concepts.execution.ownership.table.swapChainDrawable.ownership: Цепочка обмена управляет ею.
+concepts.execution.ownership.title: 'Владение и время жизни'
+concepts.execution.pipelines.description: 'Графический конвейер задаёт шейдеры, формат вершинных данных, сборку примитивов, форматы вложений и состояние для команд рисования. Выберите конвейер до привязки вершин, индексов и констант. Форматы вложений и число выборок должны соответствовать проходу рендеринга.'
+concepts.execution.pipelines.details: 'Вычислительный конвейер выбирает точку входа шейдера для запуска вычислений. Привяжите конвейер и его константы перед запуском, вне графического прохода рендеринга.'
+concepts.execution.pipelines.guidance: 'Последовательность графических команд разобрана в {link}. В {detail} перед графическим проходом добавляется вычисление изображения, которое затем выводится на экран.'
+concepts.execution.pipelines.guidance.detail: 'вычислительном примере'
+concepts.execution.pipelines.guidance.link: 'обработчике рендеринга треугольника'
+concepts.execution.pipelines.title: 'Состояние конвейера'
+concepts.execution.queues.description: '{graphicsQueue} обычно подходит для кадра, объединяющего рисование, вычисления и копирование. Размещение зависимых операций в одной очереди упрощает их упорядочивание.'
+concepts.execution.queues.details: 'Отдельные очереди позволяют независимо отправлять разные задачи, но не обязательно соответствуют независимым аппаратным блокам. Дополнительные очереди полезны, если задачи могут выиграть от перекрытия выполнения. Когда одна очередь использует данные другой, между их отправками нужна явная зависимость.'
+concepts.execution.queues.guidance: 'Даже в одной очереди зависимым операциям могут понадобиться барьеры или переходы состояния текстур, чтобы последующие обращения видели результаты предыдущих записей. Эти зависимости по памяти отличаются от порядка отправки команд. Как их задавать, описано в статье «{link}».'
+concepts.execution.queues.guidance.link: Синхронизация
+concepts.execution.queues.table.computeQueue.workToRecord: 'Запуск вычислений и совместимые операции передачи данных, без графических проходов рендеринга.'
+concepts.execution.queues.table.graphicsQueue.workToRecord: 'Проходы рендеринга, рисование, вычисления и передача данных.'
+concepts.execution.queues.table.headings.queue: Очередь
+concepts.execution.queues.table.headings.workToRecord: 'Поддерживаемые операции'
+concepts.execution.queues.table.transferQueue.workToRecord: 'Загрузка, считывание и копирование без рисования и вычислительных запусков.'
+concepts.execution.queues.title: 'Выбор очереди'
+concepts.execution.recording.context: 'Очередь возвращает завершённые командные буферы для повторного использования. Для каждой записи получите буфер через {queueCommandBuffer}, записывайте его только из одного потока одновременно и отправьте один раз. После отправки оставьте управление очереди: не изменяйте буфер, не отправляйте его повторно и не освобождайте самостоятельно.'
+concepts.execution.recording.description: '{queueCommandBuffer} возвращает {commandBuffer}, уже открытый для записи. Запишите операции и зависимости, затем один раз вызовите {submit}. Вызов завершает запись и возвращает {timelineValue}, обозначающий точку завершения в этой очереди.'
+concepts.execution.recording.details: 'После записи кадра в {commandBuffer} учебный пример отправляет команды и ожидает завершения:'
+concepts.execution.recording.guidance: 'Запись формирует последовательность команд, а {submit} планирует её выполнение. Последующий вызов {wait} блокирует поток CPU до завершения отправленной работы. Поэтому цикл кадров в уроке легко проследить: каждый кадр заканчивается до начала следующего.'
+concepts.execution.recording.title: 'Запись и отправка команд'
+concepts.execution.title: Модель выполнения
+concepts.platforms.description: 'Выводом управляет приложение или элемент интерфейса. От этого зависит, кто получает выходную текстуру кадра, записывает и отправляет команды и выводит результат. Эта выходная текстура называется drawable.'
+concepts.platforms.frame.context: 'Записывайте привязку конвейера и команды рисования внутри прохода и завершайте его до возврата из обработчика. Элемент управления сам отправит команды и выведет результат. Обработчик не должен отправлять командный буфер или освобождать любой из двух предоставленных объектов. Если он меняет состояние выходной текстуры, перед возвратом нужно восстановить {colorAttachment}, чтобы элемент управления выполнил заключительный переход.'
+concepts.platforms.frame.description: 'Элементы реализуют {iZenithView}. Приложение назначает {graphicsContext}, обновляет сцену в {updateRequested} и записывает команды в {renderRequested}, используя переданный {renderEventArgs}.'
+concepts.platforms.frame.details: 'Для каждого кадра элемент управления получает выходную текстуру и командный буфер, переводит текстуру в {colorAttachment} и вызывает обработчики обновления и рендеринга. Затем он отправляет записанные команды и выводит изображение средствами выбранного UI-фреймворка.'
+concepts.platforms.frame.guidance: 'Добавьте {usingZenithNET} в файл. Для инициализированного {iZenithView} с именем {view} и назначенным графическим контекстом зарегистрируйте обработчик, заполняющий выходную текстуру цветом:'
+concepts.platforms.frame.notes: 'Первый переход использует {undefined}, разрешая отбросить прежнее содержимое изображения. Поэтому каждый кадр должен заново заполнять или отрисовывать всё изображение. Накопленные или кешированные результаты храните в текстуре приложения, а затем рисуйте её в выходную текстуру элемента управления.'
+concepts.platforms.frame.reference: '{zenithViewHelperDrawableFormat} предоставляет цветовой формат, используемый по умолчанию в этих интеграциях.'
+concepts.platforms.frame.title: 'Рендеринг в элементах интерфейса'
+concepts.platforms.integration.context: 'WinForms, WPF и Windows-интеграция WinUI работают под Windows. Для MAUI установите рабочую нагрузку целевой платформы и зарегистрируйте обработчик элемента управления через {useZenithView}, как показано в {extensionsCs}.'
+concepts.platforms.integration.description: 'Интерфейс событий общий, но механизм вывода зависит от фреймворка:'
+concepts.platforms.integration.details: 'Реализации различаются тем, как изображение GPU попадает в интерфейс. Считывание в растровое изображение копирует пиксели в память CPU перед показом. Совместное использование текстуры исключает эту копию на CPU, но всё ещё может требовать копирования на GPU и синхронизации. При выборе интеграции учитывайте этот путь вывода наряду с общим событием {renderRequested}.'
+concepts.platforms.integration.guidance: 'Добавьте через NuGet пакет интеграции для вашего UI-фреймворка. Для Avalonia используйте {avaloniaPackage}.'
+concepts.platforms.integration.table.avalonia.integration: Avalonia
+concepts.platforms.integration.table.avalonia.presentation: 'Рисует в текстуру и считывает пиксели в {writeableBitmap}. Реализация: {zenithViewCs} и {surfaceCs}.'
+concepts.platforms.integration.table.headings.integration: Интеграция
+concepts.platforms.integration.table.headings.presentation: 'Механизм вывода'
+concepts.platforms.integration.table.maui.integration: .NET MAUI
+concepts.platforms.integration.table.maui.presentation: Использует нативные поверхности в Android, iOS и Mac Catalyst, а в Windows — композицию через разделяемые текстуры. См. {link}.
+concepts.platforms.integration.table.maui.presentation.link: реализации для платформ
+concepts.platforms.integration.table.uno.integration: Вариант Uno
+concepts.platforms.integration.table.uno.presentation: Считывает пиксели в {writeableBitmap}. См. {zenithViewUnoCs}.
+concepts.platforms.integration.table.winForms.integration: WinForms
+concepts.platforms.integration.table.winForms.presentation: Создаёт цепочку обмена Zenith.NET для дескриптора {hWND} элемента управления. См. {zenithViewCs}.
+concepts.platforms.integration.table.winUI.integration: WinUI в Windows
+concepts.platforms.integration.table.winUI.presentation: 'Рисует в импортированную общую текстуру Direct3D 11, затем копирует результат в композиционную цепочку обмена, связанную с {swapChainPanel}. Реализация: {zenithViewWinUICs}.'
+concepts.platforms.integration.table.wpf.integration: WPF
+concepts.platforms.integration.table.wpf.presentation: 'Импортирует общую текстуру Direct3D 11 и через взаимодействие с Direct3D копирует её в поверхность, отображаемую {d3DImage}. Реализация: {surfaceCs}.'
+concepts.platforms.integration.title: 'Фреймворк и вывод изображения'
+concepts.platforms.lifecycle.context: 'При создании соответствующих вложений используйте {descWidth}, {descHeight}, {descFormat} и {descSampleCount} выходной текстуры, переданной событием рендеринга. Эта текстура задаёт реальные размеры цели вывода; их не следует вычислять по размеру элемента интерфейса или масштабу экрана.'
+concepts.platforms.lifecycle.description: 'Цепочка обмена использует ширину и высоту кадрового буфера в пикселях. При масштабировании экрана они могут отличаться от логических размеров окна. Отложите рендеринг и изменение размера цепочки, пока хотя бы один размер равен нулю. Получайте {swapChainDrawable} для каждого кадра: цепочка обмена владеет этой текстурой и может заменить её после вывода или изменения размера.'
+concepts.platforms.lifecycle.details: 'При изменении размеров кадрового буфера дождитесь всех обращений GPU к старым изображениям перед вызовом {swapChainResize}. Чтобы заменить нативную цель вывода, после завершения этих обращений передайте её новое описание и дескрипторы в {swapChainRefresh}. При завершении приложения освободите цепочку обмена до уничтожения её нативной цели вывода.'
+concepts.platforms.lifecycle.guidance: 'Если размеры принадлежащих приложению текстур глубины или внеэкранных текстур следуют за окном, пересоздайте их при изменении размера и обновите константы с их дескрипторами. Конвейер можно использовать дальше, пока форматы вложений и число выборок совпадают. Повторное использование конвейера при замене текстур показано в {link}.'
+concepts.platforms.lifecycle.guidance.link: 'примере вращающегося куба'
+concepts.platforms.lifecycle.title: 'Размеры и время жизни цепочки обмена'
+concepts.platforms.ownership.description: 'Приложение владеет назначенным контекстом и своими ресурсами рендеринга, а элемент управления — ресурсами вывода. При изменении {graphicsContext} представления планировщик пересоздаёт ресурсы элемента управления для нового контекста. Приложение также должно пересоздать для этого контекста свои конвейеры, буферы и текстуры.'
+concepts.platforms.ownership.details: 'Перед заменой ресурсов рендеринга остановите обработку кадров представления и дождитесь использующей их работы GPU. Только после этого освобождайте старые ресурсы или контекст. При удалении представления приложение по-прежнему отвечает за освобождение назначенного контекста.'
+concepts.platforms.ownership.guidance: 'Общий {frameSchedulerCs} выполняет обработчики кадров через диспетчер UI представления. Ограничивайте их работу обновлением сцены и записью команд: длительные вычисления на CPU здесь задерживают и отклик интерфейса.'
+concepts.platforms.ownership.title: 'Ресурсы приложения и элемента интерфейса'
+concepts.platforms.surface.description: 'В {link} Silk.NET создаёт окно и обрабатывает события. Zenith.NET получает нативные дескрипторы окна и его размеры через описание {surface}, по которому создаёт {swapChain}. За само окно операционной системы по-прежнему отвечает приложение.'
+concepts.platforms.surface.description.link: 'уроке с треугольником'
+concepts.platforms.surface.details: 'Дескрипторы поверхности должны соответствовать бэкенду:'
+concepts.platforms.surface.guidance: 'Общая оболочка примеров в {appCs} поддерживает Windows, macOS и X11. Под Linux её окно предоставляет дескрипторы Xlib, поэтому в сеансе Wayland для запуска нужен XWayland. Приложение с нативным Wayland передаёт дескрипторы дисплея и поверхности через {surfaceWayland}.'
+concepts.platforms.surface.table.android.backend: Vulkan в Android.
+concepts.platforms.surface.table.apple.backend: Metal на платформах Apple.
+concepts.platforms.surface.table.headings.backend: 'Бэкенд и платформа'
+concepts.platforms.surface.table.headings.factory: Фабрика поверхности
+concepts.platforms.surface.table.headings.handles: Дескрипторы, предоставляемые хостом
+concepts.platforms.surface.table.wayland.backend: Vulkan с нативным хостом Wayland.
+concepts.platforms.surface.table.wayland.handles: Дисплей и поверхность Wayland
+concepts.platforms.surface.table.win32.backend: DirectX 12 или Vulkan в Windows.
+concepts.platforms.surface.table.xlib.backend: Vulkan в Linux с X11 или XWayland.
+concepts.platforms.surface.table.xlib.handles: Дисплей и окно X11
+concepts.platforms.surface.title: 'Нативные поверхности вывода'
+concepts.platforms.title: Интеграция с платформами
+concepts.resources.allocation.context: 'Размещённые в куче ресурсы зависят от её памяти в течение всего своего времени жизни. После последнего использования на GPU освободите сначала ресурсы, затем кучу.'
+concepts.resources.allocation.description: 'Используйте {heap}, если приложение должно самостоятельно управлять выделением памяти. Ресурсы размещаются в куче по явно заданным смещениям в байтах; приложение выбирает эти смещения и учитывает занятые области.'
+concepts.resources.allocation.details: 'Для каждого ресурса вызовите {contextGetSizeAndAlignment} с тем описанием буфера или текстуры, которое будет использовано при создании. Зарезервируйте возвращённый размер по смещению, кратному указанному выравниванию. Вся область должна находиться в пределах кучи и не пересекать другую ещё действующую область. Размер текстуры может включать дополнительные промежутки, зависящие от бэкенда, поэтому вычислить его только по числу пикселей и байтам на пиксель нельзя.'
+concepts.resources.allocation.guidance: 'Резидентность буфера, размещённого в куче, должна совпадать с резидентностью этой кучи. Для текстур используйте кучу, предназначенную только для GPU.'
+concepts.resources.allocation.title: 'Размещение в кучах памяти'
+concepts.resources.bufferUsages.table.constant.role: Параметры шейдера, считываемые через привязку буфера констант.
+concepts.resources.bufferUsages.table.headings.role: 'Роль'
+concepts.resources.bufferUsages.table.headings.usage: Назначение
+concepts.resources.bufferUsages.table.index.role: 'Индексы, используемые при индексированном рисовании.'
+concepts.resources.bufferUsages.table.indirect.role: 'Аргументы, считываемые командами косвенного рисования или косвенного запуска вычислений.'
+concepts.resources.bufferUsages.table.none.role: 'Флаги назначения буфера не объявлены.'
+concepts.resources.bufferUsages.table.storageReadOnly.role: 'Данные буфера, доступные через привязку шейдера только для чтения.'
+concepts.resources.bufferUsages.table.storageReadWrite.role: 'Данные буфера, доступные через привязку шейдера для чтения и записи.'
+concepts.resources.bufferUsages.table.transferDst.role: 'Память назначения для копирования на GPU.'
+concepts.resources.bufferUsages.table.transferSrc.role: 'Исходные данные для копирования на GPU.'
+concepts.resources.bufferUsages.table.vertex.role: 'Вершинные атрибуты, считываемые для рисования.'
+concepts.resources.bufferUsages.title: Назначения буфера
+concepts.resources.description: 'Описание ресурса задаёт допустимые способы использования его памяти. Например, текстуре, которая служит целью рендеринга, читается шейдером и копируется на CPU, нужны флаги назначения для всех трёх операций. Размещение памяти определяет доступ CPU, а синхронизация и управление временем жизни — когда можно обращаться к данным и освобождать память.'
+concepts.resources.memory.context: 'Для доступных CPU буферов {map} возвращает адрес, по которому можно читать или записывать данные. Ни отображение памяти, ни {unmap} не дожидаются GPU. Перед чтением результата приложение должно дождаться его записи на GPU, а перед изменением тех же байтов — всех ещё выполняющихся обращений GPU к ним.'
+concepts.resources.memory.description: 'Поле {residency} в {bufferDesc} задаёт предполагаемый доступ CPU к памяти:'
+concepts.resources.memory.details: 'Резидентность выражает требования к доступу CPU, в том числе на системах с общей физической памятью CPU и GPU. В примере треугольника {cpuWriteOnly} позволяет напрямую заполнить небольшой вершинный буфер. Для более объёмной статической геометрии может быть предпочтительнее загрузка в память, предназначенную только для GPU.'
+concepts.resources.memory.guidance: 'Текстуры, создаваемые непосредственно через контекст, используют память GPU. Поэтому в {textureDesc} нет поля резидентности: приложение обменивается данными с памятью CPU через загрузку и считывание.'
+concepts.resources.memory.table.cpuReadOnly.cPUAccess: 'Только чтение'
+concepts.resources.memory.table.cpuReadOnly.typicalRole: 'Данные, полученные на GPU для считывания на CPU'
+concepts.resources.memory.table.cpuWriteOnly.cPUAccess: 'Только запись'
+concepts.resources.memory.table.cpuWriteOnly.typicalRole: 'Данные, предоставляемые или обновляемые CPU'
+concepts.resources.memory.table.gpuOnly.cPUAccess: 'Не предусмотрен'
+concepts.resources.memory.table.gpuOnly.typicalRole: 'Статическая геометрия и рабочие данные GPU'
+concepts.resources.memory.table.headings.cPUAccess: 'Предусмотренный доступ CPU'
+concepts.resources.memory.table.headings.residency: 'Режим доступа к памяти'
+concepts.resources.memory.table.headings.typicalRole: Типичная роль
+concepts.resources.memory.title: 'Доступ CPU и размещение памяти'
+concepts.resources.textureUsages.description: 'Флаги передачи указывают, служит ли ресурс источником или приёмником при копировании на GPU. Прямой доступ CPU к его памяти определяется отдельно, при выборе размещения памяти.'
+concepts.resources.textureUsages.details: 'Флаги назначения объявляют допустимые способы использования текстуры. Переход между ними требует соблюдения правил, описанных в разделе «{link}». Устройство также должно поддерживать выбранные операции для указанного формата текстуры.'
+concepts.resources.textureUsages.details.link: 'состояния доступа и синхронизация'
+concepts.resources.textureUsages.table.colorAttachment.role: 'Цветовое вложение в проходе рендеринга.'
+concepts.resources.textureUsages.table.depthStencilAttachment.role: 'Вложение глубины и трафарета в проходе рендеринга.'
+concepts.resources.textureUsages.table.headings.role: 'Роль'
+concepts.resources.textureUsages.table.headings.usage: Назначение
+concepts.resources.textureUsages.table.none.role: 'Флаги назначения текстуры не объявлены.'
+concepts.resources.textureUsages.table.sampled.role: 'Данные текстуры, считываемые через привязку сэмплируемой текстуры.'
+concepts.resources.textureUsages.table.storage.role: 'Данные текстуры, доступные через привязку текстуры хранения для чтения или записи шейдером.'
+concepts.resources.textureUsages.table.transferDst.role: 'Память изображения назначения для копирования на GPU.'
+concepts.resources.textureUsages.table.transferSrc.role: 'Данные исходного изображения для копирования на GPU.'
+concepts.resources.textureUsages.title: Назначения текстуры
+concepts.resources.title: Управление ресурсами
+concepts.resources.transfers.context: 'При считывании данные GPU сначала копируются во внутреннюю промежуточную память. Затем библиотека переносит их в указанную память CPU, когда возвращает завершённый командный буфер для повторного использования. Вызов {wait} для значения отправки, содержащей считывание, завершает оба шага. До возврата из этого вызова память назначения должна оставаться выделенной, а управляемая память — закреплённой.'
+concepts.resources.transfers.description: 'Загрузка копирует данные CPU в ресурс GPU, а считывание — данные ресурса в память CPU. Методы буферов и текстур завершают передачу до возврата управления:'
+concepts.resources.transfers.details: 'Перед вызовом этих методов устраните конфликтующие обращения из других очередей. Внутреннее ожидание метода относится только к его собственной передаче и не создаёт зависимость от другой очереди. При прямом копировании через отображённую память ожидание конфликтующих обращений GPU полностью возлагается на вызывающий код.'
+concepts.resources.transfers.guidance: 'Чтобы отправить передачу данных вместе с другой работой GPU, запишите её через {commandBufferUpload} или {commandBufferDownload}. Требования ко времени жизни исходной памяти при загрузке и памяти назначения при считывании различаются:'
+concepts.resources.transfers.item: 'Во время вызова загрузки библиотека копирует исходные данные во временную промежуточную память. Позже GPU читает данные именно из неё, поэтому исходный указатель приложения нужен только до возврата из вызова.'
+concepts.resources.transfers.notes: 'При передаче текстур шаг строки — это расстояние в байтах между соседними строками данных CPU, а шаг среза — расстояние между соседними срезами по глубине. По этим значениям выполняется копирование между памятью CPU и хранилищем текстуры на GPU. Почему завершение GPU и запись в память назначения — разные этапы, объясняется в статье «{link}».'
+concepts.resources.transfers.notes.link: Синхронизация
+concepts.resources.transfers.table.bufferDownload.transfer: 'Для {cpuReadOnly} копирует непосредственно из отображённой памяти. В остальных случаях отправляет считывание в передающую очередь и дожидается его завершения.'
+concepts.resources.transfers.table.bufferUpload.transfer: 'Для {cpuWriteOnly} копирует непосредственно в отображённую память. В остальных случаях отправляет загрузку в передающую очередь и дожидается её завершения.'
+concepts.resources.transfers.table.headings.method: Метод
+concepts.resources.transfers.table.headings.transfer: 'Порядок передачи'
+concepts.resources.transfers.table.textureTransfers.method: '{textureUpload} / {textureDownload}'
+concepts.resources.transfers.table.textureTransfers.transfer: 'Отправляет копирование и переходы состояния текстуры в графическую очередь, затем ожидает завершения. Выбранный подресурс остаётся в запрошенном конечном состоянии.'
+concepts.resources.transfers.title: 'Немедленная и записываемая передача'
+concepts.resources.usages.description: '{bufferUsages} и {textureUsages} — флаговые перечисления, объявляющие предполагаемые роли ресурса. Они задаются в полях {bufferDescUsages} и {textureDescUsages} соответственно. Если у ресурса несколько ролей, члены флагового перечисления можно объединить с помощью {value}.'
+concepts.resources.usages.title: 'Назначения ресурсов'
+concepts.resources.views.description: '{bufferView} описывает область буфера и интерпретацию её данных. {textureView} выбирает диапазон подресурсов, совместимые тип и формат текстуры. Оба представления ссылаются на существующую память с назначениями, объявленными при её создании. Диапазоны представлений описывают доступ, но не обеспечивают одинаковую проверку границ в шейдерах на всех бэкендах.'
+concepts.resources.views.details: 'Собственный дескриптор ресурса предоставляет его представление по умолчанию. Для другой области или совместимого способа интерпретации создайте отдельное представление. Сохраняйте и представление, и исходный ресурс до завершения их использования на GPU: дескриптор сам по себе не удерживает ни один из объектов. При замене любого из них обновите константы, содержащие прежний {link}.'
+concepts.resources.views.details.link: 'дескриптор ресурса'
+concepts.resources.views.title: 'Представления и исходные ресурсы'
+concepts.shaders.binding.context: 'В вычислительном примере {inputTextureSampledHandle} присваивается {input}, а {outputTextureStorageHandle} — {output}. В Slang параметр типа {descriptorHandle} определяет доступ шейдера к ресурсу:'
+concepts.shaders.binding.description: '{setConstantBuffer} выбирает существующий буфер и смещение в байтах, с которого текущий конвейер читает константы. Загрузите параметры в эту память до обращения шейдера. Если ранее отправленная работа GPU ещё читает те же байты, дождитесь её завершения перед обновлением.'
+concepts.shaders.binding.details: 'Подготовьте командный буфер, вычислительный конвейер, заполненный константный буфер и число групп потоков. Затем запишите запуск вычислений в следующем порядке. Сначала выберите конвейер, поскольку привязка константного буфера относится именно к нему:'
+concepts.shaders.binding.guidance: 'Здесь {commandBuffer} записывает запуск {computePipeline} с параметрами из {constantBuffer}. Буферу нужно назначение {bufferUsagesConstant}; смещение 0 выбирает его начало. Если в буфере несколько записей констант, начальное смещение каждой также должно удовлетворять выравниванию привязки, требуемому бэкендом. Это отдельное условие, не зависящее от размещения полей внутри записи.'
+concepts.shaders.binding.notes: '{resourceHandle} содержит два 32-битных поля, интерпретация которых зависит от бэкенда. Их значения действительны только в контексте, где создан ресурс.'
+concepts.shaders.binding.reference: 'Сохраняйте ресурс и представление, из которого получен дескриптор, до завершения последнего обращения шейдера. При замене обновите константы со старым дескриптором. Правила владения изложены в статье «{link}».'
+concepts.shaders.binding.reference.link: Управление ресурсами
+concepts.shaders.binding.table.bufferStorageReadOnlyHandle.access: 'Чтение структурированных элементов шейдерного типа {t}'
+concepts.shaders.binding.table.bufferStorageReadWriteHandle.access: 'Чтение и запись структурированных элементов шейдерного типа {t}'
+concepts.shaders.binding.table.headings.access: Предполагаемый доступ
+concepts.shaders.binding.table.headings.cHandle: 'Дескриптор C#'
+concepts.shaders.binding.table.headings.slangFieldType: Тип поля Slang
+concepts.shaders.binding.table.samplerHandle.access: 'Состояние выборки из текстур'
+concepts.shaders.binding.table.textureSampledHandle.access: 'Чтение текстур'
+concepts.shaders.binding.table.textureStorageHandle.access: 'Чтение и запись текстур хранения'
+concepts.shaders.binding.title: 'Буферы констант и дескрипторы ресурсов'
+concepts.shaders.compilation.description: '{zenithCompiler} компилирует именованную точку входа Slang в {shaderDesc}. {graphicsApi} контекста определяет результат: DXIL для DirectX 12, библиотеку Metal для Metal или SPIR-V для Vulkan.'
+concepts.shaders.compilation.details: 'Используя уже созданный графический контекст {context}, скомпилируйте шейдер треугольника из копии рядом с исполняемым файлом:'
+concepts.shaders.compilation.guidance: 'Имя точки входа {vSMain} должно совпадать с её объявлением в файле шейдера. Компилируйте шейдеры и создавайте конвейеры при инициализации, а во время рендеринга используйте готовые конвейеры повторно. После создания конвейера объекты шейдеров можно освободить, как показано в {link}.'
+concepts.shaders.compilation.guidance.link: 'учебном примере треугольника'
+concepts.shaders.compilation.title: 'Компиляция шейдеров'
+concepts.shaders.description: 'Объявления C# и Slang должны согласованно определять, где хранится каждое значение и как шейдер к нему обращается. Описания вершинного ввода задают атрибуты вершин, объявления буферов в шейдерах — параметры и массивы, а дескрипторы указывают используемые шейдером ресурсы.'
+concepts.shaders.dispatch.description: '{dispatch} задаёт число выполняемых групп потоков. В вычислительном примере объявлено {numthreads}, поэтому каждая группа содержит 16 × 16 потоков. Чтобы обработать всё изображение, разделите ширину и высоту на 16 и округлите каждый результат вверх. Для изображения 17 × 19 пикселей нужны 2 × 2 группы. Шейдер проверяет координаты каждого потока и пропускает те, что выходят за границы изображения.'
+concepts.shaders.dispatch.details: 'Объявленный размер группы доступен через {shaderDescThreadGroupSize}. По нему можно рассчитать число групп для {dispatch}; полный расчёт показан в {computeShaderRendererCs}. Последующие обращения к полученному изображению должны соблюдать требования из статьи «{link}».'
+concepts.shaders.dispatch.details.link: Синхронизация
+concepts.shaders.dispatch.title: 'Группы потоков'
+concepts.shaders.layout.context: 'Такая организация показана в {link}: они содержат размеры изображения и два дескриптора текстур. Явные смещения указывают начало каждого поля. Для объявления используются {systemRuntimeInteropServices} и {zenithNET}:'
+concepts.shaders.layout.context.link: 'константах C# примера вычислений'
+concepts.shaders.layout.description: 'Для вершинного ввода {inputLayout} задаёт формат, смещение и семантику каждого атрибута, а также шаг между вершинами. В треугольнике за позицией {float3} следует цвет {float4}: цвет начинается со смещения 12 байт, а вершина занимает 28 байт. {inputLayoutAdd} рассчитывает эту плотную раскладку по форматам элементов. Если структура C# содержит выравнивающие промежутки, учитывайте их в смещениях и шаге.'
+concepts.shaders.layout.details: 'Константные буферы содержат параметры шейдера, а структурированные — массивы типизированных элементов. Их раскладка определяется объявлениями Slang, а не {inputLayout}. Смещения полей C#, размеры элементов и выравнивающие промежутки должны соответствовать раскладке скомпилированного шейдера. Неуправляемый тип C# можно скопировать как последовательность байтов, но его размещение в памяти не обязательно соответствует раскладке шейдера: плотно упакованный {float3} в вершине может требовать другого выравнивания в буфере шейдера.'
+concepts.shaders.layout.guidance: 'Параметры шейдера могут содержать дескрипторы ресурсов наряду с обычными значениями. Дескриптор указывает на уже существующую память, поэтому его копирование в константный буфер не копирует содержимое ресурса.'
+concepts.shaders.layout.notes: 'Соответствующее объявление в {computeShaderSlang}:'
+concepts.shaders.layout.reference: 'Два размера занимают первые восемь байт, за ними следуют два дескриптора по восемь байт каждый. В примере C# под всю запись отведено 256 байт. Размер другой записи следует определять по её собственным полям, требуемой раскладке и выравниванию.'
+concepts.shaders.layout.title: 'Размещение данных'
+concepts.shaders.matrices.description: 'Компилятор использует построчное хранение матриц: элементы каждой строки расположены подряд. В {link} три значения {matrix4x4} находятся по смещениям 0, 64 и 128 байт. Его {detail} применяет {mulVectorMatrix} в порядке модели, вида и проекции.'
+concepts.shaders.matrices.description.detail: 'шейдер'
+concepts.shaders.matrices.description.link: 'рендерере вращающегося куба'
+concepts.shaders.matrices.details: 'Порядок хранения определяет размещение элементов матрицы в памяти; порядок умножения — преобразование. Используйте согласованные соглашения в C# и Slang.'
+concepts.shaders.matrices.title: 'Соглашения о матрицах'
+concepts.shaders.title: Данные шейдеров и привязка
+concepts.synchronization.backends.description: 'DirectX 12 и Vulkan кодируют переходы текстур как изменения состояния и доступа. В реализации {transition} для Metal в Zenith.NET нет эквивалентной операции. Вместо этого бэкенд Metal объединяет команды в графические и вычислительные кодировщики и вставляет барьеры видимости при завершении кодировщика. Для зависимых операций внутри одного кодировщика, например последовательных вычислительных запусков, нужны явные вызовы {barrier}.'
+concepts.synchronization.backends.details: 'В {link} начало графического прохода завершает вычислительный кодировщик и делает его результат видимым графическим командам. Два последовательных вычислительных запуска остаются в одном кодировщике, поэтому их зависимость требует барьера, даже если между ними записан переход состояния текстуры. Переносимая последовательность команд должна учитывать и зависимости обращений, и состояния текстур.'
+concepts.synchronization.backends.details.link: примере вычислений
+concepts.synchronization.backends.title: 'Поведение бэкендов'
+concepts.synchronization.barriers.context: 'Выбирайте стадии, которые охватывают нужные обращения и поддерживаются очередью. {barrierStagesAll} включает все поддерживаемые стадии. Если зависимость затрагивает лишь часть из них, можно указать более узкий набор.'
+concepts.synchronization.barriers.description: 'Рассмотрим два запуска вычислений в одном командном буфере: первый записывает данные симуляции, второй их читает. Барьер между ними позволяет второму запуску увидеть результаты завершённых записей. Для буфера хранения он упорядочивает обращения без перехода состояния текстуры:'
+concepts.synchronization.barriers.details: 'Первый аргумент указывает стадии, которые произвели данные, второй — стадии, которым они понадобятся. Вместе они описывают порядок выполнения и видимость памяти, необходимые между двумя запусками.'
+concepts.synchronization.barriers.guidance: 'Этот порядок используется в {link} между шагами симуляции. Каждый барьер следует за запуском, который производит данные, и предшествует запуску, который их использует.'
+concepts.synchronization.barriers.guidance.link: симуляции FluidTank
+concepts.synchronization.barriers.title: 'Барьеры конвейера'
+concepts.synchronization.dependencies.description: 'Для каждого ресурса определите обращения к одним и тем же данным. Если хотя бы одно из них записывает, сохраните необходимый порядок. Два чтения не конфликтуют, хотя требования к состоянию текстуры и выводу изображения продолжают действовать. Обновление данных с CPU и освобождение ресурсов тоже должны ждать, пока GPU закончит использовать соответствующую память.'
+concepts.synchronization.dependencies.details: 'Выбор механизма зависит от места выполнения операций:'
+concepts.synchronization.dependencies.guidance: 'Одной задаче может потребоваться несколько механизмов. Например, текстуре, созданной вычислениями в другой очереди, нужны зависимость между отправками и подходящее состояние для следующего обращения. Блокировать CPU при этом нужно только тогда, когда он сам должен обратиться к данным или освободить их память.'
+concepts.synchronization.dependencies.table.cpuAccess.mechanism: Вызов {wait} для соответствующего значения завершения.
+concepts.synchronization.dependencies.table.cpuAccess.situation: 'Доступ CPU или освобождение ресурса требует завершения работы GPU'
+concepts.synchronization.dependencies.table.crossQueue.mechanism: 'Передайте {timelineValue} производителя в вызов {submit} потребителя.'
+concepts.synchronization.dependencies.table.crossQueue.situation: 'Одна очередь использует данные, полученные в другой'
+concepts.synchronization.dependencies.table.headings.mechanism: Механизм
+concepts.synchronization.dependencies.table.headings.situation: Ситуация
+concepts.synchronization.dependencies.table.layoutChange.mechanism: '{commandBufferTransition} с предыдущим и следующим состоянием текстуры.'
+concepts.synchronization.dependencies.table.layoutChange.situation: Изменение способа использования подресурса текстуры
+concepts.synchronization.dependencies.table.pipelineStages.mechanism: Вызов {commandBufferBarrier} с указанием стадий производителя и потребителя.
+concepts.synchronization.dependencies.table.pipelineStages.situation: Зависимые стадии GPU в потоке команд
+concepts.synchronization.dependencies.title: 'Зависимости обращений'
+concepts.synchronization.description: 'Синхронизация задаёт порядок обращений к общим данным и обеспечивает доступ последующих операций к нужным результатам. В зависимости между записью и чтением записывающая операция производит данные, а читающая их потребляет. Барьеры и переходы состояния текстур координируют обращения GPU; временные шкалы связывают отправки и позволяют CPU дождаться завершения работы GPU.'
+concepts.synchronization.layouts.context: 'Указывайте {undefined} как предыдущее состояние, когда существующее содержимое можно отбросить, например для нового изображения или вложения, которое будет заполнено целиком. Если содержимое нужно сохранить для загрузки, выборки или накопления, используйте состояние предыдущего обращения.'
+concepts.synchronization.layouts.description: 'Флаги назначения текстуры объявляют допустимые способы её использования. {textureLayout} описывает доступ к конкретному подресурсу в определённой точке последовательности команд. Подресурс задаётся mip-уровнем и слоем массива. При смене способа доступа запишите переход. Например, вычислительный пример записывает выходную текстуру в состоянии {storage}, затем подготавливает её к выборке в графическом проходе:'
+concepts.synchronization.layouts.details: 'Здесь {outputTexture} создана с назначением {sampledStorage}, а выбранный подресурс находится в {storage}. {default} выбирает уровень mip 0 и слой 0. Остальные уровни и слои переход не затрагивает.'
+concepts.synchronization.layouts.guidance: 'В {transition} передавайте состояние предыдущего использования подресурса. Приложение отслеживает это состояние при записи команд и передаче текстур между этапами рендеринга.'
+concepts.synchronization.layouts.title: 'Состояния текстур'
+concepts.synchronization.readback.description: '{isCompleted} сообщает о завершении работы GPU. У записанного считывания есть ещё один этап: копирование результата из внутренней промежуточной памяти в память назначения приложения.'
+concepts.synchronization.readback.details: '{commandBufferDownload} записывает копирование на GPU во внутреннюю память. После завершения отправки библиотека выполняет копию на CPU, возвращая командный буфер для повторного использования. Вызов {wait} для значения этой отправки дожидается GPU и обрабатывает завершённые буферы той же очереди, тем самым завершая оба этапа. До возврата память назначения должна оставаться выделенной, а управляемая память — закреплённой.'
+concepts.synchronization.readback.guidance: 'Один лишь опрос {isCompleted} не выполняет заключительную копию на CPU. Ожидание другой очереди тоже не возвращает завершённые буферы очереди считывания. Когда загрузка и считывание используют прямой доступ к памяти, а когда промежуточное хранилище, описано в статье «{link}».'
+concepts.synchronization.readback.guidance.link: Управление ресурсами
+concepts.synchronization.readback.title: 'Готовность данных к чтению'
+concepts.synchronization.reuse.description: 'Треугольник дожидается завершения каждого кадра перед повторным использованием данных. Чтобы выполнение кадров перекрывалось, выделите каждому ещё выполняющемуся кадру собственную изменяемую память и отслеживайте, когда она снова станет доступной. Перед изменением данных дождитесь обращений GPU к тем же байтам, а перед освобождением ресурса — всех его незавершённых использований.'
+concepts.synchronization.reuse.details: 'Изменение размера и завершение приложения подчиняются тому же правилу: прекратить новые обращения, дождаться текущих, затем заменить или освободить ресурсы. Соблюдайте этот порядок перед {swapChainResize} и перед освобождением ресурсов или контекста. Треугольник уже ждёт каждый кадр, поэтому может менять размер между вызовами обработчиков.'
+concepts.synchronization.reuse.guidance: 'После вывода изображения {swapChainPresent} устанавливает точку завершения в графической очереди и ожидает её. Поэтому треугольник всё равно ждёт при выводе, даже если удалить явное ожидание после отправки команд. Работа, независимо отправленная в другие очереди, требует отдельного отслеживания завершения.'
+concepts.synchronization.reuse.title: 'Повторное использование ресурсов'
+concepts.synchronization.timelines.context: 'Если после потребителя CPU должен прочитать результат, перезаписать общие данные или освободить ресурсы, дождитесь этой отправки:'
+concepts.synchronization.timelines.description: 'Временная шкала отмечает завершение в очереди возрастающими значениями. {submit} возвращает {timelineValue} со шкалой и значением отправки. Числовые поля {value} разных шкал нельзя сравнивать напрямую.'
+concepts.synchronization.timelines.details: 'Пусть {producer} и {consumer} — записанные буферы двух очередей одного контекста, причём потребитель читает результат производителя. Отправьте их с такой зависимостью:'
+concepts.synchronization.timelines.guidance: 'Очередь-потребитель ждёт указанного значения завершения перед выполнением своих команд. CPU может продолжать работу без вызова {producedWait}. Сохраняйте общие ресурсы до завершения {consumed} и записывайте переходы состояния текстур, необходимые для доступа потребителя.'
+concepts.synchronization.timelines.notes: 'Значение, возвращённое {queueTimelineSignal}, также можно ожидать через {wait} или передать как зависимость другой очереди. {signal} помещает точку завершения после ранее отправленной работы; GPU достигает её, когда эта работа закончена.'
+concepts.synchronization.timelines.title: 'Временные шкалы и зависимости очередей'
+concepts.synchronization.title: Синхронизация
+home.actions.reference: Справочник API
+home.actions.tutorial: Начать с треугольника
+home.entries.description: 'Учебные материалы, примеры и справочник API'
+home.entries.label: 'Разделы документации'
+home.entries.reference.action: Изучить API
+home.entries.reference.category: Справочник
+home.entries.reference.description: 'Справка по типам, членам и объявлениям API'
+home.entries.reference.title: Справочник API
+home.entries.samples.action: Обзор примеров
+home.entries.samples.category: Примеры
+home.entries.samples.description: 'Рабочие примеры графических и вычислительных методов'
+home.entries.samples.title: Примеры
+home.entries.title: 'Документация'
+home.entries.tutorial.action: 'Начать урок'
+home.entries.tutorial.category: 'Урок'
+home.entries.tutorial.description: 'От пустого проекта до первого треугольника на экране'
+home.entries.tutorial.title: Первый треугольник
+home.introduction.accent: 'на разных платформах'
+home.introduction.description: 'Zenith.NET объединяет работу с DirectX, Metal и Vulkan в одном API для графики и вычислений'
+home.introduction.title: 'Графика для .NET{lineBreak}{accent}'
+home.meta.description: 'Кроссплатформенная графика и вычисления в .NET с единым API для DirectX, Metal и Vulkan'
+home.scene.description: 'Интерактивная вращающаяся геометрия. Перетаскивайте изображение или используйте клавиши со стрелками для изменения ракурса.'
+home.scene.label: Отображение геометрии
+home.scene.surface: Поверхность
+home.scene.wireframe: Каркас
+home.title: 'Графический API для .NET'
+learning.concepts.description: 'Выбор назначений ресурсов, описание данных шейдеров и координация работы GPU'
+learning.concepts.execution.description: 'Запись команд, отправка и время жизни ресурсов'
+learning.concepts.platforms.description: 'Нативный вывод и графические элементы интерфейса'
+learning.concepts.resources.description: 'Назначения ресурсов, размещение памяти и представления'
+learning.concepts.shaders.description: 'Соглашения о размещении и привязке данных C# и Slang'
+learning.concepts.synchronization.description: 'Зависимости, видимость и завершение'
+learning.concepts.title: Основные концепции
+learning.description: 'Начните с урока о треугольнике и создайте первое приложение. Затем изучите управление ресурсами, данные шейдеров и синхронизацию в статьях об основных понятиях или выберите пример нужного графического метода.'
+learning.entries.samples.action: Обзор примеров
+learning.entries.samples.description: 'Примеры на C# и Slang: геометрия, вычисления, запросы лучей и меш-шейдинг'
+learning.entries.tutorial.action: 'Открыть урок'
+learning.entries.tutorial.description: 'Консольное приложение, которое открывает окно и рисует треугольник'
+learning.meta.description: 'Учебные материалы и основы модели программирования Zenith.NET'
+learning.title: Обучение
+reference.description: 'Справочник ресурсов, команд и платформенных интеграций'
+reference.entries.commands.description: 'Графические, вычислительные и передающие команды'
+reference.entries.commands.label: Запись команд
+reference.entries.context.description: 'Создание ресурсов GPU для выбранного бэкенда'
+reference.entries.context.label: Начало работы
+reference.meta.description: 'Пространства имён, типы и члены Zenith.NET'
+reference.navigation.core: 'Справочник основного API'
+reference.title: Справочник API
+samples.computeShader.description: 'Пример загружает {assetsTexturesShokoPng}, преобразует изображение в оттенки серого на GPU и показывает выходную текстуру. Преобразование выполняется один раз; следующие кадры показывают тот же результат. Рендерер демонстрирует привязку текстур для выборки и записи, округление числа групп потоков вверх для обработки всего изображения и проверку его границ в шейдере.'
+samples.computeShader.details: '{computeShaderRendererCs} · {computeShaderSlang}'
+samples.computeShader.title: Вычислительный шейдер
+samples.cube.description: 'Куб добавляет индексированное рисование, матрицы модели, вида и проекции в буфере констант и проверку глубины. Текстура глубины и трафарета {d32FloatS8UInt} пересоздаётся при изменении размера.'
+samples.cube.details: '{spinningCubeRendererCs} · {spinningCubeSlang}'
+samples.cube.title: Вращающийся куб
+samples.description: 'Каждый пример объединяет рендерер C# и шейдер Slang для определённого метода. Полную настройку приложения и порядок рендеринга разбирает урок «{link}».'
+samples.description.link: Первый треугольник
+samples.execution.description: 'Запустите проект {link} и выберите пример в меню консоли.'
+samples.execution.description.link: ZenithTutorials
+samples.execution.title: 'Запуск примеров'
+samples.host.description: '{appCs} инициализирует контекст, окно и цепочку обмена. На каждом кадре он получает буфер команд, вызывает выбранный рендерер, отправляет команды и выводит изображение. {iRendererCs} задаёт операции обновления, рисования, изменения размера и освобождения ресурсов.'
+samples.host.details: 'Вычислительный рендерер и рендерер трассировки лучей создают внеэкранные текстуры. {texturePresenterCs} и {presentTextureSlang} рисуют их в текущую выходную текстуру, после чего общая оболочка отправляет команды и выводит кадр.'
+samples.host.title: 'Оболочка приложения и вывод'
+samples.indirectDrawing.description: 'Косвенное рисование выводит несколько экземпляров куба по буферу аргументов индексированного рисования и структурированному буферу данных экземпляров. CPU задаёт аргументы и обновляет преобразования.'
+samples.indirectDrawing.details: '{indirectDrawingRendererCs} · {indirectDrawingSlang}'
+samples.indirectDrawing.title: 'Косвенное рисование'
+samples.meshShading.description: 'Шейдер задач отбрасывает невидимые экземпляры сфер и передаёт оставшиеся индексы меш-шейдеру, который создаёт геометрию. До создания конвейера рендерер проверяет {contextCapabilitiesMeshShadingSupported}.'
+samples.meshShading.details: '{meshShadingRendererCs} · {meshShadingSlang}'
+samples.meshShading.title: 'Меш-шейдинг'
+samples.rayTracing.description: 'Пример строит структуры ускорения нижнего и верхнего уровней для пола и процедурных сфер, затем выполняет встроенные запросы лучей в вычислительном шейдере. До инициализации проверяется {contextCapabilitiesRayTracingSupported}.'
+samples.rayTracing.details: '{rayTracingRendererCs} · {rayTracingSlang}'
+samples.rayTracing.title: Трассировка лучей
+samples.title: Примеры
+samples.triangle.description: 'Треугольник связывает буфер вершин с соответствующим описанием входа и рисует три вершины через графический конвейер.'
+samples.triangle.details: '{helloTriangleRendererCs} · {helloTriangleSlang} · {link}'
+samples.triangle.details.link: 'Полный урок'
+samples.triangle.title: 'Треугольник'
+samples.workloads.description: 'Каталог {link} основного репозитория содержит более сложные приложения, использующие исходный код библиотеки.'
+samples.workloads.description.link: 'Experiments'
+samples.workloads.details: 'Оконные эксперименты используют те же пути вывода для Windows, macOS и Linux/X11. Интеграции Avalonia, WinForms, WPF, WinUI/Uno и MAUI, их цикл кадров и исходный код описаны в статье «{link}».'
+samples.workloads.details.link: Интеграция с платформами
+samples.workloads.table.cornellBox.example: '{link}'
+samples.workloads.table.cornellBox.example.link: CornellBox
+samples.workloads.table.cornellBox.whatItDemonstratesAndRequires: 'Трассировка путей, шумоподавление, тональная компрессия, увеличение разрешения и элементы управления ImGui. Требуется поддержка трассировки лучей.'
+samples.workloads.table.fluidTank.example: '{link}'
+samples.workloads.table.fluidTank.example.link: FluidTank
+samples.workloads.table.fluidTank.whatItDemonstratesAndRequires: 'Вычислительное моделирование жидкости, зависимости между стадиями симуляции и многопроходный рендеринг поверхности.'
+samples.workloads.table.headings.example: Пример
+samples.workloads.table.headings.whatItDemonstratesAndRequires: 'Методы и требования'
+samples.workloads.table.inkCanvas.example: '{link}'
+samples.workloads.table.inkCanvas.example.link: InkCanvas
+samples.workloads.table.inkCanvas.whatItDemonstratesAndRequires: Отрисовка с помощью Skia и обработка ввода в нативном окне. Требуется бэкенд и нативная библиотека Skia для целевой платформы.
+samples.workloads.table.platformDetection.example: '{link}'
+samples.workloads.table.platformDetection.example.link: PlatformDetection
+samples.workloads.table.platformDetection.whatItDemonstratesAndRequires: 'Пытается создать контекст для каждого бэкенда и сообщает возможности устройства. Неудачная попытка отмечается как отсутствие поддержки; причиной может быть устройство, драйвер или недостающая зависимость среды выполнения.'
+samples.workloads.title: 'Более сложные приложения'
+tutorial.context.description: '{graphicsContext} связывает приложение с GPU через выбранный бэкенд и предоставляет методы создания ресурсов для рендеринга.'
+tutorial.context.details: 'Вставьте инициализацию контекста {emphasis}:'
+tutorial.context.details.emphasis: после директив using, перед {iWindowWindow}
+tutorial.context.guidance: 'Каждая ветвь выбирает бэкенд текущей платформы и запрашивает валидацию. Сообщения {validationMessage} выводятся в консоль.'
+tutorial.context.imports: 'Добавьте следующие директивы using в начало {programCs}:'
+tutorial.context.title: 'Графический контекст'
+tutorial.description: 'В этом уроке из пустого консольного проекта .NET получится приложение, которое открывает окно и рисует цветной треугольник.'
+tutorial.details: 'В уроке три проверочных этапа: пустое окно, однотонный фон и цветной треугольник. Запускайте приложение после каждого этапа, прежде чем добавлять следующую часть.'
+tutorial.draw.context: 'Аргументы метода {draw}:'
+tutorial.draw.description: 'В обработчике {windowRender} замените {addTheTriangleDrawCommandsHereLater} следующими командами между {beginRenderPass} и {endRenderPass}:'
+tutorial.draw.details: '{setPipeline} выбирает шейдеры и состояние рендеринга. Этот вызов должен предшествовать привязке вершинного буфера, поскольку бэкенд интерпретирует его данные по описанию вершинного ввода выбранного конвейера.'
+tutorial.draw.guidance: '{setVertexBufferVertexBuffer} привязывает загруженные вершинные данные со смещения 0 байт к входному слоту 0, соответствующему единственной записи {inputLayouts}.'
+tutorial.draw.notes: 'Эти вызовы записывают команды рисования. Уже добавленный вызов {submitWait} отправляет их и дожидается завершения на GPU; затем {present} запрашивает вывод готового изображения. Сохраните заполнение фоном в начале прохода, чтобы область за пределами треугольника тоже имела определённый цвет.'
+tutorial.draw.table.firstInstance.meaning: Начать нумерацию экземпляров с нуля.
+tutorial.draw.table.firstVertex.meaning: Начать с первой вершины в буфере.
+tutorial.draw.table.headings.argument: Аргумент
+tutorial.draw.table.headings.description: Значение
+tutorial.draw.table.headings.meaning: Назначение
+tutorial.draw.table.instanceCount.meaning: Нарисовать одну копию треугольника.
+tutorial.draw.table.vertexCount.meaning: Считать три вершины.
+tutorial.draw.title: 'Команды рисования'
+tutorial.execution.description: 'Соберите и запустите проект {firstTriangle}.'
+tutorial.execution.details: 'На тёмном фоне должен появиться треугольник: красный сверху, зелёный справа внизу и синий слева внизу. Интерполяция цветов вершин создаёт плавные переходы внутри треугольника.'
+tutorial.execution.guidance: 'При изменении размера окна меняются размеры изображения в пикселях, но координаты вершин остаются прежними. Область вывода преобразует эти координаты с учётом новой ширины и высоты. Поэтому изменение соотношения сторон окна растягивает треугольник.'
+tutorial.execution.title: 'Запуск приложения'
+tutorial.frame.commands.context: 'После {endRenderPass} второй переход подготавливает выходную текстуру к выводу. {submit} завершает запись и отправляет командный буфер; {wait} блокирует поток до завершения этой работы. Затем {present} запрашивает отображение готового изображения.'
+tutorial.frame.commands.description: 'Проход рендеринга объединяет команды рисования, которые записывают результаты в одни и те же вложения. В этом проходе цвет записывается в выходную текстуру цепочки обмена. {colorAttachmentClear} задаёт её начальное заполнение цветом RGBA и сохранение содержимого после прохода. {beginRenderPass} записывает начало прохода с этими настройками.'
+tutorial.frame.commands.details: 'Коллекция содержит одно цветовое вложение. {null} означает, что у прохода нет вложения глубины и трафарета.'
+tutorial.frame.commands.guidance: '{beginRenderPass} задаёт область вывода и прямоугольник отсечения по размеру всего вложения. Область вывода преобразует координаты рендеринга в позиции пикселей, а прямоугольник отсечения ограничивает область, в которую разрешена запись.'
+tutorial.frame.commands.title: 'Очистка, отправка и вывод'
+tutorial.frame.description: 'Для начала заполните окно одним цветом фона. Эта операция называется очисткой. Когда фон появился, поверхность, отправка команд и вывод уже работают вместе — можно добавлять вершины и шейдеры треугольника.'
+tutorial.frame.details: 'Silk.NET вызывает обработчик, когда нужно отрисовать кадр. Пример не использует аргумент прошедшего времени, поэтому параметр назван {value}. Зарегистрируйте обработчик {emphasis}:'
+tutorial.frame.details.emphasis: перед {windowRun}
+tutorial.frame.target.description: '{graphicsQueueCommandBuffer} возвращает буфер, готовый к записи. Запись определяет работу GPU; выполнение начинается только после отправки.'
+tutorial.frame.target.details: 'Состояние доступа к текстуре определяет, как операция будет её использовать. Первый вызов {transition} подготавливает выходную текстуру к записи цвета. Значение {default} выбирает mip-уровень 0 и слой массива 0 — базовое изображение этой текстуры. Поскольку проход заполняет всё изображение цветом фона, {undefined} разрешает отбросить прежнее содержимое.'
+tutorial.frame.target.guidance: 'Для каждого кадра используйте текущую выходную текстуру: при выводе изображения цепочка обмена может перейти к следующей.'
+tutorial.frame.target.title: 'Подготовка выходной текстуры'
+tutorial.frame.title: 'Первый кадр'
+tutorial.geometry.data.description: 'Добавьте массив вершин {emphasis}. Последующую инициализацию ресурсов также размещайте перед этим обработчиком в указанном порядке.'
+tutorial.geometry.data.description.emphasis: перед {windowRender}
+tutorial.geometry.data.details: 'Каждая запись создаёт {vertex}; два внутренних выражения {new} задают позицию {vector3} и цвет {vector4}. Позиции соответствуют верхней, нижней правой и нижней левой вершинам. Вершинный шейдер добавляет четвёртую координату {w}. При w = 1 значения x и y также являются нормализованными координатами устройства: -1 и +1 обозначают края изображения, а 0 — центр. Эти координаты не зависят от числа пикселей.'
+tutorial.geometry.data.guidance: 'Цвет задаётся красной, зелёной, синей и альфа-компонентами; альфа равна 1. Все три вершины находятся в одной плоскости при z = 0.'
+tutorial.geometry.data.title: 'Позиции и цвета вершин'
+tutorial.geometry.description: 'У каждой из трёх вершин есть позиция и цвет. По позициям GPU определяет форму треугольника, а его внутренние цвета получает интерполяцией цветов вершин.'
+tutorial.geometry.format.description: 'Добавьте объявление вершины {emphasis}:'
+tutorial.geometry.format.description.emphasis: 'в конце {programCs}, после кода освобождения ресурсов'
+tutorial.geometry.format.details: '{file} ограничивает использование типа файлом {programCs}, а {layoutKindSequential} сохраняет порядок объявления полей. Поле {position} содержит три 32-битных числа с плавающей точкой и начинается со смещения 0 байт; поле {color} содержит четыре таких числа и начинается со смещения 12 байт. Одна вершина занимает 28 байт. Далее описание входных данных укажет GPU, как читать эти поля.'
+tutorial.geometry.format.imports: 'Добавьте в начало {programCs} следующие директивы using для векторных типов и атрибута размещения полей структуры:'
+tutorial.geometry.format.title: 'Представление вершины'
+tutorial.geometry.title: 'Вершинные данные'
+tutorial.geometry.upload.context: '{fixed} не позволяет сборщику мусора перемещать массив, пока используется его адрес. Для этого буфера с доступом CPU на запись метод {upload} отображает память, копирует данные и отменяет отображение до возврата. Поэтому массив достаточно закрепить на время вызова. Загрузите вершины один раз при инициализации и сохраняйте буфер до завершения рендеринга.'
+tutorial.geometry.upload.description: 'Поместите вершины в {buffer}, чтобы GPU мог читать их при рисовании. Добавьте выделение памяти и загрузку данных сразу после массива:'
+tutorial.geometry.upload.details: 'Размер выделения — {sizeofVertexVerticesLength}: три вершины по 28 байт, всего 84 байта. {bufferUsagesVertex} объявляет вершинный ввод, {cpuWriteOnly} разрешает начальное заполнение с CPU. Шаг в описании буфера фиксирует размер элемента. Описание входа ниже определит интерпретацию каждого атрибута.'
+tutorial.geometry.upload.guidance: 'Выделение памяти не заполняет буфер. {upload} копирует массив в начало буфера, используя исходный указатель и число байтов из описания данных.'
+tutorial.geometry.upload.imports: 'В {programCs} добавьте {bufferAlias} к директивам using, чтобы отличить тип буфера Zenith.NET от {systemBuffer}.'
+tutorial.geometry.upload.notes: 'В коде освобождения в конце {programCs} вставьте {vertexBufferDispose} непосредственно перед {swapChainDispose}. Память только для GPU и загрузка крупной статической геометрии рассмотрены в статье «{link}».'
+tutorial.geometry.upload.notes.link: Управление ресурсами
+tutorial.geometry.upload.title: 'Создание и заполнение буфера'
+tutorial.input.description: 'GPU нужно знать, где хранятся атрибуты вершины, например позиция и цвет. Описание входа задаёт их формат и смещение, а также шаг — расстояние в байтах до следующей вершины. Добавьте этот код после создания шейдеров:'
+tutorial.input.details: '{inputLayoutAdd} размещает каждый атрибут сразу после предыдущего и увеличивает шаг между вершинами на размер добавленного атрибута. Смещения рассчитываются по указанным форматам, без анализа структуры C#. Для этих двух полей получается следующая раскладка:'
+tutorial.input.guidance: 'Значение {semanticIndex} по умолчанию равно нулю и соответствует {pOSITION0} и {cOLOR0} в шейдере. Позиция и цвет хранятся вместе в одном вершинном буфере, поэтому конвейер использует одно описание входных данных в слоте 0. Если порядок полей или выравнивающие промежутки в структуре C# изменятся, измените и это описание.'
+tutorial.input.table.headings.byteOffset: Байтовое смещение
+tutorial.input.table.headings.cField: 'Поле C#'
+tutorial.input.table.headings.format: Формат
+tutorial.input.table.headings.shaderInput: Вход шейдера
+tutorial.input.table.nextVertex.cField: Следующая вершина
+tutorial.input.table.nextVertex.format: 'Полный шаг: 28 байт'
+tutorial.input.table.nextVertex.shaderInput: Следующая входная запись
+tutorial.input.table.vertexColor.format: '{float4} (16 байт)'
+tutorial.input.table.vertexPosition.format: '{float3} (12 байт)'
+tutorial.input.title: 'Описание вершинного входа'
+tutorial.lifecycle.description: 'Каждый кадр выполняется в таком порядке:'
+tutorial.lifecycle.details: 'После закрытия окна {run} возвращает управление, и выполнение продолжается в коде освобождения ресурсов. Каждый кадр уже дождался завершения своих команд, поэтому использованные ими ресурсы можно освободить. Полный код после {windowRun} выглядит так:'
+tutorial.lifecycle.guidance: 'Освободите цепочку обмена перед уничтожением её окна, а контекст — после его ресурсов. Очередь повторно использует отправленные командные буферы, а цепочка обмена управляет своей выходной текстурой. Отдельный вызов {dispose} для этих объектов не нужен.'
+tutorial.lifecycle.title: 'Цикл кадра и освобождение ресурсов'
+tutorial.pipeline.behavior: 'После описания входа создайте конвейер:'
+tutorial.pipeline.context: '{cullNone} отключает отбрасывание граней. Треугольник будет нарисован независимо от того, расположены ли его вершины на экране по часовой стрелке или против неё.'
+tutorial.pipeline.description: '{graphicsPipeline} объединяет описание вершин, два шейдера и настройки сборки примитивов и записи во вложения.'
+tutorial.pipeline.details: 'Шейдеры обрабатывают вершины и фрагменты. Остальные настройки конвейера управляют сборкой примитивов, отбрасыванием граней, проверкой глубины и смешиванием цветов:'
+tutorial.pipeline.guidance: 'Цветовой формат конвейера должен совпадать с вложением прохода, поэтому берётся из {swapChainDescFormat}. {count1} задаёт одну выборку на пиксель.'
+tutorial.pipeline.item: '{triangleList} собирает каждую последовательную тройку вершин в треугольник.'
+tutorial.pipeline.notes: '{depthNone} отключает проверку и запись глубины, что соответствует отсутствию вложения глубины.'
+tutorial.pipeline.reference: '{opaque} отключает смешивание. Поэтому результат фрагментного шейдера заменяет фон внутри треугольника.'
+tutorial.pipeline.title: 'Графический конвейер'
+tutorial.pipeline.usage: 'Объекты шейдеров можно освободить после создания конвейера. Сам конвейер сохраняйте на время рендеринга; в коде освобождения добавьте {pipelineDispose} перед {vertexBufferDispose}.'
+tutorial.presentation.title: 'Графический контекст и вывод'
+tutorial.project.context: 'В настройках сборки включите «{emphasis}» ({allowUnsafeBlocks}). Небезопасный код нужен для нативных вызовов вспомогательного оконного кода и загрузки вершин.'
+tutorial.project.context.emphasis: Разрешить небезопасный код
+tutorial.project.description: 'Создайте консольный проект C# с именем {firstTriangle} для .NET 10.'
+tutorial.project.details: 'Добавьте в проект следующие пакеты NuGet:'
+tutorial.project.guidance: '{zenithNET} предоставляет общий API рендеринга, а три пакета бэкендов — его платформенные реализации. {zenithNETCompiler} компилирует шейдеры Slang для выбранного бэкенда. Silk.NET создаёт окно и обрабатывает события.'
+tutorial.project.title: 'Настройка проекта'
+tutorial.requirements.context: 'Образцы кода для этого урока — {link} и его {detail}.'
+tutorial.requirements.context.detail: 'общая оболочка'
+tutorial.requirements.context.link: 'пример Hello Triangle'
+tutorial.requirements.description: 'Нужны {emphasis} и базовое знание C#. Приложение предназначено для {net10}. GPU и драйвер должны поддерживать бэкенд, выбранный для вашей платформы:'
+tutorial.requirements.description.emphasis: .NET 10 SDK
+tutorial.requirements.guidance: 'Zenith.NET выполняет рендеринг на нативной поверхности, предоставленной приложением. Silk.NET.Windowing создаёт окно и обрабатывает его события. На macOS для вывода используется слой Metal, представленный в {cAMetalLayerCs}; вспомогательный код связывает его с окном.'
+tutorial.requirements.table.headings.graphicsBackend: Графический бэкенд
+tutorial.requirements.table.headings.platform: Платформа
+tutorial.requirements.table.linux.graphicsBackend: Vulkan 1.4
+tutorial.requirements.table.linux.platform: 'Linux (X11 или XWayland)'
+tutorial.requirements.table.macOS.graphicsBackend: Metal 4
+tutorial.requirements.table.macOS.platform: macOS
+tutorial.requirements.table.windows.graphicsBackend: DirectX 12
+tutorial.requirements.table.windows.platform: Windows
+tutorial.requirements.title: 'Необходимые условия'
+tutorial.resize.context: 'Снова соберите и запустите приложение. Фон должен заполнять всё окно и после изменения его размера: каждый новый проход задаёт область вывода и прямоугольник отсечения по размерам вложения. Сохраните обработчик рендеринга — в его проход будет добавлено рисование треугольника.'
+tutorial.resize.description: 'Размер изображений цепочки обмена должен соответствовать кадровому буферу. Зарегистрируйте обработчик {framebufferResize} после обработчика рендеринга и перед {windowRun}:'
+tutorial.resize.details: 'Отслеживайте {link}, измеряемые в пикселях, а не изменения логических размеров окна. Если при сворачивании окна ширина или высота кадрового буфера становится нулевой, обработчик изменения размера оставляет цепочку обмена без изменений. Обработчик рендеринга тоже сразу возвращается, пока хотя бы один размер равен нулю. Рендеринг возобновляется, когда размеры снова позволяют вывести изображение.'
+tutorial.resize.details.link: 'изменения размера кадрового буфера'
+tutorial.resize.guidance: 'Перед вызовом {present} обработчик рендеринга дожидается завершения команд текущего кадра. Поэтому обработчик изменения размера может заменить изображения цепочки обмена, не прерывая работу предыдущего кадра. Связь завершения команд со временем жизни ресурсов подробнее описана в статье «{link}».'
+tutorial.resize.guidance.link: Синхронизация
+tutorial.resize.title: 'Изменение размера'
+tutorial.shaders.compilation.context: '{appContextBaseDirectory} позволяет загрузить копию шейдера из выходного каталога приложения. Поэтому запуск из другого рабочего каталога не меняет место поиска файла.'
+tutorial.shaders.compilation.description: 'В свойствах {triangleSlang} задайте параметру «{emphasis}» значение «{detail}». При сборке шейдер будет скопирован рядом с исполняемым файлом.'
+tutorial.shaders.compilation.description.detail: Копировать более позднюю версию
+tutorial.shaders.compilation.description.emphasis: Копировать в выходной каталог
+tutorial.shaders.compilation.details: 'В {programCs} добавьте инициализацию шейдеров после загрузки вершин и до обработчика рендеринга:'
+tutorial.shaders.compilation.guidance: '{zenithCompiler} компилирует исходный код для {contextGraphicsApi}, а {createShader} создаёт объект шейдера из полученного описания. Имена точек входа {vSMain} и {fSMain} выбирают две функции, определённые в файле Slang.'
+tutorial.shaders.compilation.title: 'Компиляция шейдеров'
+tutorial.shaders.context: '{fSInput} связывает две шейдерные стадии. Вершинный шейдер записывает в {sVPOSITION} позицию в пространстве отсечения; по ней GPU определяет видимую часть треугольника. Поле {cOLOR0} передаёт цвет вершины, который интерполируется по треугольнику перед поступлением во фрагментный шейдер. Тип и семантика этого поля должны совпадать между стадиями.'
+tutorial.shaders.description: 'Шейдеры — программы, выполняемые на GPU. Вершинный шейдер определяет положение каждой вершины, а фрагментный — цвет в точках изображения, покрытых треугольником. Обе стадии будут записаны в одном файле Slang. Для каждой задаётся отдельная точка входа — функция, с которой начинается выполнение.'
+tutorial.shaders.details: 'Создайте {triangleSlang} с этими структурами входа и выхода:'
+tutorial.shaders.fragment.description: 'Добавьте в тот же файл точку входа фрагментного шейдера:'
+tutorial.shaders.fragment.details: 'Растеризация определяет, какие точки изображения покрывает треугольник, и создаёт для них фрагменты. Каждый фрагмент получает цвет, интерполированный из цветов трёх вершин. {fSMain} возвращает этот цвет, а {sVTARGET} направляет результат в первое цветовое вложение.'
+tutorial.shaders.fragment.title: 'Фрагментный шейдер'
+tutorial.shaders.guidance: '{vSInput} задаёт вход вершинного шейдера. {pOSITION0} и {cOLOR0} связывают атрибуты по семантикам; одних имён полей C# для этого недостаточно.'
+tutorial.shaders.title: 'Стадии шейдеров'
+tutorial.shaders.vertex.description: 'Добавьте в {triangleSlang} точку входа вершинного шейдера:'
+tutorial.shaders.vertex.details: 'GPU вызывает её для каждой входной вершины. Функция добавляет однородную координату {w} и передаёт цвет. После обработки вершин позиция делится на w. Здесь w = 1, поэтому x, y и z не меняются и непосредственно задают нормализованные координаты устройства.'
+tutorial.shaders.vertex.title: 'Вершинный шейдер'
+tutorial.surface.description: 'Задайте нативную поверхность вывода {emphasis}:'
+tutorial.surface.description.emphasis: после {windowCenter} и перед {windowRun}
+tutorial.surface.details: 'Кадровый буфер — изображение окна в пикселях. {surface} описывает нативный объект вывода и его размеры; {framebufferSize} сообщает размер в пикселях. При масштабировании экрана он может отличаться от логического размера окна.'
+tutorial.surface.guidance: 'Поверхность предоставляет нативные дескрипторы выбранного бэкенда: окно Win32 в Windows, дисплей и окно X11 в Linux или прикреплённый слой Metal в macOS.'
+tutorial.surface.title: 'Нативная поверхность'
+tutorial.swapChain.context: 'Цепочка обмена зависит от контекста и окна. Замените {windowDispose} в конце {programCs} следующим порядком освобождения:'
+tutorial.swapChain.description: 'Создайте цепочку обмена сразу после описания поверхности:'
+tutorial.swapChain.details: '{swapChain} управляет изображениями, выводимыми в окно. {drawable} предоставляет текущее изображение в виде {texture} — ресурса для хранения изображения в API. После рендеринга {present} запрашивает его вывод и переходит к следующей выходной текстуре.'
+tutorial.swapChain.guidance: '{b8G8R8A8UNorm} хранит четыре 8-битных канала в порядке синий, зелёный, красный, альфа. {uNorm} отображает целые 0–255 в значения 0–1. Выход шейдера остаётся логическим RGBA; формат задаёт представление в памяти. Конвейер использует {swapChainDescFormat} для соответствия вложению.'
+tutorial.swapChain.title: 'Цепочка обмена'
+tutorial.title: Первый треугольник
+tutorial.troubleshooting.description: 'Используйте сообщения консоли и три контрольных результата, чтобы определить этап ошибки:'
+tutorial.troubleshooting.details: '{link} и {detail} служат образцами для проверки данных вершин, описания вершинного ввода и команд рисования. В этом примере создание окна и отправка команд вынесены в общую оболочку; в уроке весь порядок работы приложения остаётся в {programCs}. Пример «{context}» знакомит с индексированным рисованием, преобразованиями и проверкой глубины.'
+tutorial.troubleshooting.details.context: Вращающийся куб
+tutorial.troubleshooting.details.detail: шейдер Slang
+tutorial.troubleshooting.details.link: 'Демонстрационный рендерер'
+tutorial.troubleshooting.table.backend.check: Убедитесь, что выбранный бэкенд поддерживается устройством и установленным графическим драйвером.
+tutorial.troubleshooting.table.backend.symptom: Сбой создания бэкенда
+tutorial.troubleshooting.table.drawing.check: 'Проверьте размер загрузки, описание вершинного входа, выбор конвейера и расположение команд рисования внутри прохода.'
+tutorial.troubleshooting.table.drawing.symptom: Фон отображается, но треугольника нет
+tutorial.troubleshooting.table.headings.check: Что проверить
+tutorial.troubleshooting.table.headings.symptom: Симптом
+tutorial.troubleshooting.table.input.check: 'Проверьте шаг в 28 байт и смещения положения и цвета: 0 и 12.'
+tutorial.troubleshooting.table.input.symptom: Искажены координаты или цвета вершин
+tutorial.troubleshooting.table.shaders.check: Проверьте скопированный файл {triangleSlang}, правильность написания точек входа и сообщения об ошибках от компилятора.
+tutorial.troubleshooting.table.shaders.symptom: Очистка цветом работает, но загрузка шейдеров завершается ошибкой
+tutorial.troubleshooting.table.window.check: Убедитесь, что проект .NET собирается и шаг с простым окном выполняется в сессии рабочего стола. В Linux проверьте доступность X11 или XWayland.
+tutorial.troubleshooting.table.window.symptom: Окно не открывается
+tutorial.troubleshooting.title: 'Диагностика ошибок'
+tutorial.window.description: 'Скачайте {cocoaHelperCs} в проект. Этот вспомогательный класс связывает слой Metal с окном macOS.'
+tutorial.window.details: 'Замените сгенерированный код {programCs} следующим. Операторы верхнего уровня выполняются по порядку без явно объявленного метода {main}. В следующих разделах будет указано, куда добавлять каждый фрагмент:'
+tutorial.window.guidance: '{graphicsAPINone} оставляет создание графического контекста за Zenith.NET. {initialize} создаёт нативное окно и открывает доступ к дескрипторам оконной системы. {center} размещает окно по центру экрана. {run} обрабатывает события до закрытия окна, после чего {dispose} освобождает его.'
+tutorial.window.notes: 'Соберите и запустите приложение. Убедитесь, что окно открывается и закрывается, его можно перемещать и менять его размер. Содержимое пока не определено, поскольку команды рендеринга ещё не отправлены. Закройте окно перед продолжением.'
+tutorial.window.title: 'Создание окна'
+ui.action.copy: Копировать
+ui.action.navigate: Перейти
+ui.action.open: Открыть
+ui.action.source: Исходный код
+ui.api.browse: Обзор пространств имён и типов.
+ui.api.clearTypeSearch: Очистить поиск типов
+ui.api.findType: Найти тип
+ui.api.loadError: Не удалось загрузить типы. Обновите страницу, чтобы повторить попытку.
+ui.api.loadingNamespaces: Загрузка пространств имён…
+ui.api.loadingTypes: Загрузка типов…
+ui.api.matchingTypes: Найденные типы
+ui.api.namespace: Пространство имён
+ui.api.namespaces: Пространства имён
+ui.api.noNamespaceTypes: В этом пространстве имён нет типов.
+ui.api.noTypes: Совпадающих типов не найдено.
+ui.api.types: Типы
+ui.content.reference: Справочник
+ui.content.source: Исходный код
+ui.content.tutorial: 'Урок'
+ui.contents.close: Закрыть оглавление
+ui.contents.open: Открыть оглавление
+ui.contents.title: Содержание
+ui.footer.caption: 'Графический API для .NET'
+ui.footer.label: Нижний колонтитул
+ui.language.label: Язык
+ui.language.unavailable: Не переведено
+ui.members.clear: Очистить поиск членов
+ui.members.close: Закрыть поиск членов
+ui.members.empty: Совпадающих членов не найдено.
+ui.members.open: Перейти к члену
+ui.members.placeholder: Имя или сигнатура…
+ui.members.search: Поиск членов
+ui.members.title: Найти член
+ui.members.undeclared: У этого типа нет объявленных членов.
+ui.navigation.api: API
+ui.navigation.concepts: 'Основные понятия'
+ui.navigation.home: Главная
+ui.navigation.label: Основная навигация
+ui.navigation.skip: Перейти к содержимому
+ui.navigation.toggle: Переключить навигацию
+ui.outline.title: На этой странице
+ui.reference.assembly: Сборка
+ui.reference.classes: Классы
+ui.reference.constructors: Конструкторы
+ui.reference.declaration: 'Объявление типа C#'
+ui.reference.delegates: Делегаты
+ui.reference.derived: Производные типы
+ui.reference.enums: Перечисления
+ui.reference.events: События
+ui.reference.examples: Примеры
+ui.reference.exceptions: Исключения
+ui.reference.extensionMethods: Методы расширения
+ui.reference.fields: Поля
+ui.reference.implementations: Явные реализации интерфейса
+ui.reference.implements: Реализует
+ui.reference.inheritance: Наследование
+ui.reference.inheritedMembers: Унаследованные члены
+ui.reference.interfaces: Интерфейсы
+ui.reference.metadata: Метаданные типа
+ui.reference.methods: Методы
+ui.reference.namespace: Пространство имён
+ui.reference.open: Открыть справку для {name}
+ui.reference.operators: Операторы
+ui.reference.parameters: Параметры
+ui.reference.properties: Свойства
+ui.reference.related: См. также
+ui.reference.relationships: Наследование и метаданные
+ui.reference.remarks: Примечания
+ui.reference.returns: Возвращаемое значение
+ui.reference.structs: Структуры
+ui.reference.typeParameters: Параметры типа
+ui.reference.value: Значение
+ui.reference.values: Значения
+ui.search.close: Закрыть поиск
+ui.search.empty: По запросу «{query}» ничего не найдено.
+ui.search.hint: 'Поиск по урокам, основным понятиям, примерам и справочнику API.'
+ui.search.loading: Загрузка поиска…
+ui.search.more: Показать больше результатов
+ui.search.pending: Поиск…
+ui.search.placeholder: Что вы ищете?
+ui.search.resultCount: 'Результатов: {count}'
+ui.search.title: Поиск по документации
+ui.search.unavailable: Поиск недоступен. Вы можете открыть страницы из списка ниже.
diff --git a/documents/locales/ru-RU/translation.md b/documents/locales/ru-RU/translation.md
new file mode 100644
index 00000000..cccf8146
--- /dev/null
+++ b/documents/locales/ru-RU/translation.md
@@ -0,0 +1,85 @@
+# Правила перевода на русский язык
+
+## Ресурсы и структура
+
+[Английский словарь ресурсов](../en-US/strings.yml) — единственный источник текста. Правила проекта описаны в [руководстве по сопровождению документации](../../maintenance.md). Начиная новый перевод, скопируйте английский файл целиком в `strings.yml` в этом каталоге. Сохраните первую строку `### YamlMime:Resources` и кодировку UTF-8. При обновлении учитывайте изменения оригинала, не перезаписывая существующие переводы. Этот файл `translation.md` сохраняется постоянно и не публикуется на сайте.
+
+Навигация, порядок глав, якоря, ссылки, форматирование, код и изображения общие для всех языков и находятся вне `locales/`. Не добавляйте в этот каталог статьи, TOC, шаблоны, сценарии или настройки.
+
+При переводе меняйте только значения в `strings.yml` этого языка. Набор ключей должен точно совпадать с английским. Не добавляйте, не удаляйте и не переименовывайте ключи по собственной инициативе, даже если тексты похожи или ещё не встретились в интерфейсе. Об ошибке в оригинале сообщите сопровождающим проект, указав ключ и суть проблемы. Сначала исправляются английский текст и общие страницы, затем переводы.
+
+## Стиль и контекст
+
+Используйте стиль технического учебного пособия: точный, сдержанный и понятный. Сначала объясняйте связь операций, затем условия и ограничения. В уроках сохраняйте конкретные действия и проверяемые результаты. Научная точность не требует тяжёлых пассивных конструкций или длинных фраз.
+
+Редактируйте статью в порядке чтения, учитывая соседние абзацы, таблицы и код. Ключ — граница хранения, а не самостоятельная единица перевода. Проверяйте падежи, предлоги и согласование ссылок и выделенных фрагментов в полном предложении.
+
+В заголовках, подписях элементов интерфейса, коротких видимых текстах главной страницы и подписи подвала не ставьте конечную точку. В абзацах, диагностике и инструкциях доступности сохраняйте обычную пунктуацию. Для каждого понятия используйте один принятый термин; не добавляйте выборочно английские пояснения в скобках. Идентификаторы API, файлов и настроек сохраняйте.
+
+Сохраняйте предложение, если оно объясняет текущую операцию, проектное решение, проверяемый результат или необходимое условие. Удаляйте перечни неиспользуемых возможностей, общие оговорки и повторные требования. Заголовки и фразы, разделённые оформлением, переводите целиком, с естественным русским порядком слов. Позиции `{accent}` и `{lineBreak}` в заголовке главной страницы можно менять: оформление не должно закреплять английский синтаксис. Удаление абзаца и его ключей сопровождающий согласованно выполняет в оригинале, общей разметке и словарях всех языков.
+
+## Текст и заполнители
+
+Переводите предложения или абзацы целиком ясным и естественным техническим русским языком. Сохраняйте условия, отрицания, единицы измерения, порядок действий и ограничения времени жизни. Не добавляйте технических гарантий, требований к платформе, версий пакетов или примечаний к выпускам, которых нет в оригинале. Названия продуктов, пакетов и файлов, а также идентификаторы C# и Slang сохраняйте без изменений, включая регистр.
+
+Каждое значение должно быть непустой строкой обычного текста, без HTML и Markdown. Соблюдайте правила кавычек и экранирования YAML. При необходимости заключайте значения в кавычки, чтобы двоеточия, решётки или текст, похожий на число, не были истолкованы неверно. Для удобства сверки сохраняйте английский порядок ключей.
+
+Именованные заполнители, например `{context}` и `{link}`, могут подставлять код, ссылки или выделенный текст из общих страниц. Заполнители интерфейса, такие как `{name}`, `{query}` и `{count}`, могут также получать обычный текст во время выполнения. Проверяйте использование ресурса на соответствующей странице или в интерфейсе, не определяя содержимое только по имени. Подписи ссылок и выделенный текст с отдельными ключами ресурсов тоже нужно переводить.
+
+Порядок заполнителей можно менять согласно русскому синтаксису. В каждом ключе сохраняйте все имена, их регистр и фигурные скобки ASCII. Не добавляйте, не удаляйте и не переименовывайте заполнители; не заменяйте их отображаемыми именами API, подписями ссылок или числами из примеров. Не разделяйте ключи ресурсов ради перестройки предложения.
+
+## Ссылки на API и технический смысл
+
+Ссылки поддерживаются в общих страницах. Идентификаторы, явно обозначающие локальный API в тексте и таблицах, ведут к соответствующему типу или члену. Переменные, имена параметров, типы из примеров, названия пакетов и файлов, а также внешние символы без локальной справочной страницы остаются обычным текстом кода. Одинаковое написание не всегда означает один и тот же символ. В обычные блоки кода учебника ссылки автоматически не добавляются. Можно перемещать заполнитель целиком, но нельзя менять ссылки, общий код или его форматирование.
+
+Если для API не создан справочный элемент, его идентификатор остаётся обычным текстом кода на общей странице, а рядом размещается явно обозначенная ссылка на исходный код. Не заменяйте её ссылкой на другой тип или член.
+
+Сохраняйте следующие различия:
+
+- Запись команд, их отправка, выполнение на GPU и завершение — разные этапы.
+- Назначение ресурса, резидентность памяти или предполагаемый доступ CPU, состояние доступа к текстуре (layout) и формат пикселя — разные понятия.
+- Владение означает ответственность за освобождение ресурса. Один лишь дескриптор не продлевает время жизни ресурса; отправка команд не передаёт владение.
+- Завершение работы GPU не всегда означает, что промежуточные данные считывания уже скопированы в целевую память CPU приложения. Сохраняйте указания об ожидании очереди, управляющей этой операцией.
+- Drawable — выходная текстура текущего кадра, не обязательно изображение цепочки обмена. Различайте представления ресурсов и элементы управления интерфейса.
+
+## Сборка и проверка перед передачей
+
+Выполняйте команды из корня репозитория в среде с требуемым проекту .NET SDK и установленным DocFX. В новой копии репозитория ещё нет сгенерированных метаданных в `documents/api/`. Сначала выполните полную сборку, включающую извлечение API:
+
+```sh
+docfx documents/docfx.json --warningsAsErrors
+```
+
+Если метаданные API уже созданы и соответствуют текущему исходному коду, для изменений только в словаре достаточно обычной сборки и предпросмотра:
+
+```sh
+docfx build documents/docfx.json --warningsAsErrors
+docfx serve documents/_site --hostname 127.0.0.1 --port 8080
+```
+
+Этот язык уже зарегистрирован. Полный словарь становится доступен после успешной сборки без изменения настроек. Отсутствующие или лишние ключи, пустые значения, значения другого типа и несовпадающие имена заполнителей приводят к ошибке сборки. Сборка не выявляет неточный перевод или оставшийся английский текст: необходима ручная вычитка.
+
+Все языки используют одинаковые пути страниц. `?lang=ru-RU` выбирает русский язык; при переключении сохраняются путь и якорь. Без явного указания сначала учитывается сохранённый ручной выбор, затем языковые настройки браузера, затем английский. Если этот перевод ещё не опубликован либо загрузка или проверка ресурсов страницы завершается ошибкой, страница отображается на английском без смешивания языков по предложениям. Список языков отсортирован по коду.
+
+Перед передачей сверьте ключи и заполнители. Проверьте главную страницу, входную страницу Learn, учебник с треугольником, концепции, примеры и интерфейс справочника API на настольном экране и при ширине 320 пикселей. Пройдите учебник с позиции начинающего: проверьте термины, кнопки, поиск, копирование кода, ссылки на API и положение раздела после смены языка. Идентификаторы, объявления и код остаются общими; переводится только текст словаря. Тестовые словари и временные файлы проверки храните вне репозитория; не сдавайте перевод с текстом-заглушкой.
+
+## Терминология
+
+| Английский термин | Принятый вариант |
+| ----------------- | ------------------------------------------------------------------------------------------------------------- |
+| API / GPU / RHI | Сохранять сокращения и пояснять их по оригиналу |
+| barrier | барьер |
+| buffer | буфер; `Buffer` не изменять |
+| command buffer | командный буфер |
+| command queue | очередь команд |
+| drawable | выходная текстура текущего кадра; `Drawable` не изменять и не считать обязательно изображением цепочки обмена |
+| graphics context | графический контекст |
+| pipeline | конвейер |
+| readback | считывание данных с GPU |
+| render pass | проход рендеринга |
+| resource | ресурс |
+| shader | шейдер |
+| swap chain | цепочка обмена |
+| texture | текстура |
+| timeline | временная шкала |
+| upload | загрузка данных на GPU |
diff --git a/documents/locales/zh-CN/strings.yml b/documents/locales/zh-CN/strings.yml
new file mode 100644
index 00000000..c20b4be4
--- /dev/null
+++ b/documents/locales/zh-CN/strings.yml
@@ -0,0 +1,593 @@
+### YamlMime:Resources
+concepts.execution.context.description: '{graphicsContext} 通过特定后端连接应用程序与 GPU 设备,并提供图形、计算和传输队列。配合使用的资源必须属于同一上下文;缓冲区、纹理、着色器、管线及其句柄不能跨上下文绑定。'
+concepts.execution.context.details: '{capabilities} 提供设备名称与支持的功能。创建光线追踪或网格着色所需的资源前,分别通过 {rayTracingSupported} 和 {meshShadingSupported} 检查支持情况。{link}与{detail}示例展示了初始化时的检查方式。'
+concepts.execution.context.details.detail: '网格着色'
+concepts.execution.context.details.link: '光线追踪'
+concepts.execution.context.title: '图形上下文'
+concepts.execution.description: 'GPU 工作从 CPU 端开始:应用程序创建资源,录制使用这些资源的命令,再将命令提交到队列。提交之后,CPU 可以继续处理其他任务,同时由 GPU 执行命令。相关资源必须保留到执行结束。'
+concepts.execution.ownership.description: '所有权决定由谁释放对象,GPU 完成状态决定何时可以释放。即使仍保留对象的 C# 引用,调用 {dispose} 也会释放其原生资源。提交命令不会将资源所有权转交给队列。'
+concepts.execution.ownership.details: '关闭程序时,先停止提交新工作,并等待所有相关队列完成最后一次资源访问。随后释放应用程序资源,最后释放上下文。上下文会释放自身的队列和内部辅助对象,因此这一步应在应用程序工作全部完成后进行。'
+concepts.execution.ownership.guidance: '渲染期间,若准备覆盖数据或替换资源,应跟踪仍在使用它们的工作。等待这些访问完成后,再修改数据或释放存储。视图与底层资源如何遵循这一生命周期规则,见{link}。'
+concepts.execution.ownership.guidance.link: '资源管理'
+concepts.execution.ownership.table.applicationResources.object: '应用程序创建的资源'
+concepts.execution.ownership.table.applicationResources.ownership: '最后一次 GPU 使用完成后,由应用程序释放。'
+concepts.execution.ownership.table.commandBuffers.object: '借用的命令缓冲区'
+concepts.execution.ownership.table.commandBuffers.ownership: '由队列管理和回收。'
+concepts.execution.ownership.table.headings.object: '对象'
+concepts.execution.ownership.table.headings.ownership: '释放责任'
+concepts.execution.ownership.table.queues.object: '图形上下文的队列及其时间线'
+concepts.execution.ownership.table.queues.ownership: '由上下文和队列管理。'
+concepts.execution.ownership.table.swapChainDrawable.ownership: '由交换链管理。'
+concepts.execution.ownership.title: '所有权与生命周期'
+concepts.execution.pipelines.description: '图形管线规定绘制所用的着色器、顶点输入布局、图元组装方式、附件格式和渲染状态。绑定顶点、索引或常量数据之前,须先选择管线;管线声明的附件格式与采样数也必须与渲染通道一致。'
+concepts.execution.pipelines.details: '计算管线选择分派所执行的着色器入口点。在图形渲染通道之外,先绑定管线及其常量,再执行分派。'
+concepts.execution.pipelines.guidance: '{link}给出了完整的图形命令顺序。{detail}则先执行计算分派,再通过图形通道显示结果。'
+concepts.execution.pipelines.guidance.detail: '计算示例'
+concepts.execution.pipelines.guidance.link: '三角形的渲染回调'
+concepts.execution.pipelines.title: '管线状态'
+concepts.execution.queues.description: '一帧同时包含绘制、计算和复制时,通常从 {graphicsQueue} 开始。将有依赖的操作放在同一队列,有助于明确执行顺序。'
+concepts.execution.queues.details: '不同队列可以分别接收独立的工作负载,但未必对应独立的硬件引擎。若工作能够从重叠执行中受益,再考虑增加队列。当一个队列使用另一个队列生成的数据时,需要在两次提交之间建立显式依赖。'
+concepts.execution.queues.guidance: '即使位于同一队列,有依赖的操作也可能需要屏障或纹理转换,才能让后续访问读到此前写入的结果。这种内存依赖与提交顺序不同,具体表达方式见{link}。'
+concepts.execution.queues.guidance.link: '同步'
+concepts.execution.queues.table.computeQueue.workToRecord: '计算分派以及兼容的传输工作,不含图形渲染通道。'
+concepts.execution.queues.table.graphicsQueue.workToRecord: '渲染通道、绘制、计算与传输操作。'
+concepts.execution.queues.table.headings.queue: '队列'
+concepts.execution.queues.table.headings.workToRecord: '支持的工作'
+concepts.execution.queues.table.transferQueue.workToRecord: '上传、回读与复制工作,不含绘制或计算分派。'
+concepts.execution.queues.title: '队列选择'
+concepts.execution.recording.context: '队列会回收已完成的命令缓冲区以供复用。每次录制时,通过 {queueCommandBuffer} 获取缓冲区,同一时间只由一个线程录制,并且只提交一次。提交后,将缓冲区交由队列管理,不再修改、重复提交或自行释放。'
+concepts.execution.recording.description: '{queueCommandBuffer} 返回的 {commandBuffer} 已处于录制状态。录制完操作及其依赖后,调用一次 {submit}。提交会结束录制,并返回标识该队列完成点的 {timelineValue}。'
+concepts.execution.recording.details: '三角形教程将一帧录制到 {commandBuffer} 后,按如下方式提交并等待完成:'
+concepts.execution.recording.guidance: '录制构造命令序列,{submit} 安排该序列执行。随后调用 {wait},CPU 线程会等待本次提交完成。教程因此采用逐帧执行的流程:当前帧完成后,才开始下一帧。'
+concepts.execution.recording.title: '录制与提交'
+concepts.execution.title: '执行模型'
+concepts.platforms.description: '呈现流程可以由应用程序管理,也可以交给 UI 渲染控件。这决定了谁来获取当前帧的输出纹理、录制和提交命令,以及显示渲染结果。'
+concepts.platforms.frame.context: '在通道内绑定管线并录制绘制命令,处理程序返回前须结束通道。控件负责提交命令并呈现结果;处理程序不得自行提交命令缓冲区,也不得释放传入的命令缓冲区或输出纹理。若改变了输出纹理的布局,返回前须恢复为 {colorAttachment},以便控件完成最后的转换。'
+concepts.platforms.frame.description: '各控件实现 {iZenithView} 接口。应用程序设置 {graphicsContext},在 {updateRequested} 中更新场景,并在 {renderRequested} 中通过传入的 {renderEventArgs} 录制命令。'
+concepts.platforms.frame.details: '每帧,控件获取输出纹理与命令缓冲区,将输出纹理转换为 {colorAttachment},再调用更新和渲染处理程序。之后,控件提交录制的工作,并通过对应框架的呈现机制显示图像。'
+concepts.platforms.frame.guidance: '先在文件中添加 {usingZenithNET}。假定名为 {view} 的 {iZenithView} 实例已初始化并设置图形上下文,可注册以下处理程序清除其输出纹理:'
+concepts.platforms.frame.notes: '初始转换使用 {undefined},允许丢弃先前图像的内容。因此,每帧都应重新绘制或清除整张图像。需要累积或缓存的渲染结果,可保存在应用程序拥有的纹理中,再将该纹理绘制到控件的输出纹理上。'
+concepts.platforms.frame.reference: '{zenithViewHelperDrawableFormat} 提供这些集成使用的默认颜色格式。'
+concepts.platforms.frame.title: '通过 UI 控件渲染'
+concepts.platforms.integration.context: 'WinForms、WPF 和 Windows 版 WinUI 集成运行于 Windows。使用 MAUI 时,安装目标平台的工作负载,并通过 {useZenithView} 注册控件处理程序,具体见 {extensionsCs}。'
+concepts.platforms.integration.description: '控件共享事件接口,但呈现机制因框架而异:'
+concepts.platforms.integration.details: '各实现将 GPU 图像送到 UI 的方式不同。位图回读先将像素复制到 CPU 内存,再显示图像;共享纹理互操作可以省去这次 CPU 复制,但仍可能需要 GPU 复制与同步。选择集成方式时,除了共用的 {renderRequested} 事件接口,也应考虑其呈现路径。'
+concepts.platforms.integration.guidance: '通过 NuGet 添加所用 UI 框架的集成包。Avalonia 对应的包为 {avaloniaPackage}。'
+concepts.platforms.integration.table.avalonia.integration: 'Avalonia'
+concepts.platforms.integration.table.avalonia.presentation: '渲染到纹理后,将像素回读到 {writeableBitmap},实现见 {zenithViewCs} 和 {surfaceCs}。'
+concepts.platforms.integration.table.headings.integration: '集成'
+concepts.platforms.integration.table.headings.presentation: '呈现机制'
+concepts.platforms.integration.table.maui.integration: '.NET MAUI'
+concepts.platforms.integration.table.maui.presentation: 'Android、iOS 和 Mac Catalyst 使用原生呈现目标;Windows 使用共享纹理合成,具体见{link}。'
+concepts.platforms.integration.table.maui.presentation.link: '平台实现'
+concepts.platforms.integration.table.uno.integration: 'Uno 路径'
+concepts.platforms.integration.table.uno.presentation: '将像素回读到 {writeableBitmap},实现见 {zenithViewUnoCs}。'
+concepts.platforms.integration.table.winForms.integration: 'WinForms'
+concepts.platforms.integration.table.winForms.presentation: '为控件的 {hWND} 创建 Zenith.NET 交换链,实现见 {zenithViewCs}。'
+concepts.platforms.integration.table.winUI.integration: 'Windows 上的 WinUI'
+concepts.platforms.integration.table.winUI.presentation: '渲染到导入的共享 Direct3D 11 纹理,再复制到与 {swapChainPanel} 连接的合成交换链,具体见 {zenithViewWinUICs}。'
+concepts.platforms.integration.table.wpf.integration: 'WPF'
+concepts.platforms.integration.table.wpf.presentation: '导入共享 Direct3D 11 纹理,再经 Direct3D 互操作复制到由 {d3DImage} 显示的表面,具体见 {surfaceCs}。'
+concepts.platforms.integration.title: '框架与呈现路径'
+concepts.platforms.lifecycle.context: '创建配套附件时,使用渲染事件传入的输出纹理的 {descWidth}、{descHeight}、{descFormat} 和 {descSampleCount}。该纹理给出了渲染目标的实际尺寸,不应根据控件的布局尺寸或显示缩放比例推算。'
+concepts.platforms.lifecycle.description: '交换链使用以像素计量的帧缓冲区宽度和高度。启用显示缩放时,这些值可能不同于窗口的逻辑尺寸。任一尺寸为零时,暂停渲染和交换链尺寸调整。每帧重新获取 {swapChainDrawable}:这张纹理由交换链管理,可能在呈现或调整大小后被替换。'
+concepts.platforms.lifecycle.details: '帧缓冲区尺寸变化时,先等待 GPU 完成对旧图像的访问,再调用 {swapChainResize}。替换原生呈现目标时,也应先等待这些访问结束,再将新的呈现目标描述与句柄传给 {swapChainRefresh}。关闭程序时,先释放交换链,再销毁其原生呈现目标。'
+concepts.platforms.lifecycle.guidance: '应用程序的深度纹理或离屏纹理若随窗口尺寸变化,就需要在调整大小时重建,并更新包含其句柄的常量。只要附件格式与采样数仍然匹配,管线就可以复用。{link}展示了如何调整纹理尺寸并保留管线。'
+concepts.platforms.lifecycle.guidance.link: '旋转立方体渲染器'
+concepts.platforms.lifecycle.title: '交换链尺寸与生命周期'
+concepts.platforms.ownership.description: '应用程序拥有所设置的上下文及其渲染资源,控件拥有呈现资源。视图的 {graphicsContext} 变化后,调度程序会为新上下文重建控件资源。应用程序也须为该上下文重建自己的管线、缓冲区和纹理。'
+concepts.platforms.ownership.details: '替换渲染资源前,先停止视图的帧活动,并等待使用这些资源的 GPU 工作完成,再释放旧资源或上下文。移除视图后,所设置的上下文仍由应用程序负责释放。'
+concepts.platforms.ownership.guidance: '共享的 {frameSchedulerCs} 通过视图的 UI 调度器运行帧处理程序。处理程序应集中于场景数据更新和命令录制,其中耗时的 CPU 工作也会延迟界面响应。'
+concepts.platforms.ownership.title: '应用程序与控件的责任'
+concepts.platforms.surface.description: '{link}使用 Silk.NET 创建窗口并处理事件。Zenith.NET 通过 {surface} 描述获取原生窗口句柄与尺寸,再据此创建 {swapChain}。操作系统窗口仍由应用程序负责管理。'
+concepts.platforms.surface.description.link: '三角形教程'
+concepts.platforms.surface.details: '原生句柄必须与所选后端匹配:'
+concepts.platforms.surface.guidance: '{appCs} 中的示例宿主支持 Windows、macOS 和 X11。Linux 窗口提供 Xlib 句柄,因此在 Wayland 会话中需要 XWayland。原生 Wayland 应用程序则通过 {surfaceWayland} 提供显示与表面句柄。'
+concepts.platforms.surface.table.android.backend: 'Android 上的 Vulkan。'
+concepts.platforms.surface.table.apple.backend: 'Apple 平台上的 Metal。'
+concepts.platforms.surface.table.headings.backend: '后端与平台'
+concepts.platforms.surface.table.headings.factory: '呈现目标工厂方法'
+concepts.platforms.surface.table.headings.handles: '宿主提供的句柄'
+concepts.platforms.surface.table.wayland.backend: '带有原生 Wayland 宿主的 Vulkan。'
+concepts.platforms.surface.table.wayland.handles: 'Wayland 显示与表面'
+concepts.platforms.surface.table.win32.backend: 'Windows 上的 DirectX 12 或 Vulkan。'
+concepts.platforms.surface.table.xlib.backend: 'Linux 上带有 X11 或 XWayland 的 Vulkan。'
+concepts.platforms.surface.table.xlib.handles: 'X11 显示与窗口'
+concepts.platforms.surface.title: '原生呈现目标'
+concepts.platforms.title: '平台集成'
+concepts.resources.allocation.context: '放置在堆中的资源在整个生命周期内都依赖堆的存储。最后一次 GPU 使用完成后,应先释放资源,再释放堆。'
+concepts.resources.allocation.description: '应用程序需要自行管理内存分配时,可以使用 {heap}。堆为资源提供存储,应用程序选择明确的字节偏移量来放置资源,并记录各区域的占用情况。'
+concepts.resources.allocation.details: '对每个资源,使用创建时所需的缓冲区或纹理描述调用 {contextGetSizeAndAlignment}。按返回的大小预留空间,起始偏移量须为返回对齐值的整数倍,且整个区域位于堆内,不与其他仍有效的分配重叠。纹理可能包含后端特有的填充,因此不能仅根据像素数量和每像素字节数计算分配大小。'
+concepts.resources.allocation.guidance: '放置在堆中的缓冲区必须使用与堆相同的驻留设置。纹理存储使用 GPU 专用堆。'
+concepts.resources.allocation.title: '堆分配'
+concepts.resources.bufferUsages.table.constant.role: '通过常量缓冲区绑定读取的着色器参数。'
+concepts.resources.bufferUsages.table.headings.role: '作用'
+concepts.resources.bufferUsages.table.headings.usage: '用途'
+concepts.resources.bufferUsages.table.index.role: '索引绘制所使用的索引。'
+concepts.resources.bufferUsages.table.indirect.role: '间接绘制或分派命令读取的参数。'
+concepts.resources.bufferUsages.table.none.role: '未声明任何缓冲区用途标志。'
+concepts.resources.bufferUsages.table.storageReadOnly.role: '通过只读着色器绑定访问的缓冲区数据。'
+concepts.resources.bufferUsages.table.storageReadWrite.role: '通过读写着色器绑定访问的缓冲区数据。'
+concepts.resources.bufferUsages.table.transferDst.role: 'GPU 复制操作的目标存储。'
+concepts.resources.bufferUsages.table.transferSrc.role: 'GPU 复制操作的源数据。'
+concepts.resources.bufferUsages.table.vertex.role: '供绘制命令读取的顶点属性。'
+concepts.resources.bufferUsages.title: '缓冲区用途'
+concepts.resources.description: '资源描述规定应用程序如何使用其存储。例如,一张纹理若要作为渲染目标、供着色器采样,并将内容复制回 CPU,就需要声明这三种操作对应的用途。内存放置决定 CPU 的访问方式,同步与生命周期管理则决定何时可以访问数据或释放存储。'
+concepts.resources.memory.context: '对于 CPU 可访问的缓冲区,{map} 提供 CPU 读写数据所用的地址。映射和 {unmap} 都不会等待 GPU 工作。CPU 读取 GPU 写入的结果前,应用程序须先等待写入完成;修改同一块内存前,也须等待 GPU 对这些字节的访问结束。'
+concepts.resources.memory.description: '{bufferDesc} 的 {residency} 字段指定了 CPU 对分配内存的预期访问方式:'
+concepts.resources.memory.details: '驻留设置表达 CPU 的访问需求,即使 CPU 与 GPU 共享物理内存也同样适用。三角形教程使用 {cpuWriteOnly} 直接初始化小型顶点缓冲区。对于较大的静态几何数据,上传到 GPU 专用存储可能更合适。'
+concepts.resources.memory.guidance: '通过上下文直接创建的纹理使用 GPU 存储,因此 {textureDesc} 没有驻留字段。应用程序通过上传和回读,在纹理与 CPU 内存之间交换数据。'
+concepts.resources.memory.table.cpuReadOnly.cPUAccess: '只读'
+concepts.resources.memory.table.cpuReadOnly.typicalRole: 'GPU 生成的回读数据'
+concepts.resources.memory.table.cpuWriteOnly.cPUAccess: '只写'
+concepts.resources.memory.table.cpuWriteOnly.typicalRole: 'CPU 提供或更新的数据'
+concepts.resources.memory.table.gpuOnly.cPUAccess: '无'
+concepts.resources.memory.table.gpuOnly.typicalRole: '静态几何体和 GPU 工作数据'
+concepts.resources.memory.table.headings.cPUAccess: '预期 CPU 访问'
+concepts.resources.memory.table.headings.residency: '内存驻留'
+concepts.resources.memory.table.headings.typicalRole: '典型用途'
+concepts.resources.memory.title: 'CPU 访问与内存放置'
+concepts.resources.textureUsages.description: '传输标志声明资源在 GPU 复制中用作源还是目标。CPU 能否直接访问这块存储,则由内存放置方式决定。'
+concepts.resources.textureUsages.details: '用途标志声明纹理可以承担的作用。在这些作用之间切换时,需要相应的{link};所选设备也必须支持以该纹理格式执行所声明的操作。'
+concepts.resources.textureUsages.details.link: '布局与同步'
+concepts.resources.textureUsages.table.colorAttachment.role: '渲染通道中的颜色附件。'
+concepts.resources.textureUsages.table.depthStencilAttachment.role: '渲染通道中的深度/模板附件。'
+concepts.resources.textureUsages.table.headings.role: '作用'
+concepts.resources.textureUsages.table.headings.usage: '用途'
+concepts.resources.textureUsages.table.none.role: '未声明任何纹理用途标志。'
+concepts.resources.textureUsages.table.sampled.role: '通过采样纹理绑定读取的纹理数据。'
+concepts.resources.textureUsages.table.storage.role: '通过存储纹理绑定供着色器读取或写入的纹理数据。'
+concepts.resources.textureUsages.table.transferDst.role: 'GPU 复制操作的目标图像存储。'
+concepts.resources.textureUsages.table.transferSrc.role: 'GPU 复制操作的源图像数据。'
+concepts.resources.textureUsages.title: '纹理用途'
+concepts.resources.title: '资源管理'
+concepts.resources.transfers.context: '回读先将 GPU 数据复制到内部回读存储。库回收已完成的命令缓冲区时,再将数据复制到指定的 CPU 目标。对本次回读提交的完成值调用 {wait},可以完成这两个步骤。在调用返回前,目标内存须保持已分配状态,托管内存须保持固定。'
+concepts.resources.transfers.description: '上传将 CPU 数据复制到 GPU 资源,回读则将资源数据复制到 CPU 内存。缓冲区和纹理上的这些方法会在传输完成后返回:'
+concepts.resources.transfers.details: '调用这些方法前,先处理与其他队列访问之间的冲突。方法内部的等待只覆盖其自身的传输,不会建立与其他队列的依赖。直接通过映射内存复制时,则完全由调用方负责等待存在冲突的 GPU 访问。'
+concepts.resources.transfers.guidance: '要将传输与其他 GPU 工作一同提交,可通过 {commandBufferUpload} 或 {commandBufferDownload} 录制。上传源与回读目标的内存生命周期要求不同:'
+concepts.resources.transfers.item: '上传调用期间,库会将源数据复制到内部的临时上传存储,即暂存内存。GPU 随后从这块存储读取,因此应用程序的源指针只需保持有效到调用返回。'
+concepts.resources.transfers.notes: '纹理传输中的行步幅是 CPU 数据相邻两行之间的字节距离;切片步幅则是相邻深度切片之间的字节距离。传输操作根据这些值,在 CPU 数据与纹理的 GPU 存储之间复制内容。GPU 完成与数据送达回读目标为何是两个步骤,见{link}。'
+concepts.resources.transfers.notes.link: '同步'
+concepts.resources.transfers.table.bufferDownload.transfer: '对于 {cpuReadOnly},直接从映射内存复制;否则向传输队列提交回读,并等待完成。'
+concepts.resources.transfers.table.bufferUpload.transfer: '对于 {cpuWriteOnly},直接复制到映射内存;否则向传输队列提交上传,并等待完成。'
+concepts.resources.transfers.table.headings.method: '方法'
+concepts.resources.transfers.table.headings.transfer: '传输行为'
+concepts.resources.transfers.table.textureTransfers.method: '{textureUpload} / {textureDownload}'
+concepts.resources.transfers.table.textureTransfers.transfer: '将复制及其纹理转换提交到图形队列,并等待完成。操作结束后,所选子资源处于指定的最终布局。'
+concepts.resources.transfers.title: '立即传输与命令录制'
+concepts.resources.usages.description: '{bufferUsages} 和 {textureUsages} 是用于声明资源用途的标志枚举,分别设置在 {bufferDescUsages} 和 {textureDescUsages} 中。资源需要承担多种用途时,可通过 {value} 组合相应成员。'
+concepts.resources.usages.title: '资源用途'
+concepts.resources.views.description: '{bufferView} 描述缓冲区的一块区域及其数据解释方式。{textureView} 选择子资源范围,以及兼容的纹理类型和格式。二者都引用已有存储,并遵循创建该存储时声明的用途。视图范围用于描述访问范围,各后端并不因此提供一致的着色器边界检查。'
+concepts.resources.views.details: '资源自身的句柄提供默认视图。需要其他区域或兼容的解释方式时,再创建独立视图。视图及其底层资源都必须保留到 GPU 使用结束;仅持有资源句柄不能使任一对象保持存活。替换其中任一对象时,还须更新包含旧{link}的常量。'
+concepts.resources.views.details.link: '资源句柄'
+concepts.resources.views.title: '视图与底层资源'
+concepts.shaders.binding.context: '计算示例将 {inputTextureSampledHandle} 写入 {input},将 {outputTextureStorageHandle} 写入 {output}。在 Slang 中,{descriptorHandle} 的类型参数规定了着色器访问资源的方式:'
+concepts.shaders.binding.description: '{setConstantBuffer} 选择已有缓冲区及字节偏移量,当前管线从该位置读取常量。着色器使用这些参数前,先将数据上传到相应存储。如果此前提交的 GPU 工作仍在读取同一批字节,须等待该工作完成后再更新。'
+concepts.shaders.binding.details: '准备好命令缓冲区、计算管线、已填入数据的常量缓冲区和线程组数量后,按以下顺序录制分派。常量缓冲区的绑定作用于当前管线,因此必须先选择管线:'
+concepts.shaders.binding.guidance: '这里,{commandBuffer} 使用 {computePipeline} 及 {constantBuffer} 中的参数录制分派。缓冲区需要声明 {bufferUsagesConstant} 用途,偏移量 0 选择其起始位置。如果多条常量记录共用缓冲区,每条记录的起始偏移量还须满足后端绑定的对齐要求,这与记录内部的字段布局是两项独立要求。'
+concepts.shaders.binding.notes: '{resourceHandle} 包含两个 32 位字段,含义由后端解释,其值仅在创建资源的上下文中有效。'
+concepts.shaders.binding.reference: '保留资源及用于获取句柄的视图,直到着色器完成最后一次访问。替换纹理或视图后,应更新仍含旧句柄的常量。相关所有权规则见{link}。'
+concepts.shaders.binding.reference.link: '资源管理'
+concepts.shaders.binding.table.bufferStorageReadOnlyHandle.access: '读取着色器类型 {t} 的结构化元素'
+concepts.shaders.binding.table.bufferStorageReadWriteHandle.access: '读写着色器类型 {t} 的结构化元素'
+concepts.shaders.binding.table.headings.access: '预期访问'
+concepts.shaders.binding.table.headings.cHandle: 'C# 句柄'
+concepts.shaders.binding.table.headings.slangFieldType: 'Slang 字段类型'
+concepts.shaders.binding.table.samplerHandle.access: '纹理采样状态'
+concepts.shaders.binding.table.textureSampledHandle.access: '纹理读取'
+concepts.shaders.binding.table.textureStorageHandle.access: '存储纹理的读写'
+concepts.shaders.binding.title: '常量缓冲区与资源句柄'
+concepts.shaders.compilation.description: '{zenithCompiler} 将指定的 Slang 入口点编译为 {shaderDesc}。上下文的 {graphicsApi} 决定目标格式:DirectX 12 使用 DXIL,Metal 使用 Metal 库,Vulkan 使用 SPIR-V。'
+concepts.shaders.compilation.details: '创建名为 {context} 的图形上下文后,可以编译已复制到可执行文件所在目录的三角形着色器:'
+concepts.shaders.compilation.guidance: '入口点名称 {vSMain} 必须与着色器文件中的函数声明一致。在初始化阶段编译着色器并创建管线,渲染期间复用管线。管线创建完成后即可释放着色器对象,具体见{link}。'
+concepts.shaders.compilation.guidance.link: '第一个三角形'
+concepts.shaders.compilation.title: '着色器编译'
+concepts.shaders.description: 'C# 与 Slang 声明必须约定一致的数据位置和访问方式。顶点输入布局描述顶点属性,着色器缓冲区声明描述参数与数组,资源句柄则标识着色器使用的资源。'
+concepts.shaders.dispatch.description: '{dispatch} 指定执行多少个线程组。计算示例声明 {numthreads},每组因此包含 16 × 16 个线程。将图像的宽和高分别除以 16 并向上取整,即可覆盖整张图像:17 × 19 的图像需要 2 × 2 组。着色器检查每个线程的坐标,并跳过位于图像范围以外的线程。'
+concepts.shaders.dispatch.details: '{shaderDescThreadGroupSize} 提供声明的组大小,可用于计算传给 {dispatch} 的线程组数量。完整计算过程见 {computeShaderRendererCs}。分派完成后,访问输出图像时还须满足{link}中说明的要求。'
+concepts.shaders.dispatch.details.link: '同步'
+concepts.shaders.dispatch.title: '线程组'
+concepts.shaders.layout.context: '{link}通过图像尺寸与两个纹理句柄展示了这种排列。各字段的起始位置由显式偏移量指定。以下声明使用 {systemRuntimeInteropServices} 和 {zenithNET} 命名空间:'
+concepts.shaders.layout.context.link: '计算示例的 C# 常量'
+concepts.shaders.layout.description: '对于顶点输入,{inputLayout} 描述各属性的格式、偏移量和语义,以及相邻顶点之间的步幅。三角形先存放 {float3} 位置,再存放 {float4} 颜色:颜色的字节偏移量为 12,每个顶点占 28 字节。{inputLayoutAdd} 根据元素格式计算这一紧密排列的布局。如果 C# 结构体包含填充,就需要在偏移量和步幅中计入这些空间。'
+concepts.shaders.layout.details: '常量缓冲区存放着色器参数,结构化缓冲区存放指定类型的元素数组。它们的布局由 Slang 声明决定,不使用 {inputLayout}。C# 字段偏移量、元素大小和填充必须与编译后的着色器布局一致。即使某个 C# 类型是非托管类型,可以直接按字节复制,其内存排列也未必符合着色器要求;例如,顶点中紧密排列的 {float3},在着色器缓冲区中可能需要不同的对齐方式。'
+concepts.shaders.layout.guidance: '着色器参数除了普通数值,也可以包含资源句柄。句柄标识的是已有存储,因此将句柄复制到常量缓冲区并不会复制资源内容。'
+concepts.shaders.layout.notes: '{computeShaderSlang} 中对应的声明为:'
+concepts.shaders.layout.reference: '两个尺寸值占用前 8 字节,后面是两个各占 8 字节的句柄。C# 示例为整条记录预留 256 字节。其他记录的大小应根据各自字段以及所需的布局和对齐方式确定。'
+concepts.shaders.layout.title: '数据布局'
+concepts.shaders.matrices.description: '编译器采用行优先的矩阵存储方式,使同一行的元素连续排列。{link}在偏移量 0、64 和 128 处保存三个 {matrix4x4} 值,其{detail}按模型、视图和投影顺序执行 {mulVectorMatrix}。'
+concepts.shaders.matrices.description.detail: '着色器'
+concepts.shaders.matrices.description.link: '旋转立方体渲染器'
+concepts.shaders.matrices.details: '存储顺序决定矩阵元素在内存中的排列,乘法顺序决定变换。C# 与 Slang 应采用相互匹配的约定。'
+concepts.shaders.matrices.title: '矩阵约定'
+concepts.shaders.title: '着色器数据与绑定'
+concepts.synchronization.backends.description: 'DirectX 12 和 Vulkan 将纹理转换记录为布局与访问状态的变化。在 Zenith.NET 的 Metal 后端中,{transition} 没有对应的 GPU 操作。Metal 后端将命令组织为渲染与计算编码器,并在编码器结束时插入可见性屏障。同一编码器内存在依赖的操作,例如连续的计算分派,通过显式调用 {barrier} 协调。'
+concepts.synchronization.backends.details: '{link}开始图形渲染通道时,会结束计算编码器,使计算输出对后续图形工作可见。连续两次计算分派仍位于同一编码器内,即使中间录制了纹理转换,也需要屏障来处理依赖。因此,可跨后端使用的命令序列既要考虑纹理布局,也要处理访问之间的依赖。'
+concepts.synchronization.backends.details.link: '计算示例'
+concepts.synchronization.backends.title: '后端行为'
+concepts.synchronization.barriers.context: '选择包含相关访问、且受到当前队列支持的阶段。{barrierStagesAll} 涵盖所有受支持的阶段;若依赖只涉及其中一部分,可选择更小的范围。'
+concepts.synchronization.barriers.description: '假设同一个命令缓冲区内有两次计算分派,第一次写入模拟数据,第二次读取这些数据。在两次分派之间设置屏障,让第二次分派读到已经完成的写入结果。对于存储缓冲区,这样即可安排访问顺序,无须进行纹理布局转换:'
+concepts.synchronization.barriers.details: '第一个参数指定产生数据的阶段,第二个参数指定使用数据的阶段。二者共同描述两次分派之间所需的执行顺序和内存可见性。'
+concepts.synchronization.barriers.guidance: '{link}在模拟步骤之间采用这一方式。每道屏障都位于产生数据的分派之后、使用数据的分派之前。'
+concepts.synchronization.barriers.guidance.link: 'FluidTank 模拟'
+concepts.synchronization.barriers.title: '管线屏障'
+concepts.synchronization.dependencies.description: '逐个资源确认哪些访问涉及同一份数据。只要其中存在写入,就必须维持所需的先后顺序。两次读取本身没有冲突,但仍需满足纹理布局和呈现要求。CPU 更新数据或释放资源前,也须等待 GPU 完成对相应存储的使用。'
+concepts.synchronization.dependencies.details: '应根据操作执行的位置选择机制:'
+concepts.synchronization.dependencies.guidance: '一项工作可能同时需要多种同步机制。例如,纹理由另一队列生成时,除了提交依赖,还需为下一次使用设置正确的布局。这两项操作本身都不需要阻塞 CPU;只有 CPU 要访问或释放相关数据时,才需要在 CPU 端等待。'
+concepts.synchronization.dependencies.table.cpuAccess.mechanism: '对相关的完成值调用 {wait}。'
+concepts.synchronization.dependencies.table.cpuAccess.situation: 'CPU 访问或资源释放需要等待 GPU 完成'
+concepts.synchronization.dependencies.table.crossQueue.mechanism: '将生产者的 {timelineValue} 传给消费者的 {submit}。'
+concepts.synchronization.dependencies.table.crossQueue.situation: '一个队列使用另一个队列生成的数据'
+concepts.synchronization.dependencies.table.headings.mechanism: '机制'
+concepts.synchronization.dependencies.table.headings.situation: '情况'
+concepts.synchronization.dependencies.table.layoutChange.mechanism: '调用 {commandBufferTransition},指定先前布局与目标布局。'
+concepts.synchronization.dependencies.table.layoutChange.situation: '纹理子资源更改其使用方式'
+concepts.synchronization.dependencies.table.pipelineStages.mechanism: '调用 {commandBufferBarrier},指定产生数据和使用数据的阶段。'
+concepts.synchronization.dependencies.table.pipelineStages.situation: '命令流中存在依赖关系的 GPU 阶段'
+concepts.synchronization.dependencies.title: '访问依赖'
+concepts.synchronization.description: '同步确定多个操作访问共享数据的顺序,并确保后续操作能够读到所需结果。在写后读依赖中,写入方称为生产者,读取方称为消费者。屏障与纹理转换协调 GPU 访问;时间线连接不同提交,并让 CPU 等待 GPU 工作完成。'
+concepts.synchronization.layouts.context: '现有内容可以丢弃时,可将先前布局设为 {undefined},例如新建图像或即将完整清除的附件。若要保留内容以供加载、采样或累积,则应使用此前访问时的布局。'
+concepts.synchronization.layouts.description: '纹理的用途标志声明允许的作用。{textureLayout} 描述命令序列中某一时刻对子资源的访问方式,子资源由 mip 级别和数组层确定。访问方式发生变化时,需要录制转换。例如,计算示例在 {storage} 布局中写入输出纹理,再为图形通道中的采样做准备:'
+concepts.synchronization.layouts.details: '这里的 {outputTexture} 已声明 {sampledStorage} 用途,所选子资源处于 {storage} 布局。{default} 选择 mip 级别 0 和数组层 0,其他级别和层不受这次转换影响。'
+concepts.synchronization.layouts.guidance: '调用 {transition} 时,提供子资源上一次使用的布局。应用程序在录制命令,以及在渲染阶段之间传递纹理时,负责跟踪这个状态。'
+concepts.synchronization.layouts.title: '纹理布局'
+concepts.synchronization.readback.description: '{isCompleted} 报告 GPU 的完成状态。通过命令录制的回读还需要一个步骤:将结果从内部回读存储复制到应用程序的目标内存。'
+concepts.synchronization.readback.details: '{commandBufferDownload} 录制向内部回读存储复制数据的 GPU 命令。提交完成后,库在回收命令缓冲区时执行最后的 CPU 复制。对本次回读提交的完成值调用 {wait},会等待 GPU 完成,并处理该队列已完成的缓冲区,从而完成这两个步骤。调用返回前,目标内存须保持已分配状态,托管内存须保持固定。'
+concepts.synchronization.readback.guidance: '仅轮询 {isCompleted} 不会执行最后的 CPU 复制;等待其他队列,也不会回收回读队列中已完成的缓冲区。上传和回读在何时使用直接内存访问或暂存存储,见{link}。'
+concepts.synchronization.readback.guidance.link: '资源管理'
+concepts.synchronization.readback.title: '回读完成'
+concepts.synchronization.reuse.description: '三角形教程在每帧完成后才复用数据。若要让帧重叠执行,应为每个尚未完成的帧准备独立的可变存储,并跟踪何时可以再次使用。修改数据前,须等待 GPU 完成对相关字节的访问;释放资源前,则须等待它的所有使用结束。'
+concepts.synchronization.reuse.details: '调整大小与关闭程序遵循相同的顺序:先停止新的资源访问,等待已有访问结束,再替换或释放资源。调用 {swapChainResize} 或释放资源、上下文前,须先完成上述等待。三角形教程已经逐帧等待,因此可以在回调之间调整大小。'
+concepts.synchronization.reuse.guidance: '{swapChainPresent} 在呈现后向图形队列发出信号并等待。因此,即使去掉显式的提交等待,三角形教程仍会在呈现时等待。独立提交到其他队列的工作,则需要单独跟踪其完成状态。'
+concepts.synchronization.reuse.title: '资源复用'
+concepts.synchronization.timelines.context: 'CPU 若要在消费者完成后读取结果、覆盖共享数据或释放资源,须先等待这次提交完成:'
+concepts.synchronization.timelines.description: '时间线用递增值标识队列进度。{submit} 返回的 {timelineValue} 同时包含时间线和本次提交的值。不同时间线上的 {value} 数值不能直接比较。'
+concepts.synchronization.timelines.details: '设 {producer} 和 {consumer} 是同一上下文中两个队列上已录制的命令缓冲区,后者使用前者写入的数据。提交时传入这项依赖:'
+concepts.synchronization.timelines.guidance: '消费者所在的队列会先等待传入的完成值,再执行命令。CPU 无需调用 {producedWait},可以继续处理其他工作。共享资源须保留到 {consumed} 完成,同时仍需录制消费者访问所要求的纹理转换。'
+concepts.synchronization.timelines.notes: '{queueTimelineSignal} 返回的值同样可以通过 {wait} 等待,也可以作为另一个队列的依赖。{signal} 将完成点排在此前已提交的工作之后,GPU 完成这些工作后便会到达该点。'
+concepts.synchronization.timelines.title: '时间线与队列依赖'
+concepts.synchronization.title: '同步'
+home.actions.reference: '浏览 API'
+home.actions.tutorial: '从三角形开始'
+home.entries.description: '教程、示例与 API 参考'
+home.entries.label: '文档分类'
+home.entries.reference.action: '探索 API'
+home.entries.reference.category: '参考'
+home.entries.reference.description: '查阅 API 类型、成员与声明'
+home.entries.reference.title: 'API 参考'
+home.entries.samples.action: '浏览示例'
+home.entries.samples.category: '示例'
+home.entries.samples.description: '图形与计算技术的可运行示例'
+home.entries.samples.title: '示例'
+home.entries.title: '文档'
+home.entries.tutorial.action: '开始教程'
+home.entries.tutorial.category: '教程'
+home.entries.tutorial.description: '从空项目开始,绘制第一个三角形'
+home.entries.tutorial.title: '第一个三角形'
+home.introduction.accent: '跨平台图形开发'
+home.introduction.description: 'Zenith.NET 提供统一的图形与计算 API,支持 DirectX、Metal 和 Vulkan'
+home.introduction.title: '面向 .NET 的{lineBreak}{accent}'
+home.meta.description: '使用 Zenith.NET 开发跨平台图形与计算应用,通过统一 API 使用 DirectX、Metal 和 Vulkan'
+home.scene.description: '可交互的旋转几何体。拖动或使用方向键调整视角。'
+home.scene.label: '几何体显示'
+home.scene.surface: '表面'
+home.scene.wireframe: '线框'
+home.title: '适用于 .NET 的现代 RHI'
+learning.concepts.description: '选择资源用途、描述着色器数据与协调 GPU 执行'
+learning.concepts.execution.description: '命令录制、提交与资源生命周期'
+learning.concepts.platforms.description: '原生呈现与 UI 渲染控件'
+learning.concepts.resources.description: '资源用途、内存放置与视图'
+learning.concepts.shaders.description: 'C# 与 Slang 的数据布局和绑定约定'
+learning.concepts.synchronization.description: '依赖关系、可见性与完成状态'
+learning.concepts.title: '核心概念'
+learning.description: '第一次使用 Zenith.NET,可以从三角形教程开始。完成后,按需要查阅资源管理、着色器数据和同步等概念,或通过示例了解具体图形技术的实现。'
+learning.entries.samples.action: '浏览示例'
+learning.entries.samples.description: '几何体、计算、光线查询与网格着色的 C# 和 Slang 实现'
+learning.entries.tutorial.action: '打开教程'
+learning.entries.tutorial.description: '创建窗口并渲染三角形的控制台应用程序'
+learning.meta.description: 'Zenith.NET 编程模型的入门教程与概念说明'
+learning.title: '学习'
+reference.description: '资源、命令与平台集成的参考文档'
+reference.entries.commands.description: '图形、计算与传输命令'
+reference.entries.commands.label: '录制命令'
+reference.entries.context.description: '为所选后端创建 GPU 资源'
+reference.entries.context.label: '从这里开始'
+reference.meta.description: 'Zenith.NET 的命名空间、类型与成员'
+reference.navigation.core: '核心 API 参考'
+reference.title: 'API 参考'
+samples.computeShader.description: '示例加载 {assetsTexturesShokoPng},通过一次计算分派将图像转换为灰度,再显示输出纹理。转换只执行一次,后续帧显示相同结果。渲染器展示了采样纹理与可写纹理的绑定、向上取整线程组数量以覆盖整张图像的计算方式,以及着色器中的图像边界检查。'
+samples.computeShader.details: '{computeShaderRendererCs} · {computeShaderSlang}'
+samples.computeShader.title: '计算着色器'
+samples.cube.description: '旋转立方体加入索引绘制、常量缓冲区中的模型、视图和投影矩阵,以及深度测试。窗口大小变化时,会重建 {d32FloatS8UInt} 深度/模板纹理。'
+samples.cube.details: '{spinningCubeRendererCs} · {spinningCubeSlang}'
+samples.cube.title: '旋转立方体'
+samples.description: '每个示例均由 C# 渲染器与 Slang 着色器组成,集中说明一项技术。完整的应用程序设置与渲染顺序见{link}。'
+samples.description.link: '第一个三角形'
+samples.execution.description: '运行 {link} 项目,并从控制台菜单选择示例。'
+samples.execution.description.link: 'ZenithTutorials'
+samples.execution.title: '运行示例'
+samples.host.description: '{appCs} 初始化上下文、窗口和交换链。每帧获取命令缓冲区,调用所选渲染器,提交命令并呈现结果。{iRendererCs} 规定渲染器的更新、渲染、调整大小和释放操作。'
+samples.host.details: '计算与光线追踪渲染器生成离屏纹理。{texturePresenterCs} 和 {presentTextureSlang} 将这些纹理绘制到当前输出纹理,随后由共享宿主提交命令并呈现这一帧。'
+samples.host.title: '宿主与呈现'
+samples.indirectDrawing.description: '间接绘制通过索引绘制参数缓冲区和保存各实例数据的结构化缓冲区,绘制多个立方体。CPU 提供绘制参数并更新变换。'
+samples.indirectDrawing.details: '{indirectDrawingRendererCs} · {indirectDrawingSlang}'
+samples.indirectDrawing.title: '间接绘制'
+samples.meshShading.description: '任务着色器剔除球体实例,将保留的索引传给负责生成几何体的网格着色器。创建管线前,渲染器检查 {contextCapabilitiesMeshShadingSupported}。'
+samples.meshShading.details: '{meshShadingRendererCs} · {meshShadingSlang}'
+samples.meshShading.title: '网格着色'
+samples.rayTracing.description: '该示例为地面和程序化球体构建底层与顶层加速结构,再在计算着色器中执行内联光线查询。初始化前检查 {contextCapabilitiesRayTracingSupported}。'
+samples.rayTracing.details: '{rayTracingRendererCs} · {rayTracingSlang}'
+samples.rayTracing.title: '光线追踪'
+samples.title: '示例'
+samples.triangle.description: '三角形示例将顶点缓冲区与对应的输入布局关联,通过图形管线绘制三个顶点。'
+samples.triangle.details: '{helloTriangleRendererCs} · {helloTriangleSlang} · {link}'
+samples.triangle.details.link: '完整教程'
+samples.triangle.title: 'Hello Triangle'
+samples.workloads.description: '主仓库的{link}包含规模较大的应用程序,这些项目直接引用库源码。'
+samples.workloads.description.link: 'Experiments 目录'
+samples.workloads.details: '窗口实验项目采用相同的 Windows、macOS 和 Linux/X11 呈现路径。Avalonia、WinForms、WPF、WinUI/Uno 和 MAUI 控件的帧生命周期及实现代码见{link}。'
+samples.workloads.details.link: '平台集成'
+samples.workloads.table.cornellBox.example: '{link}'
+samples.workloads.table.cornellBox.example.link: 'CornellBox'
+samples.workloads.table.cornellBox.whatItDemonstratesAndRequires: '路径追踪、降噪、色调映射、分辨率放大和 ImGui 控件。需要光线追踪支持。'
+samples.workloads.table.fluidTank.example: '{link}'
+samples.workloads.table.fluidTank.example.link: 'FluidTank'
+samples.workloads.table.fluidTank.whatItDemonstratesAndRequires: '计算流体模拟、模拟阶段之间的依赖,以及多通道表面渲染。'
+samples.workloads.table.headings.example: '示例'
+samples.workloads.table.headings.whatItDemonstratesAndRequires: '技术与运行要求'
+samples.workloads.table.inkCanvas.example: '{link}'
+samples.workloads.table.inkCanvas.example.link: 'InkCanvas'
+samples.workloads.table.inkCanvas.whatItDemonstratesAndRequires: '原生窗口中的 Skia 绘制与输入处理。需要对应目标平台的后端和 Skia 原生库。'
+samples.workloads.table.platformDetection.example: '{link}'
+samples.workloads.table.platformDetection.example.link: 'PlatformDetection'
+samples.workloads.table.platformDetection.whatItDemonstratesAndRequires: '尝试为每个后端创建上下文,并报告设备能力。创建失败时显示为不支持,原因可能是设备、驱动程序或缺失的运行时依赖。'
+samples.workloads.title: '综合应用'
+tutorial.context.description: '{graphicsContext} 通过所选后端连接应用程序与 GPU,并提供创建渲染资源的方法。'
+tutorial.context.details: '在{emphasis}插入上下文初始化代码:'
+tutorial.context.details.emphasis: 'using 指令之后、{iWindowWindow} 之前'
+tutorial.context.guidance: '各分支为当前平台选择后端并请求启用验证。{validationMessage} 消息会输出到控制台。'
+tutorial.context.imports: '在 {programCs} 顶部添加以下 using 指令:'
+tutorial.context.title: '图形上下文'
+tutorial.description: '本教程从一个空的 .NET 控制台项目开始,逐步完成窗口创建,并在窗口中绘制一个彩色三角形。'
+tutorial.details: '本教程有三个检查点:空窗口、单色背景和彩色三角形。每完成一个检查点,先运行程序确认结果,再继续添加下一部分。'
+tutorial.draw.context: '{draw} 的参数如下:'
+tutorial.draw.description: '在 {windowRender} 回调中,用以下命令替换 {addTheTriangleDrawCommandsHereLater},位置保持在 {beginRenderPass} 与 {endRenderPass} 之间:'
+tutorial.draw.details: '{setPipeline} 选择着色器和渲染状态。后端按管线的输入布局解释顶点绑定,因此这一步必须先于顶点绑定。'
+tutorial.draw.guidance: '{setVertexBufferVertexBuffer} 从缓冲区的字节偏移 0 开始,将已上传的顶点数据绑定到输入槽 0,对应 {inputLayouts} 中唯一的条目。'
+tutorial.draw.notes: '这些调用录制绘制命令。已有的 {submitWait} 提交命令并等待 GPU 完成,随后 {present} 请求显示完成的图像。保留通道开始时的清除操作,让三角形以外的区域也有确定的背景色。'
+tutorial.draw.table.firstInstance.meaning: '实例编号从零开始。'
+tutorial.draw.table.firstVertex.meaning: '从缓冲区中的第一个顶点开始。'
+tutorial.draw.table.headings.argument: '参数'
+tutorial.draw.table.headings.description: '值'
+tutorial.draw.table.headings.meaning: '含义'
+tutorial.draw.table.instanceCount.meaning: '绘制一个三角形实例。'
+tutorial.draw.table.vertexCount.meaning: '读取三个顶点。'
+tutorial.draw.title: '绘制命令'
+tutorial.execution.description: '构建并运行 {firstTriangle} 项目。'
+tutorial.execution.details: '窗口中应出现一个三角形:上方为红色,右下为绿色,左下为蓝色,背景为深色。三个顶点的颜色经过插值,在三角形内部形成平滑过渡。'
+tutorial.execution.guidance: '调整窗口大小时,图像的像素尺寸会改变,顶点坐标保持不变。视口将这些坐标映射到新的宽度和高度,因此改变窗口的宽高比会拉伸三角形。'
+tutorial.execution.title: '运行应用程序'
+tutorial.frame.commands.context: '{endRenderPass} 之后的第二次转换使输出纹理进入可呈现的布局。{submit} 结束录制并提交命令缓冲区,{wait} 阻塞至本次提交完成,随后 {present} 请求显示渲染结果。'
+tutorial.frame.commands.description: '渲染通道将写入同一组附件的绘制操作组织在一起。本通道用交换链的输出纹理接收颜色输出。{colorAttachmentClear} 指定以 RGBA 颜色初始化该附件,并在通道结束时保留内容。{beginRenderPass} 按这些设置录制通道的开始命令。'
+tutorial.frame.commands.details: '集合中有一个颜色附件。{null} 表示本通道不使用深度/模板附件。'
+tutorial.frame.commands.guidance: '{beginRenderPass} 默认让视口和裁剪区域覆盖整个附件。视口把渲染坐标映射为像素位置,裁剪区域则限制可以写入的范围。'
+tutorial.frame.commands.title: '清除、提交与呈现'
+tutorial.frame.description: '先把窗口填成一种背景色,这个操作称为清除。背景正常显示后,就说明呈现目标、命令提交和呈现流程已经接通,可以继续加入三角形的顶点和着色器。'
+tutorial.frame.details: 'Silk.NET 在需要绘制一帧时调用渲染回调。本示例不使用回调传入的时间间隔,因此将该参数命名为 {value}。在{emphasis}注册回调:'
+tutorial.frame.details.emphasis: '{windowRun} 之前'
+tutorial.frame.target.description: '{graphicsQueueCommandBuffer} 返回可直接录制的命令缓冲区。录制描述 GPU 工作,提交后才开始执行。'
+tutorial.frame.target.details: '纹理布局描述操作对纹理的访问方式。第一次 {transition} 将输出纹理准备为颜色附件。这里的 {default} 选择 mip 级别 0 和数组层 0,也就是输出纹理的基础图像。由于这个通道会清除整张图像,可以指定 {undefined},允许丢弃原有内容。'
+tutorial.frame.target.guidance: '呈现后可能换用另一张图像,因此每一帧都要获取当前的输出纹理。'
+tutorial.frame.target.title: '准备输出纹理'
+tutorial.frame.title: '第一帧'
+tutorial.geometry.data.description: '在{emphasis}添加顶点数组。后续资源初始化也放在该回调之前,顺序如下。'
+tutorial.geometry.data.description.emphasis: '{windowRender} 之前'
+tutorial.geometry.data.details: '每个条目构造一个 {vertex},内部的两个 {new} 表达式分别提供 {vector3} 位置和 {vector4} 颜色。三个位置对应上角、右下角和左下角。顶点着色器将 {w} 作为第四个位置坐标;w 为 1 时,x、y 也就是归一化设备坐标,-1 与 +1 对应图像边界,0 对应中心,与像素尺寸无关。'
+tutorial.geometry.data.guidance: '每个颜色包含红、绿、蓝和 alpha 分量,alpha 设为 1。三个顶点的 z 坐标均为 0,位于同一平面。'
+tutorial.geometry.data.title: '顶点位置与颜色'
+tutorial.geometry.description: '三角形的每个顶点都有位置和颜色。GPU 根据三个位置确定三角形的形状,再对顶点颜色插值,得到内部各处的颜色。'
+tutorial.geometry.format.description: '在{emphasis}追加顶点声明:'
+tutorial.geometry.format.description.emphasis: '{programCs} 的末尾、清理语句之后'
+tutorial.geometry.format.details: '{file} 将类型的使用范围限定在 {programCs} 内,{layoutKindSequential} 则保留字段的声明顺序。{position} 包含三个 32 位浮点数,字节偏移量为 0;{color} 包含四个,字节偏移量为 12。因此,每个顶点占 28 字节。稍后将通过输入布局描述这一排列,让 GPU 正确读取各字段。'
+tutorial.geometry.format.imports: '在 {programCs} 顶部添加以下 using 指令,以使用向量类型和结构体布局特性:'
+tutorial.geometry.format.title: '顶点表示'
+tutorial.geometry.title: '顶点数据'
+tutorial.geometry.upload.context: '{fixed} 防止垃圾回收器在地址使用期间移动数组。对于这个 CPU 可写缓冲区,{upload} 会映射内存、复制数据,并在返回前解除映射,因此只需在调用期间固定数组。顶点数据在初始化时上传一次即可;缓冲区则必须保留到渲染结束。'
+tutorial.geometry.upload.description: '将顶点存入 {buffer},供 GPU 在绘制时读取。在顶点数组之后紧接着添加分配与上传代码:'
+tutorial.geometry.upload.details: '分配大小为 {sizeofVertexVerticesLength},即三个 28 字节顶点,共 84 字节。{bufferUsagesVertex} 声明顶点输入用途,{cpuWriteOnly} 允许 CPU 直接初始化。描述中的步幅记录元素大小,随后定义的输入布局指定 GPU 如何读取各个顶点属性。'
+tutorial.geometry.upload.guidance: '分配不会初始化缓冲区内容。{upload} 使用数据描述中的源指针和字节数,将数组复制到目标字节偏移量 0。'
+tutorial.geometry.upload.imports: '在 {programCs} 顶部的 using 指令中添加 {bufferAlias},以区分 Zenith.NET 的缓冲区类型与 {systemBuffer}。'
+tutorial.geometry.upload.notes: '在 {programCs} 末尾的清理代码中,紧接在 {swapChainDispose} 之前加入 {vertexBufferDispose}。较大静态几何体所需的 GPU 专用存储与上传方式见{link}。'
+tutorial.geometry.upload.notes.link: '资源管理'
+tutorial.geometry.upload.title: '缓冲区分配与上传'
+tutorial.input.description: 'GPU 需要知道位置、颜色等顶点属性存在哪里。输入布局指定每个属性的格式和字节偏移量,并用步幅表示从一个顶点到下一个顶点相隔多少字节。在创建着色器之后添加以下代码:'
+tutorial.input.details: '{inputLayoutAdd} 将每个属性追加在前一个属性之后,并将其大小计入步幅。偏移量根据这里提供的格式计算,而非通过检查 C# 结构体获得。这两个字段对应的布局如下:'
+tutorial.input.guidance: '{semanticIndex} 默认为 0,与着色器中的 {pOSITION0} 和 {cOLOR0} 对应。位置与颜色存放在同一个顶点缓冲区中,因此管线只需在槽 0 使用一个输入布局。如果 C# 结构体的字段顺序或填充发生变化,也要相应调整此布局。'
+tutorial.input.table.headings.byteOffset: '字节偏移量'
+tutorial.input.table.headings.cField: 'C# 字段'
+tutorial.input.table.headings.format: '格式'
+tutorial.input.table.headings.shaderInput: '着色器输入'
+tutorial.input.table.nextVertex.cField: '下一个顶点'
+tutorial.input.table.nextVertex.format: '总步幅:28 字节'
+tutorial.input.table.nextVertex.shaderInput: '下一个输入记录'
+tutorial.input.table.vertexColor.format: '{float4}(16 字节)'
+tutorial.input.table.vertexPosition.format: '{float3}(12 字节)'
+tutorial.input.title: '顶点输入布局'
+tutorial.lifecycle.description: '每帧依次执行以下操作:'
+tutorial.lifecycle.details: '关闭窗口后,{run} 返回,程序继续执行清理代码。每一帧都已等待其提交完成,因此现在可以释放这些命令使用的资源。{windowRun} 之后完整的清理代码如下:'
+tutorial.lifecycle.guidance: '先释放交换链,再销毁其原生窗口;先释放应用程序资源,再释放上下文。已提交的命令缓冲区由队列回收,输出纹理由交换链管理,二者都不需要单独调用 {dispose}。'
+tutorial.lifecycle.title: '帧生命周期与清理'
+tutorial.pipeline.behavior: '在输入布局之后创建管线:'
+tutorial.pipeline.context: '{cullNone} 关闭面剔除,因此无论顶点在画面中呈顺时针还是逆时针排列,三角形都会被绘制。'
+tutorial.pipeline.description: '{graphicsPipeline} 将顶点布局、两个着色器,以及图元组装和附件输出所需的设置组合在一起。'
+tutorial.pipeline.details: '着色器负责处理顶点和片元。其余管线设置决定图元如何组装、是否剔除某个朝向的面,以及如何进行深度测试和颜色混合:'
+tutorial.pipeline.guidance: '管线颜色格式必须匹配渲染通道附件,因此直接取自 {swapChainDescFormat}。{count1} 表示每像素一个采样点。'
+tutorial.pipeline.item: '{triangleList} 将每组连续的三个顶点组装成一个三角形。'
+tutorial.pipeline.notes: '{depthNone} 关闭深度测试和深度写入,与通道没有深度附件的配置一致。'
+tutorial.pipeline.reference: '{opaque} 关闭混合,因此片元着色器输出直接替换三角形内部的背景色。'
+tutorial.pipeline.title: '图形管线'
+tutorial.pipeline.usage: '管线创建完成后即可释放着色器对象。渲染期间保留管线,在清理代码中将 {pipelineDispose} 放在 {vertexBufferDispose} 之前。'
+tutorial.presentation.title: '图形上下文与呈现'
+tutorial.project.context: '在项目构建设置中启用{emphasis}({allowUnsafeBlocks})。窗口辅助代码的原生调用和顶点上传需要不安全代码。'
+tutorial.project.context.emphasis: '允许不安全代码'
+tutorial.project.description: '创建名为 {firstTriangle} 的 C# 控制台项目,目标框架设为 .NET 10。'
+tutorial.project.details: '为项目添加以下 NuGet 包:'
+tutorial.project.guidance: '{zenithNET} 提供统一渲染 API,三个后端包提供各平台的实现。{zenithNETCompiler} 为所选后端编译稍后介绍的 Slang 着色器。Silk.NET 负责窗口创建与事件处理。'
+tutorial.project.title: '项目设置'
+tutorial.requirements.context: '{link}及其{detail}提供了本教程的参考实现。'
+tutorial.requirements.context.detail: '共享宿主'
+tutorial.requirements.context.link: 'Hello Triangle 示例'
+tutorial.requirements.description: '需要 {emphasis},并具备基础 C# 知识。应用程序以 {net10} 为目标框架。GPU 和驱动程序须支持所在平台选用的后端:'
+tutorial.requirements.description.emphasis: '.NET 10 SDK'
+tutorial.requirements.guidance: 'Zenith.NET 渲染到应用程序提供的原生呈现目标。Silk.NET.Windowing 创建窗口并处理事件。macOS 使用由 {cAMetalLayerCs} 表示的 Metal 图层,窗口辅助代码负责将其连接到窗口。'
+tutorial.requirements.table.headings.graphicsBackend: '图形后端'
+tutorial.requirements.table.headings.platform: '平台'
+tutorial.requirements.table.linux.graphicsBackend: 'Vulkan 1.4'
+tutorial.requirements.table.linux.platform: 'Linux(X11 或 XWayland)'
+tutorial.requirements.table.macOS.graphicsBackend: 'Metal 4'
+tutorial.requirements.table.macOS.platform: 'macOS'
+tutorial.requirements.table.windows.graphicsBackend: 'DirectX 12'
+tutorial.requirements.table.windows.platform: 'Windows'
+tutorial.requirements.title: '前置条件'
+tutorial.resize.context: '再次构建并运行程序。调整窗口大小后,背景应继续填满整个窗口:每次开始新的渲染通道时,视口和裁剪区域都会按调整后的附件尺寸设置。保留渲染回调,后面将在这个通道内绘制三角形。'
+tutorial.resize.description: '帧缓冲区尺寸变化时,交换链也要随之调整。在渲染回调之后、{windowRun} 之前注册 {framebufferResize} 回调:'
+tutorial.resize.details: '这里监听的是以像素为单位的{link},而非窗口逻辑尺寸的变化。窗口最小化时,帧缓冲区的宽度或高度可能变为零;此时,尺寸变化回调保留现有交换链,不执行调整。渲染回调也会在任一尺寸为零时直接返回,直到帧缓冲区恢复有效尺寸后再继续绘制。'
+tutorial.resize.details.link: '帧缓冲区大小变化'
+tutorial.resize.guidance: '渲染回调会等待当前帧的提交完成,再调用 {present}。因此,尺寸变化回调替换交换链图像时,此前帧的工作已经结束。关于完成状态与资源生命周期的要求,详见{link}。'
+tutorial.resize.guidance.link: '同步'
+tutorial.resize.title: '窗口尺寸变化'
+tutorial.shaders.compilation.context: '{appContextBaseDirectory} 让程序从输出目录加载已复制的着色器。即使从其他工作目录启动程序,查找文件的位置也不会改变。'
+tutorial.shaders.compilation.description: '将 {triangleSlang} 的文件属性{emphasis}设为{detail},构建时便会将着色器复制到可执行文件所在目录。'
+tutorial.shaders.compilation.description.detail: '如果较新则复制'
+tutorial.shaders.compilation.description.emphasis: '复制到输出目录'
+tutorial.shaders.compilation.details: '返回 {programCs},在顶点上传之后、渲染回调之前添加着色器初始化:'
+tutorial.shaders.compilation.guidance: '{zenithCompiler} 为 {contextGraphicsApi} 指定的后端编译源码,{createShader} 再根据编译后的描述创建着色器对象。入口点名称 {vSMain} 和 {fSMain} 分别选中 Slang 文件中定义的两个函数。'
+tutorial.shaders.compilation.title: '编译着色器'
+tutorial.shaders.context: '{fSInput} 连接两个着色器阶段。顶点着色器将裁剪空间位置写入 {sVPOSITION},GPU 据此判断三角形的可见部分。{cOLOR0} 字段传递顶点颜色,颜色经过三角形内部的插值后到达片元着色器。两个阶段必须使用一致的颜色类型与语义。'
+tutorial.shaders.description: '着色器是在 GPU 上运行的程序。顶点着色器确定每个顶点的位置,片元着色器则确定图像中被覆盖位置的颜色。我们将两个阶段写在同一个 Slang 文件中,并分别定义入口点,也就是各阶段开始执行的函数。'
+tutorial.shaders.details: '创建 {triangleSlang},先定义输入与输出结构:'
+tutorial.shaders.fragment.description: '在同一文件中追加片元入口点:'
+tutorial.shaders.fragment.details: '光栅化确定三角形在图像中的覆盖范围,并在这些位置生成片元。每个片元的颜色由三个顶点的颜色插值得到。{fSMain} 返回这个颜色,{sVTARGET} 则将结果送到第一个颜色附件。'
+tutorial.shaders.fragment.title: '片元着色'
+tutorial.shaders.guidance: '{vSInput} 定义顶点着色器输入。{pOSITION0} 和 {cOLOR0} 按语义标识属性,C# 字段名称本身不建立这一映射。'
+tutorial.shaders.title: '着色器阶段'
+tutorial.shaders.vertex.description: '在 {triangleSlang} 中追加顶点入口点:'
+tutorial.shaders.vertex.details: 'GPU 为每个输入顶点调用这一入口点,补充齐次坐标 {w} 并传递颜色。顶点处理结束后,位置要除以 w。本例 w 为 1,x、y、z 保持不变,可直接解释为归一化设备坐标。'
+tutorial.shaders.vertex.title: '顶点着色'
+tutorial.surface.description: '在{emphasis}指定原生呈现目标:'
+tutorial.surface.description.emphasis: '{windowCenter} 之后、{windowRun} 之前'
+tutorial.surface.details: '帧缓冲区是窗口用于显示的像素图像。{surface} 描述原生呈现目标及其尺寸,{framebufferSize} 给出以像素为单位的大小。启用显示缩放时,这个大小可能与窗口的逻辑尺寸不同。'
+tutorial.surface.guidance: '呈现目标提供后端所需的原生句柄:Windows 使用 Win32 窗口,Linux 使用 X11 显示与窗口,macOS 使用已连接的 Metal 图层。'
+tutorial.surface.title: '原生呈现目标'
+tutorial.swapChain.context: '交换链同时依赖上下文与窗口。将 {programCs} 末尾的 {windowDispose} 替换为以下释放顺序:'
+tutorial.swapChain.description: '紧接呈现目标描述,创建交换链:'
+tutorial.swapChain.details: '{swapChain} 管理窗口呈现的图像。{drawable} 以图像资源 {texture} 的形式提供当前图像。渲染后,{present} 请求显示该图像,并推进到下一个输出纹理。'
+tutorial.swapChain.guidance: '{b8G8R8A8UNorm} 按蓝、绿、红和 alpha 顺序保存四个 8 位通道。{uNorm} 将整数 0–255 对应到 0–1。着色器仍输出逻辑 RGBA 值,格式只规定存储表示;管线通过 {swapChainDescFormat} 匹配附件。'
+tutorial.swapChain.title: '交换链'
+tutorial.title: '第一个三角形'
+tutorial.troubleshooting.description: '结合控制台诊断与三个检查点定位失败阶段:'
+tutorial.troubleshooting.details: '{link}与{detail}可用于核对顶点数据、输入布局和绘制操作。该示例将窗口与提交逻辑放入共享宿主,本教程则保留在 {programCs} 中。{context}进一步介绍索引绘制、变换与深度测试。'
+tutorial.troubleshooting.details.context: '旋转立方体'
+tutorial.troubleshooting.details.detail: 'Slang 着色器'
+tutorial.troubleshooting.details.link: '示例渲染器'
+tutorial.troubleshooting.table.backend.check: '检查所选后端是否受设备和已安装的图形驱动程序支持。'
+tutorial.troubleshooting.table.backend.symptom: '后端创建失败'
+tutorial.troubleshooting.table.drawing.check: '检查上传大小、输入布局、管线选择以及渲染通道内绘制命令的位置。'
+tutorial.troubleshooting.table.drawing.symptom: '背景出现但三角形未出现'
+tutorial.troubleshooting.table.headings.check: '检查项'
+tutorial.troubleshooting.table.headings.symptom: '现象'
+tutorial.troubleshooting.table.input.check: '检查步幅是否为 28 字节,位置和颜色是否分别从偏移 0 和 12 字节开始。'
+tutorial.troubleshooting.table.input.symptom: '顶点位置或颜色异常'
+tutorial.troubleshooting.table.shaders.check: '检查复制的 {triangleSlang}、入口点拼写以及编译器报告的诊断信息。'
+tutorial.troubleshooting.table.shaders.symptom: '背景色正常显示,但着色器加载失败'
+tutorial.troubleshooting.table.window.check: '确认 .NET 项目能够构建成功,并且仅包含窗口的步骤可以在桌面会话中运行。在 Linux 上,检查 X11 或 XWayland 是否可用。'
+tutorial.troubleshooting.table.window.symptom: '窗口未打开'
+tutorial.troubleshooting.title: '问题排查'
+tutorial.window.description: '将 {cocoaHelperCs} 下载到项目中。该辅助类负责将 Metal 图层连接到 macOS 窗口。'
+tutorial.window.details: '将自动生成的 {programCs} 内容替换为以下代码。它使用顶级语句,按书写顺序执行,不需要显式编写 {main} 方法。后续各节会指出新代码应放在哪里:'
+tutorial.window.guidance: '{graphicsAPINone} 将图形上下文的创建交给 Zenith.NET。{initialize} 创建原生窗口,使窗口系统的句柄可供使用;{center} 把窗口放到屏幕中央。{run} 持续处理事件,直到窗口关闭,随后执行 {dispose} 释放窗口。'
+tutorial.window.notes: '构建并运行程序,检查窗口能否打开、移动、调整大小和关闭。此时还没有提交渲染命令,窗口内容尚未定义。关闭窗口后再继续。'
+tutorial.window.title: '创建窗口'
+ui.action.copy: '复制'
+ui.action.navigate: '导航'
+ui.action.open: '打开'
+ui.action.source: '查看源码'
+ui.api.browse: '浏览命名空间和类型。'
+ui.api.clearTypeSearch: '清除类型搜索'
+ui.api.findType: '查找类型'
+ui.api.loadError: '无法加载类型。刷新此页面以重试。'
+ui.api.loadingNamespaces: '正在加载命名空间…'
+ui.api.loadingTypes: '正在加载类型…'
+ui.api.matchingTypes: '匹配的类型'
+ui.api.namespace: '命名空间'
+ui.api.namespaces: '命名空间'
+ui.api.noNamespaceTypes: '此命名空间中没有类型。'
+ui.api.noTypes: '没有匹配的类型。'
+ui.api.types: '类型'
+ui.content.reference: '参考'
+ui.content.source: '源代码'
+ui.content.tutorial: '教程'
+ui.contents.close: '关闭目录'
+ui.contents.open: '打开目录'
+ui.contents.title: '目录'
+ui.footer.caption: '面向 .NET 的现代 RHI'
+ui.footer.label: '页脚'
+ui.language.label: '语言'
+ui.language.unavailable: '未翻译'
+ui.members.clear: '清除成员搜索'
+ui.members.close: '关闭成员查找器'
+ui.members.empty: '没有匹配的成员。'
+ui.members.open: '转到成员'
+ui.members.placeholder: '名称或签名…'
+ui.members.search: '搜索成员'
+ui.members.title: '查找成员'
+ui.members.undeclared: '此类型没有声明的成员。'
+ui.navigation.api: 'API'
+ui.navigation.concepts: '概念'
+ui.navigation.home: '首页'
+ui.navigation.label: '主导航'
+ui.navigation.skip: 跳转到正文
+ui.navigation.toggle: '切换导航'
+ui.outline.title: '本页导读'
+ui.reference.assembly: '程序集'
+ui.reference.classes: '类'
+ui.reference.constructors: '构造函数'
+ui.reference.declaration: 'C# 类型声明'
+ui.reference.delegates: '委托'
+ui.reference.derived: '派生类型'
+ui.reference.enums: '枚举'
+ui.reference.events: '事件'
+ui.reference.examples: '示例'
+ui.reference.exceptions: '异常'
+ui.reference.extensionMethods: '扩展方法'
+ui.reference.fields: '字段'
+ui.reference.implementations: '显式接口实现'
+ui.reference.implements: '实现'
+ui.reference.inheritance: '继承'
+ui.reference.inheritedMembers: '继承的成员'
+ui.reference.interfaces: '接口'
+ui.reference.metadata: '类型元数据'
+ui.reference.methods: '方法'
+ui.reference.namespace: '命名空间'
+ui.reference.open: '打开 {name} 的参考文档'
+ui.reference.operators: '运算符'
+ui.reference.parameters: '参数'
+ui.reference.properties: '属性'
+ui.reference.related: '另请参阅'
+ui.reference.relationships: '继承与元数据'
+ui.reference.remarks: '备注'
+ui.reference.returns: '返回值'
+ui.reference.structs: '结构体'
+ui.reference.typeParameters: '类型参数'
+ui.reference.value: '值'
+ui.reference.values: '值'
+ui.search.close: '关闭搜索'
+ui.search.empty: '没有找到与“{query}”相关的结果。'
+ui.search.hint: '搜索教程、概念、示例和 API 参考。'
+ui.search.loading: '正在加载搜索…'
+ui.search.more: '显示更多结果'
+ui.search.pending: '正在搜索…'
+ui.search.placeholder: '输入搜索内容'
+ui.search.resultCount: '结果:{count}'
+ui.search.title: '搜索文档'
+ui.search.unavailable: '搜索暂不可用,可直接打开下方页面。'
diff --git a/documents/locales/zh-CN/translation.md b/documents/locales/zh-CN/translation.md
new file mode 100644
index 00000000..29908c60
--- /dev/null
+++ b/documents/locales/zh-CN/translation.md
@@ -0,0 +1,100 @@
+# 简体中文翻译约定
+
+## 资源与结构
+
+[英文资源字典](../en-US/strings.yml)是唯一原文;项目约定见[文档维护指南](../../maintenance.md)。开始新译本时,将英文文件完整复制为本目录的 `strings.yml`,保留首行 `### YamlMime:Resources`,使用 UTF-8 编码。后续更新应对照英文变更修订现有译文,不要重新覆盖整个文件。本文件 `translation.md` 永久保留,不发布到网站。
+
+导航、章节顺序、锚点、链接、格式、代码和图片都在 `locales/` 外统一维护。不要在语言目录内新增文章、TOC、模板、脚本或配置。
+
+翻译只修改本目录 `strings.yml` 的值。键集合必须与英文完全一致,不得自行新增、删除或重命名键,包括看起来相同或暂未遇到的界面文案。如果原文存在问题,向维护者指出资源键和问题,由英文及共享页面先统一修正,再同步译文。
+
+## 文风与上下文
+
+采用严谨、自然的技术教材文风,先解释操作之间的关系,再说明条件和限制。教程保留明确的操作步骤与可观察的结果。学术风格不等于增加被动句、抽象名词或长句。
+
+按文章的阅读顺序审校,结合前后段落、表格和代码理解内容。资源键只是存储边界,不是孤立的翻译单元。链接标签和强调片段应放回完整句子检查,避免机械复述、反复请求读者操作,以及不说明实际含义的比喻。
+
+标题、界面标签、首页可见的简短介绍和页脚文案不加句末句号;正文、诊断信息和无障碍操作说明保留正常标点。每个概念使用一种通行译法,不零散附加英文括注。API 标识符、文件名和实际设置项的名称保持准确;括号可用于必要解释,不作为例行的双语标注。
+
+判断一句话是否需要保留,要看它是否解释当前操作、设计选择、可观察结果或正确使用的条件。删去未使用功能的罗列、泛泛的环境提醒和重复前提。标题及跨样式的短语也须整体翻译,不沿用英文语序。首页标题的 `{accent}` 与 `{lineBreak}` 可按中文调整位置,样式和换行不能决定语序。正文删改由维护者先统一更新英文、共享绑定及各语言键集合,再同步译文。
+
+## 文本与占位符
+
+使用自然、简洁的技术中文,以完整句子或段落为单位翻译,保留前提、否定、单位、操作顺序和生命周期约束。不要逐词拼接译文,也不要添加原文没有的技术保证、平台要求、包版本或发布说明。产品名、包名、文件名、C#/Slang 标识符和大小写保持原样。
+
+每个资源值都必须是非空字符串、纯文本,不写 HTML 或 Markdown。注意 YAML 引号和转义,避免冒号、井号或类似数字的值被误解析;需要时使用引号。保留英文键的排列顺序,方便人工核对。
+
+`{context}`、`{link}` 等命名占位符可对应共享页面中的代码、链接或强调内容;`{name}`、`{query}`、`{count}` 等界面占位符也可能由程序填入普通文本。不要凭占位符名称推断内容,应检查对应资源在共享页面或界面中的用途。链接标签或强调文字若有独立资源键,也需要翻译。
+
+可以按中文语序移动占位符,但每个键都必须保留原有名称、大小写和半角花括号,不新增、删除或改名。不要用显示出的 API 名称、链接文字或示例数字替换占位符,也不要拆分资源键来重组句子。
+
+## API 链接与技术含义
+
+链接由共享页面维护。正文和表格中明确指向站内 API 的标识符链接到对应类型或成员;变量、参数名、示例自定义类型、包名、文件名及没有本站参考页的外部符号保持普通代码文字。相同拼写不代表同一个符号。普通教程代码块不自动添加链接。译者可以移动整个占位符,但不补写链接、不修改目标,也不改动共享代码及其格式。
+
+如果某个 API 没有生成的参考条目,共享页面会保留普通代码文字,并在附近提供明确的源码链接。不要自行改成其他类型或成员的链接。
+
+翻译时尤其注意以下区别:
+
+- 命令录制、提交、GPU 执行和完成不是同一步。
+- 资源用途、内存驻留/CPU 访问意图、纹理布局和像素格式是不同概念。
+- 所有权表示释放资源的责任;持有句柄不等于保活资源,提交命令也不转移所有权。
+- GPU 完成不一定表示暂存的回读数据已复制到应用程序的 CPU 目标;保留原文关于等待所属队列的说明。
+- `drawable` 指当前帧的输出纹理,不一律等同于交换链图像;资源视图与 UI 控件也要按上下文区分。
+
+## 构建与交付检查
+
+在已具备项目所需 .NET SDK 和 DocFX 的环境中,从仓库根目录运行命令。首次检出时,`documents/api/` 的生成元数据尚不存在,需要先执行包含 API 提取的完整构建:
+
+```sh
+docfx documents/docfx.json --warningsAsErrors
+```
+
+之后,如果 API 元数据已生成且与当前源码一致,只修改字典时可使用普通构建并预览:
+
+```sh
+docfx build documents/docfx.json --warningsAsErrors
+docfx serve documents/_site --hostname 127.0.0.1 --port 8080
+```
+
+本语言已注册,完整字典经构建成功后即可使用,无需改配置。缺键、多余键、空值、非字符串值或占位符名称不匹配会导致构建失败。构建不会判断译文是否准确或是否仍残留未翻译的英文,必须人工通读。
+
+各语言共用页面路径,通过 `?lang=zh-CN` 选择语言,切换时保留路径和锚点。没有明确指定语言时,依次采用已保存的手动选择、浏览器语言偏好、英文。如果该译本尚未发布,或字典加载/页面资源校验失败时,页面回退到英文,而不是逐句混用语言。语言列表按语言代码排序。
+
+交付前核对键和占位符,并在桌面及 320 像素宽度检查首页、Learn 入口、三角形教程、概念页、示例页和 API 界面。按初学者的顺序通读教程,检查术语、按钮、搜索、代码复制、API 跳转,以及切换语言后的当前章节位置。标识符、声明和代码仍使用共享原文;只翻译字典提供的界面与正文。测试字典和临时检查文件放在仓库外,不提交占位译文。
+
+## 术语
+
+| 英文 | 约定译法或处理 |
+| ---------------------------------- | ------------------------------------------------------------- |
+| rendering hardware interface / RHI | 渲染硬件接口(RHI);之后可直接使用 RHI |
+| graphics context | 图形上下文 |
+| command queue | 命令队列 |
+| command buffer | 命令缓冲区 |
+| command recording | 命令录制 |
+| submit / submission | 提交/提交操作;与 GPU 执行区分 |
+| resource | 资源 |
+| resource usage | 资源用途 |
+| resource ownership / lifetime | 资源所有权/生命周期 |
+| buffer | 缓冲区;标识符 `Buffer` 保持原样 |
+| texture | 纹理 |
+| heap | 堆 |
+| view | 视图;区分资源视图与 UI 控件 |
+| render pass | 渲染通道 |
+| render target | 渲染目标 |
+| color attachment | 颜色附件 |
+| depth/stencil | 深度/模板 |
+| pipeline | 管线;按上下文使用图形管线或计算管线 |
+| shader / entry point | 着色器/入口点 |
+| vertex / fragment | 顶点/片元 |
+| rasterization | 光栅化 |
+| viewport / scissor | 视口/裁剪区域 |
+| swap chain / presentation | 交换链/呈现 |
+| surface(呈现语境) | 原生呈现目标;`Surface` 标识符不译 |
+| drawable | 当前帧的输出纹理;`Drawable` 标识符不译,不一律称为交换链图像 |
+| framebuffer | 帧缓冲区;尺寸以原文规定的像素单位表述 |
+| barrier / timeline | 屏障/时间线 |
+| bindless | 无绑定;保留 API 标识符 |
+| upload | 上传 |
+| download / readback | GPU 数据语境使用“回读”;下载文件时使用“下载” |
+| undefined | 未定义;不能改写为某个具体默认结果 |
diff --git a/documents/locales/zh-TW/strings.yml b/documents/locales/zh-TW/strings.yml
new file mode 100644
index 00000000..68619138
--- /dev/null
+++ b/documents/locales/zh-TW/strings.yml
@@ -0,0 +1,593 @@
+### YamlMime:Resources
+concepts.execution.context.description: '{graphicsContext} 透過特定後端連接應用程式與 GPU 裝置,並提供圖形、計算與傳輸佇列。一起使用的資源必須屬於同一上下文;緩衝區、紋理、著色器、管線及其控制代碼不能跨上下文繫結。'
+concepts.execution.context.details: '{capabilities} 提供裝置名稱與支援的功能。建立光線追蹤或網格著色所需的資源前,分別透過 {rayTracingSupported} 與 {meshShadingSupported} 確認支援情況。{link}與{detail}範例展示了初始化時的檢查方式。'
+concepts.execution.context.details.detail: '網格著色'
+concepts.execution.context.details.link: '光線追蹤'
+concepts.execution.context.title: '圖形上下文'
+concepts.execution.description: 'GPU 工作從 CPU 端開始準備:應用程式建立資源,記錄使用這些資源的命令,再提交到佇列。提交後,CPU 可以繼續處理其他工作,同時由 GPU 執行命令。相關資源必須保留到 GPU 執行結束。'
+concepts.execution.ownership.description: '所有權決定由誰釋放物件,GPU 完成狀態決定何時可以釋放。即使仍保留物件的 C# 參考,呼叫 {dispose} 也會釋放其原生資源。提交命令不會將資源所有權轉交給佇列。'
+concepts.execution.ownership.details: '結束程式時,先停止提交新工作,並等待所有相關佇列完成最後一次資源存取。接著釋放應用程式資源,最後釋放上下文。上下文會釋放自身的佇列與內部輔助物件,因此這一步必須在應用程式工作全部完成後進行。'
+concepts.execution.ownership.guidance: '渲染期間,若準備覆寫資料或替換資源,就應追蹤仍在使用它們的工作。等待這些存取完成後,再修改資料或釋放儲存空間。檢視與底層資源如何遵循這個生命週期規則,見{link}。'
+concepts.execution.ownership.guidance.link: '資源管理'
+concepts.execution.ownership.table.applicationResources.object: '應用程式建立的資源'
+concepts.execution.ownership.table.applicationResources.ownership: '最後一次 GPU 使用完成後,由應用程式釋放。'
+concepts.execution.ownership.table.commandBuffers.object: '借用的命令緩衝區'
+concepts.execution.ownership.table.commandBuffers.ownership: '由佇列管理和回收。'
+concepts.execution.ownership.table.headings.object: '物件'
+concepts.execution.ownership.table.headings.ownership: '釋放責任'
+concepts.execution.ownership.table.queues.object: '圖形上下文佇列與其時間線'
+concepts.execution.ownership.table.queues.ownership: '由上下文和佇列管理。'
+concepts.execution.ownership.table.swapChainDrawable.ownership: '由交換鏈管理。'
+concepts.execution.ownership.title: '所有權與生命週期'
+concepts.execution.pipelines.description: '圖形管線規定繪製所用的著色器、頂點輸入佈局、圖元組裝方式、附件格式和渲染狀態。繫結頂點、索引或常數資料之前,須先選擇管線;管線宣告的附件格式與取樣數也必須與渲染通道一致。'
+concepts.execution.pipelines.details: '計算管線選擇分派所執行的著色器進入點。在圖形渲染通道之外,先繫結管線及其常數,再執行分派。'
+concepts.execution.pipelines.guidance: '{link}給出了完整的圖形命令順序。{detail}則先執行計算分派,再透過圖形通道顯示結果。'
+concepts.execution.pipelines.guidance.detail: '計算範例'
+concepts.execution.pipelines.guidance.link: '三角形的渲染回呼'
+concepts.execution.pipelines.title: '管線狀態'
+concepts.execution.queues.description: '一個影格同時包含繪製、計算與複製時,通常從 {graphicsQueue} 開始。將彼此相依的操作放在同一佇列,能簡化順序管理。'
+concepts.execution.queues.details: '不同佇列可分別接收獨立的工作負載,但未必對應獨立的硬體引擎。若工作能從重疊執行中受益,再考慮增加佇列。當一個佇列使用另一個佇列產生的資料時,需要在兩次提交之間建立明確的相依關係。'
+concepts.execution.queues.guidance: '即使位於同一佇列,彼此相依的操作也可能需要屏障或紋理轉換,讓後續存取讀到先前寫入的結果。這種記憶體相依關係與提交順序不同,具體指定方式見{link}。'
+concepts.execution.queues.guidance.link: '同步'
+concepts.execution.queues.table.computeQueue.workToRecord: '計算分派與相容的傳輸工作,不包含圖形渲染通道。'
+concepts.execution.queues.table.graphicsQueue.workToRecord: '渲染通道、繪製、計算與傳輸操作。'
+concepts.execution.queues.table.headings.queue: '佇列'
+concepts.execution.queues.table.headings.workToRecord: '支援的工作'
+concepts.execution.queues.table.transferQueue.workToRecord: '上傳、回讀與複製工作,不包含繪圖或計算分派。'
+concepts.execution.queues.title: '佇列選擇'
+concepts.execution.recording.context: '佇列會回收已完成的命令緩衝區以供重複使用。每次記錄時,透過 {queueCommandBuffer} 取得緩衝區,同一時間只由一個執行緒記錄,而且只提交一次。提交後交由佇列管理,不再修改、重複提交或自行釋放。'
+concepts.execution.recording.description: '{queueCommandBuffer} 傳回的 {commandBuffer} 已開始記錄。完成操作及其相依關係的記錄後,呼叫一次 {submit},結束記錄並提交命令。傳回的 {timelineValue} 識別這次提交在佇列上的完成點。'
+concepts.execution.recording.details: '三角形教學將一個影格記錄到 {commandBuffer} 後,按如下方式提交並等待完成:'
+concepts.execution.recording.guidance: '記錄會建立命令序列,{submit} 則安排該序列執行。接著呼叫 {wait},CPU 執行緒便會等待這次提交完成。教學因此採用逐影格執行的流程:目前影格完成後,才開始下一個影格。'
+concepts.execution.recording.title: '記錄與提交'
+concepts.execution.title: '執行模型'
+concepts.platforms.description: '呈現流程可以由應用程式管理,也可以交給 UI 渲染控制項。這決定了誰來取得目前影格的輸出紋理、記錄與提交命令,以及顯示渲染結果。'
+concepts.platforms.frame.context: '在通道內繫結管線並記錄繪製命令,處理程式返回前須結束通道。控制項負責提交命令與呈現結果;處理程式不可自行提交命令緩衝區,也不可釋放傳入的命令緩衝區或輸出紋理。若改變了輸出紋理的佈局,返回前須恢復為 {colorAttachment},讓控制項能完成最後的轉換。'
+concepts.platforms.frame.description: '各控制項實作 {iZenithView} 介面。應用程式設定 {graphicsContext},在 {updateRequested} 中更新場景,並在 {renderRequested} 中透過傳入的 {renderEventArgs} 記錄命令。'
+concepts.platforms.frame.details: '每個影格中,控制項會取得輸出紋理與命令緩衝區,將輸出紋理轉換為 {colorAttachment},再呼叫更新和渲染處理程式。之後,控制項提交已記錄的工作,並透過對應框架的呈現機制顯示影像。'
+concepts.platforms.frame.guidance: '先在檔案中加入 {usingZenithNET}。假定名為 {view} 的 {iZenithView} 執行個體已初始化,且已設定圖形上下文,可註冊以下處理程式來清除其輸出紋理:'
+concepts.platforms.frame.notes: '初始轉換使用 {undefined},允許丟棄先前影像的內容。因此,每個影格都應重新繪製或清除整張影像。需要累積或快取的渲染結果,可儲存在應用程式擁有的紋理中,再將該紋理繪製到控制項的輸出紋理上。'
+concepts.platforms.frame.reference: '{zenithViewHelperDrawableFormat} 提供這些整合使用的預設色彩格式。'
+concepts.platforms.frame.title: '透過 UI 控制項渲染'
+concepts.platforms.integration.context: 'WinForms、WPF 與 Windows 版 WinUI 整合在 Windows 上執行。使用 MAUI 時,安裝目標平台的工作負載,並透過 {useZenithView} 註冊控制項處理程式,具體見 {extensionsCs}。'
+concepts.platforms.integration.description: '控制項共用事件介面,但呈現機制因框架而異:'
+concepts.platforms.integration.details: '各實作將 GPU 影像送到 UI 的方式不同。點陣圖回讀先將像素複製到 CPU 記憶體,再顯示影像;共用紋理互通能省去這次 CPU 複製,但仍可能需要 GPU 複製與同步。選擇整合方式時,除了共用的 {renderRequested} 事件介面,也應考慮其呈現方式。'
+concepts.platforms.integration.guidance: '透過 NuGet 加入所用 UI 框架的整合套件。Avalonia 對應的套件為 {avaloniaPackage}。'
+concepts.platforms.integration.table.avalonia.integration: 'Avalonia'
+concepts.platforms.integration.table.avalonia.presentation: '渲染到紋理後,將像素回讀到 {writeableBitmap},實作見 {zenithViewCs} 和 {surfaceCs}。'
+concepts.platforms.integration.table.headings.integration: '整合'
+concepts.platforms.integration.table.headings.presentation: '呈現機制'
+concepts.platforms.integration.table.maui.integration: '.NET MAUI'
+concepts.platforms.integration.table.maui.presentation: 'Android、iOS 和 Mac Catalyst 使用原生呈現目標;Windows 使用共用紋理合成,具體見{link}。'
+concepts.platforms.integration.table.maui.presentation.link: '平台實作'
+concepts.platforms.integration.table.uno.integration: 'Uno 路徑'
+concepts.platforms.integration.table.uno.presentation: '將像素回讀到 {writeableBitmap},實作見 {zenithViewUnoCs}。'
+concepts.platforms.integration.table.winForms.integration: 'WinForms'
+concepts.platforms.integration.table.winForms.presentation: '為控制項的 {hWND} 建立 Zenith.NET 交換鏈,實作見 {zenithViewCs}。'
+concepts.platforms.integration.table.winUI.integration: 'Windows 上的 WinUI'
+concepts.platforms.integration.table.winUI.presentation: '渲染到匯入的共用 Direct3D 11 紋理,再複製到與 {swapChainPanel} 連線的合成交換鏈,具體見 {zenithViewWinUICs}。'
+concepts.platforms.integration.table.wpf.integration: 'WPF'
+concepts.platforms.integration.table.wpf.presentation: '匯入共用 Direct3D 11 紋理,再經 Direct3D 互通複製到由 {d3DImage} 顯示的表面,具體見 {surfaceCs}。'
+concepts.platforms.integration.title: '框架與呈現路徑'
+concepts.platforms.lifecycle.context: '建立配套附件時,使用渲染事件傳入的輸出紋理的 {descWidth}、{descHeight}、{descFormat} 與 {descSampleCount}。該紋理提供渲染目標的實際尺寸,不應依控制項的版面配置尺寸或顯示縮放比例推算。'
+concepts.platforms.lifecycle.description: '交換鏈使用以像素計量的畫面緩衝區寬度與高度。啟用顯示縮放時,這些值可能不同於視窗的邏輯尺寸。任一尺寸為零時,暫停渲染與交換鏈尺寸調整。每個影格重新取得 {swapChainDrawable}:這張紋理由交換鏈管理,可能在呈現或調整大小後被替換。'
+concepts.platforms.lifecycle.details: '畫面緩衝區尺寸改變時,先等待 GPU 完成對舊影像的存取,再呼叫 {swapChainResize}。替換原生呈現目標時,也應先等待這些存取結束,再將新的目標描述與控制代碼傳給 {swapChainRefresh}。結束程式時,先釋放交換鏈,再銷毀其原生呈現目標。'
+concepts.platforms.lifecycle.guidance: '應用程式的深度紋理或離屏紋理若隨視窗尺寸改變,就需要在調整大小時重建,並更新包含其控制代碼的常數。只要附件格式與取樣數仍然相符,管線就可以繼續使用。{link}展示了如何調整紋理尺寸並保留管線。'
+concepts.platforms.lifecycle.guidance.link: '旋轉立方體渲染器'
+concepts.platforms.lifecycle.title: '交換鏈尺寸與生命週期'
+concepts.platforms.ownership.description: '應用程式擁有所設定的上下文及其渲染資源,控制項擁有呈現資源。檢視的 {graphicsContext} 改變後,排程程式會為新上下文重建控制項資源。應用程式也須為該上下文重建自己的管線、緩衝區與紋理。'
+concepts.platforms.ownership.details: '替換渲染資源前,先停止檢視的影格活動,並等待使用這些資源的 GPU 工作完成,再釋放舊資源或上下文。移除檢視後,所設定的上下文仍由應用程式負責釋放。'
+concepts.platforms.ownership.guidance: '共用的 {frameSchedulerCs} 透過檢視的 UI 排程器執行影格處理程式。處理程式應專注於場景資料更新與命令記錄,其中耗時的 CPU 工作也會延遲介面回應。'
+concepts.platforms.ownership.title: '應用程式與控制項的責任'
+concepts.platforms.surface.description: '{link}使用 Silk.NET 建立視窗並處理事件。Zenith.NET 透過 {surface} 描述取得原生視窗控制代碼與尺寸,再據此建立 {swapChain}。作業系統視窗仍由應用程式負責管理。'
+concepts.platforms.surface.description.link: '三角形教學'
+concepts.platforms.surface.details: '原生控制代碼必須與所選後端匹配:'
+concepts.platforms.surface.guidance: '{appCs} 中的範例宿主支援 Windows、macOS 與 X11。Linux 視窗提供 Xlib 控制代碼,因此在 Wayland 工作階段中需要 XWayland。原生 Wayland 應用程式則透過 {surfaceWayland} 提供顯示與表面的控制代碼。'
+concepts.platforms.surface.table.android.backend: 'Android 上的 Vulkan。'
+concepts.platforms.surface.table.apple.backend: 'Apple 平台上的 Metal。'
+concepts.platforms.surface.table.headings.backend: '後端與平台'
+concepts.platforms.surface.table.headings.factory: '呈現目標工廠方法'
+concepts.platforms.surface.table.headings.handles: '宿主程式提供的控制代碼'
+concepts.platforms.surface.table.wayland.backend: '具有原生 Wayland 宿主程式的 Vulkan。'
+concepts.platforms.surface.table.wayland.handles: 'Wayland 顯示與表面'
+concepts.platforms.surface.table.win32.backend: 'Windows 上的 DirectX 12 或 Vulkan。'
+concepts.platforms.surface.table.xlib.backend: 'Linux 上帶有 X11 或 XWayland 的 Vulkan。'
+concepts.platforms.surface.table.xlib.handles: 'X11 顯示與視窗'
+concepts.platforms.surface.title: '原生呈現目標'
+concepts.platforms.title: '平台整合'
+concepts.resources.allocation.context: '放置在堆積中的資源,在整個生命週期內都依賴堆積的儲存空間。最後一次 GPU 使用完成後,應先釋放資源,再釋放堆積。'
+concepts.resources.allocation.description: '應用程式需要自行管理記憶體配置時,可以使用 {heap}。堆積為資源提供儲存空間,應用程式選擇明確的位元組位移來放置資源,並記錄各區域的使用情況。'
+concepts.resources.allocation.details: '對每個資源,使用建立時所需的緩衝區或紋理描述呼叫 {contextGetSizeAndAlignment}。依傳回的大小預留空間,起始位移須為傳回對齊值的整數倍,而且整個區域須位於堆積內,不與其他仍有效的配置重疊。紋理可能包含後端特有的填充,因此不能只依像素數量與每像素位元組數計算配置大小。'
+concepts.resources.allocation.guidance: '放置在堆積中的緩衝區,必須使用與堆積相同的駐留設定。紋理儲存使用 GPU 專用堆積。'
+concepts.resources.allocation.title: '堆積配置'
+concepts.resources.bufferUsages.table.constant.role: '透過常數緩衝區繫結讀取的著色器參數。'
+concepts.resources.bufferUsages.table.headings.role: '作用'
+concepts.resources.bufferUsages.table.headings.usage: '用途'
+concepts.resources.bufferUsages.table.index.role: '索引繪圖所使用的索引。'
+concepts.resources.bufferUsages.table.indirect.role: '間接繪製或分派命令讀取的引數。'
+concepts.resources.bufferUsages.table.none.role: '未宣告任何緩衝區用途旗標。'
+concepts.resources.bufferUsages.table.storageReadOnly.role: '透過唯讀著色器繫結存取的緩衝區資料。'
+concepts.resources.bufferUsages.table.storageReadWrite.role: '透過讀寫著色器繫結存取的緩衝區資料。'
+concepts.resources.bufferUsages.table.transferDst.role: 'GPU 複製作業的目的地儲存空間。'
+concepts.resources.bufferUsages.table.transferSrc.role: 'GPU 複製作業的來源資料。'
+concepts.resources.bufferUsages.table.vertex.role: '供繪製命令讀取的頂點屬性。'
+concepts.resources.bufferUsages.title: '緩衝區用途'
+concepts.resources.description: '資源描述規定應用程式如何使用其儲存空間。例如,一張紋理若要作為渲染目標、供著色器取樣,並將內容複製回 CPU,就需要宣告這三種操作的用途。記憶體放置決定 CPU 的存取方式,同步與生命週期管理則決定何時可以存取資料或釋放儲存空間。'
+concepts.resources.memory.context: '對於 CPU 可存取的緩衝區,{map} 提供 CPU 讀寫資料所用的位址。映射與 {unmap} 都不會等待 GPU 工作。CPU 讀取 GPU 寫入的結果前,應用程式須先等待寫入完成;修改同一塊記憶體前,也須等待 GPU 對這些位元組的存取結束。'
+concepts.resources.memory.description: '{bufferDesc} 的 {residency} 欄位指定 CPU 對所配置記憶體的預期存取方式:'
+concepts.resources.memory.details: '駐留設定表達 CPU 的存取需求,即使 CPU 與 GPU 共用實體記憶體也同樣適用。三角形教學使用 {cpuWriteOnly} 直接初始化小型頂點緩衝區。較大的靜態幾何資料,則可考慮上傳到 GPU 專用儲存空間。'
+concepts.resources.memory.guidance: '透過上下文直接建立的紋理使用 GPU 儲存空間,因此 {textureDesc} 沒有駐留欄位。應用程式透過上傳與回讀,在紋理與 CPU 記憶體之間交換資料。'
+concepts.resources.memory.table.cpuReadOnly.cPUAccess: '唯讀'
+concepts.resources.memory.table.cpuReadOnly.typicalRole: 'GPU 產生的回讀資料'
+concepts.resources.memory.table.cpuWriteOnly.cPUAccess: '唯寫'
+concepts.resources.memory.table.cpuWriteOnly.typicalRole: 'CPU 提供或更新的資料'
+concepts.resources.memory.table.gpuOnly.cPUAccess: '無'
+concepts.resources.memory.table.gpuOnly.typicalRole: '靜態幾何體與 GPU 工作資料'
+concepts.resources.memory.table.headings.cPUAccess: '預期 CPU 存取'
+concepts.resources.memory.table.headings.residency: '記憶體駐留'
+concepts.resources.memory.table.headings.typicalRole: '典型用途'
+concepts.resources.memory.title: 'CPU 存取與記憶體放置'
+concepts.resources.textureUsages.description: '傳輸旗標宣告資源在 GPU 複製中作為來源或目標。CPU 能否直接存取這塊儲存空間,則由記憶體放置方式決定。'
+concepts.resources.textureUsages.details: '用途旗標宣告紋理可以發揮的作用。在這些作用之間切換時,需要對應的{link};所選裝置也必須支援以該紋理格式執行所宣告的操作。'
+concepts.resources.textureUsages.details.link: '佈局與同步'
+concepts.resources.textureUsages.table.colorAttachment.role: '渲染通道中的色彩附件。'
+concepts.resources.textureUsages.table.depthStencilAttachment.role: '渲染通道中的深度/樣板附件。'
+concepts.resources.textureUsages.table.headings.role: '作用'
+concepts.resources.textureUsages.table.headings.usage: '用途'
+concepts.resources.textureUsages.table.none.role: '未宣告任何紋理用途旗標。'
+concepts.resources.textureUsages.table.sampled.role: '透過取樣紋理繫結讀取的紋理資料。'
+concepts.resources.textureUsages.table.storage.role: '透過儲存紋理繫結供著色器讀取或寫入的紋理資料。'
+concepts.resources.textureUsages.table.transferDst.role: 'GPU 複製作業的目的地影像儲存空間。'
+concepts.resources.textureUsages.table.transferSrc.role: 'GPU 複製作業的來源影像資料。'
+concepts.resources.textureUsages.title: '紋理用途'
+concepts.resources.title: '資源管理'
+concepts.resources.transfers.context: '回讀先將 GPU 資料複製到內部回讀空間。程式庫回收已完成的命令緩衝區時,再將資料複製到指定的 CPU 目標。對這次回讀提交的完成值呼叫 {wait},可以完成這兩個步驟。在呼叫返回前,目標記憶體須保持已配置狀態,受控記憶體須保持固定。'
+concepts.resources.transfers.description: '上傳將 CPU 資料複製到 GPU 資源,回讀則將資源資料複製到 CPU 記憶體。緩衝區與紋理上的這些方法會在傳輸完成後返回:'
+concepts.resources.transfers.details: '呼叫這些方法前,先處理與其他佇列存取之間的衝突。方法內部的等待只涵蓋自身傳輸,不會建立與其他佇列的相依關係。直接透過映射記憶體複製時,則完全由呼叫端負責等待會產生衝突的 GPU 存取。'
+concepts.resources.transfers.guidance: '若要將傳輸與其他 GPU 工作一起提交,可透過 {commandBufferUpload} 或 {commandBufferDownload} 記錄。上傳來源與回讀目標的記憶體生命週期要求不同:'
+concepts.resources.transfers.item: '上傳呼叫期間,程式庫會將來源資料複製到內部的臨時上傳空間,也就是暫存記憶體。GPU 隨後從這塊空間讀取,因此應用程式的來源指標只需保持有效到呼叫返回。'
+concepts.resources.transfers.notes: '紋理傳輸中的列跨距是 CPU 資料相鄰兩列之間的位元組距離;切片跨距則是相鄰深度切片之間的位元組距離。傳輸操作依這些值,在 CPU 資料與紋理的 GPU 儲存空間之間複製內容。GPU 完成與資料送達回讀目標為何是兩個步驟,見{link}。'
+concepts.resources.transfers.notes.link: '同步'
+concepts.resources.transfers.table.bufferDownload.transfer: '對於 {cpuReadOnly},直接從映射記憶體複製;否則向傳輸佇列提交回讀,並等待完成。'
+concepts.resources.transfers.table.bufferUpload.transfer: '對於 {cpuWriteOnly},直接複製到映射記憶體;否則向傳輸佇列提交上傳,並等待完成。'
+concepts.resources.transfers.table.headings.method: '方法'
+concepts.resources.transfers.table.headings.transfer: '傳輸行為'
+concepts.resources.transfers.table.textureTransfers.method: '{textureUpload} / {textureDownload}'
+concepts.resources.transfers.table.textureTransfers.transfer: '將複製及其紋理轉換提交到圖形佇列,並等待完成。操作結束後,所選子資源處於指定的最終佈局。'
+concepts.resources.transfers.title: '立即傳輸與命令記錄'
+concepts.resources.usages.description: '{bufferUsages} 與 {textureUsages} 是宣告資源用途的旗標列舉,分別設定於 {bufferDescUsages} 與 {textureDescUsages}。資源需要多種用途時,可透過 {value} 組合對應的成員。'
+concepts.resources.usages.title: '資源用途'
+concepts.resources.views.description: '{bufferView} 描述緩衝區的一塊區域及其資料解讀方式。{textureView} 選擇子資源範圍,以及相容的紋理型別與格式。兩者都參考既有儲存空間,並遵循建立時宣告的用途。檢視範圍用來描述存取範圍,各後端並不因此提供一致的著色器邊界檢查。'
+concepts.resources.views.details: '資源本身的控制代碼提供預設檢視。需要其他區域或相容的解讀方式時,再建立獨立檢視。檢視及其底層資源都必須保留到 GPU 使用結束;僅持有資源控制代碼,無法維持任一物件的存活。替換其中任一物件時,還須更新包含舊{link}的常數。'
+concepts.resources.views.details.link: '資源控制代碼'
+concepts.resources.views.title: '檢視與底層資源'
+concepts.shaders.binding.context: '計算範例將 {inputTextureSampledHandle} 寫入 {input},將 {outputTextureStorageHandle} 寫入 {output}。在 Slang 中,{descriptorHandle} 的型別引數規定了著色器存取資源的方式:'
+concepts.shaders.binding.description: '{setConstantBuffer} 選擇既有緩衝區與位元組位移,目前管線從該位置讀取常數。著色器使用這些參數前,先將資料上傳到對應的儲存空間。如果先前提交的 GPU 工作仍在讀取相同位元組,須等待該工作完成後再更新。'
+concepts.shaders.binding.details: '準備好命令緩衝區、計算管線、已填入資料的常數緩衝區及執行緒群組數量後,依以下順序記錄分派。常數緩衝區會繫結到目前管線,因此必須先選擇管線:'
+concepts.shaders.binding.guidance: '這裡,{commandBuffer} 使用 {computePipeline} 及 {constantBuffer} 中的參數記錄分派。緩衝區需要宣告 {bufferUsagesConstant} 用途,位移 0 選擇其起始位置。若多筆常數記錄共用緩衝區,每筆記錄的起始位移還須符合後端繫結的對齊要求,這與記錄內部的欄位佈局是兩項不同要求。'
+concepts.shaders.binding.notes: '{resourceHandle} 包含兩個 32 位元欄位,含義由後端解釋,其值只在建立資源的上下文中有效。'
+concepts.shaders.binding.reference: '保留資源及用於取得控制代碼的檢視,直到著色器完成最後一次存取。替換紋理或檢視後,應更新仍含舊控制代碼的常數。相關所有權規則見{link}。'
+concepts.shaders.binding.reference.link: '資源管理'
+concepts.shaders.binding.table.bufferStorageReadOnlyHandle.access: '讀取著色器型別 {t} 的結構化元素'
+concepts.shaders.binding.table.bufferStorageReadWriteHandle.access: '讀寫著色器型別 {t} 的結構化元素'
+concepts.shaders.binding.table.headings.access: '預期存取'
+concepts.shaders.binding.table.headings.cHandle: 'C# 控制代碼'
+concepts.shaders.binding.table.headings.slangFieldType: 'Slang 欄位型別'
+concepts.shaders.binding.table.samplerHandle.access: '紋理取樣狀態'
+concepts.shaders.binding.table.textureSampledHandle.access: '紋理讀取'
+concepts.shaders.binding.table.textureStorageHandle.access: '儲存紋理的讀寫'
+concepts.shaders.binding.title: '常數緩衝區與資源控制代碼'
+concepts.shaders.compilation.description: '{zenithCompiler} 將指定的 Slang 進入點編譯為 {shaderDesc}。上下文的 {graphicsApi} 決定目標格式:DirectX 12 使用 DXIL,Metal 使用 Metal 程式庫,Vulkan 使用 SPIR-V。'
+concepts.shaders.compilation.details: '建立名為 {context} 的圖形上下文後,可以編譯已複製到執行檔所在目錄的三角形著色器:'
+concepts.shaders.compilation.guidance: '進入點名稱 {vSMain} 必須與著色器檔案中的函式宣告一致。在初始化階段編譯著色器並建立管線,渲染期間重複使用管線。管線建立完成後,即可釋放著色器物件,具體見{link}。'
+concepts.shaders.compilation.guidance.link: '第一個三角形'
+concepts.shaders.compilation.title: '著色器編譯'
+concepts.shaders.description: 'C# 與 Slang 宣告必須約定一致的資料位置與存取方式。頂點輸入佈局描述頂點屬性,著色器緩衝區宣告描述參數與陣列,資源控制代碼則識別著色器使用的資源。'
+concepts.shaders.dispatch.description: '{dispatch} 指定要執行多少個執行緒群組。計算範例宣告 {numthreads},因此每組包含 16 × 16 個執行緒。將影像寬度與高度分別除以 16,並向上取整,即可覆蓋整張影像:17 × 19 的影像需要 2 × 2 組。著色器檢查每個執行緒的座標,並跳過位於影像範圍以外的執行緒。'
+concepts.shaders.dispatch.details: '{shaderDescThreadGroupSize} 提供宣告的群組大小,可用來計算傳給 {dispatch} 的群組數量。完整計算過程見 {computeShaderRendererCs}。分派完成後,存取輸出影像時仍須符合{link}中說明的要求。'
+concepts.shaders.dispatch.details.link: '同步'
+concepts.shaders.dispatch.title: '執行緒群組'
+concepts.shaders.layout.context: '{link}透過影像尺寸與兩個紋理控制代碼展示這種排列。各欄位的起始位置由明確位移指定。以下宣告使用 {systemRuntimeInteropServices} 與 {zenithNET} 命名空間:'
+concepts.shaders.layout.context.link: '計算範例的 C# 常數'
+concepts.shaders.layout.description: '對於頂點輸入,{inputLayout} 描述各屬性的格式、位移與語義,以及相鄰頂點之間的跨距。三角形先儲存 {float3} 位置,再儲存 {float4} 色彩:色彩的位元組位移為 12,每個頂點占 28 位元組。{inputLayoutAdd} 根據元素格式計算這個緊密排列的佈局。若 C# 結構包含填充,就需要在位移與跨距中計入這些空間。'
+concepts.shaders.layout.details: '常數緩衝區儲存著色器參數,結構化緩衝區儲存指定型別的元素陣列。它們的佈局由 Slang 宣告決定,不使用 {inputLayout}。C# 欄位位移、元素大小與填充,必須符合編譯後的著色器佈局。即使某個 C# 型別是非受控型別,可以直接依位元組複製,其記憶體排列也未必符合著色器要求;例如,頂點中緊密排列的 {float3},在著色器緩衝區中可能需要不同的對齊方式。'
+concepts.shaders.layout.guidance: '著色器參數除了普通數值,也可以包含資源控制代碼。控制代碼識別的是既有儲存空間,因此將它複製到常數緩衝區,不會複製資源內容。'
+concepts.shaders.layout.notes: '{computeShaderSlang} 中對應的宣告為:'
+concepts.shaders.layout.reference: '兩個尺寸值占用前 8 位元組,後面是兩個各占 8 位元組的控制代碼。C# 範例為整筆記錄預留 256 位元組。其他記錄的大小,應依各自欄位以及所需的佈局與對齊方式決定。'
+concepts.shaders.layout.title: '資料佈局'
+concepts.shaders.matrices.description: '編譯器採用列優先的矩陣儲存方式,使同一列的元素連續排列。{link}在位移 0、64 和 128 處儲存三個 {matrix4x4} 值,其{detail}按模型、檢視和投影順序執行 {mulVectorMatrix}。'
+concepts.shaders.matrices.description.detail: '著色器'
+concepts.shaders.matrices.description.link: '旋轉立方體渲染器'
+concepts.shaders.matrices.details: '儲存順序決定矩陣元素在記憶體中的排列,乘法順序決定變換。C# 與 Slang 應採用相互配合的約定。'
+concepts.shaders.matrices.title: '矩陣約定'
+concepts.shaders.title: '著色器資料與繫結'
+concepts.synchronization.backends.description: 'DirectX 12 與 Vulkan 將紋理轉換記錄為佈局與存取狀態的變更。在 Zenith.NET 的 Metal 後端中,{transition} 沒有對應的 GPU 操作。Metal 後端將命令組織成渲染與計算編碼器,並在編碼器結束時插入可見性屏障。同一編碼器內彼此相依的操作,例如連續的計算分派,透過明確呼叫 {barrier} 協調。'
+concepts.synchronization.backends.details: '{link}開始圖形渲染通道時,會結束計算編碼器,讓後續圖形工作能讀取計算輸出。連續兩次計算分派仍位於同一編碼器內,即使中間記錄了紋理轉換,也需要屏障來處理相依關係。因此,跨後端使用的命令序列既要考慮紋理佈局,也要處理存取之間的相依關係。'
+concepts.synchronization.backends.details.link: '計算範例'
+concepts.synchronization.backends.title: '後端行為'
+concepts.synchronization.barriers.context: '選擇涵蓋相關存取,且目前佇列支援的階段。{barrierStagesAll} 包含所有支援的階段;若相依關係只涉及其中一部分,可選擇較小的範圍。'
+concepts.synchronization.barriers.description: '假設同一個命令緩衝區內有兩次計算分派,第一次寫入模擬資料,第二次讀取這些資料。在兩次分派之間設定屏障,讓第二次分派讀到已完成的寫入結果。對於儲存緩衝區,這樣即可安排存取順序,不需要進行紋理佈局轉換:'
+concepts.synchronization.barriers.details: '第一個引數指定產生資料的階段,第二個引數指定使用資料的階段。兩者共同描述兩次分派之間所需的執行順序與記憶體可見性。'
+concepts.synchronization.barriers.guidance: '{link}在模擬步驟之間採用這個方式。每道屏障都位於產生資料的分派之後、使用資料的分派之前。'
+concepts.synchronization.barriers.guidance.link: 'FluidTank 模擬'
+concepts.synchronization.barriers.title: '管線屏障'
+concepts.synchronization.dependencies.description: '逐一確認每個資源有哪些存取涉及同一份資料。只要其中包含寫入,就必須維持所需的先後順序。兩次讀取本身沒有衝突,但仍需符合紋理佈局與呈現要求。CPU 更新資料或釋放資源前,也須等待 GPU 完成對該儲存空間的使用。'
+concepts.synchronization.dependencies.details: '應根據操作執行的位置選擇機制:'
+concepts.synchronization.dependencies.guidance: '一項工作可能同時需要多種同步機制。例如,紋理由另一個佇列產生時,除了提交相依關係,還需為下一次使用設定正確的佈局。這兩項操作本身都不需要阻塞 CPU;只有 CPU 要存取或釋放相關資料時,才需要在 CPU 端等待。'
+concepts.synchronization.dependencies.table.cpuAccess.mechanism: '針對相關完成值呼叫 {wait}。'
+concepts.synchronization.dependencies.table.cpuAccess.situation: 'CPU 存取或資源釋放需要等待 GPU 完成'
+concepts.synchronization.dependencies.table.crossQueue.mechanism: '將生產者的 {timelineValue} 傳給消費者的 {submit}。'
+concepts.synchronization.dependencies.table.crossQueue.situation: '一個佇列使用另一個佇列產生的資料'
+concepts.synchronization.dependencies.table.headings.mechanism: '機制'
+concepts.synchronization.dependencies.table.headings.situation: '情況'
+concepts.synchronization.dependencies.table.layoutChange.mechanism: '呼叫 {commandBufferTransition},指定先前佈局與目標佈局。'
+concepts.synchronization.dependencies.table.layoutChange.situation: '紋理子資源變更其使用方式'
+concepts.synchronization.dependencies.table.pipelineStages.mechanism: '呼叫 {commandBufferBarrier},指定產生資料和使用資料的階段。'
+concepts.synchronization.dependencies.table.pipelineStages.situation: '命令串流中相依的 GPU 階段'
+concepts.synchronization.dependencies.title: '存取相依關係'
+concepts.synchronization.description: '同步確定多個操作存取共用資料的順序,並讓後續操作能讀到所需結果。在寫後讀相依關係中,寫入端稱為生產者,讀取端稱為消費者。屏障與紋理轉換協調 GPU 存取;時間線連接不同提交,並讓 CPU 等待 GPU 工作完成。'
+concepts.synchronization.layouts.context: '現有內容可以丟棄時,可將先前佈局設為 {undefined},例如新建影像或即將完整清除的附件。若要保留內容以供載入、取樣或累積,則應使用前次存取時的佈局。'
+concepts.synchronization.layouts.description: '紋理的用途旗標宣告允許的作用。{textureLayout} 描述命令序列中某個時點對子資源的存取方式,子資源由 mip 級別與陣列層識別。存取方式改變時,需要記錄轉換。例如,計算範例在 {storage} 佈局中寫入輸出紋理,再為圖形通道中的取樣做準備:'
+concepts.synchronization.layouts.details: '這裡的 {outputTexture} 已宣告 {sampledStorage} 用途,所選子資源處於 {storage} 佈局。{default} 選擇 mip 級別 0 和陣列層 0,其他級別和層不受這次轉換影響。'
+concepts.synchronization.layouts.guidance: '呼叫 {transition} 時,提供子資源上一次使用時的佈局。應用程式在記錄命令,以及在渲染階段之間傳遞紋理時,負責追蹤這個狀態。'
+concepts.synchronization.layouts.title: '紋理佈局'
+concepts.synchronization.readback.description: '{isCompleted} 回報 GPU 的完成狀態。透過命令記錄的回讀還需要一個步驟:將結果從內部回讀空間複製到應用程式的目標記憶體。'
+concepts.synchronization.readback.details: '{commandBufferDownload} 記錄向內部回讀空間複製資料的 GPU 命令。提交完成後,程式庫在回收命令緩衝區時執行最後的 CPU 複製。對這次回讀提交的完成值呼叫 {wait},會等待 GPU 完成,並處理該佇列已完成的緩衝區,從而完成這兩個步驟。呼叫返回前,目標記憶體須保持已配置狀態,受控記憶體須保持固定。'
+concepts.synchronization.readback.guidance: '只輪詢 {isCompleted} 不會執行最後的 CPU 複製;等待其他佇列,也不會回收回讀佇列中已完成的緩衝區。上傳與回讀何時使用直接記憶體存取或暫存空間,見{link}。'
+concepts.synchronization.readback.guidance.link: '資源管理'
+concepts.synchronization.readback.title: '回讀完成'
+concepts.synchronization.reuse.description: '三角形教學在每個影格完成後才重複使用資料。若要讓影格重疊執行,應為每個尚未完成的影格準備獨立的可變儲存空間,並追蹤何時可以再次使用。修改資料前,須等待 GPU 完成對相關位元組的存取;釋放資源前,則須等待它的所有使用結束。'
+concepts.synchronization.reuse.details: '調整大小與結束程式遵循相同順序:先停止新的資源存取,等待既有存取結束,再替換或釋放資源。呼叫 {swapChainResize} 或釋放資源、上下文前,須先完成上述等待。三角形教學已經逐影格等待,因此可以在回呼之間調整大小。'
+concepts.synchronization.reuse.guidance: '{swapChainPresent} 在呈現後向圖形佇列發出訊號並等待。因此,即使移除明確的提交等待,三角形教學仍會在呈現時等待。獨立提交到其他佇列的工作,需要另外追蹤其完成狀態。'
+concepts.synchronization.reuse.title: '資源重複使用'
+concepts.synchronization.timelines.context: 'CPU 若要在消費者完成後讀取結果、覆寫共用資料或釋放資源,須先等待這次提交完成:'
+concepts.synchronization.timelines.description: '時間線用遞增值識別佇列進度。{submit} 傳回的 {timelineValue} 同時包含時間線和本次提交的值。不同時間線上的 {value} 數值不能直接比較。'
+concepts.synchronization.timelines.details: '設 {producer} 與 {consumer} 是同一上下文中兩個佇列上已記錄的命令緩衝區,後者使用前者寫入的資料。提交時傳入以下相依關係:'
+concepts.synchronization.timelines.guidance: '消費者所在的佇列會先等待傳入的完成值,再執行命令。CPU 不必呼叫 {producedWait},可以繼續處理其他工作。共用資源須保留到 {consumed} 完成,同時仍需記錄消費者存取所要求的紋理轉換。'
+concepts.synchronization.timelines.notes: '{queueTimelineSignal} 傳回的值,同樣可以透過 {wait} 等待,也可以作為另一個佇列的相依條件。{signal} 將完成點排在先前已提交的工作之後,GPU 完成這些工作後就會到達該點。'
+concepts.synchronization.timelines.title: '時間線與佇列相依關係'
+concepts.synchronization.title: '同步'
+home.actions.reference: '瀏覽 API'
+home.actions.tutorial: '從三角形開始'
+home.entries.description: '教學、範例與 API 參考'
+home.entries.label: '文件分類'
+home.entries.reference.action: '探索 API'
+home.entries.reference.category: '參考'
+home.entries.reference.description: '查閱 API 型別、成員與宣告'
+home.entries.reference.title: 'API 參考'
+home.entries.samples.action: '瀏覽範例'
+home.entries.samples.category: '範例'
+home.entries.samples.description: '可直接執行的圖形與計算範例'
+home.entries.samples.title: '範例'
+home.entries.title: '文件'
+home.entries.tutorial.action: '開始教學'
+home.entries.tutorial.category: '教學'
+home.entries.tutorial.description: '從空白專案開始,繪製第一個三角形'
+home.entries.tutorial.title: '第一個三角形'
+home.introduction.accent: '跨平台圖形應用'
+home.introduction.description: 'Zenith.NET 提供統一的圖形與計算 API,支援 DirectX、Metal 與 Vulkan'
+home.introduction.title: '以 .NET 開發{lineBreak}{accent}'
+home.meta.description: '透過 Zenith.NET 開發跨平台圖形與計算應用程式,以共用 API 使用 DirectX、Metal 與 Vulkan'
+home.scene.description: '可互動的旋轉幾何體。拖動或使用方向鍵調整視角。'
+home.scene.label: '幾何體顯示'
+home.scene.surface: '表面'
+home.scene.wireframe: '線框'
+home.title: '適用於 .NET 的現代 RHI'
+learning.concepts.description: '選擇資源用途、描述著色器資料與協調 GPU 執行'
+learning.concepts.execution.description: '命令記錄、提交與資源生命週期'
+learning.concepts.platforms.description: '原生呈現與 UI 渲染控制項'
+learning.concepts.resources.description: '資源用途、記憶體放置與檢視'
+learning.concepts.shaders.description: 'C# 與 Slang 的資料佈局和繫結約定'
+learning.concepts.synchronization.description: '相依關係、可見性與完成狀態'
+learning.concepts.title: '核心概念'
+learning.description: '初次使用 Zenith.NET,可以從三角形教學開始。完成後,再依需要查閱資源管理、著色器資料與同步等概念,或透過範例了解各種圖形技術的實作。'
+learning.entries.samples.action: '瀏覽範例'
+learning.entries.samples.description: '幾何體、計算、光線查詢與網格著色的 C# 和 Slang 實作'
+learning.entries.tutorial.action: '開啟教學'
+learning.entries.tutorial.description: '建立視窗並渲染三角形的主控台應用程式'
+learning.meta.description: 'Zenith.NET 程式設計模型的入門教學與概念說明'
+learning.title: '學習'
+reference.description: '資源、命令與平台整合的參考文件'
+reference.entries.commands.description: '圖形、計算與傳輸命令'
+reference.entries.commands.label: '記錄命令'
+reference.entries.context.description: '為所選後端建立 GPU 資源'
+reference.entries.context.label: '從這裡開始'
+reference.meta.description: 'Zenith.NET 的命名空間、型別與成員'
+reference.navigation.core: '核心 API 參考'
+reference.title: 'API 參考'
+samples.computeShader.description: '範例載入 {assetsTexturesShokoPng},透過一次計算分派將影像轉成灰階,再顯示輸出紋理。轉換只執行一次,後續影格顯示同一結果。渲染器展示了取樣紋理與可寫紋理的繫結、向上取整群組數量以覆蓋整張影像的計算方式,以及著色器中的影像邊界檢查。'
+samples.computeShader.details: '{computeShaderRendererCs} · {computeShaderSlang}'
+samples.computeShader.title: '計算著色器'
+samples.cube.description: '旋轉立方體加入索引繪製、常數緩衝區中的模型、檢視和投影矩陣,以及深度測試。視窗調整大小時,會重建 {d32FloatS8UInt} 深度/樣板紋理。'
+samples.cube.details: '{spinningCubeRendererCs} · {spinningCubeSlang}'
+samples.cube.title: '旋轉立方體'
+samples.description: '每個範例均由 C# 渲染器與 Slang 著色器組成,集中說明一項技術。完整的應用程式設定與渲染順序見{link}。'
+samples.description.link: '第一個三角形'
+samples.execution.description: '執行 {link} 專案,並從主控台選單選擇範例。'
+samples.execution.description.link: 'ZenithTutorials'
+samples.execution.title: '執行範例'
+samples.host.description: '{appCs} 初始化上下文、視窗和交換鏈。每個影格取得命令緩衝區,呼叫所選渲染器,提交命令並呈現結果。{iRendererCs} 規定渲染器的更新、渲染、調整大小和釋放操作。'
+samples.host.details: '計算與光線追蹤渲染器產生離屏紋理。{texturePresenterCs} 與 {presentTextureSlang} 將這些紋理繪製到目前的輸出紋理,接著由共用宿主提交命令並呈現這個影格。'
+samples.host.title: '宿主與呈現'
+samples.indirectDrawing.description: '間接繪製透過索引繪製引數緩衝區,以及儲存各執行個體資料的結構化緩衝區,繪製多個立方體。CPU 提供繪製引數並更新變換。'
+samples.indirectDrawing.details: '{indirectDrawingRendererCs} · {indirectDrawingSlang}'
+samples.indirectDrawing.title: '間接繪製'
+samples.meshShading.description: '任務著色器剔除球體執行個體,將保留的索引傳給負責產生幾何體的網格著色器。建立管線前,渲染器檢查 {contextCapabilitiesMeshShadingSupported}。'
+samples.meshShading.details: '{meshShadingRendererCs} · {meshShadingSlang}'
+samples.meshShading.title: '網格著色'
+samples.rayTracing.description: '此範例為地面與程序化球體建立底層及頂層加速結構,再於計算著色器內執行內嵌光線查詢。初始化前檢查 {contextCapabilitiesRayTracingSupported}。'
+samples.rayTracing.details: '{rayTracingRendererCs} · {rayTracingSlang}'
+samples.rayTracing.title: '光線追蹤'
+samples.title: '範例'
+samples.triangle.description: '三角形範例將頂點緩衝區與對應的輸入佈局關聯,再透過圖形管線繪製三個頂點。'
+samples.triangle.details: '{helloTriangleRendererCs} · {helloTriangleSlang} · {link}'
+samples.triangle.details.link: '完整教學'
+samples.triangle.title: 'Hello Triangle'
+samples.workloads.description: '主存放庫的{link}包含規模較大的應用程式,這些專案直接參考程式庫原始碼。'
+samples.workloads.description.link: 'Experiments 目錄'
+samples.workloads.details: '視窗實驗專案採用相同的 Windows、macOS 和 Linux/X11 呈現路徑。Avalonia、WinForms、WPF、WinUI/Uno 和 MAUI 控制項的影格生命週期及實作程式碼見{link}。'
+samples.workloads.details.link: '平台整合'
+samples.workloads.table.cornellBox.example: '{link}'
+samples.workloads.table.cornellBox.example.link: 'CornellBox'
+samples.workloads.table.cornellBox.whatItDemonstratesAndRequires: '路徑追蹤、降噪、色調對應、影像升頻與 ImGui 控制項。需要光線追蹤支援。'
+samples.workloads.table.fluidTank.example: '{link}'
+samples.workloads.table.fluidTank.example.link: 'FluidTank'
+samples.workloads.table.fluidTank.whatItDemonstratesAndRequires: '計算流體模擬、模擬階段之間的相依關係,以及多通道表面渲染。'
+samples.workloads.table.headings.example: '範例'
+samples.workloads.table.headings.whatItDemonstratesAndRequires: '技術與執行要求'
+samples.workloads.table.inkCanvas.example: '{link}'
+samples.workloads.table.inkCanvas.example.link: 'InkCanvas'
+samples.workloads.table.inkCanvas.whatItDemonstratesAndRequires: '在原生視窗中進行 Skia 繪圖與輸入處理。需要目標平台的後端與 Skia 原生程式庫。'
+samples.workloads.table.platformDetection.example: '{link}'
+samples.workloads.table.platformDetection.example.link: 'PlatformDetection'
+samples.workloads.table.platformDetection.whatItDemonstratesAndRequires: '嘗試為每個後端建立上下文,並回報裝置功能。建立失敗時顯示為不支援,原因可能是裝置、驅動程式或缺少執行時所需的元件。'
+samples.workloads.title: '綜合應用'
+tutorial.context.description: '{graphicsContext} 透過後端連接應用程式與 GPU,並提供建立渲染資源的方法。'
+tutorial.context.details: '在{emphasis}插入上下文初始化程式碼:'
+tutorial.context.details.emphasis: 'using 指示詞之後、{iWindowWindow} 之前'
+tutorial.context.guidance: '各分支為目前平台選擇後端並要求啟用驗證。{validationMessage} 訊息會輸出到主控台。'
+tutorial.context.imports: '在 {programCs} 頂端加入以下 using 指示詞:'
+tutorial.context.title: '圖形上下文'
+tutorial.description: '本教學從空白的 .NET 主控台專案開始,逐步建立視窗,並在其中繪製一個彩色三角形。'
+tutorial.details: '本教學分三個階段確認結果:空白視窗、單色背景,以及彩色三角形。每完成一個階段,先執行程式檢查畫面,再繼續下一步。'
+tutorial.draw.context: '{draw} 的引數如下:'
+tutorial.draw.description: '在 {windowRender} 回呼中,用以下命令替換 {addTheTriangleDrawCommandsHereLater},位置保持在 {beginRenderPass} 與 {endRenderPass} 之間:'
+tutorial.draw.details: '{setPipeline} 選擇著色器和渲染狀態。後端按管線的輸入佈局解釋頂點繫結,因此這一步必須先於頂點繫結。'
+tutorial.draw.guidance: '{setVertexBufferVertexBuffer} 從緩衝區的位元組位移 0 開始,將已上傳的頂點資料繫結到輸入槽 0,對應 {inputLayouts} 中唯一的項目。'
+tutorial.draw.notes: '這些呼叫記錄繪製命令。既有的 {submitWait} 提交命令並等待 GPU 完成,接著由 {present} 要求顯示完成的影像。保留通道開始時的清除操作,讓三角形以外的區域也有確定的背景色。'
+tutorial.draw.table.firstInstance.meaning: '執行個體編號從零開始。'
+tutorial.draw.table.firstVertex.meaning: '從緩衝區中的第一個頂點開始。'
+tutorial.draw.table.headings.argument: '引數'
+tutorial.draw.table.headings.description: '值'
+tutorial.draw.table.headings.meaning: '意義'
+tutorial.draw.table.instanceCount.meaning: '繪製一個三角形執行個體。'
+tutorial.draw.table.vertexCount.meaning: '讀取三個頂點。'
+tutorial.draw.title: '繪製命令'
+tutorial.execution.description: '建置並執行 {firstTriangle} 專案。'
+tutorial.execution.details: '視窗中應出現一個三角形:上方為紅色,右下方為綠色,左下方為藍色,背景為深色。頂點色彩經過內插,在三角形內形成平滑的漸層。'
+tutorial.execution.guidance: '調整視窗大小時,影像的像素尺寸會改變,頂點座標則維持不變。視口將這些座標對應到新的寬度與高度,因此改變視窗的寬高比會拉伸三角形。'
+tutorial.execution.title: '執行應用程式'
+tutorial.frame.commands.context: '{endRenderPass} 之後的第二次轉換讓輸出紋理進入可呈現的佈局。{submit} 結束記錄並提交命令緩衝區,{wait} 阻塞至本次提交完成,隨後 {present} 請求顯示渲染結果。'
+tutorial.frame.commands.description: '渲染通道將寫入同一組附件的繪製操作放在一起。本通道使用交換鏈的輸出紋理接收色彩結果。{colorAttachmentClear} 指定以 RGBA 色彩初始化附件,並在通道結束時保留內容。{beginRenderPass} 依這些設定記錄通道的開始命令。'
+tutorial.frame.commands.details: '集合中有一個色彩附件。{null} 表示此通道不使用深度/樣板附件。'
+tutorial.frame.commands.guidance: '{beginRenderPass} 預設讓視口與裁剪區域覆蓋整個附件。視口將渲染座標映射為像素位置,裁剪區域則限制可以寫入的範圍。'
+tutorial.frame.commands.title: '清除、提交與呈現'
+tutorial.frame.description: '先將視窗填滿單一背景色,這個操作稱為清除。背景正常顯示後,就能確認呈現目標、命令提交與呈現流程已連接完成,接著再加入三角形的頂點與著色器。'
+tutorial.frame.details: 'Silk.NET 在需要繪製一個影格時呼叫渲染回呼。本範例不使用回呼傳入的時間間隔,因此將該引數命名為 {value}。在{emphasis}註冊回呼:'
+tutorial.frame.details.emphasis: '{windowRun} 之前'
+tutorial.frame.target.description: '{graphicsQueueCommandBuffer} 傳回可直接記錄的命令緩衝區。記錄描述 GPU 工作,提交後才開始執行。'
+tutorial.frame.target.details: '紋理佈局描述操作如何存取紋理。第一次 {transition} 讓輸出紋理準備好作為色彩附件。這裡的 {default} 選擇 mip 級別 0 與陣列層 0,也就是輸出紋理的基本影像。由於這個通道會清除整張影像,可以指定 {undefined},允許丟棄先前內容。'
+tutorial.frame.target.guidance: '呈現後可能換用另一張影像,因此每個影格都要重新取得目前的輸出紋理。'
+tutorial.frame.target.title: '準備輸出紋理'
+tutorial.frame.title: '第一個影格'
+tutorial.geometry.data.description: '在{emphasis}新增頂點陣列。後續資源初始化也放在該回呼之前,順序如下。'
+tutorial.geometry.data.description.emphasis: '{windowRender} 之前'
+tutorial.geometry.data.details: '每個條目構造一個 {vertex},內部的兩個 {new} 表示式分別提供 {vector3} 位置和 {vector4} 顏色。三個位置對應上角、右下角和左下角。頂點著色器將 {w} 作為第四個位置座標;w 為 1 時,x、y 也就是正規化裝置座標,-1 與 +1 對應影像邊界,0 對應中心,與像素尺寸無關。'
+tutorial.geometry.data.guidance: '每個色彩包含紅、綠、藍與 alpha 分量,alpha 設為 1。三個頂點的 z 座標均為 0,位於同一平面。'
+tutorial.geometry.data.title: '頂點位置與顏色'
+tutorial.geometry.description: '三角形的每個頂點都有位置與色彩。GPU 依三個位置決定三角形的形狀,再對頂點色彩內插,算出內部各處的色彩。'
+tutorial.geometry.format.description: '在{emphasis}追加頂點宣告:'
+tutorial.geometry.format.description.emphasis: '{programCs} 的結尾處、清理陳述式之後'
+tutorial.geometry.format.details: '{file} 將型別的使用範圍限定在 {programCs},{layoutKindSequential} 則保留欄位的宣告順序。{position} 包含三個 32 位元浮點數,位元組位移為 0;{color} 包含四個,位元組位移為 12。因此,每個頂點占 28 位元組。稍後會透過輸入佈局描述這個排列,讓 GPU 正確讀取各欄位。'
+tutorial.geometry.format.imports: '在 {programCs} 頂端加入以下 using 指示詞,以使用向量型別與結構佈局屬性:'
+tutorial.geometry.format.title: '頂點表示'
+tutorial.geometry.title: '頂點資料'
+tutorial.geometry.upload.context: '{fixed} 可避免記憶體回收行程在位址使用期間移動陣列。對於這個 CPU 可寫緩衝區,{upload} 會映射記憶體、複製資料,再解除映射後返回,因此只需在呼叫期間固定陣列。頂點資料在初始化時上傳一次即可;緩衝區則必須保留到渲染結束。'
+tutorial.geometry.upload.description: '將頂點存入 {buffer},供 GPU 在繪製時讀取。緊接在頂點陣列之後,加入配置與上傳程式碼:'
+tutorial.geometry.upload.details: '配置大小為 {sizeofVertexVerticesLength},即三個 28 位元組頂點,共 84 位元組。{bufferUsagesVertex} 宣告頂點輸入用途,{cpuWriteOnly} 允許 CPU 直接初始化。描述中的跨距記錄元素大小,隨後定義的輸入佈局指定 GPU 如何讀取各個頂點屬性。'
+tutorial.geometry.upload.guidance: '配置空間不會初始化緩衝區內容。{upload} 使用資料描述中的來源指標與位元組數,將陣列複製到目標位元組位移 0。'
+tutorial.geometry.upload.imports: '在 {programCs} 頂端的 using 指示詞中加入 {bufferAlias},以區分 Zenith.NET 的緩衝區型別與 {systemBuffer}。'
+tutorial.geometry.upload.notes: '在 {programCs} 結尾的清理程式碼中,緊接在 {swapChainDispose} 之前加入 {vertexBufferDispose}。較大型靜態幾何體所需的 GPU 專用儲存與上傳方式,見{link}。'
+tutorial.geometry.upload.notes.link: '資源管理'
+tutorial.geometry.upload.title: '緩衝區配置與上傳'
+tutorial.input.description: 'GPU 需要知道位置、色彩等頂點屬性儲存在哪裡。輸入佈局指定各屬性的格式與位元組位移,並以跨距表示兩個相鄰頂點相隔多少位元組。在建立著色器之後加入以下程式碼:'
+tutorial.input.details: '{inputLayoutAdd} 將屬性依序排列,並將每個屬性的大小加到跨距中。位移是根據這裡提供的格式計算,不是透過檢查 C# 結構取得。這兩個欄位的佈局如下:'
+tutorial.input.guidance: '{semanticIndex} 預設為 0,對應著色器中的 {pOSITION0} 與 {cOLOR0}。位置與色彩存放在同一個頂點緩衝區,因此管線只需在槽 0 使用一個輸入佈局。若 C# 結構的欄位順序或填充改變,也要一併調整此佈局。'
+tutorial.input.table.headings.byteOffset: '位元組位移'
+tutorial.input.table.headings.cField: 'C# 欄位'
+tutorial.input.table.headings.format: '格式'
+tutorial.input.table.headings.shaderInput: '著色器輸入'
+tutorial.input.table.nextVertex.cField: '下一個頂點'
+tutorial.input.table.nextVertex.format: '總跨距:28 位元組'
+tutorial.input.table.nextVertex.shaderInput: '下一筆輸入記錄'
+tutorial.input.table.vertexColor.format: '{float4}(16 位元組)'
+tutorial.input.table.vertexPosition.format: '{float3}(12 位元組)'
+tutorial.input.title: '頂點輸入佈局'
+tutorial.lifecycle.description: '每個影格依次執行以下操作:'
+tutorial.lifecycle.details: '關閉視窗後,{run} 返回,程式繼續執行清理程式碼。每個影格都已等待其提交完成,因此現在可以釋放這些命令使用的資源。{windowRun} 之後完整的清理程式碼如下:'
+tutorial.lifecycle.guidance: '先釋放交換鏈,再銷毀其原生視窗;先釋放應用程式資源,再釋放上下文。已提交的命令緩衝區由佇列回收,輸出紋理由交換鏈管理,兩者都不需要單獨呼叫 {dispose}。'
+tutorial.lifecycle.title: '影格生命週期與清理'
+tutorial.pipeline.behavior: '在輸入佈局之後建立管線:'
+tutorial.pipeline.context: '{cullNone} 關閉面剔除,因此無論頂點在畫面中呈順時針或逆時針排列,三角形都會被繪製。'
+tutorial.pipeline.description: '{graphicsPipeline} 將頂點佈局、兩個著色器,以及圖元組裝和附件輸出所需的設定組合在一起。'
+tutorial.pipeline.details: '著色器負責處理頂點與片元。其餘管線設定決定圖元如何組裝、是否剔除特定朝向的面,以及如何執行深度測試和色彩混合:'
+tutorial.pipeline.guidance: '管線顏色格式必須匹配渲染通道附件,因此直接取自 {swapChainDescFormat}。{count1} 表示每像素一個取樣點。'
+tutorial.pipeline.item: '{triangleList} 將每組連續的三個頂點組裝成一個三角形。'
+tutorial.pipeline.notes: '{depthNone} 關閉深度測試和深度寫入,與通道沒有深度附件的設定一致。'
+tutorial.pipeline.reference: '{opaque} 關閉混合,因此片元著色器輸出直接替換三角形內部的背景色。'
+tutorial.pipeline.title: '圖形管線'
+tutorial.pipeline.usage: '管線建立完成後即可釋放著色器物件。渲染期間保留管線,在清理程式碼中將 {pipelineDispose} 放在 {vertexBufferDispose} 之前。'
+tutorial.presentation.title: '圖形上下文與呈現'
+tutorial.project.context: '在專案建置設定中啟用{emphasis}({allowUnsafeBlocks})。視窗輔助程式碼的原生呼叫和頂點上傳需要不安全程式碼。'
+tutorial.project.context.emphasis: '允許不安全的程式碼'
+tutorial.project.description: '建立名為 {firstTriangle} 的 C# 主控台專案,目標框架設為 .NET 10。'
+tutorial.project.details: '為專案加入以下 NuGet 套件:'
+tutorial.project.guidance: '{zenithNET} 提供統一渲染 API,三個後端套件提供各平台的實作。{zenithNETCompiler} 為所選後端編譯稍後介紹的 Slang 著色器。Silk.NET 負責視窗建立與事件處理。'
+tutorial.project.title: '專案設定'
+tutorial.requirements.context: '{link}及其{detail}提供了本教學的參考實作。'
+tutorial.requirements.context.detail: '共用宿主程式'
+tutorial.requirements.context.link: 'Hello Triangle 範例'
+tutorial.requirements.description: '需要 {emphasis},並具備基礎 C# 知識。應用程式以 {net10} 為目標框架。GPU 與驅動程式須支援所在平台選用的後端:'
+tutorial.requirements.description.emphasis: '.NET 10 SDK'
+tutorial.requirements.guidance: 'Zenith.NET 渲染到應用程式提供的原生呈現目標。Silk.NET.Windowing 建立視窗並處理事件。macOS 使用由 {cAMetalLayerCs} 表示的 Metal 圖層,視窗輔助程式碼負責將它連接到視窗。'
+tutorial.requirements.table.headings.graphicsBackend: '圖形後端'
+tutorial.requirements.table.headings.platform: '平台'
+tutorial.requirements.table.linux.graphicsBackend: 'Vulkan 1.4'
+tutorial.requirements.table.linux.platform: 'Linux(X11 或 XWayland)'
+tutorial.requirements.table.macOS.graphicsBackend: 'Metal 4'
+tutorial.requirements.table.macOS.platform: 'macOS'
+tutorial.requirements.table.windows.graphicsBackend: 'DirectX 12'
+tutorial.requirements.table.windows.platform: 'Windows'
+tutorial.requirements.title: '準備事項'
+tutorial.resize.context: '再次建置並執行程式。調整視窗大小後,背景應繼續填滿整個視窗:每次開始新的渲染通道時,視口與裁剪區域都會依附件的新尺寸設定。保留渲染回呼,稍後將在這個通道內繪製三角形。'
+tutorial.resize.description: '畫面緩衝區尺寸改變時,交換鏈也要跟著調整。在渲染回呼之後、{windowRun} 之前註冊 {framebufferResize} 回呼:'
+tutorial.resize.details: '這裡接收的是以像素為單位的{link},而不是視窗邏輯尺寸的變化。視窗最小化時,畫面緩衝區的寬度或高度可能變成零。此時,尺寸變更回呼保留現有交換鏈,不進行調整。渲染回呼也會在任一尺寸為零時直接返回,直到畫面緩衝區恢復有效尺寸後才繼續繪製。'
+tutorial.resize.details.link: '畫面緩衝區大小變更'
+tutorial.resize.guidance: '渲染回呼會等待目前影格的提交完成,再呼叫 {present}。因此,尺寸變更回呼替換交換鏈影像時,先前影格的工作已經結束。完成狀態與資源生命週期的要求,詳見{link}。'
+tutorial.resize.guidance.link: '同步'
+tutorial.resize.title: '視窗尺寸變化'
+tutorial.shaders.compilation.context: '{appContextBaseDirectory} 讓程式從輸出目錄載入已複製的著色器。即使從其他工作目錄啟動程式,尋找檔案的位置也不會改變。'
+tutorial.shaders.compilation.description: '將 {triangleSlang} 的檔案屬性{emphasis}設為{detail},建置時便會將著色器複製到執行檔所在目錄。'
+tutorial.shaders.compilation.description.detail: '有更新時才複製'
+tutorial.shaders.compilation.description.emphasis: '複製到輸出目錄'
+tutorial.shaders.compilation.details: '回到 {programCs},在頂點上傳之後、渲染回呼之前加入著色器初始化:'
+tutorial.shaders.compilation.guidance: '{zenithCompiler} 為 {contextGraphicsApi} 指定的後端編譯原始碼,{createShader} 再依編譯後的描述建立著色器物件。進入點名稱 {vSMain} 與 {fSMain} 分別選取 Slang 檔案中定義的兩個函式。'
+tutorial.shaders.compilation.title: '編譯著色器'
+tutorial.shaders.context: '{fSInput} 連接兩個著色器階段。頂點著色器將裁剪空間位置寫入 {sVPOSITION},GPU 據此判斷三角形的可見部分。{cOLOR0} 欄位傳遞頂點色彩,經過三角形內部的內插後再交給片元著色器。兩個階段必須採用相同的色彩型別與語義。'
+tutorial.shaders.description: '著色器是在 GPU 上執行的程式。頂點著色器決定每個頂點的位置,片元著色器則決定影像中被覆蓋位置的色彩。接下來會將兩個階段寫在同一個 Slang 檔案,並分別定義進入點,也就是各階段開始執行的函式。'
+tutorial.shaders.details: '建立 {triangleSlang},先定義輸入與輸出結構:'
+tutorial.shaders.fragment.description: '在同一檔案中追加片元進入點:'
+tutorial.shaders.fragment.details: '光柵化判定三角形在影像中的覆蓋範圍,並在這些位置產生片元。每個片元的色彩由三個頂點的色彩內插而得。{fSMain} 傳回這個色彩,{sVTARGET} 則將結果送到第一個色彩附件。'
+tutorial.shaders.fragment.title: '片元著色'
+tutorial.shaders.guidance: '{vSInput} 定義頂點著色器輸入。{pOSITION0} 和 {cOLOR0} 按語義識別屬性,C# 欄位名稱本身不建立這一映射。'
+tutorial.shaders.title: '著色器階段'
+tutorial.shaders.vertex.description: '在 {triangleSlang} 中追加頂點進入點:'
+tutorial.shaders.vertex.details: 'GPU 為每個輸入頂點呼叫這一進入點,補充齊次座標 {w} 並傳遞顏色。頂點處理結束後,位置要除以 w。本例 w 為 1,x、y、z 保持不變,可直接解釋為正規化裝置座標。'
+tutorial.shaders.vertex.title: '頂點著色'
+tutorial.surface.description: '在{emphasis}指定原生呈現目標:'
+tutorial.surface.description.emphasis: '{windowCenter} 之後、{windowRun} 之前'
+tutorial.surface.details: '畫面緩衝區是視窗用來顯示的像素影像。{surface} 描述原生呈現目標及其尺寸,{framebufferSize} 則提供像素大小。啟用顯示縮放時,這個大小可能與視窗的邏輯尺寸不同。'
+tutorial.surface.guidance: '呈現目標提供後端所需的原生控制代碼:Windows 使用 Win32 視窗,Linux 使用 X11 顯示與視窗,macOS 使用已連接的 Metal 圖層。'
+tutorial.surface.title: '原生呈現目標'
+tutorial.swapChain.context: '交換鏈需要上下文與視窗才能運作。將 {programCs} 結尾的 {windowDispose} 換成以下釋放順序:'
+tutorial.swapChain.description: '緊接呈現目標描述,建立交換鏈:'
+tutorial.swapChain.details: '{swapChain} 管理視窗呈現的影像。{drawable} 以影像資源 {texture} 的形式提供目前影像。渲染後,{present} 請求顯示該影像,並推進到下一個輸出紋理。'
+tutorial.swapChain.guidance: '{b8G8R8A8UNorm} 按藍、綠、紅和 alpha 順序儲存四個 8 位元通道。{uNorm} 將整數 0–255 對應到 0–1。著色器仍輸出邏輯 RGBA 值,格式只規定儲存表示;管線透過 {swapChainDescFormat} 匹配附件。'
+tutorial.swapChain.title: '交換鏈'
+tutorial.title: '第一個三角形'
+tutorial.troubleshooting.description: '結合主控台診斷與三個檢查點定位失敗階段:'
+tutorial.troubleshooting.details: '{link}與{detail}可用於核對頂點資料、輸入佈局和繪製操作。該範例將視窗與提交邏輯放入共用宿主程式,本教學則保留在 {programCs} 中。{context}進一步介紹索引繪製、變換與深度測試。'
+tutorial.troubleshooting.details.context: '旋轉立方體'
+tutorial.troubleshooting.details.detail: 'Slang 著色器'
+tutorial.troubleshooting.details.link: '範例渲染器'
+tutorial.troubleshooting.table.backend.check: '檢查選取的後端是否受到裝置與已安裝的圖形驅動程式支援。'
+tutorial.troubleshooting.table.backend.symptom: '後端建立失敗'
+tutorial.troubleshooting.table.drawing.check: '檢查上傳大小、輸入佈局、管線選擇,以及繪製命令是否位於渲染通道內。'
+tutorial.troubleshooting.table.drawing.symptom: '背景出現但三角形未出現'
+tutorial.troubleshooting.table.headings.check: '檢查項目'
+tutorial.troubleshooting.table.headings.symptom: '徵狀'
+tutorial.troubleshooting.table.input.check: '檢查跨距是否為 28 位元組,位置與色彩是否分別從位移 0 和 12 位元組開始。'
+tutorial.troubleshooting.table.input.symptom: '頂點位置或色彩異常'
+tutorial.troubleshooting.table.shaders.check: '檢查複製的 {triangleSlang}、進入點拼寫,以及編譯器回報的診斷資訊。'
+tutorial.troubleshooting.table.shaders.symptom: '背景色正常顯示,但著色器載入失敗'
+tutorial.troubleshooting.table.window.check: '確認 .NET 專案可成功建置,且僅含視窗的步驟可在桌面工作階段中執行。在 Linux 上,檢查 X11 或 XWayland 是否可用。'
+tutorial.troubleshooting.table.window.symptom: '視窗未開啟'
+tutorial.troubleshooting.title: '問題排查'
+tutorial.window.description: '將 {cocoaHelperCs} 下載至專案中。此輔助類別負責將 Metal 圖層連接到 macOS 視窗。'
+tutorial.window.details: '將自動產生的 {programCs} 內容換成以下程式碼。它使用最上層陳述式,依書寫順序執行,不需要明確撰寫 {main} 方法。後續各節會指出新程式碼應加入的位置:'
+tutorial.window.guidance: '{graphicsAPINone} 將圖形上下文的建立交給 Zenith.NET。{initialize} 建立原生視窗,讓程式取得視窗系統的控制代碼;{center} 把視窗放在螢幕中央。{run} 持續處理事件直到視窗關閉,之後再執行 {dispose} 釋放視窗。'
+tutorial.window.notes: '建置並執行程式,確認視窗可以開啟、移動、調整大小及關閉。此時尚未提交渲染命令,視窗內容還沒有定義。關閉視窗後再繼續。'
+tutorial.window.title: '建立視窗'
+ui.action.copy: '複製'
+ui.action.navigate: '巡覽'
+ui.action.open: '開啟'
+ui.action.source: '檢視原始碼'
+ui.api.browse: '瀏覽命名空間與型別。'
+ui.api.clearTypeSearch: '清除型別搜尋'
+ui.api.findType: '尋找型別'
+ui.api.loadError: '無法載入型別。重新載入此頁面以重試。'
+ui.api.loadingNamespaces: '正在載入命名空間…'
+ui.api.loadingTypes: '正在載入型別…'
+ui.api.matchingTypes: '相符的型別'
+ui.api.namespace: '命名空間'
+ui.api.namespaces: '命名空間'
+ui.api.noNamespaceTypes: '此命名空間中沒有型別。'
+ui.api.noTypes: '沒有相符的型別。'
+ui.api.types: '型別'
+ui.content.reference: '參考'
+ui.content.source: '原始碼'
+ui.content.tutorial: '教學'
+ui.contents.close: '關閉目錄'
+ui.contents.open: '開啟目錄'
+ui.contents.title: '目錄'
+ui.footer.caption: '面向 .NET 的現代 RHI'
+ui.footer.label: '頁尾'
+ui.language.label: '語言'
+ui.language.unavailable: '未翻譯'
+ui.members.clear: '清除成員搜尋'
+ui.members.close: '關閉成員尋找工具'
+ui.members.empty: '沒有相符的成員。'
+ui.members.open: '前往成員'
+ui.members.placeholder: '名稱或簽章…'
+ui.members.search: '搜尋成員'
+ui.members.title: '尋找成員'
+ui.members.undeclared: '此型別沒有宣告的成員。'
+ui.navigation.api: 'API'
+ui.navigation.concepts: '概念'
+ui.navigation.home: '首頁'
+ui.navigation.label: '主要導覽'
+ui.navigation.skip: 跳至正文
+ui.navigation.toggle: '切換導覽'
+ui.outline.title: '本頁內容'
+ui.reference.assembly: '組件'
+ui.reference.classes: '類別'
+ui.reference.constructors: '建構函式'
+ui.reference.declaration: 'C# 型別宣告'
+ui.reference.delegates: '委派'
+ui.reference.derived: '衍生型別'
+ui.reference.enums: '列舉'
+ui.reference.events: '事件'
+ui.reference.examples: '範例'
+ui.reference.exceptions: '例外狀況'
+ui.reference.extensionMethods: '擴充方法'
+ui.reference.fields: '欄位'
+ui.reference.implementations: '明確介面實作'
+ui.reference.implements: '實作'
+ui.reference.inheritance: '繼承'
+ui.reference.inheritedMembers: '繼承的成員'
+ui.reference.interfaces: '介面'
+ui.reference.metadata: '型別中繼資料'
+ui.reference.methods: '方法'
+ui.reference.namespace: '命名空間'
+ui.reference.open: '開啟 {name} 的參考'
+ui.reference.operators: '運算子'
+ui.reference.parameters: '參數'
+ui.reference.properties: '屬性'
+ui.reference.related: '請參閱'
+ui.reference.relationships: '繼承與中繼資料'
+ui.reference.remarks: '備註'
+ui.reference.returns: '傳回'
+ui.reference.structs: '結構'
+ui.reference.typeParameters: '型別參數'
+ui.reference.value: '值'
+ui.reference.values: '值'
+ui.search.close: '關閉搜尋'
+ui.search.empty: '找不到「{query}」的結果。'
+ui.search.hint: '搜尋教學、概念、範例與 API 參考。'
+ui.search.loading: '正在載入搜尋…'
+ui.search.more: '顯示更多結果'
+ui.search.pending: '正在搜尋…'
+ui.search.placeholder: '輸入搜尋內容'
+ui.search.resultCount: '結果:{count}'
+ui.search.title: '搜尋文件'
+ui.search.unavailable: '搜尋暫時無法使用,可直接開啟下方頁面。'
diff --git a/documents/locales/zh-TW/translation.md b/documents/locales/zh-TW/translation.md
new file mode 100644
index 00000000..3a968e68
--- /dev/null
+++ b/documents/locales/zh-TW/translation.md
@@ -0,0 +1,103 @@
+# 繁體中文翻譯約定
+
+## 資源與結構
+
+[英文資源字典](../en-US/strings.yml)是唯一原文;專案規範請參閱[文件維護指南](../../maintenance.md)。建立新譯本時,將英文檔案完整複製為本目錄的 `strings.yml`,保留首行 `### YamlMime:Resources`,並使用 UTF-8 編碼。後續更新應對照英文變更修訂既有譯文,不要重新覆寫整個檔案。本檔案 `translation.md` 永久保留,不發布至網站。
+
+導覽、章節順序、錨點、連結、格式、程式碼和圖片都在 `locales/` 外統一維護。不要在語言目錄內新增文章、TOC、範本、指令碼或設定。
+
+翻譯僅修改本目錄 `strings.yml` 的值。鍵集合必須與英文完全一致,不得自行新增、刪除或重新命名鍵,包括看似相同或尚未遇到的介面文字。如果原文有問題,請向維護者指出資源鍵及問題,先統一修正英文與共用頁面,再同步譯文。
+
+## 文風與上下文
+
+採用嚴謹、自然的技術教材文風,先說明操作之間的關係,再交代條件與限制。教學保留明確的步驟及可觀察的結果。學術風格不代表必須增加被動句、抽象名詞或長句。
+
+依文章的閱讀順序審校,搭配前後段落、表格與程式碼理解內容。資源鍵只是儲存邊界,不是獨立的翻譯單位。將連結標籤與強調片段放回完整句子,檢查語意與銜接,避免逐詞翻譯和重複要求讀者操作。
+
+標題、介面標籤、首頁可見的簡短介紹與頁尾文案不加句末句號;正文、診斷訊息及無障礙操作說明保留正常標點。每個概念使用一種通行譯法,不只為部分術語附加英文括注。API 識別字、檔名與實際設定項目的名稱保持準確;括號可用於必要解釋,不作為例行的雙語標示。
+
+判斷一句話是否需要保留,應看它是否說明目前操作、設計選擇、可觀察結果或正確使用的條件。刪去未使用功能的羅列、籠統的環境提醒與重複前提。標題及跨樣式的短語也須整體翻譯,不照搬英文語序。首頁標題的 `{accent}` 與 `{lineBreak}` 可依中文調整位置,樣式和換行不能決定語序。正文刪改由維護者先統一更新英文、共用繫結與各語言鍵集合,再同步譯文。
+
+## 文字與預留位置
+
+使用自然、簡潔的臺灣繁體中文,以完整句子或段落為單位翻譯,保留前提、否定、單位、操作順序及生命週期限制。不要逐字拼接譯文,也不要加入原文沒有的技術保證、平台需求、套件版本或版本說明。產品名稱、套件名稱、檔名、C#/Slang 識別項及大小寫保持原樣。
+
+每個資源值都必須是非空字串、純文字,不寫 HTML 或 Markdown。注意 YAML 引號與逸出,避免冒號、井字號或類似數字的值遭到誤解析;必要時使用引號。保留英文鍵的排列順序,方便人工核對。
+
+`{context}`、`{link}` 等具名預留位置可對應共用頁面中的程式碼、連結或強調內容;`{name}`、`{query}`、`{count}` 等介面預留位置也可能由程式填入純文字。不要只憑名稱推斷內容,應查看對應資源在共用頁面或介面中的用途。連結標籤或強調文字若有獨立資源鍵,也需要翻譯。
+
+可以依中文語序移動預留位置,但每個鍵都必須保留原有名稱、大小寫與半形大括號,不新增、刪除或改名。不要用顯示出的 API 名稱、連結文字或範例數字取代預留位置,也不要拆分資源鍵來重組句子。
+
+## API 連結與技術含義
+
+連結由共用頁面維護。正文與表格中明確指向站內 API 的識別項連結至對應型別或成員;變數、參數名稱、範例自訂型別、套件名稱、檔名及沒有本站參考頁面的外部符號維持一般程式碼文字。相同拼寫不代表同一個符號。一般教學程式碼區塊不會自動加入連結。譯者可以移動整個預留位置,但不補寫連結、不修改目標,也不改動共用程式碼及其格式。
+
+若某個 API 沒有產生的參考項目,共用頁面會保留一般程式碼文字,並在附近提供明確的原始碼連結。不要自行改成其他型別或成員的連結。
+
+翻譯時尤其注意以下區別:
+
+- 命令記錄、提交、GPU 執行及完成並非同一步驟。
+- 資源用途、記憶體駐留/CPU 存取意圖、紋理配置與像素格式是不同概念。
+- 所有權表示釋放資源的責任;持有控制代碼不等於維持資源存活,提交命令也不會移轉所有權。
+- GPU 完成不一定代表暫存的回讀資料已複製至應用程式的 CPU 目標;保留原文關於等待所屬佇列的說明。
+- `drawable` 指目前影格的輸出紋理,不一律等同於交換鏈影像;資源檢視與 UI 控制項也須依情境區分。
+
+## 建置與交付檢查
+
+在已具備專案所需 .NET SDK 與 DocFX 的環境中,從儲存庫根目錄執行命令。首次取出儲存庫時,`documents/api/` 的產生中繼資料尚不存在,需要先執行包含 API 擷取的完整建置:
+
+```sh
+docfx documents/docfx.json --warningsAsErrors
+```
+
+之後,若 API 中繼資料已產生且與目前原始碼一致,僅修改字典時可使用一般建置並預覽:
+
+```sh
+docfx build documents/docfx.json --warningsAsErrors
+docfx serve documents/_site --hostname 127.0.0.1 --port 8080
+```
+
+本語言已註冊,完整字典建置成功後即可使用,無須修改設定。缺鍵、多餘鍵、空值、非字串值或預留位置名稱不符都會使建置失敗。建置不會判斷譯文是否準確或是否仍留有未翻譯的英文,必須人工通讀。
+
+各語言共用頁面路徑,以 `?lang=zh-TW` 選擇語言,切換時保留路徑與錨點。未明確指定語言時,依序採用已儲存的手動選擇、瀏覽器語言偏好、英文。若此譯本尚未發布,或字典載入/頁面資源驗證失敗時,頁面回復為英文,而非逐句混用語言。語言清單依語言代碼排序。
+
+交付前核對鍵與預留位置,並在桌面及 320 像素寬度檢查首頁、Learn 入口、三角形教學、概念頁、範例頁與 API 介面。依初學者的順序通讀教學,檢查術語、按鈕、搜尋、程式碼複製、API 跳轉,以及切換語言後的目前章節位置。識別項、宣告與程式碼仍使用共用原文;僅翻譯字典提供的介面與正文。測試字典及暫存檢查檔案放在儲存庫外,不提交暫代譯文。
+
+## 術語
+
+| 英文 | 約定用語 |
+| ----------------------------- | --------------------------------------------------------- |
+| API / GPU / RHI | 保留縮寫,首次依原文說明 |
+| barrier | 屏障 |
+| bindless | 無繫結 |
+| buffer | 緩衝區;`Buffer` 不變 |
+| color attachment | 色彩附件 |
+| command buffer | 命令緩衝區 |
+| command queue | 命令佇列 |
+| command recording | 命令記錄 |
+| depth/stencil | 深度/樣板 |
+| drawable | 目前影格的輸出紋理;`Drawable` 不變,不一律稱為交換鏈影像 |
+| framebuffer | 畫面緩衝區,尺寸依原文的像素單位表述 |
+| graphics context | 圖形上下文 |
+| heap | 堆積 |
+| pipeline | 管線;依情境區分圖形或計算管線 |
+| rasterization | 光柵化 |
+| readback | 從 GPU 回讀資料;下載檔案另用「下載」 |
+| render pass | 渲染通道 |
+| render target | 渲染目標 |
+| resource | 資源 |
+| resource ownership / lifetime | 資源所有權/生命週期 |
+| resource usage | 資源用途 |
+| shader / entry point | 著色器/進入點 |
+| submit / submission | 提交/提交操作,與 GPU 執行區分 |
+| surface | 呈現情境使用「原生呈現目標」;`Surface` 不變 |
+| swap chain / presentation | 交換鏈/呈現 |
+| texture | 紋理 |
+| timeline | 時間線 |
+| undefined | 未定義,不能改寫為某個預設結果 |
+| upload | 上傳資料 |
+| vertex / fragment | 頂點/片元 |
+| view | 資源檢視;與 UI 檢視區分 |
+| viewport / scissor | 視口/裁剪區域 |
+
+列與行的用法依臺灣慣例:row 譯為「列」,column 譯為「行」。row-major 為「列優先」,row stride 為「列跨距」。記憶體配置與資料佈局須依語境區分。
diff --git a/documents/maintenance.md b/documents/maintenance.md
new file mode 100644
index 00000000..428aeb4e
--- /dev/null
+++ b/documents/maintenance.md
@@ -0,0 +1,242 @@
+# Documentation maintenance guide
+
+This is the contributor reference for maintaining the documentation and its translations. It is excluded from the published site and its navigation.
+
+## Architecture and theme baseline
+
+The DocFX framework, content paths, navigation and single dark theme are the accepted baseline. Maintain content within the existing pages. The tutorial, samples index and concept articles are authored content; the API reference is generated from source.
+
+Keep the existing filenames, routes and `toc.yml` hierarchy. Retain the original brand assets and the shared templates. Use the existing Markdown components instead of introducing page-specific CSS, scripts, controls or a second theme. A reproducible rendering or accessibility defect can receive a focused fix; new visual directions and navigation changes are separate work.
+
+The brand footer belongs only to the landing page. Learn and API pages end with their content, keeping the reading layout and sidebars continuous to the bottom.
+
+Keep keyboard access to the content through the localized skip link. Scroll positions must follow actual element sizes and CSS anchor spacing; dialog closing must follow the animation's completion. Keep intentional design dimensions in CSS and name repeated behavior limits. Do not patch layout with guessed pixel offsets, duplicated animation timers, or clipped interactive content. Short viewports must allow the whole dialog or sidebar pane to scroll when its controls no longer fit.
+
+All pages share one centered shell, including the header and landing-page footer. Its width grows up to 1920 pixels with shared responsive gutters; the homepage does not override it. Learn and API pages share sidebar widths and column gaps. Prose has a separate 1080-pixel reading limit, while hub cards and API listings use the available content column. Keep these dimensions in the shared theme tokens. The home scene's glow must fade to transparent within its bounds.
+
+Keep scratch pages, validation scripts and preview fixtures outside the repository. Do not recreate `documents/tests`, publish test content, or reintroduce the temporary `Buffer` XML comments. Do not edit generated `documents/api/*.yml` or `documents/_site` files.
+
+## Page responsibilities
+
+Shared page paths below are relative to `documents/`. English text lives exclusively in `locales/en-US/strings.yml`. A Markdown filename determines its corresponding `.html` route.
+
+| Page | Responsibility |
+| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
+| `index.md` | The shared homepage composition, bound to `home.*` text resources. |
+| `learn/index.md` | Orient readers and link to the tutorial, samples and concepts. Keep the existing card structure. |
+| `learn/first-triangle.md` | One complete, runnable triangle tutorial, from an empty project to the first rendered frame. |
+| `learn/samples.md` | Curated links to actual sample source, with a short explanation of what each demonstrates and any prerequisites. |
+| `learn/concepts/execution-model.md` | Contexts, queues, command recording, submission and ownership. |
+| `learn/concepts/resource-management.md` | Resource creation, usages, memory placement, heaps, views and lifetime. |
+| `learn/concepts/shader-data-and-binding.md` | Slang shaders, data layout and resource handles/binding. |
+| `learn/concepts/synchronization.md` | Execution order, resource visibility, barriers, timelines and CPU/GPU coordination. |
+| `learn/concepts/platform-integration.md` | Surfaces, presentation, resize/lifecycle and integration with supported UI frameworks. |
+| `api/index.md` and generated reference pages | Look up namespaces, declarations and members. API behavior comes from source and any intentional source XML comments. |
+
+Do not turn the samples index into a sequence of advanced tutorials. The triangle can have ordered sections within its single page; other examples remain independent. Concepts explain decisions and constraints, linking to the tutorial, samples or API instead of duplicating them.
+
+## Content rules
+
+- Maintain the canonical prose in American English in `documents/locales/en-US/strings.yml`. Other `documents/locales//` directories hold translated dictionaries and follow that directory's permanent `translation.md`. Keep C# identifiers and namespace names exact.
+- Describe the current API directly. Avoid release histories, migration narratives, product-version badges, "new in" labels and temporary preview-package instructions. A new package release alone does not require a documentation edit when the documented behavior remains accurate.
+- List required NuGet packages by package ID in alphabetical order, without package versions. Necessary platform/SDK prerequisites and target frameworks may be stated when needed to run the example; these are environment requirements, not a product-version narrative.
+- Keep setup instructions specific to the example: its project type and name, target framework, and required packages. Assume readers know the basic operations for creating, opening, building, and running a .NET project and obtaining a repository. Avoid routine workflow narration and phrases such as "in your usual development environment." Do not prescribe shell commands or require a particular editor, IDE, or Git client.
+- Request validation by default with `useValidationLayer: true` in documented context creation. Runnable tutorials should connect `ValidationMessage` to console output so reported diagnostics are visible.
+- Verify behavior against `sources/` and runnable examples before writing. Read the implementation when ownership, synchronization or platform support is unclear; do not infer it from a type's name. If package behavior and repository source differ, resolve the mismatch before calling an example runnable.
+- Introduce unfamiliar graphics terms when they first matter. Distinguish descriptions from recorded commands, and recording from GPU execution. Explain prerequisites and insertion points before asking the reader to use them.
+- Start each article with its practical purpose. Explain the reason for a non-obvious choice close to the relevant code. Avoid restating signatures, generic graphics introductions, decorative statistics and repeated navigation instructions.
+- Preserve source declaration order when listing enum members or writing combinations of flags.
+- Introduce enum types through the meaning and scope of their members. Use concise explanations or tables; do not turn an enum introduction into a resource-construction walkthrough.
+- Use one H1 and a clear H2/H3 hierarchy. Give explicit anchors to sections linked from other pages when their headings may change. Use the existing shared Markdown/HTML components with resource-bound text; use tables for comparisons, notices for necessary constraints and diagrams only when they clarify a relationship.
+- Keep diagrams small enough to read on a phone. Prefer a vertical sequence to a wide horizontal graph when scaling would make its labels too small.
+- Keep the homepage, Learn overview and README consistent with the documentation's contents. Do not publish placeholder pages or temporary validation notes.
+
+## Editorial style
+
+Use the register of a technical textbook: precise, measured, and readable. Explain the relationship between operations before stating their constraints. Tutorials still need direct instructions and observable checkpoints; academic style does not require passive voice, abstract phrasing, or longer sentences.
+
+Edit each article in its reading order, including the preceding paragraph, code, table, and following explanation. Resource keys are storage boundaries, not independent translation units. Link labels and emphasized fragments must agree grammatically with the complete sentence in which they appear. Avoid repeated requests to the reader, stock transitions, and literal phrases such as "the control owns the frame boundary" when the intended meaning is responsibility for the frame lifecycle.
+
+Keep a sentence when it explains the current operation, a design choice, an observable result, or a condition needed for correct use. Omit inventories of features the example does not use, generic platform caveats, repeated prerequisites, and comparisons with techniques not yet introduced. State a prerequisite once, where the reader needs it. Preserve concrete lifetime, synchronization, and layout requirements, and explain their effect on the operation at hand.
+
+Translate a heading or sentence as a whole, including text split across styled elements. Use movable named slots for emphasis and line breaks when the language order differs; do not hard-code English word order in shared markup or compensate by swapping unrelated resource values. The homepage title demonstrates this with its `accent` and `lineBreak` slots. When removing a redundant passage, remove its shared binding and retire its dedicated keys from all dictionaries together.
+
+Use concise headings and interface labels without terminal sentence punctuation. Visible homepage headings, introductory lines, card summaries, and the footer caption also omit terminal periods; write them as coherent short phrases instead of disconnected sentences. Body paragraphs, diagnostics, and accessibility instructions retain normal punctuation in the target language.
+
+Use one established term for each concept within a language. Do not append English equivalents selectively to translated terms or link labels. Preserve API identifiers, product names, filenames, and the named slots needed to locate code or IDE settings. Parentheses are appropriate for an actual explanation or setting identifier, not as a routine translation gloss.
+
+## Triangle and sample workflow
+
+First locate and inspect the existing tutorial/sample source. This checkout has examples under `sources/Experiments/`; it does not contain a dedicated triangle tutorial project. Locate any separately maintained tutorial repository before depending on it, and verify each linked file and API. Do not invent repository paths or treat an experiment as the canonical triangle tutorial without inspecting it.
+
+The first triangle is a general .NET console application using NuGet packages. Specify the project name, target framework, and required packages. Do not replace package installation with source project references or cloning the main repository to accommodate the validation machine. Keep any explicitly authorized test-only package selection in a temporary project outside the repository.
+
+Introduce the windowing library, backend selection and native-surface differences before graphics setup. Use one shared application flow for Windows, macOS and Linux, identify the windowing requirements, and keep platform interop in a small, verified support file where possible. Do not turn this into a platform-specific SDK setup tutorial.
+
+Teach the triangle in cumulative steps: an empty window, a presented clear color, vertex data and upload, shaders, input layout, pipeline state, drawing, presentation and cleanup. Explain what each step contributes, why its settings are chosen and how its data connects to the next stage. Mark the exact file and insertion or replacement point for every partial snippet. Provide observable checkpoints, and ensure the assembled result is complete. Link to concepts for deeper decisions; do not substitute a finished-project code dump or a validation report for the reader's learning sequence.
+
+Follow the established tutorial source's code style: explicit control flow, target-typed initialization, and its blank lines between logical operations and fields. Do not compress branches into nested conditional expressions, manually wrap method calls to fit the page, or introduce constants and helper functions solely to reorganize the article. Long code lines scroll within the existing code blocks.
+
+Use ordinary file-level `using` directives, introducing each directive or alias in the step that first needs it. The initial window code should import only the windowing namespace. For a downloaded support file, provide its link and purpose. Treat adapting its namespace and imports as routine C# integration work; do not prescribe a namespace or walk through those adjustments.
+
+Introduce file settings when the file is created. For a shader, describe its Copy to Output Directory setting after writing the shader; do not front-load project XML for files the reader has not created. Prefer a directly linked support filename with a short purpose statement over raw-file download instructions.
+
+Keep snippets consistent with the complete application. The assembled source must not depend on omitted setup or unexplained helper methods. Keep platform-specific alternatives only where they are necessary to run it.
+
+For each sample, provide a verified source link, its purpose and relevant requirements. Avoid copying its implementation into the page. Use the repository's normal branch links rather than transient local paths or release-specific URLs unless the example requires otherwise.
+
+For screenshots maintained in the tutorial repository, use its main-branch raw image URLs in shared Markdown. Link each preview to the full-size image, include its intrinsic dimensions, and use native lazy loading. Bind alternative text with `alt="@resource.key"` and `data-resource-alt="resource.key"`; an existing sample title can identify its preview when the adjacent prose explains the result. The build renders English alternative text, and language switching updates it from the selected dictionary.
+
+Compile the complete tutorial using its documented setup and run it to verify the visible triangle on the selected platform. A DocFX build validates the documentation, not the C# or shader examples. Report any platform that could not be exercised; do not claim cross-platform execution from a single-platform check.
+
+## Inline references
+
+In Learn prose and tables, link every explicitly named Zenith.NET API identifier that has a reference on this site. Use DocFX UIDs so the build checks the target and the link follows the generated API. Link a type to its type page, a member to its member entry, and a method family to its overload group when the discussion applies to all overloads. Repeated mentions follow the same rule; do not link only an arbitrary first occurrence.
+
+Keep the link and code styling in the shared page's slot. The English dictionary contains the complete sentence and its named placeholder, as described under [Resource keys and placeholders](#resource-keys-and-placeholders):
+
+```html
+GraphicsContext
+```
+
+For a code expression, link only the API symbols. Keep local variables, arguments, punctuation and operators as text within the same slot so translators can move the complete expression:
+
+```html
+context.GraphicsApi
+```
+
+Plain code is intentional for variables, parameter names, keywords, literals, sample-defined types, package names, filenames and external symbols without a local reference. Resolve the meaning in context: the sample's `Vertex` struct differs from `BufferUsages.Vertex`, and the `Zenith.NET` package name differs from the namespace. A window's `Dispose()` must not link to the RHI's disposal method just because they share a name.
+
+If a documented API is absent from the generated reference, keep its identifier plain and provide a clearly labeled source link nearby. Do not substitute a containing type or unrelated member. Links belong to the shared markup, not translated strings; translators retain and may reorder the named placeholders without choosing API destinations.
+
+Ordinary tutorial code fences remain unlinked. Syntax coloring does not imply a reference. The explicit linked-block mechanism and the generated declaration scope below remain separate from this prose rule.
+
+## Linked code blocks
+
+Ordinary fenced code blocks retain their usual syntax highlighting and copy behavior. When a block needs clickable symbols, use a native HTML code block with DocFX `xref` elements:
+
+```html
+ context = CreateContext();
+ buffer = context. (description);
+```
+
+The `text` attribute is the exact code to display and copy. Escape HTML characters such as `<`, `>` and `&` in the code text and attribute values. A reference can cover a qualified name or a complete generic type expression.
+
+DocFX resolves the UID during the build. The shared code-link renderer preserves the reference through highlighting; copying returns only the displayed code. The same renderer handles generated API declarations. Comments, strings, parameter names and tuple element names do not acquire links by matching another symbol's spelling.
+
+Use `csharp` for C#, `slang` for shaders and `sh` for shell commands. The theme supplements C# highlighting for conventional type identifiers, calls, member access and operators, and provides Slang-specific types and shader semantics. This is lexical coloring, not compiler symbol resolution. A colored token is only a link when it has an explicit reference or falls within the generated API link scope below; code text and copy output stay unchanged.
+
+## Automatic link scope
+
+Generated API declarations link **types with a reference page on this site** in these positions:
+
+- Field, property and event types, and method/operator return types.
+- Method, constructor and indexer parameter types.
+- Base classes and implemented interfaces in a type declaration.
+
+This includes local types inside generic, nullable, array, pointer and tuple expressions. The declared member's own name, parameter names, generic parameter names, tuple element names, attributes, generic constraints, modifiers and literal values remain text. Framework and third-party types without a local API page also remain text; automatic references do not lead to external documentation.
+
+Tutorial code and XML code examples do not acquire guessed links. Authored Markdown/HTML `xref` and XML prose `see` references are explicit and may point to local or external documentation. Only rendered links receive the code-reference color, pointer cursor and focus treatment.
+
+## XML comments
+
+Write a short `` describing behavior. Add ` `, ``, `` and `` when they explain something beyond the declaration. These sections are displayed only when their descriptions contain content. Use `` for constraints, `` for failure conditions and `` for usage.
+
+Use ` ` for references in prose, `` and `` for names, and `` for literal inline code. The compiler resolves `cref` in the source's context.
+
+```csharp
+/// Creates a buffer using .
+/// The size, usage and memory requirements.
+/// The newly created buffer.
+/// The caller owns the returned resource.
+///
+/// See .
+///
+///
+```
+
+Keep XML `` contents literal using `CDATA`. Put `` references in the surrounding prose: DocFX extracts the text of XML code examples and discards nested reference elements, which can remove the referenced identifier. The HTML `xref` form above applies to authored Markdown pages.
+
+## Resource dictionaries and shared pages
+
+This is a WPF-style resource model. `index.md`, `learn/` and `api/index.md` contain the single shared page structure, anchors, links, illustrations and code. `toc.yml` is the only hand-maintained navigation tree. The API TOC is generated from C# metadata; it is not another authored navigation or a language-specific copy.
+
+`locales/en-US/strings.yml` is the sole English source dictionary. Each other language folder retains its permanent `translation.md` and, when translated, a `strings.yml`. A translator adds or updates only `strings.yml`, keeping the `### YamlMime:Resources` header and the complete source key set. No localized pages, TOCs, images, templates, scripts or configuration are needed. All resource values are strings.
+
+Language folders, registrations and menus use code order: `de-DE`, `en-US`, `es-ES`, `fr-FR`, `ja-JP`, `ko-KR`, `pt-BR`, `ru-RU`, `zh-CN`, `zh-TW`. English is explicitly the source and fallback. Complete resource dictionaries become available automatically after the normal DocFX build. Missing keys, stale extra keys, non-string values and changed placeholder names fail that build.
+
+Pages share the same URL paths. `?lang=` selects a dictionary, retaining the page and fragment. Without an explicit language, the site uses a saved manual selection, browser preferences, then English. An explicit unsupported or unpublished language falls back to English, without substituting a different saved language. A missing, stale or malformed downloaded dictionary is rejected before any translated text is applied, keeping the complete English page usable. The English HTML is fully rendered during the build; other static dictionaries bind to that same markup in the browser. No translation API is used. Search uses translated resources with the shared generated API index.
+
+Site-owned images remain in `images/`; screenshots maintained in the tutorial repository are referenced by their main-branch URLs. All generated API YAML remains in `api/`. This is a complete refactor, with no legacy route or compatibility layer. Do not create `pages/`, `snippets/`, a parallel content graph or an additional build command for localization.
+
+## Resource keys and placeholders
+
+Keys name stable roles or concepts, such as `tutorial.context.description`, `concepts.resources.memory.description` or `ui.search.title`. Do not encode current wording, article numbers, screen positions, language codes or release versions. A copy edit changes the value, not the key. Give different meanings separate keys even if their current English values happen to match.
+
+Values are plain text, not HTML or Markdown. Keep a complete sentence or paragraph together. Inline code, links and emphasis live in named slots in the shared page, so translations can move those slots without copying their markup or changing their destinations.
+
+For example, in the dictionary:
+
+```yaml
+tutorial.context.description: A {context} provides the graphics connection.
+```
+
+And in the shared page:
+
+```html
+
+```
+
+The binding escapes text values and inserts only the shared slot markup. Preserve every placeholder name, including case, when translating. Reordering is allowed; adding or removing names is not. Use descriptive slot names for new content. Nested emphasis or link labels can have their own resources when they contain translatable prose. Executable examples, shader code and diagnostic/illustrative code blocks stay in shared Markdown unchanged.
+
+Page front matter uses `title: '@tutorial.title'` and an optional resource-bound `description`. Navigation names bind keys through `name: '@tutorial.title'` in the root TOC. Keep heading IDs in shared markup so rewording or translating a title cannot change links.
+
+The binding is implemented inside the existing template system: one native resource schema loads flat dictionaries, a small shared formatter handles named placeholders, and the conceptual template resolves English text at build time. The browser uses the same placeholder format. Do not add a general Markdown interpreter, external translation service or content-generation pipeline.
+
+## Validation
+
+Install DocFX as a .NET tool and use the repository's .NET 10 SDK, required workloads, and platform SDKs. Full API extraction loads the projects under `sources/`, including the MAUI integration; its workload and Android SDK must be available even when only reviewing documentation.
+
+On a fresh checkout, or after changing C# source or XML comments, run the full extraction and build from the repository root:
+
+```sh
+docfx documents/docfx.json --warningsAsErrors
+```
+
+Once API metadata has been generated and matches the current source, Markdown, resource-dictionary, or theme-only changes can use `docfx build documents/docfx.json --warningsAsErrors`.
+
+The build produces ignored API YAML and `.manifest` files under `documents/api/`, and the published site under `documents/_site/`. Commit the authored `api/index.md`, shared pages, dictionaries, images, and templates; keep generated output out of the PR. `maintenance.md` and the permanent `locales/*/translation.md` files are contributor documentation, not temporary files.
+
+Preview the generated site from the repository root:
+
+```sh
+docfx serve documents/_site --hostname 127.0.0.1 --port 8080
+```
+
+Check the resulting type page and a tutorial page in the browser. Verify links after highlighting, keyboard focus, copied code, long generic signatures and narrow layouts. A documented member should keep its summary, parameter descriptions, return/value explanation, remarks, examples and exceptions. An undocumented member should remain compact.
+
+Before handing off a content change, ensure the DocFX build has no warnings or errors, local links and fragments resolve, and the filled pages work at desktop and phone widths. Check headings, code/table overflow, search results and keyboard navigation on the changed pages. Summarize the pages filled, the source used, compilation/rendering checks performed and any unfinished content.
+
+## Template organization
+
+The template keeps DocFX's existing entry points and the `layout`, `partials` and `public` directories.
+
+- `layout/_master.tmpl` assembles the fixed dark page shell from the `site.*` partials. The header uses `primary-navigation` and the API browser uses `site:api-toc`, avoiding DocFX’s automatic navigation and theme controls.
+- `ManagedReference.html.primary.tmpl` assembles API pages and their shared remarks, examples and related links. The `api.*` partials render each content section; `api.code` keeps declarations and their reference metadata together. `ManagedReference.extension.js` prepares names, descriptions and unique member anchors.
+- `public/main.css` is the stylesheet entry. `theme.css` defines the palette and typography; `layout.css` owns the shell and navigation; `search.css` owns both search dialogs; `content.css` owns code, tables and prose links. Home and motion styles remain separate. `api.css` is loaded only for API pages. Reuse semantic theme colors instead of adding slightly different control and text colors.
+- `public/main.js` is DocFX's extension entry. `site.js` owns mobile header interactions and code-block labels; `dialog.js` shares keyboard and backdrop behavior between the search dialogs.
+- `languages.js` selects a resource dictionary and owns the language menu. `navigation.js` renders the header and Learn sidebar from the same root TOC. `search.js` owns page-index loading and search recovery. `resources.js` validates complete page bindings before applying translated text, while `resource-format.js` shares placeholder parsing with the build host. `resources.common.js` and the conceptual extension bind and validate the English resource values; `Resources.json.primary.js` publishes the dictionaries through DocFX's native schema processor.
+- `outline.js` builds the article's nested heading navigation and tracks the current section. `syntax.js` configures the bundled highlighter for documentation code; it does not add links.
+- `code-links.js` preserves authored references through highlighting. `signature-links.js` supplies the bounded, metadata-driven references for API declarations.
+
+Keep the generated `_site` and API metadata out of source edits. Rebuild after template changes, and check existing page URLs, fragment links and copy behavior before publishing.
+
+## Framework maintenance boundary
+
+The resource-dictionary model and current theme are the framework baseline. New translations add only a complete `strings.yml`; ordinary copy changes edit values without renaming keys. Keep the single authored TOC, shared pages, generated API, and common images. Do not recreate compatibility routes, language-specific pages, a second navigation tree, a separate build command, or a repository test directory.
+
+For a framework change, check the homepage, Learn overview, a long article, a namespace, a type/member page and an enum. Include wide, narrow and short landscape viewports, plus the effective small viewport produced by browser zoom. Check the bounds and keyboard reachability of controls as well as document overflow, the skip link, scrollable code/tables, menu exclusivity, search return/retry behavior, and member navigation. Verify interrupted and disabled animations. Translation fixtures must remain outside the repository and cover successful switching, explicit-language priority, unchanged fragments, and invalid dictionary fallback. Retain the original English text and code unless the task explicitly includes content edits.
diff --git a/documents/templates/ManagedReference.extension.js b/documents/templates/ManagedReference.extension.js
index 64fa9aeb..ba8aea37 100644
--- a/documents/templates/ManagedReference.extension.js
+++ b/documents/templates/ManagedReference.extension.js
@@ -1,11 +1,71 @@
-exports.postTransform = function (model) {
- if (!model.namespace?.uid || !model.namespace.specName) {
- return model;
+const resources = require('./resources.common.js');
+const relationshipKeys = ['inheritance', 'implements', 'derivedClasses', 'inheritedMembers', 'extensionMethods'];
+
+function prepareSignature(syntax, strings) {
+ if (!syntax) return;
+ const value = syntax.fieldValue || syntax.propertyValue || syntax.eventType || syntax.return;
+ if (value) {
+ const labelKey = 'ui.reference.' + (syntax.return ? 'returns' : 'value');
+ syntax.apiValueReference = value.type?.specName?.[0]?.value;
+ syntax.apiResult = {
+ labelKey,
+ label: resources.get(strings, labelKey),
+ type: syntax.apiValueReference,
+ description: value.description,
+ showType: !syntax.content?.[0]?.value
+ };
}
+ syntax.apiParameterDescriptions = (syntax.parameters || []).filter(parameter => parameter.description?.trim());
+ syntax.apiTypeParameterDescriptions = (syntax.typeParameters || []).filter(parameter => parameter.description?.trim());
+}
+
+function uidSuffix(uid) {
+ let hash = 2166136261;
+ for (let index = 0; index < uid.length; index++) hash = Math.imul(hash ^ uid.charCodeAt(index), 16777619);
+ return (hash >>> 0).toString(16);
+}
- for (const name of model.namespace.specName) {
- name.value = `${model.namespace.uid} `;
+exports.postTransform = function (model) {
+ const strings = resources.prepare(model);
+ model.apiName = model.name?.[0]?.value || model.uid;
+ model.title = model.apiName;
+ model.apiHasMemberFinder = !model.isNamespace;
+ model.apiSeeAlsoId = 'seealso';
+ const hasRelationships = relationshipKeys.some(key => model[key]?.length > 0);
+ model.apiHasTypeDetails = !model.isNamespace && (hasRelationships || !!model.namespace?.uid || !!model.assemblies?.length);
+ model.apiTypeDetailsKey = 'ui.reference.' + (hasRelationships ? 'relationships' : 'metadata');
+ model.apiTypeDetailsLabel = resources.get(strings, model.apiTypeDetailsKey);
+ prepareSignature(model.syntax, strings);
+ if (model.syntax) {
+ model.syntax.apiBaseReferences = [...(model.inheritance || []), ...(model.implements || [])]
+ .map(type => ({ type: type.specName?.[0]?.value })).filter(reference => reference.type);
}
+ const anchors = new Set([model.id]);
+ const overloads = new Set();
+ for (const group of model.children || []) {
+ group.apiTitleKey = 'ui.reference.' + (model.isEnum ? 'values' : group.id === 'eii' ? 'implementations' : group.id);
+ group.apiTitle = resources.get(strings, group.apiTitleKey);
+ for (const item of group.children || []) {
+ item.apiName = item.name?.[0]?.value || item.uid;
+ // DocFX normalizes pointer/by-ref UIDs to identical IDs in some overloads.
+ // Keep the first canonical anchor; later members get stable local anchors.
+ let anchor = item.id;
+ if (anchors.has(anchor)) anchor += '_' + uidSuffix(item.uid);
+ item.apiAnchor = anchor;
+ anchors.add(anchor);
+ item.apiSeeAlsoId = anchor + '_seealso';
+ if (item.overload && !overloads.has(item.overload.id)) {
+ item.apiOverload = item.overload;
+ overloads.add(item.overload.id);
+ }
+ prepareSignature(item.syntax, strings);
+ }
+ }
+ if (model.namespace?.uid && model.namespace.specName) {
+ for (const name of model.namespace.specName) {
+ name.value = `${model.namespace.uid} `;
+ }
+ }
return model;
};
diff --git a/documents/templates/ManagedReference.html.primary.tmpl b/documents/templates/ManagedReference.html.primary.tmpl
new file mode 100644
index 00000000..cf7172f4
--- /dev/null
+++ b/documents/templates/ManagedReference.html.primary.tmpl
@@ -0,0 +1,31 @@
+{{!master(layout/_master.tmpl)}}
+
+ {{>partials/api.header}}
+ {{#isNamespace}}
+ {{>partials/api.namespace}}
+ {{/isNamespace}}
+ {{#isEnum}}
+ {{>partials/api.enum}}
+ {{/isEnum}}
+ {{^isNamespace}}
+ {{^isEnum}}
+ {{>partials/api.type}}
+ {{/isEnum}}
+ {{/isNamespace}}
+ {{#remarks}}
+
+ {{/remarks}}
+ {{#example.0}}
+
+ {{r.ui.reference.examples}}
+ {{#example}}
+ {{{.}}}
+ {{/example}}
+
+ {{/example.0}}
+ {{^isNamespace}}
+ {{>partials/api.seealso}}
+ {{/isNamespace}}
+
diff --git a/documents/templates/Resources.json.primary.js b/documents/templates/Resources.json.primary.js
new file mode 100644
index 00000000..0490adbf
--- /dev/null
+++ b/documents/templates/Resources.json.primary.js
@@ -0,0 +1,3 @@
+const resources = require('./resources.common.js');
+exports.getOptions = () => ({ isShared: true });
+exports.transform = model => ({ content: JSON.stringify(resources.validate(model)) });
diff --git a/documents/templates/Resources.json.primary.tmpl b/documents/templates/Resources.json.primary.tmpl
new file mode 100644
index 00000000..9e1fd712
--- /dev/null
+++ b/documents/templates/Resources.json.primary.tmpl
@@ -0,0 +1 @@
+{{{content}}}
diff --git a/documents/templates/conceptual.extension.js b/documents/templates/conceptual.extension.js
new file mode 100644
index 00000000..83ebff62
--- /dev/null
+++ b/documents/templates/conceptual.extension.js
@@ -0,0 +1,10 @@
+const resources = require('./resources.common.js');
+exports.postTransform = function (model) {
+ const strings = resources.prepare(model);
+ const templates = [];
+ model._isLanding = model._layout === 'landing';
+ model.rawTitle = resources.bind(model.rawTitle || '', strings, templates);
+ model.conceptual = resources.bind(model.conceptual, strings, templates);
+ model._resourceTemplates = templates.join('');
+ return model;
+};
diff --git a/documents/templates/conceptual.html.primary.tmpl b/documents/templates/conceptual.html.primary.tmpl
new file mode 100644
index 00000000..bf8ad30a
--- /dev/null
+++ b/documents/templates/conceptual.html.primary.tmpl
@@ -0,0 +1,3 @@
+{{!master(layout/_master.tmpl)}}
+{{{rawTitle}}}
+{{{conceptual}}}
diff --git a/documents/templates/layout/_master.tmpl b/documents/templates/layout/_master.tmpl
new file mode 100644
index 00000000..138eef05
--- /dev/null
+++ b/documents/templates/layout/_master.tmpl
@@ -0,0 +1,93 @@
+{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}}
+{{!include(/^public/.*/)}}
+
+
+
+
+
+ {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}}
+
+
+ {{#description}}
+
+ {{/description}}
+ {{^description}}
+ {{#_description}}
+
+ {{/_description}}
+ {{/description}}
+
+
+
+ {{#_apiReference}}
+
+ {{/_apiReference}}
+
+
+ {{#_apiReference}}
+
+ {{/_apiReference}}
+ {{#_noindex}}
+
+ {{/_noindex}}
+
+
+
+
+ {{#_resourceTitle}}
+
+ {{/_resourceTitle}}
+ {{#_resourceDescription}}
+
+ {{/_resourceDescription}}
+
+
+
+
+
+
+ {{>partials/site.header}}
+
+
+ {{>partials/site.sidebar}}
+
+
+ {{^_disableToc}}
+ {{#apiHasMemberFinder}}
{{/apiHasMemberFinder}}
+
+
+ {{r.ui.contents.title}}
+
+ {{#apiHasMemberFinder}}
+ {{>partials/api.member-launcher}}
+
+ {{/apiHasMemberFinder}}
+ {{/_disableToc}}
+
+
+ {{!body}}
+
+
+
+ {{^_disableAffix}}
+
+ {{/_disableAffix}}
+
+
+ {{! Keep translation slot templates outside the article indexed by DocFX.}}
+ {{{_resourceTemplates}}}
+
+ {{>partials/site.member-finder}}
+ {{>partials/site.search}}
+
+ {{#_isLanding}}
+ {{>partials/site.footer}}
+ {{/_isLanding}}
+
+
diff --git a/documents/templates/partials/api.code.tmpl.partial b/documents/templates/partials/api.code.tmpl.partial
new file mode 100644
index 00000000..e32c58e2
--- /dev/null
+++ b/documents/templates/partials/api.code.tmpl.partial
@@ -0,0 +1,14 @@
+{{#syntax.content.0.value}}
+ {{syntax.content.0.value}}
+
+ {{#syntax.apiValueReference}}
+ {{{syntax.apiValueReference}}}
+ {{/syntax.apiValueReference}}
+ {{#syntax.parameters}}
+ {{{type.specName.0.value}}}
+ {{/syntax.parameters}}
+ {{#syntax.apiBaseReferences}}
+ {{{type}}}
+ {{/syntax.apiBaseReferences}}
+
+{{/syntax.content.0.value}}
diff --git a/documents/templates/partials/api.enum.tmpl.partial b/documents/templates/partials/api.enum.tmpl.partial
new file mode 100644
index 00000000..db8f347f
--- /dev/null
+++ b/documents/templates/partials/api.enum.tmpl.partial
@@ -0,0 +1,23 @@
+{{!Based on the .NET Foundation DocFX templates, licensed under the MIT license.}}
+{{#children}}
+
+
+
{{apiTitle}}
+
+
+ {{#children}}
+
+
+ {{syntax.content.0.value}}
+
+ {{#summary}}
+
{{{summary}}}
+ {{/summary}}
+ {{#remarks}}
+
{{{remarks}}}
+ {{/remarks}}
+
+ {{/children}}
+
+
+{{/children}}
diff --git a/documents/templates/partials/api.header.tmpl.partial b/documents/templates/partials/api.header.tmpl.partial
new file mode 100644
index 00000000..25df996e
--- /dev/null
+++ b/documents/templates/partials/api.header.tmpl.partial
@@ -0,0 +1,22 @@
+{{!Based on the .NET Foundation DocFX templates, licensed under the MIT license.}}
+
+ {{apiName}}
+ {{#summary}}
+ {{{summary}}}
+ {{/summary}}
+ {{#conceptual}}
+ {{{conceptual}}}
+ {{/conceptual}}
+
+{{#syntax.content.0.value}}
+
+ {{>partials/api.source}}
+ {{>partials/api.code}}
+
+{{/syntax.content.0.value}}
+{{#syntax}}
+ {{>partials/api.signature}}
+{{/syntax}}
+{{#apiHasTypeDetails}}
+ {{>partials/api.relationships}}
+{{/apiHasTypeDetails}}
diff --git a/documents/templates/partials/api.member-launcher.tmpl.partial b/documents/templates/partials/api.member-launcher.tmpl.partial
new file mode 100644
index 00000000..becaee2f
--- /dev/null
+++ b/documents/templates/partials/api.member-launcher.tmpl.partial
@@ -0,0 +1,5 @@
+
+
+ {{r.ui.members.title}}
+ Alt M
+
diff --git a/documents/templates/partials/api.namespace.tmpl.partial b/documents/templates/partials/api.namespace.tmpl.partial
new file mode 100644
index 00000000..6d1a4cdf
--- /dev/null
+++ b/documents/templates/partials/api.namespace.tmpl.partial
@@ -0,0 +1,18 @@
+{{!Based on the .NET Foundation DocFX templates, licensed under the MIT license.}}
+{{#children}}
+
+
+
{{apiTitle}}
+
+
+ {{#children}}
+
+
{{name.0.value}}
+ {{#summary}}
+
{{{summary}}}
+ {{/summary}}
+
+ {{/children}}
+
+
+{{/children}}
diff --git a/documents/templates/partials/api.relationships.tmpl.partial b/documents/templates/partials/api.relationships.tmpl.partial
new file mode 100644
index 00000000..ddd02d0a
--- /dev/null
+++ b/documents/templates/partials/api.relationships.tmpl.partial
@@ -0,0 +1,85 @@
+{{!Based on the .NET Foundation DocFX templates, licensed under the MIT license.}}
+
+ {{apiTypeDetailsLabel}}
+
+
+ {{#namespace.uid}}
+
+
{{r.ui.reference.namespace}}
+ {{{namespace.specName.0.value}}}
+
+ {{/namespace.uid}}
+ {{#assemblies.0}}
+
+
{{r.ui.reference.assembly}}
+ {{assemblies.0}}.dll
+
+ {{/assemblies.0}}
+
+ {{#inheritance.0}}
+
+
{{r.ui.reference.inheritance}}
+
+ {{#inheritance}}
+ {{{specName.0.value}}}
+ {{/inheritance}}
+ {{apiName}}
+
+
+ {{/inheritance.0}}
+ {{#implements.0}}
+
+
{{r.ui.reference.interfaces}}
+
+ {{#implements}}
+ {{{specName.0.value}}}
+ {{/implements}}
+
+
+ {{/implements.0}}
+ {{#derivedClasses.0}}
+
+
{{r.ui.reference.derived}}
+
+ {{#derivedClasses}}
+ {{{specName.0.value}}}
+ {{/derivedClasses}}
+
+
+ {{/derivedClasses.0}}
+ {{#inheritedMembers.0}}
+
+
{{r.ui.reference.inheritedMembers}}
+
+ {{#inheritedMembers}}
+
+ {{#definition}}
+
+ {{/definition}}
+ {{^definition}}
+
+ {{/definition}}
+
+ {{/inheritedMembers}}
+
+
+ {{/inheritedMembers.0}}
+ {{#extensionMethods.0}}
+
+
{{r.ui.reference.extensionMethods}}
+
+ {{#extensionMethods}}
+
+ {{#definition}}
+
+ {{/definition}}
+ {{^definition}}
+
+ {{/definition}}
+
+ {{/extensionMethods}}
+
+
+ {{/extensionMethods.0}}
+
+
diff --git a/documents/templates/partials/api.seealso.tmpl.partial b/documents/templates/partials/api.seealso.tmpl.partial
new file mode 100644
index 00000000..0417a071
--- /dev/null
+++ b/documents/templates/partials/api.seealso.tmpl.partial
@@ -0,0 +1,18 @@
+{{!Based on the .NET Foundation DocFX templates, licensed under the MIT license.}}
+{{#seealso.0}}
+
+{{/seealso.0}}
diff --git a/documents/templates/partials/api.signature.tmpl.partial b/documents/templates/partials/api.signature.tmpl.partial
new file mode 100644
index 00000000..fbf83436
--- /dev/null
+++ b/documents/templates/partials/api.signature.tmpl.partial
@@ -0,0 +1,51 @@
+{{!Based on the .NET Foundation DocFX templates, licensed under the MIT license.}}
+{{#apiTypeParameterDescriptions.0}}
+
+
{{r.ui.reference.typeParameters}}
+
+ {{#apiTypeParameterDescriptions}}
+
+
+ {{id}}
+
+ {{{description}}}
+
+ {{/apiTypeParameterDescriptions}}
+
+
+{{/apiTypeParameterDescriptions.0}}
+{{#apiParameterDescriptions.0}}
+
+
{{r.ui.reference.parameters}}
+
+ {{#apiParameterDescriptions}}
+
+
+ {{id}}
+
+ {{{description}}}
+
+ {{/apiParameterDescriptions}}
+
+
+{{/apiParameterDescriptions.0}}
+{{#apiResult}}
+ {{#showType}}
+
+ {{label}}
+ {{{type}}}
+ {{#description}}
+ {{{description}}}
+ {{/description}}
+
+
+ {{/showType}}
+ {{^showType}}
+ {{#description}}
+
+
{{label}}
+
{{{description}}}
+
+ {{/description}}
+ {{/showType}}
+{{/apiResult}}
diff --git a/documents/templates/partials/api.source.tmpl.partial b/documents/templates/partials/api.source.tmpl.partial
new file mode 100644
index 00000000..ad10699b
--- /dev/null
+++ b/documents/templates/partials/api.source.tmpl.partial
@@ -0,0 +1,5 @@
+{{#sourceurl}}
+
+
+
+{{/sourceurl}}
diff --git a/documents/templates/partials/api.type.tmpl.partial b/documents/templates/partials/api.type.tmpl.partial
new file mode 100644
index 00000000..1cbda4fa
--- /dev/null
+++ b/documents/templates/partials/api.type.tmpl.partial
@@ -0,0 +1,58 @@
+{{!Based on the .NET Foundation DocFX templates, licensed under the MIT license.}}
+{{#children}}
+
+
+
{{apiTitle}}
+
+
+ {{#children}}
+ {{#apiOverload}}
+
+ {{/apiOverload}}
+
+
+
{{apiName}}
+ {{>partials/api.source}}
+
+ {{>partials/api.code}}
+ {{#summary}}
+ {{{summary}}}
+ {{/summary}}
+ {{#conceptual}}
+ {{{conceptual}}}
+ {{/conceptual}}
+ {{#syntax}}
+ {{>partials/api.signature}}
+ {{/syntax}}
+ {{#remarks}}
+
+ {{{remarks}}}
+
+ {{/remarks}}
+ {{#example.0}}
+
+
{{r.ui.reference.examples}}
+ {{#example}}
+ {{{.}}}
+ {{/example}}
+
+ {{/example.0}}
+ {{#exceptions.0}}
+
+
{{r.ui.reference.exceptions}}
+
+ {{#exceptions}}
+
+
{{{type.specName.0.value}}}
+ {{{description}}}
+
+ {{/exceptions}}
+
+
+ {{/exceptions.0}}
+ {{>partials/api.seealso}}
+
+ {{/children}}
+
+
+{{/children}}
diff --git a/documents/templates/partials/site.footer.tmpl.partial b/documents/templates/partials/site.footer.tmpl.partial
new file mode 100644
index 00000000..eb5bf3f5
--- /dev/null
+++ b/documents/templates/partials/site.footer.tmpl.partial
@@ -0,0 +1,13 @@
+
diff --git a/documents/templates/partials/site.header.tmpl.partial b/documents/templates/partials/site.header.tmpl.partial
new file mode 100644
index 00000000..e0b77dd4
--- /dev/null
+++ b/documents/templates/partials/site.header.tmpl.partial
@@ -0,0 +1,31 @@
+
diff --git a/documents/templates/partials/site.language.tmpl.partial b/documents/templates/partials/site.language.tmpl.partial
new file mode 100644
index 00000000..9ea41674
--- /dev/null
+++ b/documents/templates/partials/site.language.tmpl.partial
@@ -0,0 +1,18 @@
+
diff --git a/documents/templates/partials/site.member-finder.tmpl.partial b/documents/templates/partials/site.member-finder.tmpl.partial
new file mode 100644
index 00000000..e250986c
--- /dev/null
+++ b/documents/templates/partials/site.member-finder.tmpl.partial
@@ -0,0 +1,33 @@
+{{#apiHasMemberFinder}}
+
+
+
+
{{r.ui.members.title}}
+
{{fullName.0.value}}
+
+ Esc
+
+
+
+
+
{{r.ui.members.empty}}
+
+
+
+{{/apiHasMemberFinder}}
diff --git a/documents/templates/partials/site.search.tmpl.partial b/documents/templates/partials/site.search.tmpl.partial
new file mode 100644
index 00000000..b6ceae68
--- /dev/null
+++ b/documents/templates/partials/site.search.tmpl.partial
@@ -0,0 +1,40 @@
+{{#_enableSearch}}
+
+ {{r.ui.search.title}}
+
+
+
{{r.ui.search.hint}}
+
+
+
{{r.ui.search.more}}
+
+
+
+{{/_enableSearch}}
diff --git a/documents/templates/partials/site.sidebar.tmpl.partial b/documents/templates/partials/site.sidebar.tmpl.partial
new file mode 100644
index 00000000..51689508
--- /dev/null
+++ b/documents/templates/partials/site.sidebar.tmpl.partial
@@ -0,0 +1,48 @@
+{{^_disableToc}}
+
+
+
+
+ {{#_apiReference}}
+
+
+
+ {{r.ui.api.namespace}}
+ {{r.ui.api.loadingNamespaces}}
+
+ {{r.ui.api.loadingNamespaces}}
+
+
+
+
+
+
+
+
+
+
+
+ {{r.ui.api.types}}
+
+ {{#apiHasMemberFinder}}
+
+ {{>partials/api.member-launcher}}
+
+ {{/apiHasMemberFinder}}
+
+ {{/_apiReference}}
+ {{^_apiReference}}
+
+ {{/_apiReference}}
+
+
+
+{{/_disableToc}}
diff --git a/documents/templates/public/api.css b/documents/templates/public/api.css
new file mode 100644
index 00000000..43c3bd25
--- /dev/null
+++ b/documents/templates/public/api.css
@@ -0,0 +1,983 @@
+/* Zenith API reference: generated content, site-owned presentation. */
+body.zenith-theme[data-section="api"] > main > .content {
+ padding-block: 40px 72px;
+}
+
+body.zenith-theme[data-section="api"] > main > .toc-offcanvas {
+ padding: 40px 22px 24px 0;
+}
+
+.api-reference {
+ min-width: 0;
+}
+
+/* A namespace picker and flat type list replace the expanding namespace tree. */
+.api-browser {
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ height: 100%;
+ font-size: 13px;
+}
+
+.api-browser-controls {
+ flex-shrink: 0;
+ margin-bottom: 24px;
+ overflow: hidden;
+ border: 1px solid var(--zenith-control-border);
+ border-radius: var(--zenith-radius);
+ background: var(--zenith-surface);
+}
+
+.api-namespace-label {
+ display: block;
+ margin-bottom: 4px;
+ color: var(--zenith-muted);
+ font: 11px/1.4 var(--bs-body-font-family);
+}
+
+.api-namespace-picker {
+ border-radius: calc(var(--zenith-radius) - 1px) calc(var(--zenith-radius) - 1px) 0 0;
+ position: relative;
+ min-height: 44px;
+ padding: 11px 32px 11px 12px;
+ color: var(--zenith-text);
+ font: 500 13px/1.6 var(--bs-body-font-family);
+}
+
+#api-namespace-name {
+ display: block;
+ overflow-wrap: anywhere;
+ pointer-events: none;
+}
+
+.api-namespace-picker select {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0;
+ cursor: pointer;
+}
+
+.api-namespace-picker select:focus {
+ outline: none;
+}
+
+.api-namespace-picker, .api-type-search {
+ transition: box-shadow var(--zenith-state-duration), background-color var(--zenith-state-duration);
+}
+
+.api-namespace-picker:hover, .api-type-search:hover {
+ background: var(--zenith-control-bg);
+}
+
+/* The focus edge stays inside the field, clear of the scrolling sidebar's clip. */
+.api-namespace-picker:focus-within, .api-type-search:focus-within {
+ box-shadow: inset 0 0 0 2px var(--zenith-focus);
+}
+
+.api-namespace-picker > i {
+ position: absolute;
+ right: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: var(--zenith-muted);
+ pointer-events: none;
+}
+
+.api-type-search {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ min-height: 42px;
+ padding: 8px 12px;
+ border-top: 1px solid var(--zenith-border);
+ border-radius: 0 0 calc(var(--zenith-radius) - 1px) calc(var(--zenith-radius) - 1px);
+ color: var(--zenith-muted);
+}
+
+.api-type-search input {
+ appearance: none;
+ -webkit-appearance: none;
+ width: 100%;
+ min-width: 0;
+ padding: 0;
+ border: 0;
+ border-radius: 0;
+ outline: 0;
+ box-shadow: none;
+ background: none;
+ color: var(--zenith-text);
+ font: 13px/1.5 var(--bs-body-font-family);
+}
+
+.api-type-search input::placeholder {
+ color: var(--zenith-muted);
+}
+
+.api-type-search input::-webkit-search-cancel-button, .api-type-search input::-webkit-search-decoration {
+ display: none;
+}
+
+#api-type-clear {
+ flex-shrink: 0;
+ display: grid;
+ place-items: center;
+ width: 28px;
+ height: 28px;
+ margin: -2px -6px -2px 0;
+ padding: 0;
+ border: 0;
+ background: none;
+ color: #aebdd0;
+ font-size: 12px;
+ cursor: pointer;
+}
+
+#api-type-clear[hidden] {
+ display: none;
+}
+
+#api-type-clear:hover {
+ color: var(--zenith-text);
+}
+
+#api-type-clear:focus-visible {
+ outline-offset: -2px;
+}
+
+.api-browser-caption {
+ flex-shrink: 0;
+ margin-bottom: 10px;
+ color: var(--zenith-muted);
+ font-size: 11px;
+}
+
+.api-type-scroll {
+ position: relative;
+ /* Keep wrapped links usable; the outer pane scrolls on short screens. */
+ min-height: 4lh;
+ flex: 1;
+ overflow-y: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+}
+
+#api-type-list {
+ margin: 0;
+ padding: 0 6px 0 0;
+ list-style: none;
+}
+
+#api-type-list li {
+ margin: 2px 0;
+}
+
+#api-type-list a {
+ display: block;
+ padding: 8px 10px;
+ border-radius: 4px;
+ color: #aeb8c7;
+ font-size: 13px;
+ line-height: 1.5;
+ text-decoration: none;
+ overflow-wrap: anywhere;
+ transition: background-color var(--zenith-state-duration), color var(--zenith-state-duration);
+}
+
+#api-type-list a:hover {
+ background: var(--zenith-surface);
+ color: var(--zenith-text);
+}
+
+#api-type-list a:focus-visible {
+ outline-offset: -2px;
+}
+
+#api-type-list a[aria-current] {
+ background: var(--zenith-graphite);
+ color: var(--zenith-text);
+ font-weight: 600;
+}
+
+#api-type-list small {
+ display: block;
+ margin-top: 3px;
+ color: var(--zenith-muted);
+ font-size: 10px;
+}
+
+.api-browser-status {
+ color: var(--zenith-muted);
+ font-size: 12px;
+}
+
+/* Identity and compact declarations. */
+.api-heading {
+ margin-bottom: 26px;
+}
+
+.api-reference .api-heading h1 {
+ margin: 0;
+ min-width: 0;
+ font-size: clamp(30px, 3.1vw, 46px);
+ font-weight: 600;
+ line-height: 1.15;
+ letter-spacing: -.045em;
+ overflow-wrap: anywhere;
+}
+
+.api-heading > .api-intro {
+ margin-top: 16px;
+}
+
+.api-summary {
+ color: var(--zenith-body);
+ font-size: 14px;
+ line-height: 1.75;
+}
+
+.api-heading > .api-summary {
+ margin-top: 18px;
+}
+
+.api-reference :is(.api-summary, .api-value-description, .api-parameters dd, .api-member-notes) > :is(p, ul, ol, dl) {
+ margin: 0 0 12px;
+}
+
+.api-reference :is(.api-summary, .api-value-description, .api-parameters dd, .api-member-notes) > :last-child {
+ margin-bottom: 0;
+}
+
+.api-reference :is(.api-summary, .api-member-notes) :is(ul, ol) {
+ padding-left: 20px;
+}
+
+.api-intro {
+ max-width: 620px;
+ margin: 0;
+ color: var(--zenith-body);
+ font-size: 16px;
+}
+
+.api-declaration {
+ position: relative;
+ margin-bottom: 12px;
+ border: 1px solid var(--zenith-control-border);
+ border-radius: var(--zenith-radius);
+ background: var(--zenith-surface);
+}
+
+.api-reference pre, .api-reference pre[data-language] {
+ --code-content-padding: 15px 16px;
+ margin: 14px 0;
+ padding: var(--code-content-padding);
+ border: 1px solid var(--zenith-border);
+ border-radius: 5px;
+ background: var(--zenith-code);
+ font-size: 13px;
+ line-height: 1.7;
+ box-shadow: none;
+}
+
+/* Declarations stay compact; examples retain the shared language/copy header. */
+.api-reference pre[data-language]:has(> code[data-api-symbol])::before {
+ content: none;
+}
+
+.api-reference .api-declaration pre {
+ position: static;
+ margin: 0;
+ padding: 20px 98px 20px 20px;
+ border: 0;
+ background: none;
+}
+
+.api-declaration pre > code, .api-member > pre > code {
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+}
+
+/* Source and copy share one quiet corner instead of competing with each text row. */
+.api-reference .api-source,
+.api-reference :is(.api-declaration, .api-member) > pre .code-action {
+ position: absolute;
+ top: 14px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ min-height: 32px;
+ padding: 5px;
+ border: 0;
+ border-radius: 5px;
+ background: none;
+ color: var(--zenith-action);
+ font-size: 15px;
+ line-height: 1;
+ text-decoration: none;
+ opacity: .45;
+ transition: opacity var(--zenith-state-duration), background-color var(--zenith-state-duration);
+}
+
+.api-reference .api-source {
+ right: 50px;
+}
+
+.api-reference :is(.api-declaration, .api-member) > pre .code-action {
+ right: 14px;
+}
+
+.api-reference :is(.api-declaration, .api-member):is(:hover, :focus-within) :is(.api-source, .code-action) {
+ opacity: 1;
+}
+
+.api-reference .api-source:hover, .api-reference :is(.api-declaration, .api-member) > pre .code-action:hover {
+ background: var(--zenith-hover);
+}
+
+@media (hover: none) {
+ .api-reference .api-source, .api-reference :is(.api-declaration, .api-member) > pre .code-action {
+ opacity: .8;
+ }
+}
+
+.api-reference :not(pre) > code {
+ padding: 1px 4px;
+ border-radius: 3px;
+ background: var(--zenith-control-bg);
+ color: var(--zenith-body);
+}
+
+.api-reference a.xref {
+ color: var(--zenith-link);
+ text-decoration: none;
+}
+
+.api-reference a.xref:hover {
+ color: var(--zenith-text);
+ text-decoration: underline;
+}
+
+/* Context stays available without competing with the API itself. */
+.api-relationships {
+ margin: 0 0 24px;
+ border-block: 1px solid var(--zenith-border);
+ font-size: 12px;
+}
+
+.api-reference summary {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 14px;
+ padding: 13px 2px;
+ color: #a6bad5;
+ cursor: pointer;
+ list-style: none;
+}
+
+.api-reference summary::-webkit-details-marker {
+ display: none;
+}
+
+.api-reference summary::after {
+ content: "+";
+ flex-shrink: 0;
+ width: 20px;
+ color: #c4daf6;
+ font: 18px/1 var(--bs-font-monospace);
+ text-align: center;
+ transition: transform var(--zenith-state-duration);
+}
+
+.api-reference details[open] > summary::after {
+ transform: rotate(45deg);
+}
+
+.api-reference summary:hover {
+ color: var(--zenith-text);
+}
+
+.api-relationships > summary {
+ padding: 12px 8px;
+ color: var(--zenith-body);
+ font-size: 12px;
+ font-weight: 500;
+}
+
+.api-relationships > summary:hover {
+ background: var(--zenith-surface);
+}
+
+.api-reference details[open] > :not(summary) {
+ animation: zenith-panel-in 220ms ease-out;
+}
+
+.api-relationships-body {
+ display: grid;
+ gap: 22px;
+ padding: 10px 14px 20px;
+}
+
+.api-reference .api-metadata {
+ display: grid;
+ gap: 8px;
+ margin: 0;
+}
+
+.api-metadata:empty {
+ display: none;
+}
+
+.api-metadata > div {
+ display: grid;
+ grid-template-columns: 82px minmax(0, 1fr);
+ gap: 12px;
+}
+
+.api-metadata dt {
+ color: var(--zenith-muted);
+ font-weight: 400;
+}
+
+.api-metadata dd {
+ margin: 0;
+ color: var(--zenith-body);
+ overflow-wrap: anywhere;
+}
+
+.api-reference .api-relation h4 {
+ margin: 0 0 10px;
+ color: var(--zenith-text);
+ font-size: 12px;
+}
+
+.api-relation ul, .api-related ul {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.api-relation li {
+ max-width: 100%;
+ margin: 0;
+ padding: 5px 9px;
+ border: 1px solid var(--zenith-border);
+ border-radius: 4px;
+ background: var(--zenith-surface);
+ overflow-wrap: anywhere;
+}
+
+.api-reference :is(.api-group-heading h2, .api-member-heading h3, .api-enum-value h3):target {
+ animation: api-location-cue 900ms ease-out;
+}
+
+@keyframes api-location-cue {
+ from {
+ color: var(--zenith-text);
+ text-shadow: 0 0 18px #a9cfff99;
+ }
+
+ to {
+ color: var(--zenith-text);
+ text-shadow: none;
+ }
+}
+
+.api-group {
+ min-width: 0;
+ margin: 0 0 40px;
+}
+
+.api-group-heading {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.api-reference .api-group-heading h2 {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 22px;
+ font-weight: 550;
+ letter-spacing: -.5px;
+}
+
+/* Namespace directories use one dense, scannable grid. */
+.api-type-grid, .api-enum-values {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 0;
+ overflow: hidden;
+ border: 1px solid var(--zenith-control-border);
+ border-radius: var(--zenith-radius);
+ background: var(--zenith-surface);
+}
+
+.api-type-entry {
+ position: relative;
+ display: flex;
+ flex-wrap: wrap;
+ align-content: center;
+ gap: 9px;
+ min-width: 0;
+ min-height: 68px;
+ padding: 17px 18px;
+ background: var(--zenith-surface);
+ transition: background-color var(--zenith-state-duration);
+}
+
+.api-type-entry > .api-summary a {
+ position: relative;
+ z-index: 1;
+ pointer-events: auto;
+}
+
+.api-type-entry, .api-enum-value {
+ box-shadow: inset -1px -1px 0 var(--zenith-border);
+}
+
+.api-type-entry:hover, .api-type-entry:focus-within {
+ background: var(--zenith-hover);
+}
+
+.api-type-entry > a {
+ flex: 1;
+ min-width: 0;
+ color: var(--zenith-text);
+ font: 500 13px/1.6 var(--bs-font-monospace);
+ text-decoration: none;
+ overflow-wrap: anywhere;
+}
+
+.api-type-entry > a::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+}
+
+.api-type-entry > a:focus-visible {
+ outline: none;
+}
+
+.api-type-entry:focus-within {
+ outline: 2px solid var(--zenith-focus);
+ outline-offset: -3px;
+}
+
+.api-type-entry > .api-summary {
+ pointer-events: none;
+ flex-basis: 100%;
+ font-size: 12px;
+}
+
+.api-enum-value {
+ min-width: 0;
+ padding: 17px 18px;
+ background: var(--zenith-surface);
+}
+
+.api-reference .api-enum-value h3 {
+ margin: 0;
+ font-size: 13px;
+ line-height: 1.7;
+ font-weight: 400;
+}
+
+.api-reference .api-enum-value code {
+ padding: 0;
+ background: none;
+ color: var(--zenith-body);
+ font: inherit;
+ font-family: var(--bs-font-monospace);
+}
+
+.api-enum-value:has(:target) {
+ background: var(--zenith-hover);
+}
+
+.api-enum-value > .api-summary {
+ margin-top: 10px;
+ font-size: 12px;
+}
+
+/* Members keep their original IDs and complete signatures. */
+.api-members {
+ display: grid;
+ gap: 14px;
+}
+
+.api-member {
+ position: relative;
+ min-width: 0;
+ padding: 20px;
+ border: 1px solid var(--zenith-control-border);
+ border-radius: var(--zenith-radius);
+ background: var(--zenith-surface);
+}
+
+.api-member:target, .api-member:has(:target) {
+ border-color: var(--zenith-focus);
+}
+
+/* Focus from the finder belongs to the member block, not a box around static text. */
+.api-reference :is(.api-member-heading h3, .api-enum-value h3)[tabindex="-1"]:focus {
+ outline: none;
+}
+
+.api-member:has(.api-member-heading h3:focus) {
+ border-color: var(--zenith-focus);
+ box-shadow: inset 0 0 0 1px #8aafd338;
+}
+
+.api-enum-value:has(h3:focus) {
+ background: var(--zenith-hover);
+ box-shadow: inset 0 0 0 1px var(--zenith-focus);
+}
+
+.api-member-heading {
+ padding-right: 76px;
+}
+
+.api-reference .api-member > pre {
+ position: static;
+ margin: 7px 0 0;
+ padding: 0;
+ border: 0;
+ border-radius: 0;
+ background: none;
+}
+
+.api-reference .api-member-heading h3 {
+ min-width: 0;
+ margin: 0;
+ color: var(--zenith-text);
+ font: 550 17px/1.5 var(--bs-font-monospace);
+ letter-spacing: -.04em;
+ overflow-wrap: anywhere;
+}
+
+.api-member > .api-summary {
+ margin-top: 12px;
+}
+
+.api-reference .api-value {
+ display: flex;
+ align-items: baseline;
+ gap: 14px;
+ margin: 0;
+ font-size: 12px;
+}
+
+.api-value > dt {
+ color: var(--zenith-muted);
+ font-weight: 400;
+}
+
+.api-value > dd {
+ min-width: 0;
+ margin: 0;
+ color: var(--zenith-body);
+}
+
+.api-value dd > div {
+ margin-top: 8px;
+}
+
+.api-value-description {
+ margin: 0;
+ color: var(--zenith-body);
+ font-size: 13px;
+}
+
+.api-signature-section, .api-member-notes, .api-related {
+ margin-top: 18px;
+}
+
+.api-reference :is(.api-signature-section, .api-member-notes, .api-related) > h4 {
+ margin: 0 0 10px;
+ color: #bbc9dc;
+ font: 600 12px/1.5 var(--bs-body-font-family);
+}
+
+.api-reference .api-parameters {
+ margin: 0;
+ font-size: 13px;
+ line-height: 1.7;
+}
+
+.api-parameters > div {
+ display: grid;
+ grid-template-columns: minmax(120px, 20%) minmax(0, 1fr);
+ gap: 20px;
+ padding: 10px 0;
+ border-top: 1px solid var(--zenith-border);
+}
+
+.api-parameters dt {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+ gap: 8px 12px;
+ min-width: 0;
+ color: var(--zenith-body);
+ font-weight: 400;
+}
+
+.api-parameters dd {
+ margin: 0;
+ color: var(--zenith-body);
+}
+
+.api-parameters dd:empty {
+ display: none;
+}
+
+.api-parameters > div:has(dd:empty) {
+ grid-template-columns: 1fr;
+}
+
+.api-parameters :is(dt, dd) > :last-child {
+ margin-bottom: 0;
+}
+
+.api-member-notes {
+ color: var(--zenith-body);
+ font-size: 13px;
+}
+
+.api-member-notes table {
+ margin: 0;
+ font-size: 13px;
+}
+
+.api-member-notes :is(th, td) {
+ padding: 9px 12px;
+}
+
+.api-related li {
+ margin: 0;
+ font-size: 12px;
+}
+
+/* API overview. */
+.api-start-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 14px;
+ margin: 32px 0 38px;
+}
+
+.api-start-grid > a {
+ padding: 24px;
+ border: 1px solid var(--zenith-control-border);
+ border-radius: var(--zenith-radius);
+ background: var(--zenith-surface);
+ text-decoration: none;
+}
+
+.api-start-grid > a:hover {
+ border-color: var(--zenith-border-hover);
+ background: var(--zenith-hover);
+}
+
+.api-start-grid span {
+ display: block;
+ margin-bottom: 14px;
+ color: var(--zenith-muted);
+ font-size: 11px;
+}
+
+.api-start-grid strong {
+ color: var(--zenith-text);
+ font: 500 18px/1.5 var(--bs-font-monospace);
+ overflow-wrap: anywhere;
+}
+
+.api-start-grid p {
+ margin: 12px 0 0;
+ color: var(--zenith-body);
+ font-size: 13px;
+}
+
+.api-namespace-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.api-namespace-card {
+ display: block;
+ padding: 20px;
+ border: 1px solid var(--zenith-control-border);
+ border-radius: 5px;
+ background: var(--zenith-surface);
+ color: var(--zenith-link);
+ text-decoration: none;
+ overflow-wrap: anywhere;
+}
+
+.api-namespace-card:hover {
+ background: var(--zenith-hover);
+ border-color: var(--zenith-border-hover);
+ color: var(--zenith-text);
+}
+
+.api-namespace-card strong {
+ font-size: 14px;
+ font-weight: 500;
+}
+
+/* Page-local member lookup stays in navigation until requested. */
+.api-member-access {
+ flex-shrink: 0;
+ margin-top: 14px;
+ padding-top: 14px;
+ border-top: 1px solid var(--zenith-border);
+}
+
+.api-member-access:has(.api-member-launcher[hidden]) {
+ display: none;
+}
+
+.api-member-launcher {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 9px;
+ width: 100%;
+ min-height: 40px;
+ padding: 10px;
+ border: 0;
+ border-radius: 5px;
+ background: var(--zenith-control-bg);
+ color: var(--zenith-action);
+ font: 500 12px/1.5 var(--bs-body-font-family);
+ cursor: pointer;
+ transition: color var(--zenith-state-duration), background-color var(--zenith-state-duration);
+}
+
+.api-member-launcher:focus-visible {
+ outline-offset: -2px;
+}
+
+.api-member-launcher[hidden] {
+ display: none;
+}
+
+.api-member-launcher:hover {
+ background: var(--zenith-hover);
+ color: var(--zenith-text);
+}
+
+.api-member-launcher kbd {
+ margin-left: auto;
+ padding: 0;
+ background: none;
+ color: var(--zenith-muted);
+ font: 10px/1.5 var(--bs-font-monospace);
+}
+
+.api-mobile-tools, .api-menu-member {
+ display: none;
+}
+
+.api-menu-member kbd, .api-mobile-tools .api-member-launcher kbd {
+ display: none;
+}
+
+@media (max-width: 1199.98px) {
+ .api-namespace-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (min-width: 768px) and (max-width: 999.98px) {
+ .api-type-grid, .api-enum-values, .api-start-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 767.98px) {
+ body.zenith-theme[data-section="api"] > main > .content {
+ padding-top: 0;
+ }
+
+ body.zenith-theme[data-section="api"] > main > .toc-offcanvas {
+ padding: 0;
+ }
+
+ .api-type-search input {
+ font-size: 16px;
+ }
+
+ .api-reference .api-heading h1 {
+ font-size: 34px;
+ }
+
+ .api-member {
+ padding: 18px 16px;
+ }
+
+ .api-member-heading {
+ padding-right: 0;
+ }
+
+ .api-reference .api-member-heading h3 {
+ font-size: 15px;
+ min-height: 32px;
+ padding-right: 76px;
+ }
+
+ body.zenith-theme > main > .content > .api-mobile-tools {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-bottom: 24px;
+ }
+
+ .api-mobile-tools > .contents-toggle {
+ display: inline-flex;
+ margin: 0;
+ }
+
+ .api-mobile-tools > .api-member-launcher {
+ width: auto;
+ }
+
+ .menu-open .api-menu-member {
+ display: flex;
+ justify-content: flex-start;
+ margin-top: 14px;
+ }
+
+ .api-member-access kbd {
+ display: none;
+ }
+
+ .api-start-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .api-parameters > div {
+ grid-template-columns: 1fr;
+ gap: 6px;
+ }
+}
+
+@media (max-width: 479.98px) {
+ .api-type-grid, .api-enum-values, .api-namespace-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .api-reference .api-heading h1 {
+ font-size: 30px;
+ }
+}
diff --git a/documents/templates/public/api.js b/documents/templates/public/api.js
new file mode 100644
index 00000000..2eb634d5
--- /dev/null
+++ b/documents/templates/public/api.js
@@ -0,0 +1,132 @@
+import { t } from './resources.js';
+import { pageUrl } from './languages.js';
+
+// Use DocFX's generated TOC as the source of truth for the custom API browser.
+export async function initializeApi() {
+ const browser = document.getElementById('api-browser');
+ if (!browser) return;
+
+ const picker = document.getElementById('api-namespace');
+ const filter = document.getElementById('api-type-filter');
+ const clear = document.getElementById('api-type-clear');
+ const list = document.getElementById('api-type-list');
+ const scroller = browser.querySelector('.api-type-scroll');
+ const status = document.getElementById('api-browser-status');
+ const scope = document.getElementById('api-type-scope');
+ filter.disabled = true;
+
+ function writeNamespace(element, name) {
+ name.split('.').forEach((part, index) => {
+ if (index) element.append('.', document.createElement('wbr'));
+ element.append(part);
+ });
+ }
+
+ try {
+ const tocUrl = new URL(document.querySelector('meta[name="site:api-toc"]').content, location.href);
+ const response = await fetch(tocUrl, { cache: 'no-cache' });
+ if (!response.ok) throw new Error('API navigation could not be loaded.');
+ const documentToc = new DOMParser().parseFromString(await response.text(), 'text/html');
+ const readLink = link => ({ name: link.textContent.trim(), href: pageUrl(new URL(link.getAttribute('href'), tocUrl).href) });
+ const namespaces = [...documentToc.querySelectorAll('.nav.level1 > li')].map(item => {
+ const link = item.querySelector(':scope > a');
+ return { ...readLink(link), types: [...item.querySelectorAll(':scope > ul > li > a')].map(readLink) };
+ }).sort((a, b) => {
+ const rank = name => name === 'Zenith.NET' ? 0 : name.startsWith('Zenith.NET.') ? 1 : 2;
+ return rank(a.name) - rank(b.name) || a.name.localeCompare(b.name);
+ });
+ const current = href => new URL(href).pathname === location.pathname;
+ const activeNamespace = namespaces.find(item => current(item.href) || item.types.some(type => current(type.href))) || namespaces[0];
+ if (!activeNamespace) throw new Error('No namespaces were generated.');
+ const allTypes = namespaces.flatMap(namespace => namespace.types.map(type => ({ ...type, namespace: namespace.name })));
+ picker.replaceChildren(...namespaces.map(item => {
+ const option = document.createElement('option');
+ option.value = item.href;
+ option.textContent = item.name;
+ return option;
+ }));
+ picker.disabled = false;
+ filter.disabled = false;
+ picker.value = activeNamespace.href;
+ picker.title = activeNamespace.name;
+ const selectedName = document.getElementById('api-namespace-name');
+ selectedName.replaceChildren();
+ writeNamespace(selectedName, activeNamespace.name);
+ picker.addEventListener('change', () => location.assign(picker.value));
+
+ function renderTypes(resetScroll = true) {
+ const query = filter.value.trim().toLowerCase();
+ clear.hidden = !filter.value;
+ const entries = query
+ ? allTypes.filter(type => `${type.name} ${type.namespace}`.toLowerCase().includes(query))
+ : activeNamespace.types;
+ const fragment = document.createDocumentFragment();
+ for (const type of entries) {
+ const item = document.createElement('li');
+ const link = document.createElement('a');
+ const name = document.createElement('span');
+ // Keep identifiers intact while allowing breaks between their words.
+ type.name.split(/(?<=[a-z])(?=[A-Z])|(?<=[0-9A-Z])(?=[A-Z][a-z])|(?<=\.)/).forEach((part, index) => {
+ if (index) name.append(document.createElement('wbr'));
+ name.append(part);
+ });
+ link.href = type.href;
+ link.title = type.name;
+ link.append(name);
+ if (current(type.href)) link.setAttribute('aria-current', 'page');
+ if (query) {
+ const namespace = document.createElement('small');
+ writeNamespace(namespace, type.namespace);
+ link.append(namespace);
+ }
+ item.append(link);
+ fragment.append(item);
+ }
+ list.replaceChildren(fragment);
+ if (resetScroll) scroller.scrollTop = 0;
+ scope.textContent = query ? t('ui.api.matchingTypes') : t('ui.api.types');
+ status.hidden = entries.length > 0;
+ status.textContent = query ? t('ui.api.noTypes') : t('ui.api.noNamespaceTypes');
+ }
+ filter.addEventListener('input', () => renderTypes());
+ clear.addEventListener('click', () => {
+ filter.value = '';
+ renderTypes();
+ filter.focus();
+ });
+ filter.addEventListener('keydown', event => {
+ if (event.isComposing) return;
+ if (event.key === 'Escape') {
+ filter.value = '';
+ renderTypes();
+ } else if (event.key === 'ArrowDown') {
+ event.preventDefault();
+ list.querySelector('a')?.focus();
+ }
+ });
+ renderTypes();
+ const active = list.querySelector('[aria-current="page"]');
+ if (active) scroller.scrollTop = active.offsetTop - (scroller.clientHeight - active.offsetHeight) / 2;
+ // Apply page-owned state after history restores the browser's form values.
+ window.addEventListener('pageshow', () => setTimeout(() => {
+ picker.value = activeNamespace.href;
+ renderTypes(false);
+ }, 0));
+
+ const grid = document.getElementById('api-namespace-grid');
+ if (grid) {
+ for (const namespace of namespaces) {
+ const link = document.createElement('a');
+ link.href = namespace.href;
+ link.className = 'api-namespace-card';
+ const name = document.createElement('strong');
+ writeNamespace(name, namespace.name);
+ link.append(name);
+ grid.append(link);
+ }
+ }
+ } catch {
+ status.hidden = false;
+ status.textContent = t('ui.api.loadError');
+ }
+}
diff --git a/documents/templates/public/code-links.js b/documents/templates/public/code-links.js
new file mode 100644
index 00000000..86c137f8
--- /dev/null
+++ b/documents/templates/public/code-links.js
@@ -0,0 +1,68 @@
+import { t } from './resources.js';
+
+// Explicit DocFX references and generated API signatures share this DOM renderer.
+const pendingReferences = new WeakMap();
+
+export function collectCodeReferences(el) {
+ const references = new Map();
+ const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
+ let offset = 0;
+ while (walker.nextNode()) {
+ const node = walker.currentNode;
+ const anchor = node.parentElement.closest('a[href]');
+ if (anchor && el.contains(anchor)) {
+ if (!references.has(anchor)) references.set(anchor, { start: offset, end: offset, link: anchor });
+ references.get(anchor).end = offset + node.length;
+ }
+ offset += node.length;
+ }
+ return [...references.values()];
+}
+
+export function preserveCodeReferences({ el }) {
+ if (el.dataset.highlighted || !el.matches('article pre > code')) return;
+ const references = collectCodeReferences(el);
+ if (!references.length) return;
+ pendingReferences.set(el, references);
+ // The highlighter receives plain code, never authored HTML or link markup.
+ el.textContent = el.textContent;
+}
+
+export function restoreCodeReferences({ el }) {
+ const references = pendingReferences.get(el);
+ if (!references) return;
+ pendingReferences.delete(el);
+ for (const reference of references.reverse()) linkCodeRange(el, reference.start, reference.end, reference.link);
+}
+
+export function linkCodeRange(code, start, end, reference) {
+ if (start >= end) return;
+ const walker = document.createTreeWalker(code, NodeFilter.SHOW_TEXT);
+ let offset = 0;
+ let first;
+ let last;
+ while (walker.nextNode()) {
+ const node = walker.currentNode;
+ const next = offset + node.length;
+ if (!first && start < next) first = { node, offset: start - offset };
+ if (first && node.parentElement.closest('a')) return;
+ if (first && end <= next) {
+ last = { node, offset: end - offset };
+ break;
+ }
+ offset = next;
+ }
+ if (!first || !last) return;
+ const range = document.createRange();
+ range.setStart(first.node, first.offset);
+ range.setEnd(last.node, last.offset);
+ const link = document.createElement('a');
+ for (const attribute of ['href', 'target', 'rel', 'aria-label']) {
+ if (reference.hasAttribute(attribute)) link.setAttribute(attribute, reference.getAttribute(attribute));
+ }
+ link.className = 'code-reference';
+ link.title = reference.getAttribute('title') || t('ui.reference.open', { name: range.toString() });
+ link.append(range.extractContents());
+ range.insertNode(link);
+ range.detach();
+}
diff --git a/documents/templates/public/content.css b/documents/templates/public/content.css
new file mode 100644
index 00000000..c573c241
--- /dev/null
+++ b/documents/templates/public/content.css
@@ -0,0 +1,263 @@
+/* Code, tables, notices, and generated API content */
+article :not(pre) > code {
+ padding: 2px 5px;
+ border-radius: 4px;
+ background: var(--zenith-graphite);
+ color: var(--zenith-text);
+ font-size: .875em;
+}
+
+article pre {
+ --code-padding-inline: 20px;
+ --code-content-padding: 20px var(--code-padding-inline);
+ --code-header-height: 0px;
+ position: relative;
+ margin-block: 24px;
+ padding: var(--code-content-padding);
+ border: 1px solid var(--zenith-border);
+ border-radius: var(--zenith-radius);
+ background: var(--zenith-code);
+ color: var(--zenith-body);
+ font-size: 14px;
+ line-height: 1.65;
+ scrollbar-width: thin;
+ box-shadow: 0 8px 24px rgb(0 0 0 / 12%), inset 0 1px rgb(255 255 255 / 3%);
+}
+
+article pre[data-language] {
+ --code-header-height: 36px;
+ --code-content-padding: 16px var(--code-padding-inline) 20px;
+}
+
+article pre[data-language]::before {
+ content: attr(data-language);
+ position: absolute;
+ inset: 0 0 auto;
+ height: var(--code-header-height);
+ padding: 8px 14px;
+ border-bottom: 1px solid var(--zenith-border);
+ background: #22262e;
+ color: var(--zenith-muted);
+ font-family: var(--bs-body-font-family);
+ font-size: 11px;
+}
+
+.zenith-theme article pre code.hljs {
+ display: block;
+ padding: 0;
+ overflow: visible;
+ background: transparent;
+ color: var(--zenith-body);
+}
+
+/* The body owns its padding so the scrollbar sits at the frame's bottom edge. */
+.zenith-theme article pre:has(> code:not([data-api-symbol])) {
+ padding: var(--code-header-height) 0 0;
+ overflow: hidden;
+}
+
+.zenith-theme article pre > code:not([data-api-symbol]) {
+ display: block;
+ padding: var(--code-content-padding);
+ overflow-x: auto;
+ scrollbar-width: thin;
+}
+
+.zenith-theme article pre > code:not([data-api-symbol]):focus-visible {
+ outline-offset: -2px;
+}
+
+article pre .code-action {
+ display: inline-flex;
+ top: 0;
+ right: 4px;
+ z-index: 1;
+ padding: 5px 9px;
+ opacity: 1;
+ font-size: 14px;
+}
+
+.zenith-theme article :is(.hljs-keyword, .hljs-selector-tag, .hljs-meta) {
+ color: #b9b5e7;
+}
+
+.zenith-theme article :is(.hljs-title, .hljs-built_in, .hljs-type) {
+ color: #a8d3c5;
+}
+
+.zenith-theme article :is(.hljs-title.function_, .hljs-function > .hljs-title) {
+ color: #dfcda8;
+}
+
+.zenith-theme article .hljs-property {
+ color: #c5d3e7;
+}
+
+.zenith-theme article .hljs-operator {
+ color: #a8b3c3;
+}
+
+.zenith-theme article :is(.hljs-string, .hljs-regexp, .hljs-addition) {
+ color: #b8cba6;
+}
+
+.zenith-theme article :is(.hljs-number, .hljs-literal, .hljs-symbol) {
+ color: #d7bf9a;
+}
+
+.zenith-theme article :is(.hljs-attr, .hljs-attribute, .hljs-variable, .hljs-params) {
+ color: #d0d4dc;
+}
+
+.zenith-theme article :is(.hljs-comment, .hljs-quote) {
+ color: #969fac;
+}
+
+.zenith-theme article .hljs-deletion {
+ color: #e5a4aa;
+}
+
+.zenith-theme article :is(.hljs-section, .hljs-name) {
+ color: #c6d7e9;
+}
+
+.line-highlight {
+ background: var(--zenith-graphite);
+}
+
+article .table-responsive {
+ margin-block: 24px;
+ border: 1px solid var(--zenith-border);
+ border-radius: var(--zenith-radius);
+ scrollbar-width: thin;
+}
+
+article .table {
+ --bs-table-color: var(--zenith-body);
+ --bs-table-bg: var(--zenith-canvas);
+ --bs-table-border-color: var(--zenith-border);
+ --bs-table-striped-bg: var(--zenith-surface);
+ --bs-table-striped-color: var(--zenith-body);
+ --bs-table-hover-bg: var(--zenith-surface);
+ --bs-table-hover-color: var(--zenith-text);
+ margin: 0;
+ font-size: 14px;
+ overflow-wrap: normal;
+}
+
+article .table > :not(caption) > * > * {
+ padding: 12px 16px;
+ border-width: 0 0 1px;
+}
+
+article .table th {
+ background: var(--zenith-surface);
+ color: var(--zenith-text);
+ font-weight: 600;
+}
+
+article table:focus-visible {
+ outline-offset: -2px;
+}
+
+article .table tbody tr:last-child > * {
+ border-bottom: 0;
+}
+
+article .alert {
+ --zenith-notice: #a8cce4;
+ margin-block: 24px;
+ padding: 16px 20px;
+ border: 1px solid var(--zenith-border);
+ border-left: 3px solid var(--zenith-notice);
+ border-radius: var(--zenith-radius);
+ background: var(--zenith-surface);
+ color: var(--zenith-body);
+}
+
+article .alert-warning {
+ --zenith-notice: #d7bf9a;
+}
+
+article .alert-danger {
+ --zenith-notice: #e5a4aa;
+}
+
+article .alert-success {
+ --zenith-notice: #b8cba6;
+}
+
+article .alert > h5 {
+ margin: 0 0 8px;
+ color: var(--zenith-notice);
+ font-size: 14px;
+}
+
+article .alert > :last-child {
+ margin-bottom: 0;
+}
+
+article .tabGroup .nav-link {
+ color: var(--zenith-muted);
+ font-size: 14px;
+}
+
+article .tabGroup .nav-link.active,
+article .tabGroup > section.card {
+ background: var(--zenith-surface);
+ border-color: var(--zenith-border);
+ color: var(--zenith-text);
+}
+
+article pre.mermaid {
+ padding: 24px;
+ text-align: center;
+ background: var(--zenith-surface);
+}
+
+article .xref {
+ overflow-wrap: anywhere;
+}
+
+/* References in prose and code keep the same meaning on API and tutorial pages. */
+article code a.code-reference, article a.xref > code {
+ color: var(--zenith-code-link);
+ font-weight: 500;
+ text-decoration: none;
+ transition: color var(--zenith-state-duration), background-color var(--zenith-state-duration);
+}
+
+.zenith-theme article code a.code-reference * {
+ color: inherit;
+}
+
+article code a.code-reference:hover, article a.xref:hover > code {
+ color: #fff;
+ background: #2d3b4c;
+}
+
+article code a.code-reference:focus-visible {
+ outline-offset: 2px;
+}
+
+article code[data-api-symbol] :is(.hljs-type, .hljs-title, .hljs-built_in) {
+ color: var(--zenith-body);
+}
+
+article code[data-api-symbol] a.code-reference,
+article code[data-api-symbol] a.code-reference * {
+ color: var(--zenith-code-link);
+ cursor: pointer;
+}
+
+article code[data-api-symbol] a.code-reference:hover,
+article code[data-api-symbol] a.code-reference:hover * {
+ color: #fff;
+}
+
+@media (max-width: 767.98px) {
+ article pre {
+ --code-padding-inline: 14px;
+ font-size: 13px;
+ }
+}
diff --git a/documents/templates/public/dialog.js b/documents/templates/public/dialog.js
new file mode 100644
index 00000000..e8280067
--- /dev/null
+++ b/documents/templates/public/dialog.js
@@ -0,0 +1,27 @@
+export function isBackdropClick(event, dialog) {
+ if (event.target !== dialog) return false;
+ const bounds = dialog.getBoundingClientRect();
+ return event.clientX < bounds.left || event.clientX > bounds.right
+ || event.clientY < bounds.top || event.clientY > bounds.bottom;
+}
+
+// Escape closes once, including Safari search inputs; arrows stay within visible results.
+export function bindDialogKeys(dialog, { items, close }) {
+ dialog.addEventListener('keydown', event => {
+ if (event.isComposing) return;
+ if (event.key === 'Escape') {
+ event.preventDefault();
+ event.stopPropagation();
+ close();
+ return;
+ }
+ if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return;
+ const links = [...dialog.querySelectorAll(items)].filter(link => link.getClientRects().length);
+ if (!links.length) return;
+ event.preventDefault();
+ const current = links.indexOf(document.activeElement);
+ const next = event.key === 'ArrowDown' ? (current + 1) % links.length
+ : (current <= 0 ? links.length - 1 : current - 1);
+ links[next].focus();
+ }, { capture: true });
+}
diff --git a/documents/templates/public/home.css b/documents/templates/public/home.css
new file mode 100644
index 00000000..c958f182
--- /dev/null
+++ b/documents/templates/public/home.css
@@ -0,0 +1,538 @@
+/* Home: typography and an interactive geometry study */
+body.zenith-theme[data-layout="landing"] > main.container-xxl {
+ display: block;
+}
+
+body.zenith-theme[data-layout="landing"] > main.container-xxl > .content {
+ padding: 0;
+}
+
+.home-page {
+ padding-block: 12px 76px;
+}
+
+.render-hero {
+ position: relative;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 28px;
+ align-items: start;
+}
+
+.render-copy {
+ position: relative;
+ z-index: 1;
+ padding-top: clamp(48px, 4vw, 80px);
+}
+
+.render-copy h1 {
+ margin: 0 0 24px;
+ font-size: clamp(46px, 5.1vw, 88px);
+ font-weight: 600;
+ letter-spacing: -.035em;
+ line-height: 1.08;
+}
+
+.render-copy h1 span {
+ color: #bdd7f7;
+}
+
+.render-description {
+ max-width: 460px;
+ color: var(--zenith-body);
+ font-size: 18px;
+ line-height: 1.65;
+}
+
+.hero-actions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 12px;
+ margin-top: 28px;
+}
+
+.backend-note {
+ margin: 24px 0 0;
+ color: var(--zenith-muted);
+ font-size: 13px;
+}
+
+.build-button {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 40px;
+ padding: 9px 16px;
+ border: 0;
+ background: var(--zenith-action);
+ color: #15191f;
+ font: 600 14px/1.5 var(--bs-body-font-family);
+ text-decoration: none;
+ border-radius: 5px;
+}
+
+.build-button:hover {
+ color: #101216;
+ background: #f7f9fc;
+ box-shadow: 0 0 20px #d8e4f31a;
+}
+
+.build-button.secondary {
+ background: var(--zenith-control-bg);
+ color: var(--zenith-action);
+ font-weight: 500;
+}
+
+.build-button.secondary:hover {
+ background: var(--zenith-hover);
+ color: #fff;
+}
+
+.geometry-stage {
+ display: grid;
+ grid-template-rows: minmax(0, 1fr) auto;
+ gap: 16px;
+ min-width: 0;
+ height: clamp(540px, 30vw, 640px);
+ padding-bottom: 16px;
+ background: radial-gradient(ellipse closest-side at 50% 45%, #789ac82b, #789ac800);
+}
+
+#home-geometry {
+ display: block;
+ width: 100%;
+ height: 100%;
+ min-height: 0;
+ touch-action: pan-y;
+ cursor: grab;
+}
+
+#home-geometry.is-dragging {
+ cursor: grabbing;
+}
+
+.scene-toolbar {
+ min-width: 0;
+ justify-self: center;
+}
+
+.scene-options {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 0 24px;
+}
+
+.scene-options button {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ min-height: 44px;
+ padding: 8px 0;
+ border: 0;
+ border-radius: 0;
+ background: none;
+ color: var(--zenith-muted);
+ font: 500 12px/1.5 var(--bs-body-font-family);
+ cursor: pointer;
+}
+
+.scene-options button::before {
+ content: "";
+ width: 8px;
+ height: 8px;
+ border: 1px solid #69788b;
+ border-radius: 50%;
+ transition: background-color 240ms, border-color 240ms, box-shadow 240ms;
+}
+
+.scene-options button:hover {
+ color: #fff;
+}
+
+.scene-options button[aria-pressed="true"] {
+ color: #e9f3ff;
+}
+
+.scene-options button[aria-pressed="true"]::before {
+ border-color: var(--zenith-action);
+ background: var(--zenith-action);
+ box-shadow: 0 0 10px #c6ddfa4d;
+}
+
+.home-paths {
+ padding-top: 38px;
+ border-top: 1px solid var(--zenith-border);
+}
+
+.paths-intro {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 24px;
+ margin-bottom: 28px;
+}
+
+.paths-intro h2 {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: clamp(28px, 3vw, 38px);
+ line-height: 1.15;
+ letter-spacing: -1.5px;
+}
+
+.paths-intro p {
+ margin: 0;
+ color: var(--zenith-muted);
+ font-size: 13px;
+ line-height: 1.8;
+}
+
+.path-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 18px;
+}
+
+.path-card {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ min-height: 310px;
+ padding: 26px;
+ border: 1px solid var(--zenith-control-border);
+ border-radius: var(--zenith-radius);
+ background: var(--zenith-surface);
+ color: var(--zenith-text);
+ text-decoration: none;
+}
+
+.path-label {
+ display: flex;
+ justify-content: space-between;
+ color: var(--zenith-muted);
+ font: 11px/1.5 var(--bs-font-monospace);
+}
+
+.path-label > span {
+ color: #6f89ac;
+}
+
+.path-art {
+ position: absolute;
+ top: 48px;
+ right: 22px;
+ width: 136px;
+ height: 76px;
+ opacity: .8;
+ pointer-events: none;
+ transition: opacity 240ms;
+}
+
+.path-card h3 {
+ margin: 90px 0 12px;
+ color: var(--zenith-text);
+ font-size: 24px;
+ font-weight: 550;
+ letter-spacing: -.65px;
+}
+
+.path-card p {
+ max-width: 310px;
+ margin: 0 0 24px;
+ color: var(--zenith-muted);
+ font-size: 13px;
+ line-height: 1.8;
+}
+
+.path-cta, .entry-action {
+ align-self: flex-start;
+ margin-top: auto;
+ color: var(--zenith-action);
+ font-size: 13px;
+ font-weight: 600;
+ transition: color var(--zenith-state-duration);
+}
+
+.path-card:hover {
+ border-color: var(--zenith-border-hover);
+ color: #fff;
+ box-shadow: inset 0 0 32px #b3d3ff0a;
+}
+
+.path-card:hover .path-art {
+ opacity: 1;
+}
+
+.path-card:is(:hover, :focus-visible) .path-cta {
+ color: #fff;
+}
+
+/* Learning hub */
+.learning-entry {
+ position: relative;
+ display: grid;
+ grid-template-rows: 92px auto 1fr auto;
+ align-content: start;
+ gap: 0;
+ min-width: 0;
+ padding: 28px;
+ border: 1px solid var(--zenith-control-border);
+ border-radius: var(--zenith-radius);
+ background: var(--zenith-surface);
+ color: var(--zenith-text);
+ text-decoration: none;
+}
+
+.learning-entry:hover {
+ border-color: var(--zenith-border-hover);
+ color: var(--zenith-text);
+ box-shadow: inset 0 0 0 1px #c3d6f029, inset 0 0 36px #c3d6f009;
+}
+
+.learning-entry h2 {
+ margin: 0 0 10px;
+ padding: 0;
+ border: 0;
+ font-size: 22px;
+ letter-spacing: -.6px;
+}
+
+.learning-entry p {
+ margin: 0 0 24px;
+ color: var(--zenith-muted);
+ font-size: 14px;
+ line-height: 1.7;
+}
+
+.learning-entry:is(:hover, :focus-visible) .entry-action {
+ color: #fff;
+}
+
+.learn-hub > h1 {
+ margin-bottom: 12px;
+ font-size: clamp(34px, 3.3vw, 48px);
+ letter-spacing: -1.5px;
+}
+
+.page-intro {
+ max-width: 640px;
+ color: var(--zenith-muted);
+ font-size: 16px;
+ line-height: 1.8;
+}
+
+.learning-entries {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 18px;
+ margin-top: 32px;
+}
+
+.triangle-diagram {
+ width: 80px;
+ height: 80px;
+ justify-self: start;
+ margin: -8px 0 0 -8px;
+ overflow: visible;
+}
+
+.learn-hub .hub-section-title {
+ margin: 44px 0 6px;
+ padding: 0;
+ border: 0;
+ font-size: 22px;
+}
+
+.section-description {
+ color: var(--zenith-muted);
+ font-size: 14px;
+}
+
+.concept-list {
+ border-top: 1px solid var(--zenith-border);
+}
+
+.concept-list > a {
+ display: grid;
+ grid-template-columns: 36px minmax(0, 1fr);
+ align-items: center;
+ gap: 16px;
+ padding: 19px 8px;
+ border-bottom: 1px solid var(--zenith-border);
+ text-decoration: none;
+}
+
+.concept-list > a:hover {
+ background: #ffffff04;
+}
+
+.concept-list > a > i:first-child {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ border: 0;
+ background: none;
+ color: #bbc6d6;
+}
+
+.concept-list strong {
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.concept-list small {
+ display: block;
+ margin-top: 2px;
+ color: var(--zenith-muted);
+ font-size: 12px;
+}
+
+/* Landing-page footer */
+body.zenith-theme > footer {
+ flex: 0 0 auto;
+ height: auto;
+ padding: 0;
+ background: var(--zenith-canvas);
+ font-size: 12px;
+}
+
+.zenith-theme > footer > div {
+ justify-content: space-between;
+ gap: 24px;
+ min-height: 88px;
+ padding-block: 24px;
+}
+
+.footer-brand {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ color: var(--zenith-text);
+ font-weight: 500;
+}
+
+.footer-caption {
+ color: var(--zenith-muted);
+ font-weight: 400;
+}
+
+.footer-links {
+ display: flex;
+ gap: 24px;
+}
+
+.footer-links a {
+ color: var(--zenith-muted);
+ text-decoration: none;
+}
+
+.footer-links a:hover {
+ color: var(--zenith-text);
+}
+
+@media (min-width: 768px) and (max-width: 899.98px) {
+ .learning-entries {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 767.98px) {
+ .zenith-theme > footer > div {
+ flex-wrap: wrap;
+ gap: 20px;
+ }
+
+ .footer-links {
+ gap: 20px;
+ }
+
+ .home-page {
+ padding-block: 8px 44px;
+ }
+
+ .render-hero {
+ grid-template-columns: 1fr;
+ gap: 8px;
+ }
+
+ .render-copy {
+ padding-top: 24px;
+ }
+
+ .render-copy h1 {
+ font-size: clamp(38px, 8.8vw, 56px);
+ letter-spacing: -.03em;
+ }
+
+ .render-description {
+ font-size: 16px;
+ }
+
+ .hero-actions {
+ margin-top: 24px;
+ }
+
+ .geometry-stage {
+ height: 360px;
+ padding-bottom: 12px;
+ }
+
+ .home-paths {
+ padding-top: 30px;
+ }
+
+ .paths-intro {
+ display: block;
+ margin-bottom: 22px;
+ }
+
+ .paths-intro h2 {
+ margin-bottom: 12px;
+ font-size: 32px;
+ }
+
+ .path-grid {
+ grid-template-columns: 1fr;
+ gap: 14px;
+ }
+
+ .path-card {
+ min-height: 240px;
+ padding: 24px;
+ }
+
+ .path-card h3 {
+ margin-top: 48px;
+ font-size: 23px;
+ }
+
+ .path-art {
+ top: 48px;
+ width: 96px;
+ height: 54px;
+ opacity: .6;
+ }
+
+ .path-card p {
+ max-width: 420px;
+ }
+
+ .learning-entries {
+ grid-template-columns: 1fr;
+ }
+
+ .learning-entry {
+ padding: 24px;
+ }
+
+ .concept-list > a {
+ gap: 12px;
+ }
+}
diff --git a/documents/templates/public/home.js b/documents/templates/public/home.js
new file mode 100644
index 00000000..0f31b6b9
--- /dev/null
+++ b/documents/templates/public/home.js
@@ -0,0 +1,200 @@
+// A slowly rotating Canvas geometry study, rendered only while visible.
+export function initializeHomeScene() {
+ const canvas = document.getElementById('home-geometry');
+ if (!canvas) return;
+ const context = canvas.getContext('2d');
+ if (!context) return;
+ const stage = canvas.parentElement;
+ const modeButtons = [...stage.querySelectorAll('[data-scene-mode]')];
+ const preference = window.matchMedia('(prefers-reduced-motion: reduce)');
+ const faces = [];
+ const steps = 144;
+ const sides = 12;
+ const rotationSpeed = .16;
+ const rotationAcceleration = .2;
+ const pointerSensitivity = .008;
+ const keyboardStep = .15;
+ const keyboardPauseMs = 1000;
+ const maxFrameSeconds = .05;
+ const maxPixelRatio = 2;
+ let yaw = .4;
+ let pitch = -.45;
+ let mode = 'surface';
+ let pointer;
+ let visible = false;
+ let suspended = false;
+ let frame = 0;
+ let lastTime = 0;
+ let speed = 0;
+ let pauseUntil = 0;
+ let dirty = true;
+ let width = 0, height = 0, ratio = 1;
+
+ const normalize = vector => {
+ const length = Math.hypot(...vector);
+ return vector.map(value => value / length);
+ };
+ const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
+ const lightDirection = normalize([-.55, -.8, 1]);
+ const halfDirection = normalize([lightDirection[0], lightDirection[1], lightDirection[2] + 1]);
+ const dot = (a, b) => a.reduce((sum, value, i) => sum + value * b[i], 0);
+ const center = t => [(1.2 + .38 * Math.cos(3 * t)) * Math.cos(2 * t), (1.2 + .38 * Math.cos(3 * t)) * Math.sin(2 * t), .58 * Math.sin(3 * t)];
+ const point = (step, side) => {
+ const t = step / steps * Math.PI * 2;
+ const c = center(t);
+ const next = center(t + .001);
+ const tangent = normalize(next.map((v, i) => v - c[i]));
+ const normal = normalize(cross(tangent, [0, 0, 1]));
+ const binormal = cross(tangent, normal);
+ const angle = side / sides * Math.PI * 2;
+ return c.map((v, i) => v + .26 * (normal[i] * Math.cos(angle) + binormal[i] * Math.sin(angle)));
+ };
+ for (let i = 0; i < steps; i++) {
+ for (let j = 0; j < sides; j++) {
+ const vertices = [point(i, j), point(i + 1, j), point(i + 1, j + 1), point(i, j + 1)];
+ const normal = normalize(cross(vertices[3].map((v, index) => v - vertices[0][index]), vertices[1].map((v, index) => v - vertices[0][index])));
+ faces.push({ vertices, normal });
+ }
+ }
+
+ function draw() {
+ if (!width || !height) return;
+ context.setTransform(ratio, 0, 0, ratio, 0, 0);
+ context.clearRect(0, 0, width, height);
+ const cy = Math.cos(yaw), sy = Math.sin(yaw), cp = Math.cos(pitch), sp = Math.sin(pitch);
+ const rotate = ([x, y, z]) => {
+ const a = x * cy + z * sy, b = z * cy - x * sy;
+ return [a, y * cp - b * sp, y * sp + b * cp];
+ };
+ const scale = Math.min(width, height) * .24;
+ const project = ([x, y, z]) => {
+ const perspective = 5 / (5 - z);
+ return [width / 2 + x * scale * perspective, height / 2 + y * scale * perspective];
+ };
+ const transformed = faces.map(face => {
+ const vertices = face.vertices.map(rotate);
+ return { vertices, normal: rotate(face.normal), depth: vertices.reduce((sum, v) => sum + v[2], 0) / 4 };
+ }).sort((a, b) => a.depth - b.depth);
+ for (const { vertices, normal, depth } of transformed) {
+ const diffuse = Math.max(0, dot(normal, lightDirection));
+ const highlight = Math.pow(Math.max(0, dot(normal, halfDirection)), 28);
+ const brightness = Math.round(18 + diffuse * 135 + highlight * 70 + (depth + 2) * 5);
+ const points = vertices.map(project);
+ context.beginPath();
+ points.forEach(([x, y], i) => i ? context.lineTo(x, y) : context.moveTo(x, y));
+ context.closePath();
+ if (mode === 'surface') {
+ context.fillStyle = `rgb(${brightness}, ${brightness + 4}, ${brightness + 10})`;
+ context.fill();
+ context.strokeStyle = 'rgba(220,230,244,.07)';
+ context.lineWidth = .5;
+ } else {
+ context.strokeStyle = `rgba(200,211,226,${.1 + (depth + 1.7) * .12})`;
+ context.lineWidth = .6;
+ }
+ context.stroke();
+ }
+ }
+
+ function schedule() {
+ if (!frame && visible && !document.hidden && !suspended) frame = requestAnimationFrame(tick);
+ }
+
+ function tick(time) {
+ frame = 0;
+ const elapsed = lastTime ? Math.min((time - lastTime) / 1000, maxFrameSeconds) : 0;
+ lastTime = time;
+ const rotating = !preference.matches && !pointer && time >= pauseUntil;
+ if (rotating) {
+ // Ease back into a roughly 40-second revolution after manual input.
+ speed = Math.min(rotationSpeed, speed + elapsed * rotationAcceleration);
+ yaw = (yaw - speed * elapsed) % (Math.PI * 2);
+ }
+ if (dirty || rotating) draw();
+ dirty = false;
+ if (!preference.matches && !pointer) schedule();
+ }
+
+ function syncMotion() {
+ cancelAnimationFrame(frame);
+ frame = 0;
+ lastTime = 0;
+ speed = 0;
+ if (dirty || !preference.matches && !pointer) schedule();
+ }
+
+ function render() {
+ dirty = true;
+ schedule();
+ }
+
+ preference.addEventListener('change', syncMotion);
+
+ modeButtons.forEach(button => {
+ button.addEventListener('click', () => {
+ mode = button.dataset.sceneMode;
+ modeButtons.forEach(item => item.setAttribute('aria-pressed', String(item === button)));
+ render();
+ });
+ });
+ canvas.addEventListener('pointerdown', event => {
+ if (event.button !== 0 || pointer) return;
+ pointer = { id: event.pointerId, x: event.clientX, y: event.clientY };
+ canvas.setPointerCapture(event.pointerId);
+ canvas.classList.add('is-dragging');
+ syncMotion();
+ });
+ canvas.addEventListener('pointermove', event => {
+ if (!pointer || pointer.id !== event.pointerId) return;
+ yaw += (event.clientX - pointer.x) * pointerSensitivity;
+ pitch += (event.clientY - pointer.y) * pointerSensitivity;
+ pointer.x = event.clientX;
+ pointer.y = event.clientY;
+ render();
+ });
+ const release = () => {
+ if (!pointer) return;
+ pointer = undefined;
+ canvas.classList.remove('is-dragging');
+ syncMotion();
+ };
+ canvas.addEventListener('pointerup', release);
+ canvas.addEventListener('pointercancel', release);
+ canvas.addEventListener('lostpointercapture', release);
+ canvas.addEventListener('keydown', event => {
+ const delta = { ArrowLeft: [-keyboardStep, 0], ArrowRight: [keyboardStep, 0], ArrowUp: [0, -keyboardStep], ArrowDown: [0, keyboardStep] }[event.key];
+ if (!delta) return;
+ event.preventDefault();
+ yaw += delta[0];
+ pitch += delta[1];
+ pauseUntil = performance.now() + keyboardPauseMs;
+ speed = 0;
+ render();
+ });
+ new ResizeObserver(() => {
+ ({ width, height } = canvas.getBoundingClientRect());
+ ratio = Math.min(devicePixelRatio || 1, maxPixelRatio);
+ const pixelWidth = Math.round(width * ratio), pixelHeight = Math.round(height * ratio);
+ if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) {
+ canvas.width = pixelWidth;
+ canvas.height = pixelHeight;
+ }
+ render();
+ }).observe(canvas);
+ new IntersectionObserver(([entry]) => {
+ visible = entry.isIntersecting;
+ syncMotion();
+ }).observe(canvas);
+ document.addEventListener('visibilitychange', syncMotion);
+ window.addEventListener('pagehide', () => {
+ suspended = true;
+ release();
+ syncMotion();
+ });
+ window.addEventListener('pageshow', () => {
+ suspended = false;
+ dirty = true;
+ syncMotion();
+ });
+ syncMotion();
+}
diff --git a/documents/templates/public/languages.js b/documents/templates/public/languages.js
new file mode 100644
index 00000000..e503cc3a
--- /dev/null
+++ b/documents/templates/public/languages.js
@@ -0,0 +1,105 @@
+import { applyResources, resourceState, setStrings } from './resources.js';
+import { closeHeaderMenu } from './site.js';
+
+export const siteRoot = new URL(document.querySelector('meta[name="site:root"]').content || './', location.href);
+export const sourceLanguage = resourceState.sourceLanguage;
+export const edition = { code: sourceLanguage };
+const preferenceKey = 'zenith-language:' + siteRoot.pathname;
+const available = resourceState.languages.filter(language => language.available);
+const resourceTimeoutMs = 8000;
+
+function savedLanguage() {
+ try { return localStorage.getItem(preferenceKey); } catch { return null; }
+}
+
+function matchLanguage(code) {
+ if (!code) return;
+ const exact = available.find(language => language.code.toLowerCase() === code.toLowerCase());
+ if (exact) return exact.code;
+ let locale;
+ try { locale = new Intl.Locale(code); } catch { return; }
+ return (locale.language === 'zh'
+ ? available.find(language => language.code === (locale.maximize().script === 'Hant' ? 'zh-TW' : 'zh-CN'))
+ : available.find(language => language.code.split('-')[0] === locale.language))?.code;
+}
+
+function preferredLanguage() {
+ const requested = new URL(location.href).searchParams.get('lang');
+ if (requested !== null) return matchLanguage(requested) || sourceLanguage;
+ return [savedLanguage(), ...navigator.languages].map(matchLanguage).find(Boolean) || sourceLanguage;
+}
+
+export function pageUrl(path) {
+ const url = new URL(path, siteRoot);
+ if (url.origin === siteRoot.origin && url.pathname.startsWith(siteRoot.pathname) && (url.pathname.endsWith('.html') || url.pathname.endsWith('/'))) url.searchParams.set('lang', edition.code);
+ return url.href;
+}
+
+export async function initializeLanguages() {
+ initializeMenu();
+ edition.code = preferredLanguage();
+ if (edition.code !== sourceLanguage) {
+ try {
+ const response = await fetch(new URL(`locales/${edition.code}/strings.json`, siteRoot), { cache: 'no-cache', signal: AbortSignal.timeout(resourceTimeoutMs) });
+ if (!response.ok) throw new Error('Language resources unavailable');
+ setStrings(await response.json());
+ } catch {
+ edition.code = sourceLanguage;
+ }
+ }
+ applyResources(edition.code);
+ if (location.hash) requestAnimationFrame(() => {
+ let id;
+ try { id = decodeURIComponent(location.hash.slice(1)); } catch { return; }
+ document.getElementById(id)?.scrollIntoView({ block: 'start', behavior: 'auto' });
+ });
+ const current = new URL(location.href);
+ if (current.searchParams.has('lang') || edition.code !== sourceLanguage) {
+ current.searchParams.set('lang', edition.code);
+ history.replaceState(history.state, '', current);
+ }
+ updateMenu();
+ for (const link of document.querySelectorAll('a[href]:not([href^="#"])')) {
+ if (link.closest('#language-menu')) continue;
+ link.href = pageUrl(link.href);
+ const url = new URL(link.href);
+ if (/^https?:$/.test(url.protocol) && url.origin !== location.origin) {
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ }
+ }
+}
+
+function updateMenu() {
+ for (const link of document.querySelectorAll('#language-menu a[data-language-code]')) {
+ const url = new URL(location.href);
+ url.searchParams.set('lang', link.dataset.languageCode);
+ link.href = url.href;
+ if (link.dataset.languageCode === edition.code) link.setAttribute('aria-current', 'true');
+ else link.removeAttribute('aria-current');
+ }
+}
+
+function initializeMenu() {
+ const menu = document.getElementById('language-menu');
+ updateMenu();
+ menu.addEventListener('toggle', () => {
+ if (menu.open) {
+ closeHeaderMenu();
+ updateMenu();
+ }
+ });
+ menu.addEventListener('click', event => {
+ const link = event.target.closest('a[data-language-code]');
+ if (link) {
+ try { localStorage.setItem(preferenceKey, link.dataset.languageCode); } catch { /* Optional preference. */ }
+ }
+ });
+ document.addEventListener('click', event => { if (!menu.contains(event.target)) menu.open = false; });
+ document.addEventListener('keydown', event => {
+ if (event.key === 'Escape' && menu.open) {
+ menu.open = false;
+ menu.querySelector('summary').focus();
+ }
+ });
+}
diff --git a/documents/templates/public/layout.css b/documents/templates/public/layout.css
new file mode 100644
index 00000000..7acc6378
--- /dev/null
+++ b/documents/templates/public/layout.css
@@ -0,0 +1,561 @@
+/* Navigation and command search */
+.zenith-theme > .site-header {
+ /* Use the same scrollbar-adjusted width as the document. */
+ position: sticky;
+ top: 0;
+ z-index: 1030;
+ height: var(--zenith-header-height);
+ min-height: var(--zenith-header-height);
+ background: var(--zenith-canvas);
+ border-bottom: 1px solid #ffffff09;
+}
+
+/* Lock the viewport; a scrolling body would become the sticky header's container. */
+html:has(.site-search[open], #tocOffcanvas:is(.showing, .show, .hiding)) {
+ overflow: hidden;
+}
+
+.zenith-theme .container-xxl {
+ max-width: var(--zenith-width);
+ padding-inline: var(--zenith-gutter);
+}
+
+.site-nav {
+ position: relative;
+ display: grid;
+ grid-template-columns: auto auto minmax(0, 1fr);
+ align-items: center;
+ gap: 38px;
+ min-height: var(--zenith-header-height);
+}
+
+.skip-link {
+ position: absolute;
+ z-index: 1;
+ top: 12px;
+ left: var(--zenith-gutter);
+ padding: 8px 12px;
+ border-radius: 5px;
+ background: var(--zenith-action);
+ color: var(--zenith-canvas);
+ font-size: 14px;
+}
+
+#main-content:focus {
+ outline: none;
+}
+
+.site-nav .navbar-brand {
+ min-width: 0;
+ justify-self: start;
+ margin: 0;
+ padding: 16px 0;
+}
+
+.site-nav #logo {
+ width: auto;
+ max-width: 100%;
+ height: 36px;
+ object-fit: contain;
+}
+
+#primary-navigation .navbar-nav {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 8px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#primary-navigation .nav-link {
+ position: relative;
+ display: flex;
+ align-items: center;
+ min-height: 36px;
+ padding: 7px 12px;
+ border: 0;
+ border-radius: 5px;
+ background: none;
+ color: #aeb9c8;
+ font: 550 15px/1.4 var(--bs-body-font-family);
+ letter-spacing: 0;
+ text-decoration: none;
+}
+
+#primary-navigation .nav-link:hover {
+ color: #fff;
+ background: var(--zenith-surface);
+}
+
+#primary-navigation .nav-link.active {
+ color: #f2f6fc;
+ background: var(--zenith-graphite);
+}
+
+#primary-navigation .nav-link:focus-visible {
+ outline-offset: 2px;
+}
+
+.site-actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 4px;
+}
+
+.search-launcher, .source-link, .language-menu > summary {
+ display: grid;
+ place-items: center;
+ width: 44px;
+ min-height: 44px;
+ padding: 0;
+ border: 0;
+ border-radius: 5px;
+ background: none;
+ color: #bac3d0;
+ font-size: 18px;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.search-launcher:hover, .source-link:hover, .language-menu > summary:hover {
+ color: #fff;
+ background: var(--zenith-hover);
+}
+
+.language-menu {
+ position: relative;
+}
+
+.language-menu > summary {
+ list-style: none;
+}
+
+.language-menu > summary::-webkit-details-marker {
+ display: none;
+}
+
+.language-menu[open] > summary {
+ color: var(--zenith-text);
+ background: var(--zenith-hover);
+}
+
+.language-options {
+ position: absolute;
+ top: calc(100% + 8px);
+ right: 0;
+ width: 240px;
+ max-height: calc(100dvh - var(--zenith-header-height) - 24px);
+ overflow-y: auto;
+ padding: 6px;
+ border: 1px solid var(--zenith-border);
+ border-radius: var(--zenith-radius);
+ background: var(--zenith-surface);
+ box-shadow: 0 12px 28px #0005;
+}
+
+.language-options > :is(a, .language-pending) {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 8px;
+ min-height: 38px;
+ padding: 8px 10px;
+ border-radius: 5px;
+ color: var(--zenith-muted);
+ font-size: 13px;
+ line-height: 1.5;
+ white-space: nowrap;
+ text-decoration: none;
+}
+
+.language-options a:hover, .language-options a[aria-current] {
+ background: var(--zenith-graphite);
+ color: var(--zenith-text);
+}
+
+.language-pending {
+ cursor: default;
+}
+
+.language-pending small {
+ flex: 0 0 auto;
+ font-size: 10px;
+}
+
+.menu-toggle {
+ display: none;
+ width: 40px;
+ height: 44px;
+ padding: 10px;
+ border: 0;
+ border-radius: 5px;
+ background: none;
+ color: var(--zenith-text);
+}
+
+.menu-toggle span {
+ display: block;
+ height: 1px;
+ margin: 6px 0;
+ background: currentColor;
+ transition: transform var(--zenith-state-duration) var(--zenith-state-ease);
+}
+
+.menu-open .menu-toggle span:first-child {
+ transform: translateY(3.5px) rotate(45deg);
+}
+
+.menu-open .menu-toggle span:last-child {
+ transform: translateY(-3.5px) rotate(-45deg);
+}
+
+/* Document shell: navigation, article, and on-page outline */
+body.zenith-theme > main.container-xxl {
+ display: grid;
+ grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr);
+ column-gap: var(--zenith-gap);
+ align-items: start;
+ flex: 1;
+ padding-block: 0;
+}
+
+body.zenith-theme > main.container-xxl > .content {
+ display: block;
+ grid-column: 2;
+ width: 100%;
+ min-width: 0;
+ margin: 0;
+ padding-block: 36px 80px;
+}
+
+/* Keep prose readable inside the same shell used by the home and API pages. */
+body.zenith-theme[data-section="learn"]:not([data-page-kind="hub"]) > main > .content {
+ max-width: var(--zenith-reading-width);
+ justify-self: center;
+}
+
+body.zenith-theme > main.container-xxl > :is(.toc-offcanvas, .affix) {
+ position: sticky;
+ top: var(--zenith-header-height);
+ width: 100%;
+ min-width: 0;
+ max-width: none;
+ max-height: calc(100dvh - var(--zenith-header-height));
+ padding: 0;
+ scrollbar-width: thin;
+}
+
+body.zenith-theme > main.container-xxl > .affix {
+ display: flex;
+ flex-direction: column;
+ grid-column: 3;
+ padding-block: 36px 24px;
+ overflow: hidden;
+}
+
+body.zenith-theme > main.container-xxl > .toc-offcanvas {
+ height: calc(100dvh - var(--zenith-header-height));
+ padding: 32px 20px 28px 0;
+ border-right: 1px solid var(--zenith-border);
+}
+
+.zenith-theme #tocOffcanvas {
+ --bs-offcanvas-bg: var(--zenith-surface);
+ --bs-offcanvas-width: min(320px, 90vw);
+ --bs-offcanvas-color: var(--zenith-body);
+ --bs-offcanvas-transition: transform var(--zenith-panel-duration) cubic-bezier(.16, 1, .3, 1);
+}
+
+.zenith-theme #tocOffcanvas .offcanvas-body {
+ display: flex;
+ flex-direction: column;
+ overflow-y: auto;
+ scrollbar-width: thin;
+}
+
+#toc {
+ flex: 1;
+ min-height: 0;
+ font-size: 14px;
+}
+
+#toc > .overflow-y-auto {
+ padding-right: 0;
+}
+
+#toc li {
+ margin-block: 3px;
+ border: 0;
+}
+
+#toc li > a {
+ display: block;
+ max-width: 100%;
+ padding: 8px 11px;
+ border-radius: 6px;
+ color: var(--zenith-muted);
+ text-decoration: none;
+ line-height: 1.5;
+}
+
+#toc li > a:hover {
+ background: var(--zenith-surface);
+ color: var(--zenith-text);
+}
+
+#toc li > a:focus-visible {
+ outline-offset: -2px;
+}
+
+#toc li.active:not(.expander) > a {
+ background: var(--zenith-graphite);
+ color: var(--zenith-text);
+ font-weight: 500;
+}
+
+#toc .toc-group-title {
+ display: block;
+ margin-top: 8px;
+ padding: 4px 11px;
+ color: var(--zenith-text);
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 1.5;
+}
+
+.contents-toggle {
+ display: none;
+}
+
+.contents-toggle, .contents-close {
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ min-height: 40px;
+ padding: 8px 10px;
+ border: 0;
+ border-radius: 5px;
+ background: var(--zenith-control-bg);
+ color: #e1ebf8;
+ font: 500 13px/1.5 var(--bs-body-font-family);
+ cursor: pointer;
+}
+
+.contents-close {
+ display: inline-flex;
+ width: 40px;
+}
+
+.contents-toggle:hover, .contents-close:hover {
+ color: #fff;
+ background: var(--zenith-hover);
+}
+
+#tocOffcanvas .offcanvas-header {
+ justify-content: space-between;
+ min-height: var(--zenith-header-height);
+ padding: 12px 20px;
+ border-bottom: 1px solid var(--zenith-border);
+}
+
+#tocOffcanvas .offcanvas-title {
+ color: var(--zenith-text);
+ font-size: 16px;
+ font-weight: 600;
+}
+
+#article-outline {
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+ font-size: 13px;
+ line-height: 1.55;
+}
+
+.outline-title {
+ flex: 0 0 auto;
+ padding: 0 10px 14px;
+ color: var(--zenith-text);
+ font-size: 13px;
+ font-weight: 600;
+}
+
+#article-outline ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#article-outline .outline-list {
+ min-height: 0;
+ overflow-y: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ scrollbar-color: var(--zenith-border) transparent;
+ padding: 2px 4px 2px 2px;
+}
+
+#article-outline .outline-list > li + li {
+ margin-top: 5px;
+}
+
+#article-outline li > ul {
+ padding-left: 14px;
+ margin-block: 2px 6px;
+ font-size: 12px;
+}
+
+.outline-link {
+ display: block;
+ padding: 6px 9px;
+ border-radius: 5px;
+ color: var(--zenith-muted);
+ overflow-wrap: break-word;
+ text-decoration: none;
+ transition: color var(--zenith-state-duration), background-color var(--zenith-state-duration);
+}
+
+.outline-section-current > .outline-link {
+ color: var(--zenith-text);
+}
+
+.outline-link:hover {
+ color: var(--zenith-text);
+ background: var(--zenith-surface);
+ text-decoration: none;
+}
+
+.outline-link[aria-current] {
+ color: var(--zenith-text);
+ background: var(--zenith-graphite);
+}
+
+.outline-link:focus-visible {
+ outline-offset: -2px;
+}
+
+.outline-target:focus {
+ outline: none;
+}
+
+@media (min-width: 1200px) {
+ body.zenith-theme > main.container-xxl:has(> .article-outline:not([hidden])) {
+ grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr) var(--zenith-outline-width);
+ }
+}
+
+@media (max-width: 1199.98px) {
+ body.zenith-theme > main.container-xxl > .affix {
+ display: none;
+ }
+}
+
+@media (max-width: 767.98px) {
+ .site-nav {
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 12px;
+ }
+
+ .site-nav .navbar-brand {
+ padding-block: 14px;
+ }
+
+ .site-actions {
+ gap: 8px;
+ }
+
+ .source-link {
+ display: none;
+ }
+
+ .menu-toggle {
+ display: block;
+ }
+
+ .site-menu {
+ position: absolute;
+ inset: var(--zenith-header-height) 0 auto;
+ padding: 16px var(--zenith-gutter) 20px;
+ max-height: calc(100dvh - var(--zenith-header-height));
+ overflow-y: auto;
+ border-bottom: 1px solid var(--zenith-border);
+ background: var(--zenith-canvas);
+ opacity: 0;
+ visibility: hidden;
+ clip-path: inset(0 0 100%);
+ transition: clip-path var(--zenith-panel-duration) cubic-bezier(.16, 1, .3, 1), opacity var(--zenith-state-duration), visibility var(--zenith-panel-duration);
+ }
+
+ .menu-open .site-menu {
+ opacity: 1;
+ visibility: visible;
+ clip-path: inset(0);
+ }
+
+ body.zenith-theme > main.container-xxl {
+ grid-template-columns: minmax(0, 1fr);
+ padding-top: 20px;
+ }
+
+ body.zenith-theme > main.container-xxl > .content {
+ grid-column: 1;
+ grid-row: 1;
+ padding-top: 0;
+ }
+
+ body.zenith-theme > main.container-xxl > .toc-offcanvas {
+ grid-column: 1;
+ grid-row: 1;
+ position: static;
+ width: 0;
+ height: 0;
+ overflow: visible;
+ padding: 0;
+ border: 0;
+ }
+
+ #tocOffcanvas .offcanvas-body {
+ padding: 20px;
+ }
+
+ body.zenith-theme > main > .content > .contents-toggle {
+ display: inline-flex;
+ margin-bottom: 24px;
+ }
+}
+
+@media print {
+ body.zenith-theme > main.container-xxl {
+ display: block;
+ padding: 0;
+ }
+
+ .zenith-theme > main > :is(.toc-offcanvas, .affix),
+ article .code-action {
+ display: none !important;
+ }
+}
+
+@media (max-width: 399.98px) {
+ .language-options {
+ position: fixed;
+ top: calc(var(--zenith-header-height) + 8px);
+ right: 16px;
+ max-width: calc(100vw - 32px);
+ }
+
+ .site-nav #logo {
+ height: 32px;
+ }
+
+ .site-actions {
+ gap: 2px;
+ }
+
+ .language-menu > summary, .search-launcher, .menu-toggle {
+ width: 36px;
+ }
+}
diff --git a/documents/templates/public/main.css b/documents/templates/public/main.css
index 434fd288..201aa778 100644
--- a/documents/templates/public/main.css
+++ b/documents/templates/public/main.css
@@ -1,3777 +1,8 @@
-a.external[href]::after {
- content: none;
-}
-
-.next-article .bi-chevron-right {
- display: none;
-}
-
-/**
- * Zenith.NET documentation theme.
- * Brand-led landing page plus a compact, readable DocFX shell.
- */
-
-/* ==========================================================================
- Design tokens
- ========================================================================== */
-
-:root {
- --zenith-navy-900: #231e2e;
- --zenith-purple-700: #512bd4;
- --zenith-magenta-600: #512bd4;
- --zenith-blue-500: #536dfe;
- --zenith-green-500: #0f9f78;
- --zenith-text: #231e2e;
- --zenith-text-soft: #655f70;
- --zenith-text-faint: #918a9d;
- --zenith-surface: #fbfafc;
- --zenith-surface-soft: #f3f1f7;
- --zenith-surface-tint: #f6f1ff;
- --zenith-border: #e1dce8;
- --zenith-border-strong: #cec6da;
- --zenith-code-surface: #f6f8fc;
- --zenith-code-toolbar: #eef2f8;
- --zenith-code-border: #d8deea;
- --zenith-code-text: #1f2430;
- --zenith-code-muted: #687386;
- --zenith-code-hover: #e2e7f1;
- --zenith-code-accent: #512bd4;
- --zenith-code-accent-border: rgba(81, 43, 212, 0.34);
- --zenith-code-success: #0f766e;
- --zenith-inline-code: #512bd4;
- --zenith-inline-code-background: rgba(81, 43, 212, 0.08);
- --zenith-table-surface: #ffffff;
- --zenith-table-header: #eef2f7;
- --zenith-table-stripe: #f8fafc;
- --zenith-table-hover: #f1f5fb;
- --zenith-table-border: #d8deea;
- --zenith-table-text: #4b5565;
- --zenith-table-heading: #1f2937;
- --zenith-link: #512bd4;
- --zenith-link-hover: #7652e8;
- --zenith-header-surface: rgba(251, 250, 252, 0.95);
- --zenith-header-border: rgba(35, 30, 46, 0.12);
- --zenith-nav-text: #70697c;
- --zenith-nav-active: #231e2e;
- --zenith-search-surface: #f2f0f5;
- --zenith-search-border: #e1dce8;
- --zenith-search-focus: #7150e8;
- --zenith-accent: #512bd4;
- --zenith-accent-soft: rgba(81, 43, 212, 0.08);
- --zenith-logo-width: 115px;
- --zenith-info: var(--zenith-blue-500);
- --zenith-success: var(--zenith-green-500);
- --zenith-warning: #c76620;
- --zenith-danger: #d94f70;
- --zenith-syntax-keyword: #af00db;
- --zenith-syntax-literal: #0000ff;
- --zenith-syntax-string: #a31515;
- --zenith-syntax-type: #267f99;
- --zenith-syntax-function: #795e26;
- --zenith-syntax-variable: #001080;
- --zenith-syntax-comment: #008000;
- --zenith-syntax-number: #098658;
- --zenith-syntax-meta: #9f4f00;
- --zenith-syntax-symbol: #b0006d;
- --zenith-flow-space: 24px;
- --zenith-panel-radius: var(--zenith-radius);
- --zenith-control-radius: var(--zenith-radius-small);
- --zenith-copy-size: 30px;
- --zenith-toolbar-height: 36px;
- --zenith-code-padding-block: 12px;
- --zenith-code-padding-inline: 14px;
- --zenith-table-padding-block: 12px;
- --zenith-table-padding-inline: 16px;
- --zenith-brand-gradient: linear-gradient(110deg, #512bd4 0%, #7150e8 56%, #34c9f5 100%);
- --zenith-shadow-small: 0 2px 8px rgba(41, 27, 69, 0.07);
- --zenith-shadow-medium: 0 12px 30px rgba(41, 27, 69, 0.12);
- --zenith-radius-small: 5px;
- --zenith-radius: 8px;
- --zenith-header-height: 78px;
- --zenith-header-bar-height: 58px;
- --zenith-header-inset: 10px;
- --zenith-header-inline-inset: clamp(10px, 1vw, 18px);
- --zenith-content-width: 1200px;
- --zenith-docs-width: 1760px;
- --zenith-page-gutter: clamp(16px, 1.25vw, 40px);
- --zenith-column-gap: clamp(20px, 1.5vw, 28px);
- --zenith-sidebar-width: clamp(240px, 18vw, 300px);
- --zenith-affix-width: 14%;
- --zenith-api-sidebar-width: clamp(280px, 21vw, 340px);
- --zenith-scrollbar-size: 8px;
- --zenith-scrollbar-inset: 2px;
- --zenith-scrollbar-edge-gap: 4px;
- --zenith-scrollbar-min-thumb: 36px;
- --zenith-scrollbar-thumb: color-mix(in srgb, var(--zenith-text-faint) 62%, transparent);
- --zenith-scrollbar-thumb-hover: color-mix(in srgb, var(--zenith-text-soft) 82%, transparent);
- --zenith-ease: cubic-bezier(0.22, 1, 0.36, 1);
- --bs-body-font-family: "Aptos", "Segoe UI Variable Text", sans-serif;
- --bs-body-color: var(--zenith-text);
- --bs-body-bg: var(--zenith-surface);
- --bs-link-color: var(--zenith-link);
- --bs-link-color-rgb: 81, 43, 212;
- --bs-link-hover-color: var(--zenith-link-hover);
- --bs-link-hover-color-rgb: 118, 82, 232;
- --bs-border-color: var(--zenith-border);
- --bs-border-radius: var(--zenith-radius-small);
-}
-
-[data-bs-theme="dark"] {
- --zenith-purple-700: #b9a5ff;
- --zenith-magenta-600: #d1c4ff;
- --zenith-text: #f5f1fa;
- --zenith-text-soft: #b9b1c4;
- --zenith-text-faint: #81798d;
- --zenith-surface: #100d16;
- --zenith-surface-soft: #17121f;
- --zenith-surface-tint: #1d1728;
- --zenith-border: #312940;
- --zenith-border-strong: #463a5a;
- --zenith-code-surface: #191621;
- --zenith-code-toolbar: #211d2a;
- --zenith-code-border: #393243;
- --zenith-code-text: #e4dfea;
- --zenith-code-muted: #aaa1b5;
- --zenith-code-hover: #2d2737;
- --zenith-code-accent: #b9a5ff;
- --zenith-code-accent-border: rgba(185, 165, 255, 0.42);
- --zenith-code-success: #70cdb5;
- --zenith-inline-code: #b69cff;
- --zenith-inline-code-background: rgba(155, 123, 255, 0.13);
- --zenith-shadow-small: 0 2px 8px rgba(0, 0, 0, 0.24);
- --zenith-shadow-medium: 0 12px 30px rgba(0, 0, 0, 0.34);
- --zenith-table-surface: #17131e;
- --zenith-table-header: #211b2b;
- --zenith-table-stripe: #1b1624;
- --zenith-table-hover: #241d30;
- --zenith-table-border: #393044;
- --zenith-table-text: #c0b8ca;
- --zenith-table-heading: #f1edf6;
- --zenith-header-surface: rgba(16, 13, 22, 0.95);
- --zenith-link: #b9a5ff;
- --zenith-link-hover: #d1c4ff;
- --zenith-nav-active: #f7f3ff;
- --zenith-search-surface: #191421;
- --zenith-search-border: #312940;
- --zenith-info: #8fa2ff;
- --zenith-success: #70cdb5;
- --zenith-warning: #ffb16f;
- --zenith-danger: #ff8fa3;
- --zenith-syntax-keyword: #c586c0;
- --zenith-syntax-literal: #569cd6;
- --zenith-syntax-string: #ce9178;
- --zenith-syntax-type: #4ec9b0;
- --zenith-syntax-function: #dcdcaa;
- --zenith-syntax-variable: #9cdcfe;
- --zenith-syntax-comment: #6a9955;
- --zenith-syntax-number: #b5cea8;
- --zenith-syntax-meta: #d7ba7d;
- --zenith-syntax-symbol: #c8c8c8;
- --bs-body-color: var(--zenith-text);
- --bs-body-bg: var(--zenith-surface);
- --bs-link-color: var(--zenith-link);
- --bs-link-color-rgb: 185, 165, 255;
- --bs-link-hover-color: var(--zenith-link-hover);
- --bs-link-hover-color-rgb: 209, 196, 255;
- --bs-border-color: var(--zenith-border);
-}
-
-/* ==========================================================================
- Base and typography
- ========================================================================== */
-
-html {
- scroll-padding-top: calc(var(--zenith-header-height) + 24px);
-}
-
-@supports not selector(::-webkit-scrollbar) {
- * {
- scrollbar-color: var(--zenith-scrollbar-thumb) transparent;
- scrollbar-width: thin;
- }
-}
-
-*::-webkit-scrollbar {
- width: var(--zenith-scrollbar-size);
- height: var(--zenith-scrollbar-size);
-}
-
-*::-webkit-scrollbar-track,
-*::-webkit-scrollbar-corner {
- background: transparent;
-}
-
-*::-webkit-scrollbar-thumb {
- background-color: var(--zenith-scrollbar-thumb);
- background-clip: padding-box;
- border: var(--zenith-scrollbar-inset) solid transparent;
- border-radius: 999px;
-}
-
- *::-webkit-scrollbar-thumb:hover {
- background-color: var(--zenith-scrollbar-thumb-hover);
- }
-
-body {
- min-width: 0;
- padding-top: var(--zenith-header-height) !important;
- background: var(--zenith-surface);
- color: var(--zenith-text);
- font-family: var(--bs-body-font-family);
- font-size: 16px;
- line-height: 1.68;
- letter-spacing: 0;
- -webkit-font-smoothing: antialiased;
- text-rendering: optimizeLegibility;
-}
-
-::selection {
- background: rgba(198, 0, 189, 0.18);
- color: var(--zenith-text);
-}
-
-a {
- color: var(--zenith-link);
- text-decoration-color: rgba(81, 43, 212, 0.3);
- text-underline-offset: 3px;
- transition: color 160ms ease, text-decoration-color 160ms ease;
-}
-
- a:hover,
- a:focus {
- color: var(--zenith-link-hover);
- text-decoration: none;
- }
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
- color: var(--zenith-text);
- font-family: "Aptos Display", "Segoe UI Variable Display", "Bahnschrift", sans-serif;
- font-weight: 750;
- letter-spacing: 0;
-}
-
-img {
- max-width: 100%;
-}
-
-:focus-visible {
- outline: 3px solid rgba(113, 56, 232, 0.42) !important;
- outline-offset: 3px;
-}
-
-/* ==========================================================================
- Global header
- ========================================================================== */
-
-body > header.bg-body,
-body > header {
- position: fixed !important;
- top: var(--zenith-header-inset);
- left: 50%;
- z-index: 1030;
- width: min(calc(100% - (2 * var(--zenith-header-inline-inset))), var(--zenith-docs-width));
- height: var(--zenith-header-bar-height);
- background: var(--zenith-header-surface) !important;
- border: 1px solid var(--zenith-header-border) !important;
- border-radius: var(--zenith-radius);
- box-shadow: 0 8px 24px rgba(20, 18, 32, 0.08);
- backdrop-filter: blur(16px) saturate(1.25);
- -webkit-backdrop-filter: blur(16px) saturate(1.25);
- transform: translateX(-50%);
- transition: background-color 240ms ease, border-color 240ms ease, box-shadow 240ms ease;
-}
-
- body > header.is-scrolled {
- box-shadow: 0 12px 32px rgba(20, 18, 32, 0.13);
- }
-
-[data-bs-theme="dark"] body > header.is-scrolled {
- box-shadow: 0 14px 34px rgba(0, 0, 0, 0.38);
-}
-
-.zenith-reading-progress {
- position: absolute;
- right: 8px;
- bottom: -1px;
- left: 8px;
- z-index: 2;
- height: 2px;
- overflow: hidden;
- pointer-events: none;
-}
-
- .zenith-reading-progress > span {
- display: block;
- width: 100%;
- height: 100%;
- background: var(--zenith-brand-gradient);
- box-shadow: 0 0 10px rgba(81, 43, 212, 0.34);
- transform: scaleX(0);
- transform-origin: left center;
- will-change: transform;
- }
-
-@supports (animation-timeline: scroll()) {
- .zenith-reading-progress > span {
- animation: zenith-reading-progress linear both;
- animation-timeline: scroll(root block);
- animation-range: 0% 100%;
- }
-}
-
-[data-bs-theme="dark"] body > header.bg-body,
-[data-bs-theme="dark"] body > header {
- background: var(--zenith-header-surface) !important;
- border-color: var(--zenith-border) !important;
-}
-
-body[data-layout="landing"] > header.bg-body,
-body[data-layout="landing"] > header {
- background: rgba(251, 250, 252, 0.58) !important;
- border-color: rgba(35, 30, 46, 0.08) !important;
- box-shadow: 0 8px 24px rgba(41, 27, 69, 0.045);
-}
-
-[data-bs-theme="dark"] body[data-layout="landing"] > header.bg-body,
-[data-bs-theme="dark"] body[data-layout="landing"] > header {
- background: rgba(16, 13, 22, 0.5) !important;
- border-color: rgba(185, 165, 255, 0.1) !important;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
-}
-
-body[data-layout="landing"] > header.is-scrolled {
- background: rgba(251, 250, 252, 0.88) !important;
- border-color: rgba(35, 30, 46, 0.12) !important;
- box-shadow: 0 12px 32px rgba(41, 27, 69, 0.11);
-}
-
-[data-bs-theme="dark"] body[data-layout="landing"] > header.is-scrolled {
- background: rgba(16, 13, 22, 0.84) !important;
- border-color: rgba(185, 165, 255, 0.16) !important;
- box-shadow: 0 14px 34px rgba(0, 0, 0, 0.28);
-}
-
-.navbar {
- width: 100%;
- height: 100%;
- min-height: 0;
- padding: 0 !important;
-}
-
- .navbar > .container-xxl {
- align-items: center;
- width: 100%;
- height: 100%;
- max-width: var(--zenith-content-width) !important;
- min-height: 0;
- margin: 0 auto;
- padding: 0 18px !important;
- }
-
-.navbar-brand {
- position: relative;
- display: inline-flex;
- flex: 0 0 auto;
- align-items: center;
- margin-right: 10px !important;
- padding: 0 !important;
-}
-
-#logo {
- display: block;
- width: var(--zenith-logo-width);
- height: 36px;
- object-fit: contain;
- object-position: left center;
- transform-origin: left center;
- transition: filter 220ms ease, transform 220ms var(--zenith-ease);
-}
-
-.navbar-brand:hover #logo,
-.navbar-brand:focus-visible #logo {
- filter: drop-shadow(0 5px 10px rgba(81, 43, 212, 0.18));
- transform: translateY(-1px) scale(1.012);
-}
-
-.navbar-brand:active #logo {
- transform: scale(0.992);
-}
-
-.navbar-collapse,
-#navbar {
- min-width: 0;
-}
-
-#navbar {
- display: flex;
- flex: 1 1 auto;
- align-items: center;
- justify-content: flex-end;
- gap: 6px;
-}
-
- #navbar .navbar-nav {
- display: flex;
- order: 1;
- flex: 0 1 auto;
- align-items: center;
- gap: 2px;
- margin-right: auto;
- }
-
- #navbar .nav-link {
- position: relative;
- display: inline-flex;
- align-items: center;
- height: 36px;
- padding: 0 12px !important;
- color: var(--zenith-nav-text) !important;
- border-radius: 0 !important;
- font-size: 14px;
- font-weight: 600;
- line-height: 1;
- letter-spacing: 0;
- text-decoration: none;
- transition: color 180ms ease, background-color 180ms ease, transform 180ms var(--zenith-ease);
- }
-
-[data-bs-theme="dark"] #navbar .nav-link {
- color: var(--zenith-text-soft) !important;
-}
-
-#navbar .nav-link::after {
- content: "";
- position: absolute;
- right: 12px;
- bottom: 4px;
- left: 12px;
- height: 2px;
- background: var(--zenith-accent);
- opacity: 0;
- transform: scaleX(0.3);
- transition: opacity 160ms ease, transform 160ms ease;
-}
-
-#navbar .nav-link:hover,
-#navbar .nav-link.active {
- color: var(--zenith-nav-active) !important;
-}
-
-[data-bs-theme="dark"] #navbar .nav-link:hover,
-[data-bs-theme="dark"] #navbar .nav-link.active {
- color: var(--zenith-nav-active) !important;
-}
-
-#navbar .nav-link:hover::after,
-#navbar .nav-link.active::after {
- opacity: 1;
- transform: scaleX(1);
-}
-
-#navbar form.search {
- order: 2 !important;
- width: 210px;
- margin: 0 !important;
-}
-
- #navbar form.search > i {
- left: 12px;
- color: var(--zenith-text-faint);
- }
-
- #navbar form.search > input {
- width: 100%;
- height: 36px;
- padding-left: 36px;
- background: var(--zenith-search-surface);
- color: var(--zenith-text);
- border: 1px solid var(--zenith-search-border);
- border-radius: 6px;
- box-shadow: none;
- font-size: 13px;
- }
-
- #navbar form.search.zenith-search {
- position: relative;
- display: flex;
- width: 36px;
- min-width: 36px;
- height: 36px;
- align-items: center;
- overflow: hidden;
- border-radius: 6px;
- transition: width 300ms var(--zenith-ease), box-shadow 180ms ease;
- }
-
- #navbar form.search.zenith-search.is-expanded {
- width: 230px;
- box-shadow: 0 8px 22px rgba(20, 15, 30, 0.1);
- }
-
- #navbar form.search.zenith-search > i {
- display: none;
- }
-
- #navbar form.search.zenith-search > input {
- position: absolute;
- inset: 0;
- z-index: 0;
- width: 100%;
- padding-right: 12px;
- padding-left: 38px;
- opacity: 0;
- pointer-events: none;
- transform: translateX(8px);
- transition: opacity 180ms ease, transform 300ms var(--zenith-ease), border-color 180ms ease, background-color 180ms ease;
- }
-
- #navbar form.search.zenith-search.is-expanded > input {
- opacity: 1;
- pointer-events: auto;
- transform: translateX(0);
- }
-
-.zenith-search-toggle {
- position: relative;
- z-index: 1;
- display: inline-flex;
- width: 36px;
- height: 36px;
- flex: 0 0 36px;
- align-items: center;
- justify-content: center;
- padding: 0;
- color: var(--zenith-text-soft);
- background: transparent;
- border: 1px solid transparent;
- border-radius: 6px;
- font-size: 15px;
- transition: color 180ms ease, background-color 180ms ease, border-color 180ms ease, transform 220ms var(--zenith-ease);
-}
-
- .zenith-search-toggle:hover,
- .zenith-search-toggle:focus-visible {
- color: var(--zenith-accent);
- background: transparent;
- border-color: transparent;
- }
-
-#navbar form.search.zenith-search.is-expanded .zenith-search-toggle {
- color: var(--zenith-accent);
- border-color: transparent;
- transform: rotate(-8deg) scale(0.94);
-}
-
-#navbar form.search.zenith-search.is-loading .zenith-search-toggle {
- color: var(--zenith-text-faint);
-}
-
-.navbar .icons {
- order: 3 !important;
- display: flex;
- flex: 0 0 auto;
- align-items: center;
- gap: 6px;
-}
-
- .navbar .icons .zenith-theme-toggle {
- order: 1;
- }
-
- .navbar .icons > a.btn {
- order: 2;
- }
-
- .navbar .icons .btn,
- .navbar .icons > a.btn {
- display: inline-flex !important;
- align-items: center;
- justify-content: center;
- min-width: 36px;
- height: 36px;
- padding: 0 9px !important;
- color: var(--zenith-text-soft) !important;
- background: transparent !important;
- border: 0 !important;
- border-radius: 6px !important;
- box-shadow: none !important;
- transition: color 180ms ease, background-color 180ms ease, transform 180ms var(--zenith-ease) !important;
- }
-
- .zenith-search-toggle > i,
- .navbar .icons .btn > i,
- .navbar .icons > a.btn > i {
- display: inline-flex;
- width: 16px;
- height: 16px;
- align-items: center;
- justify-content: center;
- font-size: 16px;
- line-height: 1;
- transform-origin: center;
- }
-
- .zenith-search-toggle > i::before,
- .navbar .icons .btn > i::before,
- .navbar .icons > a.btn > i::before {
- display: block;
- line-height: 1;
- vertical-align: 0;
- }
-
- .navbar .icons .zenith-theme-toggle > i {
- transition: color 180ms ease, transform 220ms var(--zenith-ease);
- }
-
- .navbar .icons .btn.zenith-theme-toggle:hover,
- .navbar .icons .btn.zenith-theme-toggle:focus-visible {
- color: var(--zenith-accent) !important;
- background: transparent !important;
- transform: none !important;
- }
-
- .navbar .icons .zenith-theme-toggle:hover > i {
- transform: rotate(12deg) scale(1.08);
- }
-
- .navbar .icons .btn:hover,
- .navbar .icons > a.btn:hover {
- color: var(--zenith-accent) !important;
- background: var(--zenith-accent-soft) !important;
- transform: translateY(-1px);
- }
-
- .navbar .icons > a.btn:hover,
- .navbar .icons > a.btn:focus-visible {
- color: var(--zenith-accent) !important;
- background: transparent !important;
- transform: none !important;
- }
-
- .navbar .icons > a.btn > i {
- transition: color 180ms ease, transform 220ms var(--zenith-ease);
- }
-
- .navbar .icons > a.btn:hover > i {
- transform: scale(1.08);
- }
-
- .navbar .icons .zenith-theme-toggle.is-theme-leaving > i {
- animation: zenith-theme-icon-leave 120ms ease-in forwards;
- }
-
- .navbar .icons .zenith-theme-toggle.is-theme-entering > i {
- animation: zenith-theme-icon-enter 260ms var(--zenith-ease) both;
- }
-
-@keyframes zenith-theme-icon-leave {
- to {
- opacity: 0;
- transform: rotate(70deg) scale(0.52);
- }
-}
-
-@keyframes zenith-theme-icon-enter {
- from {
- opacity: 0;
- transform: rotate(-70deg) scale(0.52);
- }
-
- 68% {
- opacity: 1;
- transform: rotate(8deg) scale(1.12);
- }
-
- to {
- opacity: 1;
- transform: rotate(0) scale(1);
- }
-}
-
-.dropdown-menu {
- padding: 5px !important;
- background: var(--zenith-surface) !important;
- border: 1px solid var(--zenith-border) !important;
- border-radius: 7px !important;
- box-shadow: var(--zenith-shadow-medium) !important;
-}
-
-.dropdown-item {
- color: var(--zenith-text) !important;
- border-radius: 5px !important;
-}
-
- .dropdown-item:hover,
- .dropdown-item:focus {
- color: var(--zenith-purple-700) !important;
- background: rgba(113, 56, 232, 0.08) !important;
- }
-
-/* ==========================================================================
- Landing layout reset
- ========================================================================== */
-
-body[data-layout="landing"] {
- overflow-x: clip;
- padding-top: 0 !important;
-}
-
- body[data-layout="landing"] > main.container-xxl {
- width: 100%;
- max-width: none !important;
- margin: 0 !important;
- padding: 0 !important;
- }
-
- body[data-layout="landing"] main > .content {
- width: 100%;
- max-width: none;
- margin: 0;
- padding: 0;
- }
-
- body[data-layout="landing"] .actionbar,
- body[data-layout="landing"] .next-article,
- body[data-layout="landing"] main > .affix {
- display: none !important;
- }
-
- body[data-layout="landing"] article {
- width: 100%;
- max-width: none;
- margin: 0;
- padding: 0;
- }
-
-.landing-page {
- width: 100%;
- color: var(--zenith-text);
-}
-
-.landing-shell {
- width: calc(100% - clamp(36px, 4vw, 64px));
- max-width: 1440px;
- margin: 0 auto;
-}
-
-.landing-page p {
- color: var(--zenith-text-soft);
-}
-
-/* ==========================================================================
- Landing hero
- ========================================================================== */
-
-.hero-copy {
- min-width: 0;
- padding: 24px 0;
-}
-
- .hero-copy h1 {
- max-width: 660px;
- margin: 0 0 22px;
- color: var(--zenith-navy-900);
- border: 0;
- font-size: 53px;
- font-weight: 800;
- line-height: 1.04;
- letter-spacing: 0;
- }
-
-[data-bs-theme="dark"] .hero-copy h1 {
- color: #f3f6ff;
-}
-
-.hero-copy h1 span {
- color: var(--zenith-magenta-600);
- background: var(--zenith-brand-gradient);
- -webkit-background-clip: text;
- background-clip: text;
- -webkit-text-fill-color: transparent;
-}
-
-.hero-lede {
- max-width: 590px;
- margin: 0 0 26px;
- font-size: 17px;
- line-height: 1.75;
-}
-
-.hero-actions {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 12px;
-}
-
-.landing-button {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- min-height: 46px;
- padding: 0 19px;
- border: 1px solid transparent;
- border-radius: 6px;
- font-size: 14px;
- font-weight: 750;
- line-height: 1;
- text-decoration: none !important;
- white-space: nowrap;
- transition: color 170ms ease, background 170ms ease, border-color 170ms ease, box-shadow 170ms ease, transform 170ms ease;
-}
-
-.landing-button-primary {
- color: #ffffff !important;
- background: var(--zenith-brand-gradient);
- box-shadow: 0 8px 18px rgba(181, 12, 189, 0.22);
-}
-
- .landing-button-primary:hover {
- color: #ffffff !important;
- box-shadow: 0 11px 24px rgba(181, 12, 189, 0.3);
- transform: translateY(-2px);
- }
-
-.landing-button-secondary {
- color: var(--zenith-navy-900) !important;
- background: rgba(255, 255, 255, 0.86);
- border-color: rgba(30, 44, 76, 0.13);
- box-shadow: var(--zenith-shadow-small);
-}
-
-[data-bs-theme="dark"] .landing-button-secondary {
- color: var(--zenith-text) !important;
- background: rgba(27, 29, 38, 0.9);
- border-color: var(--zenith-border-strong);
-}
-
-.landing-button-secondary:hover {
- color: var(--zenith-purple-700) !important;
- background: var(--zenith-surface);
- border-color: rgba(113, 56, 232, 0.36);
- transform: translateY(-2px);
-}
-
-.backend-support {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 8px;
- margin-top: 30px;
-}
-
-.backend-label {
- margin-right: 3px;
- color: var(--zenith-text-faint);
- font-size: 11px;
- font-weight: 650;
-}
-
-.backend-pill {
- display: inline-flex;
- align-items: center;
- min-height: 26px;
- padding: 3px 10px;
- color: #384966;
- background: rgba(255, 255, 255, 0.82);
- border: 1px solid rgba(26, 46, 79, 0.09);
- border-radius: 999px;
- font-size: 10px;
- font-weight: 750;
- line-height: 1;
-}
-
-[data-bs-theme="dark"] .backend-pill {
- color: var(--zenith-text-soft);
- background: rgba(24, 26, 34, 0.82);
- border-color: var(--zenith-border);
-}
-
-/* ==========================================================================
- Documentation shell
- ========================================================================== */
-
-body:not([data-layout="landing"]) > main.container-xxl {
- display: grid;
- grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr) var(--zenith-affix-width);
- width: 100%;
- max-width: var(--zenith-docs-width) !important;
- min-height: calc(100vh - var(--zenith-header-height));
- margin: 0 auto;
- padding: 0 var(--zenith-page-gutter) !important;
- column-gap: var(--zenith-column-gap);
-}
-
-body:is([data-yaml-mime="ManagedReference"], [data-zenith-api]) > main.container-xxl {
- grid-template-columns: var(--zenith-api-sidebar-width) minmax(0, 1fr) var(--zenith-api-sidebar-width);
-}
-
-body:not([data-layout="landing"]) > main.container-xxl > .toc-offcanvas {
- width: 100% !important;
- max-width: none;
-}
-
-body:not([data-layout="landing"]) main > .content {
- grid-column: 2;
- min-width: 0;
- width: auto;
- margin-right: 0 !important;
- margin-left: 0 !important;
- padding: 24px 0 70px;
-}
-
-body:not([data-layout="landing"]) main > .affix {
- grid-column: 3;
- min-width: 0;
- width: 100% !important;
- max-width: 100%;
- padding-top: 16px;
-}
-
-body[data-yaml-mime="ManagedReference"]:not([data-search]) > main.container-xxl > .affix {
- position: sticky;
- top: var(--zenith-header-height);
- align-self: start;
- height: calc(100vh - var(--zenith-header-height));
- max-height: none;
- overflow: visible;
-}
-
-.actionbar {
- min-height: 34px;
- margin-bottom: 18px;
-}
-
-.breadcrumb {
- margin: 0;
- padding: 0;
- background: transparent;
- font-size: 12px;
-}
-
-.breadcrumb-item,
-.breadcrumb-item a {
- color: var(--zenith-text-faint);
- text-decoration: none !important;
-}
-
- .breadcrumb-item.active,
- .breadcrumb-item a:hover {
- color: var(--zenith-purple-700);
- }
-
- .breadcrumb-item + .breadcrumb-item::before {
- color: var(--zenith-border-strong);
- }
-
-body:not([data-layout="landing"]) article {
- width: 100%;
- margin: 0 auto;
-}
-
-article > h1:first-child,
-article > h1:first-of-type {
- margin-top: 0;
-}
-
-article h1 {
- margin: 0 0 18px;
- padding: 0 0 16px;
- border-bottom: 1px solid var(--zenith-border);
- font-size: 36px;
- font-weight: 800;
- line-height: 1.18;
-}
-
-article h2 {
- position: relative;
- margin: 42px 0 17px;
- padding: 0 0 10px;
- border-bottom: 1px solid var(--zenith-border);
- font-size: 25px;
- line-height: 1.25;
-}
-
- article h2::after {
- content: "";
- position: absolute;
- bottom: -1px;
- left: 0;
- width: 52px;
- height: 2px;
- background: var(--zenith-brand-gradient);
- border-radius: 2px;
- transform: scaleX(1);
- transform-origin: left center;
- transition: transform 520ms var(--zenith-ease);
- }
-
-html.zenith-motion-ready article h2.zenith-reveal:not(.is-visible)::after {
- transform: scaleX(0);
- transition: none;
-}
-
-html.zenith-motion-ready article h2.is-visible::after {
- transform: scaleX(1);
-}
-
-article h3 {
- margin: 30px 0 12px;
- font-size: 19px;
- line-height: 1.35;
-}
-
-article h4 {
- margin: 24px 0 10px;
- font-size: 16px;
-}
-
-article p,
-article li,
-article dd,
-article td {
- color: var(--zenith-text-soft);
-}
-
-article p {
- margin-bottom: 16px;
-}
-
-article strong {
- color: var(--zenith-text);
- font-weight: 750;
-}
-
-article ul,
-article ol {
- padding-left: 1.35rem;
-}
-
-article li {
- margin: 5px 0;
-}
-
- article li::marker {
- color: var(--zenith-purple-700);
- }
-
-article img:not(.svg) {
- border-radius: 7px;
-}
-
-article a:not(.btn):not(.landing-button) {
- font-weight: 600;
-}
-
-article .anchorjs-link {
- display: none !important;
-}
-
-article hr {
- margin: 34px 0;
- border-color: var(--zenith-border);
- opacity: 1;
-}
-
-article blockquote {
- margin: 22px 0;
- padding: 14px 18px;
- color: var(--zenith-text-soft);
- background: var(--zenith-surface-tint);
- border-left: 3px solid var(--zenith-purple-700);
- border-radius: 0 6px 6px 0;
-}
-
- article blockquote > :last-child {
- margin-bottom: 0;
- }
-
-code {
- padding: 0.14em 0.38em;
- color: var(--zenith-inline-code);
- background: var(--zenith-inline-code-background);
- border-radius: 4px;
- font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace;
- font-size: 0.88em;
-}
-
-article pre,
-.codewrapper pre {
- position: relative;
- margin: 20px 0;
- padding: 22px 24px;
- overflow: auto;
- color: var(--zenith-code-text);
- background: var(--zenith-code-surface) !important;
- border: 1px solid var(--zenith-code-border) !important;
- border-radius: 6px !important;
- box-shadow: none;
- font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace;
- font-size: 13px;
- line-height: 1.7;
-}
-
-.doc-code-frame {
- position: relative;
- margin: var(--zenith-flow-space) 0;
- overflow: hidden;
- color: var(--zenith-code-text);
- background: var(--zenith-code-surface);
- border: 1px solid var(--zenith-code-border);
- border-radius: var(--zenith-panel-radius);
- transition: border-color 180ms ease, box-shadow 220ms ease;
-}
-
- .doc-code-frame::before {
- content: "";
- position: absolute;
- top: 0;
- right: 0;
- left: 0;
- z-index: 2;
- height: 2px;
- pointer-events: none;
- background: var(--zenith-brand-gradient);
- opacity: 0;
- transform: scaleX(0.35);
- transform-origin: left center;
- transition: opacity 180ms ease, transform 360ms var(--zenith-ease);
- }
-
- .doc-code-frame:hover,
- .doc-code-frame:focus-within {
- border-color: var(--zenith-code-accent-border);
- box-shadow: 0 10px 26px rgba(35, 30, 46, 0.08);
- }
-
-[data-bs-theme="dark"] .doc-code-frame:hover,
-[data-bs-theme="dark"] .doc-code-frame:focus-within {
- box-shadow: 0 12px 28px rgba(0, 0, 0, 0.22);
-}
-
-.doc-code-frame:hover::before,
-.doc-code-frame:focus-within::before {
- opacity: 0.86;
- transform: scaleX(1);
-}
-
-.doc-code-toolbar {
- display: flex;
- height: var(--zenith-toolbar-height);
- align-items: center;
- padding: 0 4px 0 12px;
- color: var(--zenith-code-muted);
- background: var(--zenith-code-toolbar);
- border-bottom: 1px solid var(--zenith-code-border);
-}
-
-.doc-code-language {
- font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace;
- font-size: 10px;
- font-weight: 700;
- text-transform: uppercase;
-}
-
-.doc-code-copy {
- display: inline-flex;
- width: var(--zenith-copy-size);
- min-width: var(--zenith-copy-size);
- height: var(--zenith-copy-size);
- align-items: center;
- justify-content: center;
- margin-left: auto;
- padding: 0;
- color: var(--zenith-code-muted);
- background: var(--zenith-code-surface);
- border: 1px solid var(--zenith-code-border);
- border-radius: var(--zenith-control-radius);
- font-family: Aptos, "Segoe UI Variable Text", sans-serif;
- font-size: 12px;
- font-weight: 600;
- cursor: pointer;
- transition: color 160ms ease, background-color 160ms ease, border-color 160ms ease, transform 160ms var(--zenith-ease);
-}
-
- .doc-code-copy:active {
- transform: scale(0.92);
- }
-
- .doc-code-copy i {
- font-size: 13px;
- }
-
- .doc-code-copy:hover {
- color: var(--zenith-code-accent);
- background: var(--zenith-code-hover);
- border-color: var(--zenith-code-accent-border);
- }
-
- .doc-code-copy.is-copied {
- color: var(--zenith-code-success);
- background: rgba(15, 118, 110, 0.1);
- border-color: rgba(15, 118, 110, 0.28);
- }
-
- .doc-code-copy.is-copied i {
- animation: zenith-copy-confirm 320ms var(--zenith-ease) both;
- }
-
- .doc-code-copy.is-failed {
- color: #b42318;
- background: rgba(180, 35, 24, 0.08);
- border-color: rgba(180, 35, 24, 0.24);
- }
-
-article .doc-code-frame > pre,
-.codewrapper .doc-code-frame > pre {
- margin: 0;
- padding: var(--zenith-code-padding-block) var(--zenith-code-padding-inline);
- background: transparent !important;
- border: 0 !important;
- border-radius: 0 !important;
- box-shadow: none;
-}
-
-article pre code,
-.codewrapper pre code {
- padding: 0;
- color: inherit;
- background: transparent;
- border-radius: 0;
- font-size: inherit;
-}
-
-.shiki,
-.shiki span {
- color: var(--shiki-light, var(--zenith-code-text));
- background-color: transparent !important;
- font-style: var(--shiki-light-font-style, inherit);
- font-weight: var(--shiki-light-font-weight, inherit);
- text-decoration: var(--shiki-light-text-decoration, inherit);
-}
-
-[data-bs-theme="dark"] .shiki,
-[data-bs-theme="dark"] .shiki span {
- color: var(--shiki-dark, var(--zenith-code-text));
- font-style: var(--shiki-dark-font-style, inherit);
- font-weight: var(--shiki-dark-font-weight, inherit);
- text-decoration: var(--shiki-dark-text-decoration, inherit);
-}
-
-.codewrapper {
- margin: 18px 0;
-}
-
- .codewrapper .btn-copy,
- pre .btn-copy {
- color: var(--zenith-code-muted) !important;
- background: var(--zenith-code-toolbar) !important;
- border: 1px solid var(--zenith-code-border) !important;
- border-radius: 5px !important;
- }
-
-.hljs-keyword,
-.hljs-selector-tag,
-.hljs-section,
-.hljs-link {
- color: var(--zenith-syntax-keyword);
-}
-
-.hljs-literal {
- color: var(--zenith-syntax-literal);
-}
-
-.hljs-string,
-.hljs-symbol,
-.hljs-bullet,
-.hljs-addition,
-.hljs-template-tag,
-.hljs-template-variable {
- color: var(--zenith-syntax-string);
-}
-
-.hljs-title,
-.hljs-name,
-.hljs-type,
-.hljs-attribute,
-.hljs-built_in {
- color: var(--zenith-syntax-type);
-}
-
- .hljs-title.function_,
- .hljs-function > .hljs-title,
- .hljs-property,
- .hljs-attr {
- color: var(--zenith-syntax-function);
- }
-
-.hljs-variable,
-.hljs-params,
-.hljs-subst {
- color: var(--zenith-syntax-variable);
-}
-
-.hljs-comment,
-.hljs-quote,
-.hljs-deletion {
- color: var(--zenith-syntax-comment);
-}
-
-.hljs-number {
- color: var(--zenith-syntax-number);
-}
-
-.hljs-meta,
-.hljs-meta-keyword,
-.hljs-doctag {
- color: var(--zenith-syntax-meta);
-}
-
-.hljs-symbol,
-.hljs-regexp {
- color: var(--zenith-syntax-symbol);
-}
-
-.hljs-operator,
-.hljs-punctuation {
- color: var(--zenith-code-muted);
-}
-
-/* ==========================================================================
- Tables, alerts, tabs, and API surfaces
- ========================================================================== */
-
-.doc-table-frame {
- position: relative;
- margin: var(--zenith-flow-space) 0;
- overflow: hidden;
- background: var(--zenith-table-surface);
- border: 1px solid var(--zenith-table-border);
- border-radius: var(--zenith-panel-radius);
- transition: border-color 180ms ease, box-shadow 220ms ease;
-}
-
- .doc-table-frame:hover,
- .doc-table-frame:focus-within {
- border-color: color-mix(in srgb, var(--zenith-purple-700) 34%, var(--zenith-table-border));
- box-shadow: 0 10px 24px rgba(35, 30, 46, 0.06);
- }
-
- .doc-table-frame::after {
- content: "";
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- z-index: 2;
- width: 22px;
- pointer-events: none;
- background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--zenith-table-surface) 94%, transparent));
- opacity: 0;
- transition: opacity 180ms ease;
- }
-
- .doc-table-frame.is-scrollable:not(.is-scroll-end)::after {
- opacity: 1;
- }
-
- .doc-table-frame > .table-responsive {
- margin: 0;
- overflow-x: auto;
- border: 0;
- border-radius: 0;
- }
-
-article .doc-table-frame table,
-article .doc-table-frame .table {
- --bs-table-color: var(--zenith-table-text);
- --bs-table-bg: var(--zenith-table-surface);
- --bs-table-border-color: var(--zenith-table-border);
- --bs-table-striped-bg: var(--zenith-table-stripe);
- --bs-table-hover-bg: var(--zenith-table-hover);
- width: 100%;
- min-width: 560px;
- margin: 0;
- color: var(--zenith-table-text);
- background: var(--zenith-table-surface);
- border: 0;
- border-collapse: collapse;
- border-radius: 0;
- font-size: 14px;
- line-height: 1.5;
-}
-
- article .doc-table-frame table > :not(caption) > * {
- border: 0;
- }
-
- article .doc-table-frame table > :not(caption) > * > * {
- box-shadow: none;
- }
-
-article .doc-table-frame th {
- padding: var(--zenith-table-padding-block) var(--zenith-table-padding-inline);
- color: var(--zenith-table-heading);
- background: var(--zenith-table-header);
- border: 0;
- border-right: 1px solid var(--zenith-table-border);
- border-bottom: 1px solid var(--zenith-table-border);
- font-size: 12px;
- font-weight: 700;
- text-align: left;
- text-transform: none;
- vertical-align: bottom;
-}
-
-article .doc-table-frame td {
- padding: var(--zenith-table-padding-block) var(--zenith-table-padding-inline);
- background: var(--zenith-table-surface);
- border: 0;
- border-right: 1px solid var(--zenith-table-border);
- border-bottom: 1px solid var(--zenith-table-border);
- vertical-align: middle;
- transition: background-color 160ms ease, color 160ms ease;
-}
-
- article .doc-table-frame th:last-child,
- article .doc-table-frame td:last-child {
- border-right: 0;
- }
-
-article .doc-table-frame tbody tr:nth-child(even) td {
- background: var(--zenith-table-stripe);
-}
-
-article .doc-table-frame tbody tr:last-child td {
- border-bottom: 0;
-}
-
-article .doc-table-frame tbody tr:hover td {
- background: var(--zenith-table-hover);
-}
-
-article .doc-table-frame tbody td:first-child {
- color: var(--zenith-table-heading);
- font-weight: 650;
-}
-
-article .doc-table-frame td > code:only-child {
- white-space: nowrap;
-}
-
-.alert {
- position: relative;
- margin: var(--zenith-flow-space) 0;
- padding: 16px 18px;
- color: var(--zenith-text-soft);
- background: var(--zenith-surface-soft);
- border: 1px solid var(--zenith-border);
- border-left-width: 4px;
- border-radius: var(--zenith-panel-radius);
- font-size: 14px;
- transition: border-color 180ms ease, box-shadow 220ms ease, transform 220ms var(--zenith-ease);
-}
-
- .alert:hover,
- .alert:focus-within {
- box-shadow: 0 8px 22px rgba(35, 30, 46, 0.07);
- transform: translateY(-1px);
- }
-
-[data-bs-theme="dark"] .alert:hover,
-[data-bs-theme="dark"] .alert:focus-within {
- box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2);
-}
-
-.alert > h5,
-.alert > .alert-title {
- margin-bottom: 5px;
- color: var(--zenith-text);
- font-size: 13px;
- font-weight: 850;
-}
-
-.alert-info,
-.alert-note {
- background: color-mix(in srgb, var(--zenith-info) 8%, transparent);
- border-left-color: var(--zenith-info);
-}
-
-.alert-tip,
-.alert-success {
- background: color-mix(in srgb, var(--zenith-success) 8%, transparent);
- border-left-color: var(--zenith-success);
-}
-
-.alert-warning,
-.alert-important {
- background: color-mix(in srgb, var(--zenith-warning) 8%, transparent);
- border-left-color: var(--zenith-warning);
-}
-
-.alert-danger,
-.alert-caution {
- background: color-mix(in srgb, var(--zenith-danger) 8%, transparent);
- border-left-color: var(--zenith-danger);
-}
-
-.tabGroup {
- margin: var(--zenith-flow-space) 0;
- border: 1px solid var(--zenith-border);
- border-radius: var(--zenith-panel-radius);
- overflow: hidden;
-}
-
- .tabGroup > ul.nav-tabs {
- padding: 0 10px;
- background: var(--zenith-surface-soft);
- border-bottom: 1px solid var(--zenith-border);
- }
-
- .tabGroup > ul.nav-tabs > li > a {
- padding: 10px 12px;
- color: var(--zenith-text-soft);
- border: 0;
- border-bottom: 2px solid transparent;
- font-size: 13px;
- text-decoration: none;
- }
-
- .tabGroup > ul.nav-tabs > li.active > a,
- .tabGroup > ul.nav-tabs > li > a:hover {
- color: var(--zenith-purple-700);
- border-bottom-color: var(--zenith-purple-700);
- }
-
- .tabGroup > section {
- padding: 18px;
- }
-
-article .inheritance,
-article .implements,
-article .inheritedMembers,
-article .derivedClasses {
- color: var(--zenith-text-soft);
-}
-
-article dl.typelist,
-article dl.parameters,
-article dl.returns {
- margin: 18px 0;
-}
-
-article dl dt {
- color: var(--zenith-text);
- font-weight: 750;
-}
-
-article .section {
- scroll-margin-top: calc(var(--zenith-header-height) + 20px);
-}
-
-article h3[id],
-article a[data-uid] + h3 {
- padding-top: 5px;
-}
-
-/* ==========================================================================
- Side navigation and in-page navigation
- ========================================================================== */
-
-#sidetoggle {
- position: sticky;
- top: var(--zenith-header-height);
- align-self: start;
- height: calc(100vh - var(--zenith-header-height));
- overflow: hidden;
- border-right: 1px solid var(--zenith-border);
-}
-
- #sidetoggle > div {
- height: 100%;
- }
-
-.sidefilter {
- padding: 18px 14px 10px 0;
-}
-
-.toc-filter {
- position: relative;
-}
-
- .toc-filter > input,
- #toc_filter_input {
- width: 100%;
- height: 34px;
- padding: 7px 12px 7px 33px;
- color: var(--zenith-text);
- background: var(--zenith-surface-soft);
- border: 1px solid var(--zenith-border);
- border-radius: 6px;
- box-shadow: none;
- font-size: 12px;
- }
-
- .toc-filter .filter-icon {
- position: absolute;
- top: 50%;
- left: 11px;
- z-index: 1;
- display: inline-flex;
- width: 16px;
- height: 16px;
- align-items: center;
- justify-content: center;
- color: var(--zenith-text-faint);
- line-height: 1;
- transform: translateY(-50%);
- }
-
-.sidetoc {
- height: calc(100% - 62px);
- padding: 4px 12px 30px 0;
- overflow: auto;
-}
-
-.toc {
- padding: 0 16px 28px;
- font-size: 13px;
-}
-
-#toc.zenith-scroll-host > .overflow-y-auto,
-main > .affix.zenith-scroll-host > #affix {
- scrollbar-width: none;
-}
-
-#toc > .overflow-y-auto {
- overflow-x: hidden !important;
-}
-
-#toc.zenith-scroll-host > .overflow-y-auto::-webkit-scrollbar,
-main > .affix.zenith-scroll-host > #affix::-webkit-scrollbar {
- display: none;
- width: 0;
- height: 0;
-}
-
-.zenith-scroll-host {
- position: relative;
-}
-
-.zenith-overlay-scrollbar {
- position: absolute;
- right: var(--zenith-scrollbar-inset);
- z-index: 8;
- width: var(--zenith-scrollbar-size);
- opacity: 0;
- pointer-events: none;
- touch-action: none;
- transition: opacity 160ms ease;
-}
-
-main > .affix > .zenith-overlay-scrollbar {
- right: calc(-1 * (var(--zenith-scrollbar-size) + var(--zenith-scrollbar-edge-gap)));
-}
-
-.zenith-overlay-scrollbar > span {
- position: absolute;
- top: 0;
- right: var(--zenith-scrollbar-inset);
- left: var(--zenith-scrollbar-inset);
- min-height: var(--zenith-scrollbar-min-thumb);
- background: var(--zenith-scrollbar-thumb);
- border-radius: 999px;
- cursor: grab;
- transition: background-color 160ms ease;
-}
-
-.zenith-scroll-host.has-scroll-range:hover > .zenith-overlay-scrollbar,
-.zenith-scroll-host.has-scroll-range:focus-within > .zenith-overlay-scrollbar,
-.zenith-scroll-host.has-scroll-range.is-scrolling > .zenith-overlay-scrollbar,
-.zenith-overlay-scrollbar.is-dragging {
- opacity: 1;
- pointer-events: auto;
-}
-
- .zenith-overlay-scrollbar > span:hover,
- .zenith-overlay-scrollbar.is-dragging > span {
- background: var(--zenith-scrollbar-thumb-hover);
- }
-
- .zenith-overlay-scrollbar.is-dragging > span {
- cursor: grabbing;
- }
-
-.zenith-overlay-scrollbar:focus-visible {
- outline: 0;
-}
-
- .zenith-overlay-scrollbar:focus-visible > span {
- background: var(--zenith-scrollbar-thumb-hover);
- box-shadow: 0 0 0 1px var(--zenith-accent);
- }
-
-#toc > form.filter {
- position: relative;
- margin: 16px 0 18px;
-}
-
- #toc > form.filter input {
- height: 36px;
- padding-left: 36px;
- color: var(--zenith-text);
- background: var(--zenith-search-surface);
- border-color: var(--zenith-search-border);
- border-radius: 6px;
- box-shadow: none;
- font-size: 13px;
- }
-
- #navbar form.search > input:focus,
- #toc > form.filter input:focus,
- .toc-filter > input:focus,
- #toc_filter_input:focus {
- outline: 0 !important;
- color: var(--zenith-text);
- background: var(--zenith-surface);
- border-color: var(--zenith-search-focus);
- box-shadow: inset 0 0 0 1px rgba(81, 43, 212, 0.22);
- }
-
- #toc > form.filter i {
- position: absolute;
- top: 50%;
- left: 12px;
- z-index: 1;
- display: inline-flex;
- width: 16px;
- height: 16px;
- align-items: center;
- justify-content: center;
- color: var(--zenith-text-faint);
- line-height: 1;
- transform: translateY(-50%);
- }
-
-.toc ul {
- margin: 0;
- padding-left: 0;
- list-style: none;
-}
-
-.toc li > a {
- white-space: nowrap;
-}
-
-#toc > .overflow-y-auto > ul > li > a wbr {
- display: none;
-}
-
-.toc ul ul {
- margin: 3px 0 9px 10px;
- padding-left: 11px;
- border-left: 1px solid var(--zenith-border);
-}
-
-.toc li {
- position: relative;
-}
-
- .toc li > a {
- position: relative;
- display: block;
- margin: 1px 0;
- padding: 6px 9px;
- color: var(--zenith-text-soft);
- border-radius: 5px;
- font-weight: 550;
- line-height: 1.35;
- text-decoration: none !important;
- transition: color 160ms ease, background-color 160ms ease, box-shadow 180ms ease, transform 180ms var(--zenith-ease);
- }
-
- .toc li > a:hover {
- color: var(--zenith-purple-700);
- background: rgba(113, 56, 232, 0.06);
- }
-
- .toc li.active:not(.expander) > a,
- .toc li > a.active {
- color: var(--zenith-nav-active);
- background: transparent;
- font-weight: 750;
- box-shadow: none;
- text-decoration: none !important;
- }
-
- .toc li.active:not(.expander) > a::before,
- .toc li > a.active::before {
- content: "";
- position: absolute;
- top: 50%;
- bottom: auto;
- left: 0;
- width: 2px;
- height: 14px;
- background: var(--zenith-purple-700);
- border-radius: 2px;
- transform: translateY(-50%);
- }
-
- .toc li.expander > a {
- color: var(--zenith-text);
- font-weight: 700;
- }
-
-.toc > div > ul > li:first-child > a {
- color: var(--zenith-purple-700);
- font-size: 11px;
- font-weight: 800;
- text-transform: uppercase;
-}
-
-.toc .expand-stub {
- position: absolute;
- top: 0;
- left: 0;
- z-index: 1;
- display: block;
- width: 14px;
- height: 31px;
- color: var(--zenith-text-faint);
- cursor: pointer;
-}
-
- .toc .expand-stub::before {
- position: absolute;
- top: 50%;
- left: 50%;
- display: block;
- width: 14px;
- height: 14px;
- margin: 0;
- line-height: 14px;
- text-align: center;
- transform: translate(-50%, -50%);
- transform-origin: center;
- transition: color 160ms ease, transform 220ms var(--zenith-ease);
- }
-
-.toc li.expanded > .expand-stub::before {
- transform: translate(-50%, -50%) rotate(90deg);
-}
-
-#affix {
- position: static;
- top: auto;
- max-height: calc(100vh - var(--zenith-header-height) - 32px);
- padding-left: 0;
- overflow: auto;
- border-left: 0;
-}
-
- #affix::before {
- content: none;
- }
-
- #affix > h5 {
- display: block;
- width: 100%;
- margin: 0 0 14px;
- padding: 0 0 12px;
- color: var(--zenith-text-faint);
- border-bottom: 1px solid var(--zenith-border) !important;
- font-size: 10px;
- font-weight: 850;
- text-transform: uppercase;
- }
-
- #affix ul {
- margin: 0;
- padding: 0;
- border-left: 0;
- list-style: none;
- }
-
- #affix a {
- position: relative;
- display: block;
- overflow: hidden;
- margin: 1px 0;
- padding: 6px 9px;
- color: var(--zenith-text-faint);
- border: 0;
- border-radius: 6px;
- font-size: 12px;
- line-height: 1.4;
- text-decoration: none !important;
- text-overflow: ellipsis;
- white-space: nowrap;
- transition: color 160ms ease, background-color 160ms ease, box-shadow 180ms ease, transform 180ms var(--zenith-ease);
- }
-
- #affix a.link-body-emphasis {
- font-weight: 600;
- }
-
- #affix a.link-secondary {
- padding-left: 18px;
- font-size: 11px;
- font-weight: 400;
- }
-
- #affix a wbr {
- display: none;
- }
-
-body[data-yaml-mime="ManagedReference"] #affix {
- position: static;
- overflow-x: hidden;
- overflow-y: auto;
-}
-
-#affix a:hover {
- color: var(--zenith-purple-700);
- background: rgba(113, 56, 232, 0.06);
-}
-
-#affix a.active,
-#affix a.is-active {
- color: var(--zenith-nav-active) !important;
- background: transparent;
- font-weight: 700;
- box-shadow: none;
-}
-
- #affix a.active::before,
- #affix a.is-active::before {
- content: "";
- position: absolute;
- top: 50%;
- bottom: auto;
- left: 0;
- width: 2px;
- height: 14px;
- background: var(--zenith-purple-700);
- border-radius: 2px;
- transform: translateY(-50%);
- }
-
-#affix a.link-secondary.active::before,
-#affix a.link-secondary.is-active::before {
- left: 8px;
-}
-
-[data-bs-theme="dark"] .toc li > a:hover,
-[data-bs-theme="dark"] #affix a:hover {
- color: #d1c4ff !important;
-}
-
-[data-bs-theme="dark"] .toc li.active:not(.expander) > a,
-[data-bs-theme="dark"] .toc li > a.active,
-[data-bs-theme="dark"] #affix a.active,
-[data-bs-theme="dark"] #affix a.is-active {
- color: var(--zenith-nav-active) !important;
-}
-
-.doc-code-frame pre > .code-action {
- display: none !important;
-}
-
-@media (max-width: 575.98px) {
- article .doc-code-frame > pre {
- padding: 12px;
- }
-
- .doc-code-toolbar {
- padding-left: 12px;
- }
-}
-
-/* ==========================================================================
- Search, footer, pagination
- ========================================================================== */
-
-#search-results {
- width: 100%;
- max-width: var(--zenith-docs-width) !important;
- min-height: 65vh;
- margin: 0 auto;
- padding: 42px var(--zenith-page-gutter) !important;
-}
-
-body[data-search] > main.container-xxl {
- display: none !important;
-}
-
-body[data-search] #search-results {
- display: grid;
- grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr) var(--zenith-affix-width);
- column-gap: var(--zenith-column-gap);
-}
-
-body:is([data-yaml-mime="ManagedReference"], [data-zenith-api])[data-search] #search-results {
- grid-template-columns: var(--zenith-api-sidebar-width) minmax(0, 1fr) var(--zenith-api-sidebar-width);
-}
-
-body[data-search] #search-results > * {
- grid-column: 2;
- min-width: 0;
-}
-
-#search-results > .sr-items {
- display: grid;
- grid-template-columns: minmax(0, 1fr);
- gap: var(--zenith-column-gap);
-}
-
-#search-results .sr-item {
- min-width: 0;
- padding: 18px 20px;
- background: var(--zenith-surface);
- border: 1px solid var(--zenith-border);
- border-radius: var(--zenith-panel-radius);
- animation: zenith-reveal-in 420ms var(--zenith-ease) backwards;
- transition: border-color 160ms ease, box-shadow 200ms ease, transform 200ms var(--zenith-ease);
-}
-
- #search-results .sr-item:nth-child(2) {
- animation-delay: 35ms;
- }
-
- #search-results .sr-item:nth-child(3) {
- animation-delay: 70ms;
- }
-
- #search-results .sr-item:nth-child(4) {
- animation-delay: 105ms;
- }
-
- #search-results .sr-item:nth-child(5) {
- animation-delay: 140ms;
- }
-
- #search-results .sr-item:nth-child(n + 6) {
- animation-delay: 175ms;
- }
-
- #search-results .sr-item:hover {
- border-color: rgba(113, 56, 232, 0.36);
- box-shadow: var(--zenith-shadow-small);
- transform: translateY(-2px);
- }
-
-#search-results .item-title {
- overflow-wrap: anywhere;
- color: var(--zenith-purple-700);
- font-size: 17px;
- font-weight: 750;
- text-decoration: none;
-}
-
-#search-results .item-href {
- overflow-wrap: anywhere;
- color: var(--zenith-text-faint);
- font-size: 11px;
-}
-
-#search-results .item-brief {
- overflow-wrap: anywhere;
- color: var(--zenith-text-soft);
- font-size: 14px;
-}
-
-html.zenith-motion-ready .zenith-reveal.is-visible {
- animation: zenith-reveal-in 720ms var(--zenith-ease) var(--zenith-reveal-delay, 0ms) backwards;
-}
-
-html.zenith-motion-ready .zenith-reveal[data-reveal-from="left"].is-visible {
- animation-name: zenith-reveal-left;
-}
-
-html.zenith-motion-ready .zenith-reveal[data-reveal-from="right"].is-visible {
- animation-name: zenith-reveal-right;
-}
-
-@keyframes zenith-copy-confirm {
- 0% {
- transform: scale(0.72);
- }
-
- 58% {
- transform: scale(1.16);
- }
-
- 100% {
- transform: scale(1);
- }
-}
-
-@keyframes zenith-reveal-in {
- from {
- opacity: 0;
- transform: translateY(18px) scale(0.985);
- }
-
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-@keyframes zenith-reveal-left {
- from {
- opacity: 0;
- transform: translateX(-24px) scale(0.985);
- }
-
- to {
- opacity: 1;
- transform: translateX(0);
- }
-}
-
-@keyframes zenith-reveal-right {
- from {
- opacity: 0;
- transform: translateX(24px) scale(0.985);
- }
-
- to {
- opacity: 1;
- transform: translateX(0);
- }
-}
-
-@keyframes zenith-reading-progress {
- from {
- transform: scaleX(0);
- }
-
- to {
- transform: scaleX(1);
- }
-}
-
-.next-article {
- width: 100%;
- margin: 26px auto 0;
- padding-top: 18px;
- border-color: var(--zenith-border) !important;
-}
-
- .next-article a {
- text-decoration: none !important;
- }
-
-body > footer {
- position: relative;
- z-index: 2;
- color: var(--zenith-text-faint) !important;
- background: var(--zenith-surface-soft);
- border-color: var(--zenith-border) !important;
- font-size: 12px;
-}
-
- body > footer .container-xxl {
- max-width: var(--zenith-content-width) !important;
- padding: 0 18px !important;
- }
-
- body > footer a {
- color: var(--zenith-purple-700);
- }
-
-/* ==========================================================================
- Responsive behavior
- ========================================================================== */
-
-@media (max-width: 1180px) {
- #navbar form.search {
- width: 165px;
- }
-
- .hero-copy h1 {
- font-size: 46px;
- }
-
- body:not([data-layout="landing"]) > main.container-xxl {
- grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr);
- column-gap: 20px;
- }
-
- body:is([data-yaml-mime="ManagedReference"], [data-zenith-api]) > main.container-xxl {
- grid-template-columns: minmax(320px, 28%) minmax(0, 1fr);
- }
-
- body[data-search] #search-results {
- grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr);
- column-gap: 20px;
- }
-
- body:is([data-yaml-mime="ManagedReference"], [data-zenith-api])[data-search] #search-results {
- grid-template-columns: minmax(320px, 28%) minmax(0, 1fr);
- }
-
- body:not([data-layout="landing"]) main > .affix {
- display: none !important;
- }
-}
-
-@media (max-width: 991.98px) {
- :root {
- --zenith-header-height: 70px;
- --zenith-header-bar-height: 54px;
- --zenith-header-inset: 8px;
- }
-
- #logo {
- width: 109px;
- height: 34px;
- }
-
- .navbar-brand {
- margin-right: 18px !important;
- }
-
- #navbar .nav-link {
- padding: 0 8px !important;
- font-size: 13px;
- }
-
- #navbar .nav-link::after {
- right: 8px;
- left: 8px;
- }
-
- #navbar form.search {
- display: none;
- }
-
- .hero-copy {
- max-width: 690px;
- margin: 0 auto;
- text-align: center;
- }
-
- .hero-copy h1,
- .hero-lede {
- margin-right: auto;
- margin-left: auto;
- }
-
- .hero-actions,
- .backend-support {
- justify-content: center;
- }
-
- body:not([data-layout="landing"]) > main.container-xxl {
- grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr);
- padding-right: 14px !important;
- padding-left: 14px !important;
- column-gap: 18px;
- }
-
- body:is([data-yaml-mime="ManagedReference"], [data-zenith-api]) > main.container-xxl {
- grid-template-columns: minmax(320px, 34%) minmax(0, 1fr);
- }
-
- body[data-search] #search-results {
- grid-template-columns: var(--zenith-sidebar-width) minmax(0, 1fr);
- column-gap: 18px;
- }
-
- body:is([data-yaml-mime="ManagedReference"], [data-zenith-api])[data-search] #search-results {
- grid-template-columns: minmax(320px, 34%) minmax(0, 1fr);
- }
-}
-
-@media (max-width: 767.98px) {
- :root {
- --zenith-header-height: 64px;
- --zenith-header-bar-height: 64px;
- --zenith-header-inset: 0px;
- --zenith-header-inline-inset: 0px;
- }
-
- body > header.bg-body,
- body > header {
- top: 0;
- left: 0;
- width: 100%;
- height: auto;
- min-height: var(--zenith-header-height);
- border: 0 !important;
- border-bottom: 1px solid var(--zenith-header-border) !important;
- border-radius: 0;
- box-shadow: 0 1px 6px rgba(20, 28, 58, 0.04);
- transform: none;
- }
-
- .navbar > .container-xxl {
- min-height: var(--zenith-header-bar-height);
- }
-
- .navbar .navbar-toggler,
- .navbar button[data-bs-toggle="collapse"] {
- color: var(--zenith-text);
- border: 0 !important;
- border-radius: 6px !important;
- }
-
- .navbar-collapse {
- position: absolute;
- top: var(--zenith-header-bar-height);
- right: 0;
- left: 0;
- padding: 10px 18px 16px;
- background: var(--zenith-surface);
- border-bottom: 1px solid var(--zenith-border);
- box-shadow: var(--zenith-shadow-medium);
- }
-
- #navbar {
- display: flex;
- align-items: stretch;
- flex-direction: column;
- gap: 8px;
- }
-
- #navbar .navbar-nav {
- order: 1;
- width: 100%;
- align-items: stretch;
- flex-direction: column;
- gap: 2px;
- }
-
- #navbar .nav-link {
- padding: 10px 12px !important;
- border-radius: 5px !important;
- }
-
- #navbar .nav-link::after {
- display: none;
- }
-
- #navbar form.search {
- display: block;
- order: 2 !important;
- width: 100%;
- }
-
- #navbar form.search.zenith-search {
- display: flex;
- width: 36px;
- }
-
- #navbar form.search.zenith-search.is-expanded {
- width: 100%;
- }
-
- .navbar .icons {
- order: 3 !important;
- justify-content: flex-start;
- }
-
- .landing-shell {
- width: calc(100% - 28px);
- }
-
- .hero-copy h1 {
- font-size: 40px;
- line-height: 1.08;
- }
-
- .hero-lede {
- font-size: 16px;
- }
-
- body:not([data-layout="landing"]) > main.container-xxl {
- display: block;
- padding: 0 16px !important;
- }
-
- body:not([data-layout="landing"]) main > .content {
- padding-top: 18px;
- }
-
- body[data-search] #search-results {
- display: block;
- }
-
- #sidetoggle {
- position: static;
- height: auto;
- border-right: 0;
- }
-
- .sidetoc {
- height: auto;
- }
-
- article h1 {
- font-size: 31px;
- }
-
- article h2 {
- font-size: 23px;
- }
-}
-
-@media (max-width: 575.98px) {
- #logo {
- width: 102px;
- height: 32px;
- }
-
- .navbar > .container-xxl {
- padding-right: 12px !important;
- padding-left: 12px !important;
- }
-
- .hero-copy h1 {
- font-size: 34px;
- }
-
- .hero-actions .landing-button {
- width: 100%;
- }
-
- .backend-label {
- width: 100%;
- }
-
- article h1 {
- font-size: 28px;
- }
-
- article h2 {
- margin-top: 35px;
- font-size: 21px;
- }
-
- article pre,
- .codewrapper pre {
- padding: 18px;
- font-size: 11px;
- }
-}
-
-@media (prefers-reduced-motion: reduce) {
- *,
- *::before,
- *::after {
- scroll-behavior: auto !important;
- animation-duration: 0.01ms !important;
- animation-iteration-count: 1 !important;
- transition-duration: 0.01ms !important;
- }
-
- .zenith-reading-progress > span {
- animation: none !important;
- transform: scaleX(0) !important;
- transition: none !important;
- }
-
- html.zenith-motion-ready .zenith-reveal {
- opacity: 1 !important;
- transform: none !important;
- }
-}
-
-@media print {
- body {
- padding-top: 0 !important;
- }
-
- body > header,
- #sidetoggle,
- main > .affix,
- body > footer,
- .next-article {
- display: none !important;
- }
-
- body:not([data-layout="landing"]) > main.container-xxl {
- display: block;
- max-width: none !important;
- padding: 0 !important;
- }
-
- article {
- max-width: none !important;
- }
-}
-
-/* ==========================================================================
- Renderer Console V2
- ========================================================================== */
-
-.render-hero {
- --render-viewport-surface: #17131f;
- --render-viewport-toolbar: #211b2b;
- --render-viewport-border: rgba(53, 43, 66, 0.46);
- --render-viewport-text: #f1edf6;
- --render-viewport-muted: #aaa1b5;
- --render-viewport-shadow: 0 24px 58px rgba(0, 0, 0, 0.26), 0 3px 12px rgba(0, 0, 0, 0.18);
- position: relative;
- min-height: calc(620px + var(--zenith-header-height));
- overflow: hidden;
- color: #ffffff;
- background: #100d16;
-}
-
-.render-hero-inner {
- position: relative;
- z-index: 2;
- display: grid;
- grid-template-columns: minmax(0, 0.9fr) minmax(470px, 1.1fr);
- gap: clamp(28px, 3vw, 48px);
- min-height: calc(620px + var(--zenith-header-height));
- align-items: center;
- padding-top: calc(48px + var(--zenith-header-height));
- padding-bottom: 48px;
-}
-
-.render-hero .hero-copy {
- width: 100%;
- max-width: 650px;
- margin: 0;
- padding: 0;
- text-align: left;
- animation: none;
-}
-
- .render-hero .hero-copy h1,
- .render-hero .hero-lede,
- .render-hero .hero-actions,
- .render-hero .backend-support {
- animation: render-hero-copy-in 720ms var(--zenith-ease) both;
- }
-
- .render-hero .hero-copy h1 {
- animation-delay: 80ms;
- }
-
-.render-hero .hero-lede {
- animation-delay: 170ms;
-}
-
-.render-hero .hero-actions {
- animation-delay: 260ms;
-}
-
-.render-hero .backend-support {
- animation-delay: 350ms;
-}
-
-.render-viewport {
- position: relative;
- width: 100%;
- min-width: 0;
- overflow: hidden;
- color: var(--render-viewport-text);
- background: var(--render-viewport-surface);
- border: 1px solid var(--render-viewport-border);
- border-radius: 6px;
- box-shadow: var(--render-viewport-shadow);
- animation: render-scene-in 820ms var(--zenith-ease) 210ms both;
-}
-
- .render-viewport::before {
- content: "";
- position: absolute;
- top: -1px;
- right: 0;
- left: 0;
- z-index: 3;
- height: 1px;
- pointer-events: none;
- background: linear-gradient(90deg, #512bd4, #7c4dff 52%, #34c9f5);
- }
-
-.render-viewport-toolbar {
- position: relative;
- z-index: 2;
- display: flex;
- height: 38px;
- align-items: center;
- gap: 12px;
- padding: 0 8px 0 13px;
- color: var(--render-viewport-muted);
- background: var(--render-viewport-toolbar);
- border-bottom: 1px solid var(--render-viewport-border);
- font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace;
- font-size: 10px;
- line-height: 1;
-}
-
-.render-viewport-title,
-.render-viewport-details {
- display: inline-flex;
- align-items: center;
- gap: 7px;
- white-space: nowrap;
-}
-
-.render-viewport-title {
- color: var(--render-viewport-text);
- font-weight: 700;
- text-transform: uppercase;
-}
-
-.zenith-carousel-status {
- position: absolute;
- width: 1px;
- height: 1px;
- margin: -1px;
- padding: 0;
- overflow: hidden;
- clip: rect(0 0 0 0);
- white-space: nowrap;
- border: 0;
-}
-
-.render-viewport-title > i {
- color: var(--zenith-purple-700);
- font-size: 12px;
-}
-
-.render-viewport-details {
- margin-left: auto;
-}
-
-.render-viewport-open {
- display: inline-flex;
- width: 28px;
- height: 28px;
- align-items: center;
- justify-content: center;
- color: var(--render-viewport-muted) !important;
- border-radius: 4px;
- text-decoration: none !important;
- transition: color 160ms ease, background-color 160ms ease;
-}
-
- .render-viewport-open:hover,
- .render-viewport-open:focus-visible {
- color: var(--render-viewport-text) !important;
- background: rgba(255, 255, 255, 0.08);
- }
-
-.render-carousel-controls {
- display: inline-flex;
- align-items: center;
- gap: 2px;
-}
-
-.render-viewport-canvas {
- position: relative;
- aspect-ratio: 16 / 9;
- overflow: hidden;
- background: #dce6f2;
-}
-
- .render-viewport-canvas > a {
- display: block;
- width: 100%;
- height: 100%;
- color: transparent;
- text-decoration: none;
- }
-
-.render-carousel-slide {
- position: absolute;
- inset: 0;
- z-index: 0;
- opacity: 0;
- pointer-events: none;
- transition: opacity 480ms ease;
-}
-
- .render-carousel-slide.is-active {
- z-index: 1;
- opacity: 1;
- pointer-events: auto;
- }
-
-.render-carousel-control {
- display: none;
- width: 28px;
- height: 28px;
- align-items: center;
- justify-content: center;
- padding: 0;
- color: var(--render-viewport-muted);
- background: transparent;
- border: 1px solid transparent;
- border-radius: 4px;
- transition: color 160ms ease, background-color 160ms ease, border-color 160ms ease;
-}
-
- .render-carousel-control:hover,
- .render-carousel-control:focus-visible {
- color: var(--render-viewport-text);
- background: rgba(255, 255, 255, 0.08);
- border-color: rgba(255, 255, 255, 0.12);
- }
-
-.render-carousel-progress {
- position: absolute;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 3;
- display: none;
- height: 3px;
- overflow: hidden;
- pointer-events: none;
- background: rgba(16, 13, 22, 0.26);
-}
-
- .render-carousel-progress > span {
- display: block;
- width: 100%;
- height: 100%;
- background: linear-gradient(90deg, #7847ff, #43d8ff);
- box-shadow: 0 0 8px rgba(124, 77, 255, 0.42);
- transform: scaleX(0);
- transform-origin: left center;
- }
-
- .render-carousel-progress > span.is-timing {
- animation: render-carousel-timing 6500ms linear forwards;
- animation-play-state: paused;
- }
-
-.render-viewport.is-carousel-ready .render-carousel-control,
-.render-viewport.is-carousel-ready .render-carousel-progress {
- display: flex;
-}
-
-.render-viewport.is-carousel-playing .render-carousel-progress > span.is-timing {
- animation-play-state: running;
-}
-
-.render-hero-media {
- display: block;
- width: 100%;
- height: 100%;
- max-width: none;
- object-fit: cover;
- object-position: center;
- border: 0 !important;
- border-radius: 0 !important;
- outline: 0;
- box-shadow: none;
-}
-
-@media (max-width: 767.98px) {
- .render-carousel-control {
- width: 26px;
- height: 26px;
- }
-
- .render-carousel-control[data-carousel-toggle] {
- display: none !important;
- }
-
- .render-carousel-progress {
- display: none !important;
- }
-}
-
-@media (prefers-reduced-motion: reduce) {
- .render-carousel-slide,
- .render-carousel-control {
- transition: none !important;
- }
-
- .render-carousel-control[data-carousel-toggle] {
- display: none !important;
- }
-
- .render-carousel-progress {
- display: none !important;
- }
-}
-
-@keyframes render-carousel-timing {
- from {
- transform: scaleX(0);
- }
-
- to {
- transform: scaleX(1);
- }
-}
-
-.render-hero .hero-copy h1 {
- max-width: 650px;
- margin: 0 0 24px;
- padding: 0 0 6px;
- color: #f4faf8;
- font-size: 62px;
- font-weight: 760;
- line-height: 1.08;
- overflow: visible;
- text-shadow: 0 2px 24px rgba(0, 0, 0, 0.28);
-}
-
- .render-hero .hero-copy h1 span {
- display: block;
- width: fit-content;
- margin-top: -4px;
- padding: 10px 0;
- color: #43d8ff;
- background: none;
- white-space: nowrap;
- -webkit-text-fill-color: currentColor;
- }
-
-@media (min-width: 992px) {
- .render-hero .hero-copy h1 {
- font-size: clamp(41px, calc(4.4643vw - 3.2857px), 62px);
- }
-}
-
-.render-hero .hero-lede {
- max-width: 620px;
- margin-bottom: 30px;
- padding-bottom: 2px;
- color: #c5bdce;
- font-size: 17px;
- line-height: 1.8;
- overflow: visible;
-}
-
-.render-hero .landing-button-primary {
- color: #ffffff !important;
- background: #512bd4;
- box-shadow: none;
-}
-
- .render-hero .landing-button-primary:hover {
- color: #ffffff !important;
- background: #6844df;
- box-shadow: 0 12px 28px rgba(81, 43, 212, 0.3);
- }
-
-.render-hero .landing-button-secondary {
- color: #f8f5fc !important;
- background: rgba(16, 13, 22, 0.5);
- border-color: rgba(222, 213, 235, 0.36);
- box-shadow: none;
- backdrop-filter: blur(8px);
-}
-
- .render-hero .landing-button-secondary:hover {
- color: #ffffff !important;
- background: rgba(43, 32, 57, 0.84);
- border-color: rgba(67, 216, 255, 0.7);
- }
-
-.render-hero .backend-support {
- gap: 7px;
- margin-top: 34px;
-}
-
-.render-hero .backend-label {
- color: #8b8298;
- font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace;
- font-size: 9px;
- line-height: 1;
- letter-spacing: 0;
-}
-
-.render-hero .backend-pill {
- --backend-dot: #58b947;
- --backend-dot-glow: #58b947;
- --backend-dot-ring: transparent;
- min-height: 25px;
- gap: 7px;
- color: #d2cadb;
- background: rgba(16, 13, 22, 0.58);
- border-color: rgba(206, 194, 221, 0.24);
- border-radius: 4px;
- font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace;
- font-size: 9px;
- font-weight: 650;
- backdrop-filter: blur(8px);
-}
-
- .render-hero .backend-pill::before {
- content: "";
- width: 6px;
- height: 6px;
- flex: 0 0 6px;
- background: var(--backend-dot);
- border-radius: 50%;
- box-shadow: inset 0 0 0 1px var(--backend-dot-ring), 0 0 8px color-mix(in srgb, var(--backend-dot-glow) 48%, transparent);
- }
-
-.render-hero .backend-metal {
- --backend-dot: linear-gradient(135deg, #ffffff 0%, #a6afb9 48%, #e4e8ec 100%);
- --backend-dot-glow: #c9d0d7;
- --backend-dot-ring: rgba(74, 82, 92, 0.38);
-}
-
-.render-hero .backend-vulkan {
- --backend-dot: #b8202d;
- --backend-dot-glow: #d63b45;
-}
-
-.toc .nav > li > a:hover,
-.toc .nav > li.active > a,
-.toc .nav > li > a.active {
- color: var(--zenith-purple-700);
-}
-
-.toc .nav > li > a:hover {
- background: rgba(81, 43, 212, 0.07);
-}
-
-.toc .nav > li.active > a,
-.toc .nav > li > a.active {
- background: transparent;
-}
-
-@keyframes render-scene-in {
- from {
- opacity: 0;
- transform: translateX(24px) scale(0.985);
- }
-
- to {
- opacity: 1;
- transform: translateX(0) scale(1);
- }
-}
-
-@keyframes render-hero-copy-in {
- from {
- opacity: 0;
- transform: translateY(16px);
- }
-
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-@media (max-width: 991.98px) {
- .render-hero,
- .render-hero-inner {
- min-height: 0;
- }
-
- .render-hero-inner {
- grid-template-columns: minmax(0, 1fr);
- gap: 42px;
- padding-top: calc(56px + var(--zenith-header-height));
- padding-bottom: 64px;
- }
-
- .render-hero .hero-copy {
- max-width: none;
- }
-
- .render-viewport {
- width: min(760px, 100%);
- margin: 0 auto;
- }
-
- .render-hero .hero-actions,
- .render-hero .backend-support {
- justify-content: flex-start;
- }
-}
-
-@media (max-width: 767.98px) {
- .render-hero-inner {
- gap: 34px;
- padding-top: calc(48px + var(--zenith-header-height));
- padding-bottom: 54px;
- }
-
- .render-hero .hero-copy h1 {
- font-size: 43px;
- }
-}
-
-@media (max-width: 575.98px) {
- .render-hero .hero-copy h1 {
- font-size: 34px;
- line-height: 1.04;
- }
-
- .render-hero .hero-copy h1 span {
- margin-top: 0;
- padding: 6px 0 7px;
- }
-
- .render-hero .hero-lede {
- font-size: 15px;
- }
-
- .render-hero .backend-label {
- width: 100%;
- }
-
- .render-viewport-toolbar {
- gap: 8px;
- padding-left: 10px;
- font-size: 9px;
- }
-}
-
-@media (max-width: 374.98px) {
- .render-hero .hero-copy h1 span {
- font-size: 32px;
- }
-
- .render-viewport-details > :not(.render-viewport-position) {
- display: none;
- }
-}
-
-@media (max-width: 349.98px) {
- .render-hero .hero-copy h1 span {
- font-size: 29px;
- }
-}
-
-/* ==========================================================================
- Layout and border refinement
- ========================================================================== */
-
-.landing-button {
- gap: 9px;
-}
-
-@media (min-width: 768px) {
- .navbar > .container-xxl {
- max-width: none !important;
- padding-right: 18px !important;
- padding-left: 18px !important;
- transform: none;
- }
-
- .navbar-brand {
- margin-right: 10px !important;
- }
-
- #navbar {
- gap: 6px;
- }
-
- #navbar .navbar-nav {
- margin-right: auto !important;
- }
-
- #navbar form.search {
- margin-left: 0 !important;
- }
-
- .navbar .icons {
- margin-left: 0 !important;
- margin-right: 0 !important;
- }
-}
-
-article pre code.hljs,
-.codewrapper pre code.hljs {
- padding: 0 !important;
- overflow: visible;
- color: inherit;
- background: transparent !important;
-}
-
-/* ==========================================================================
- Theme-aware landing hero
- ========================================================================== */
-
-:root:not([data-bs-theme="dark"]) .render-hero {
- --render-viewport-surface: #ffffff;
- --render-viewport-toolbar: rgba(248, 247, 250, 0.96);
- --render-viewport-border: rgba(52, 43, 69, 0.15);
- --render-viewport-text: #342b45;
- --render-viewport-muted: #746b7e;
- --render-viewport-shadow: 0 18px 46px rgba(41, 27, 69, 0.1), 0 2px 8px rgba(41, 27, 69, 0.06);
- color: #231e2e;
- background-color: #fbfafc;
- background-image: none;
-}
-
-:root:not([data-bs-theme="dark"]) .render-viewport-open:hover,
-:root:not([data-bs-theme="dark"]) .render-viewport-open:focus-visible {
- background: rgba(81, 43, 212, 0.08);
-}
-
-[data-bs-theme="dark"] .render-hero {
- background-color: #100d16;
- background-image: linear-gradient(135deg, rgba(155, 123, 255, 0.1) 0%, rgba(155, 123, 255, 0.025) 38%, rgba(67, 216, 255, 0.06) 78%, transparent 100%);
-}
-
-:root:not([data-bs-theme="dark"]) .render-hero .hero-copy h1 {
- color: #2b2435;
- text-shadow: none;
-}
-
- :root:not([data-bs-theme="dark"]) .render-hero .hero-copy h1 span {
- color: #512bd4;
- }
-
-:root:not([data-bs-theme="dark"]) .render-hero .hero-lede {
- color: #655d70;
-}
-
-:root:not([data-bs-theme="dark"]) .render-hero .landing-button-secondary {
- color: #342b45 !important;
- background: rgba(255, 255, 255, 0.72);
- border-color: rgba(52, 43, 69, 0.22);
-}
-
- :root:not([data-bs-theme="dark"]) .render-hero .landing-button-secondary:hover {
- color: #512bd4 !important;
- background: rgba(255, 255, 255, 0.94);
- border-color: rgba(81, 43, 212, 0.46);
- }
-
-:root:not([data-bs-theme="dark"]) .render-hero .backend-label {
- color: #746b7e;
-}
-
-:root:not([data-bs-theme="dark"]) .render-hero .backend-pill {
- color: #40374b;
- background: rgba(255, 255, 255, 0.68);
- border-color: rgba(64, 52, 81, 0.2);
-}
-
-/* ==========================================================================
- Reference-inspired landing sections
- ========================================================================== */
-
-body[data-layout="landing"] > footer {
- display: none;
-}
-
-.landing-page .features-section {
- padding: 72px 0 104px;
-}
-
-.landing-page .section-heading,
-.landing-page .resources-heading {
- max-width: 760px;
- margin: 0 auto 56px;
- text-align: center;
-}
-
-.landing-page .section-kicker {
- display: block;
- margin-bottom: 14px;
- color: var(--zenith-purple-700);
- font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace;
- font-size: 10px;
- font-weight: 800;
- line-height: 1;
- letter-spacing: 0;
-}
-
-.landing-page .section-heading h2,
-.landing-page .architecture-copy h2,
-.landing-page .resources-heading h2 {
- margin: 0;
- padding: 0;
- color: #182746;
- border: 0;
- font-size: 39px;
- font-weight: 800;
- line-height: 1.08;
- letter-spacing: 0;
-}
-
- .landing-page .section-heading h2 span,
- .landing-page .architecture-copy h2 span {
- padding: 6px 5px 7px;
- color: #512bd4;
- background: linear-gradient(100deg, #512bd4, #c416b9);
- -webkit-background-clip: text;
- background-clip: text;
- -webkit-text-fill-color: transparent;
- }
-
- .landing-page .section-heading h2 > span {
- display: block;
- width: fit-content;
- margin-inline: auto;
- }
-
- .landing-page .architecture-copy h2 > span {
- display: inline-block;
- margin-left: -5px;
- }
-
- .landing-page .section-heading h2 > .anchorjs-link {
- display: none;
- }
-
-.landing-page h2::after {
- content: none;
-}
-
-.landing-page .section-heading p {
- max-width: 680px;
- margin: 18px auto 0;
- color: #6b7180;
- font-size: 15px;
- line-height: 1.65;
-}
-
-[data-bs-theme="dark"] .landing-page .section-heading h2,
-[data-bs-theme="dark"] .landing-page .architecture-copy h2,
-[data-bs-theme="dark"] .landing-page .resources-heading h2 {
- color: #f4f1fa;
-}
-
-[data-bs-theme="dark"] .landing-page .section-heading p {
- color: #aca4b7;
-}
-
-.landing-page .feature-grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 22px;
-}
-
-.landing-page .feature-card {
- display: flex;
- min-height: 214px;
- flex-direction: column;
- padding: 26px 24px;
- justify-content: flex-start;
- background: rgba(255, 255, 255, 0.84);
- border: 1px solid #e4e6ee;
- border-radius: var(--zenith-panel-radius);
- box-shadow: 0 8px 24px rgba(31, 43, 74, 0.045);
- transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease;
-}
-
- .landing-page .feature-card::before {
- display: none;
- }
-
- .landing-page .feature-card:hover {
- background: #ffffff;
- border-color: rgba(81, 43, 212, 0.24);
- box-shadow: 0 12px 30px rgba(31, 43, 74, 0.08);
- transform: translateY(-2px);
- }
-
-.landing-page .feature-icon {
- display: inline-flex;
- width: 40px;
- height: 40px;
- align-items: center;
- justify-content: center;
- margin-bottom: 22px;
- border: 0;
- border-radius: var(--zenith-panel-radius);
- font-size: 18px;
-}
-
-.landing-page .feature-icon-violet {
- color: #512bd4;
- background: #f1edff !important;
-}
-
-.landing-page .feature-icon-pink {
- color: #d31491;
- background: #fff0f8 !important;
-}
-
-.landing-page .feature-icon-blue {
- color: #1671e9;
- background: #edf5ff !important;
-}
-
-.landing-page .feature-icon-cyan {
- color: #087f9c;
- background: #e9faff !important;
-}
-
-.landing-page .feature-card h3 {
- margin: 0 0 8px;
- padding: 0;
- color: #172a4d;
- font-size: 18px;
- line-height: 1.3;
-}
-
-.landing-page .feature-card p {
- margin: 0;
- color: #697184;
- font-size: 14px;
- line-height: 1.6;
-}
-
-[data-bs-theme="dark"] .landing-page .feature-card {
- background: rgba(25, 20, 34, 0.88);
- border-color: #352c45;
- box-shadow: none;
-}
-
- [data-bs-theme="dark"] .landing-page .feature-card:hover {
- background: #20182b;
- border-color: rgba(155, 123, 255, 0.38);
- box-shadow: 0 12px 30px rgba(4, 3, 7, 0.28);
- }
-
- [data-bs-theme="dark"] .landing-page .feature-card h3 {
- color: #f4f1fa;
- }
-
- [data-bs-theme="dark"] .landing-page .feature-card p {
- color: #aaa3b4;
- }
-
-.architecture-section {
- padding: 96px 0;
-}
-
-.architecture-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- align-items: center;
- gap: clamp(48px, 6vw, 88px);
-}
-
-.architecture-copy .section-kicker {
- margin-bottom: 14px;
- text-align: left;
-}
-
-.architecture-copy h2 {
- max-width: 560px;
-}
-
-.architecture-copy > p {
- max-width: 610px;
- margin: 20px 0 28px;
- color: #626a7a;
- font-size: 15px;
- line-height: 1.7;
-}
-
-.architecture-points {
- display: grid;
- gap: 12px;
-}
-
- .architecture-points > a {
- display: grid;
- grid-template-columns: 26px minmax(0, 1fr);
- gap: 12px;
- align-items: center;
- padding: 14px 16px;
- color: #172a4d;
- background: rgba(255, 255, 255, 0.82);
- border: 1px solid rgba(81, 43, 212, 0.1);
- border-radius: 8px;
- text-decoration: none;
- transition: border-color 160ms ease, transform 160ms ease;
- }
-
- .architecture-points > a:hover {
- border-color: rgba(81, 43, 212, 0.32);
- transform: translateX(3px);
- }
-
- .architecture-points .architecture-step {
- display: inline-flex;
- width: 26px;
- height: 26px;
- align-items: center;
- justify-content: center;
- color: var(--zenith-purple-700);
- background: rgba(81, 43, 212, 0.08);
- border: 1px solid rgba(81, 43, 212, 0.18);
- border-radius: 50%;
- font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace;
- font-size: 9px;
- font-weight: 800;
- line-height: 1;
- }
-
- .architecture-points span,
- .architecture-points strong,
- .architecture-points small {
- display: block;
- }
-
- .architecture-points strong {
- margin-bottom: 3px;
- font-size: 13px;
- }
-
- .architecture-points small {
- color: #777d8c;
- font-size: 11px;
- line-height: 1.45;
- }
-
-[data-bs-theme="dark"] .architecture-copy > p {
- color: #aaa3b4;
-}
-
-[data-bs-theme="dark"] .architecture-points > a {
- color: #f4f1fa;
- background: rgba(31, 24, 43, 0.88);
- border-color: #3b304d;
-}
-
-[data-bs-theme="dark"] .architecture-points small {
- color: #9e96aa;
-}
-
-.resources-section {
- padding: 90px 0 96px;
-}
-
-.landing-page .resources-heading {
- margin-bottom: 50px;
-}
-
- .landing-page .resources-heading h2 {
- font-size: 36px;
- }
-
-.resource-grid {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 22px;
-}
-
-.resource-card {
- display: flex;
- min-height: 244px;
- flex-direction: column;
- padding: 28px;
- overflow: hidden;
- color: #ffffff !important;
- border-radius: 8px;
- box-shadow: 0 14px 30px rgba(31, 43, 91, 0.12);
- text-decoration: none !important;
- transition: transform 170ms ease, box-shadow 170ms ease;
-}
-
- .resource-card:hover {
- color: #ffffff !important;
- box-shadow: 0 18px 38px rgba(31, 43, 91, 0.18);
- transform: translateY(-4px);
- }
-
-.resource-card-docs {
- background: linear-gradient(135deg, #512bd4, #7542ed);
-}
-
-.resource-card-nuget {
- background: linear-gradient(135deg, #d10ba7, #ec35bd);
-}
-
-.resource-card-community {
- background: linear-gradient(135deg, #0869b8, #2689dc);
-}
-
-.resource-icon {
- display: inline-flex;
- width: 38px;
- height: 38px;
- align-items: center;
- justify-content: center;
- margin-bottom: 26px;
- background: rgba(255, 255, 255, 0.16);
- border-radius: 8px;
- font-size: 18px;
-}
-
-.resource-title {
- display: block;
- margin: 0 0 9px;
- padding: 0;
- color: #ffffff;
- font-size: 18px;
- font-weight: 800;
-}
-
-.resource-card p {
- margin: 0 0 24px;
- color: rgba(255, 255, 255, 0.82);
- font-size: 13px;
- line-height: 1.6;
-}
-
-.resource-card > strong {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- margin-top: auto;
- color: #ffffff;
- font-size: 13px;
-}
-
-/* Keep every landing section on one continuous theme canvas. */
-.landing-page {
- --landing-canvas: #fbfafc;
- --landing-atmosphere: linear-gradient(145deg, rgba(81, 43, 212, 0.055) 0%, rgba(81, 43, 212, 0.012) 46%, rgba(22, 113, 233, 0.04) 100%);
- --landing-grid-row: rgba(81, 43, 212, 0.034);
- --landing-grid-column: rgba(52, 201, 245, 0.03);
- --landing-footer-text: #667187;
- --landing-footer-heading-primary: #512bd4;
- --landing-footer-heading-secondary: #d31491;
- --landing-footer-link: #60708a;
- --landing-footer-link-hover: #512bd4;
- --landing-footer-icon: #512bd4;
- --landing-footer-icon-bg: rgba(81, 43, 212, 0.08);
- --landing-footer-icon-bg-hover: rgba(81, 43, 212, 0.15);
- --landing-footer-rule: rgba(24, 39, 70, 0.1);
- background-color: var(--landing-canvas);
- background-image: var(--landing-atmosphere), linear-gradient(var(--landing-grid-row) 1px, transparent 1px), linear-gradient(90deg, var(--landing-grid-column) 1px, transparent 1px);
- background-size: 100% 100%, 64px 64px, 64px 64px;
-}
-
-:root:not([data-bs-theme="dark"]) .landing-page .render-hero {
- background-color: transparent;
- background-image: none;
-}
-
-[data-bs-theme="dark"] .landing-page {
- --landing-canvas: #100d16;
- --landing-atmosphere: linear-gradient(145deg, rgba(155, 123, 255, 0.055) 0%, rgba(155, 123, 255, 0.012) 46%, rgba(67, 216, 255, 0.035) 100%);
- --landing-grid-row: rgba(155, 123, 255, 0.038);
- --landing-grid-column: rgba(67, 216, 255, 0.03);
- --landing-footer-text: #9f98ab;
- --landing-footer-heading-primary: #b9a5ff;
- --landing-footer-heading-secondary: #ff70c0;
- --landing-footer-link: #aaa3b8;
- --landing-footer-link-hover: #d1c4ff;
- --landing-footer-icon: #b9a5ff;
- --landing-footer-icon-bg: rgba(155, 123, 255, 0.1);
- --landing-footer-icon-bg-hover: rgba(155, 123, 255, 0.18);
- --landing-footer-rule: rgba(222, 213, 235, 0.1);
-}
-
- [data-bs-theme="dark"] .landing-page .render-hero {
- background-color: transparent;
- background-image: none;
- }
-
-.landing-page .features-section,
-.landing-page .architecture-section,
-.landing-page .resources-section {
- background: transparent;
-}
-
-.landing-footer {
- padding: 78px 0 34px;
- color: var(--landing-footer-text);
- background: transparent;
-}
-
-.landing-footer-grid {
- display: grid;
- grid-template-columns: minmax(0, 1.45fr) minmax(180px, 0.7fr) minmax(180px, 0.7fr);
- gap: 72px;
- padding-bottom: 58px;
-}
-
-.landing-footer-logo {
- display: block;
- width: auto;
- height: 48px;
-}
-
-.landing-footer-brand p {
- max-width: 480px;
- margin: 24px 0 28px;
- color: var(--landing-footer-text);
- font-size: 15px;
- line-height: 1.7;
-}
-
-.landing-footer-github {
- display: inline-flex;
- width: 42px;
- height: 42px;
- align-items: center;
- justify-content: center;
- color: var(--landing-footer-icon) !important;
- background: var(--landing-footer-icon-bg);
- border-radius: 8px;
- font-size: 20px;
-}
-
- .landing-footer-github:hover {
- color: var(--landing-footer-link-hover) !important;
- background: var(--landing-footer-icon-bg-hover);
- }
-
-.landing-footer-column {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 14px;
-}
-
-.landing-footer-heading {
- display: block;
- margin: 0 0 10px;
- padding: 0;
- color: var(--landing-footer-heading-primary);
- font-size: 14px;
- font-weight: 800;
-}
-
-.landing-footer-column:last-child .landing-footer-heading {
- color: var(--landing-footer-heading-secondary);
-}
-
-.landing-footer-column a {
- color: var(--landing-footer-link);
- font-size: 14px;
- text-decoration: none;
-}
-
- .landing-footer-column a:hover {
- color: var(--landing-footer-link-hover);
- }
-
-.landing-footer-bottom {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding-top: 30px;
- border-top: 1px solid var(--landing-footer-rule);
- font-family: "Cascadia Code", "Cascadia Mono", Consolas, monospace;
- font-size: 12px;
-}
-
- .landing-footer-bottom > span:last-child {
- display: flex;
- gap: 28px;
- }
-
- .landing-footer-bottom a {
- color: var(--landing-footer-link);
- text-decoration: none;
- }
-
- .landing-footer-bottom a:hover {
- color: var(--landing-footer-link-hover);
- }
-
-@media (max-width: 991.98px) {
- .landing-page .features-section,
- .architecture-section,
- .resources-section {
- padding-top: 78px;
- padding-bottom: 82px;
- }
-
- .landing-page .feature-grid,
- .resource-grid {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-
- .architecture-grid {
- grid-template-columns: 1fr;
- gap: 46px;
- }
-
- .architecture-copy {
- max-width: 760px;
- width: 100%;
- margin: 0 auto;
- }
-
- .landing-footer-grid {
- grid-template-columns: minmax(0, 1fr) 180px 180px;
- gap: 36px;
- }
-}
-
-@media (max-width: 767.98px) {
- .landing-page .section-heading,
- .landing-page .resources-heading {
- margin-bottom: 38px;
- }
-
- .landing-page .section-heading h2,
- .landing-page .architecture-copy h2,
- .landing-page .resources-heading h2 {
- font-size: 31px;
- }
-
- .landing-page .feature-grid,
- .resource-grid {
- grid-template-columns: 1fr;
- }
-
- .landing-page .feature-card {
- min-height: 0;
- }
-
- .landing-footer-grid {
- grid-template-columns: 1fr 1fr;
- }
-
- .landing-footer-brand {
- grid-column: 1 / -1;
- }
-}
-
-@media (max-width: 575.98px) {
- .landing-page .features-section,
- .architecture-section,
- .resources-section {
- padding-top: 64px;
- padding-bottom: 68px;
- }
-
- .landing-page .feature-card,
- .resource-card {
- padding: 24px;
- }
-
- .architecture-points > a {
- padding: 13px 14px;
- }
-
- .landing-footer {
- padding-top: 62px;
- }
-
- .landing-footer-grid {
- grid-template-columns: 1fr;
- gap: 42px;
- padding-bottom: 44px;
- }
-
- .landing-footer-brand,
- .landing-footer-column {
- grid-column: auto;
- }
-
- .landing-footer-bottom {
- align-items: flex-start;
- flex-direction: column;
- gap: 16px;
- }
-}
+/* DocFX entry point. Keep framework styles below the site-owned theme. */
+@import url("./docfx.min.css") layer(docfx);
+@import url("./theme.css");
+@import url("./layout.css");
+@import url("./search.css");
+@import url("./content.css");
+@import url("./home.css");
+@import url("./motion.css");
diff --git a/documents/templates/public/main.js b/documents/templates/public/main.js
index 7ddb5fa4..a9697682 100644
--- a/documents/templates/public/main.js
+++ b/documents/templates/public/main.js
@@ -1,1107 +1,59 @@
-const shikiModuleUrl = 'https://esm.sh/shiki@4.3.1';
-const slangGrammarUrl = 'https://raw.githubusercontent.com/shader-slang/slang-vscode-extension/v2.0.10/syntaxes/slang.tmLanguage.json';
-const shikiLanguageAliases = {
- bash: 'bash',
- console: 'shellsession',
- cs: 'csharp',
- csharp: 'csharp',
- json: 'json',
- powershell: 'powershell',
- shell: 'bash',
- slang: 'slang',
- text: 'text',
- txt: 'text',
- xml: 'xml',
- yaml: 'yaml',
- yml: 'yaml'
-};
-
-let fallbackHighlighter;
-let shikiHighlighter;
-
-const loadShiki = () => {
- shikiHighlighter ??= Promise.all([
- import(shikiModuleUrl),
- fetch(slangGrammarUrl, { cache: 'force-cache' }).then(response => {
- if (!response.ok) throw new Error(`Unable to load Slang grammar: HTTP ${response.status}`);
- return response.json();
- })
- ]).then(([{ createHighlighter }, slangGrammar]) => {
- slangGrammar.name = 'slang';
-
- return createHighlighter({
- themes: ['light-plus', 'dark-plus'],
- langs: ['bash', 'csharp', 'json', 'powershell', 'shellsession', 'xml', 'yaml', slangGrammar]
- });
- });
-
- return shikiHighlighter;
-};
-
-const highlightCode = async (code, language) => {
- const source = code.textContent;
- const pre = code.closest('pre');
-
- try {
- const highlighter = await loadShiki();
- const highlighted = highlighter.codeToHtml(source, {
- lang: shikiLanguageAliases[language] || 'text',
- themes: { light: 'light-plus', dark: 'dark-plus' },
- defaultColor: false
- });
- const template = document.createElement('template');
- template.innerHTML = highlighted;
- const shikiPre = template.content.querySelector('pre');
- const shikiCode = shikiPre?.querySelector('code');
- if (!pre || !shikiPre || !shikiCode) throw new Error('Shiki returned invalid markup.');
-
- code.innerHTML = shikiCode.innerHTML;
- code.classList.remove('hljs');
- code.dataset.highlighted = 'yes';
- code.dataset.shiki = 'true';
- pre.classList.add('shiki', 'shiki-themes', 'light-plus', 'dark-plus');
-
- for (const property of shikiPre.style) {
- if (property.startsWith('--shiki-')) {
- pre.style.setProperty(property, shikiPre.style.getPropertyValue(property));
- }
- }
- } catch (error) {
- console.warn('Shiki highlighting failed; using the DocFX highlighter.', error);
- code.textContent = source;
- code.classList.remove('hljs');
- code.removeAttribute('data-highlighted');
-
- try {
- fallbackHighlighter?.highlightElement(code);
- } catch {
- code.dataset.highlighted = 'yes';
- }
- }
-};
-
+import { initializeApi } from './api.js';
+import { preserveCodeReferences, restoreCodeReferences } from './code-links.js';
+import { initializeHomeScene } from './home.js';
+import { initializeLanguages } from './languages.js';
+import { initializeMemberFinder } from './member-finder.js';
+import { initializeMotion } from './motion.js';
+import { initializeNavigation } from './navigation.js';
+import { initializeOutline } from './outline.js';
+import { initializeSearch } from './search.js';
+import { linkSignatureType } from './signature-links.js';
+import { initializeSite } from './site.js';
+import { configureSyntax } from './syntax.js';
+
+// Keep DocFX's generated API and code tools; the site owns navigation and resources.
+// The master template sets the fixed dark appearance before the first paint.
export default {
+ defaultTheme: 'dark',
configureHljs: hljs => {
- fallbackHighlighter = hljs;
- hljs.registerAliases(['slang'], { languageName: 'cpp' });
- },
- iconLinks: [
- {
- icon: 'github',
- href: 'https://github.com/qian-o/Zenith.NET',
- title: 'GitHub'
- }
- ],
- start: () => {
- const rootPath = document.querySelector('meta[name="docfx:rel"]')?.content || '';
- const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
- const observeUntil = (target, options, initialize, timeout = 5000) => {
- if (initialize() || !target) return;
-
- let timeoutId;
- const observer = new MutationObserver(() => {
- if (!initialize()) return;
- observer.disconnect();
- window.clearTimeout(timeoutId);
- });
- observer.observe(target, options);
- timeoutId = window.setTimeout(() => observer.disconnect(), timeout);
- };
- document.body.toggleAttribute('data-zenith-api', /\/api(?:\/|$)/i.test(window.location.pathname));
-
- const initializeThemeCycle = () => {
- const themes = [
- { value: 'light', label: 'Light', icon: 'bi-sun' },
- { value: 'dark', label: 'Dark', icon: 'bi-moon' },
- { value: 'auto', label: 'Auto', icon: 'bi-circle-half' }
- ];
- const systemTheme = window.matchMedia('(prefers-color-scheme: dark)');
- const navbar = document.getElementById('navbar');
- if (!navbar) return;
-
- const enhanceThemeToggle = () => {
- const dropdown = navbar.querySelector('.icons .dropdown');
- const currentToggle = dropdown?.querySelector('.dropdown-toggle');
- if (!dropdown || !currentToggle) return false;
-
- const themeToggle = document.createElement('button');
- const icon = document.createElement('i');
- let animationTimer;
-
- themeToggle.type = 'button';
- themeToggle.className = 'btn border-0 zenith-theme-toggle';
- icon.setAttribute('aria-hidden', 'true');
- themeToggle.append(icon);
- dropdown.replaceWith(themeToggle);
-
- const getTheme = () => {
- const storedTheme = window.localStorage.getItem('theme');
- return themes.find(theme => theme.value === storedTheme) || themes[2];
- };
-
- const getNextTheme = theme => themes[(themes.indexOf(theme) + 1) % themes.length];
-
- const renderTheme = theme => {
- const nextTheme = getNextTheme(theme);
- icon.className = `bi ${theme.icon}`;
- themeToggle.dataset.theme = theme.value;
- themeToggle.title = `${theme.label} theme; switch to ${nextTheme.label}`;
- themeToggle.setAttribute(
- 'aria-label',
- `${theme.label} theme. Switch to ${nextTheme.label}.`
- );
- };
-
- const applyTheme = theme => {
- const resolvedTheme = theme.value === 'auto'
- ? (systemTheme.matches ? 'dark' : 'light')
- : theme.value;
- window.localStorage.setItem('theme', theme.value);
- document.documentElement.dataset.bsTheme = resolvedTheme;
- renderTheme(theme);
- };
-
- const switchTheme = () => {
- const nextTheme = getNextTheme(getTheme());
- window.clearTimeout(animationTimer);
-
- if (reducedMotion.matches) {
- applyTheme(nextTheme);
- return;
- }
-
- themeToggle.disabled = true;
- themeToggle.classList.remove('is-theme-entering');
- themeToggle.classList.add('is-theme-leaving');
- animationTimer = window.setTimeout(() => {
- applyTheme(nextTheme);
- themeToggle.classList.remove('is-theme-leaving');
- themeToggle.classList.add('is-theme-entering');
- animationTimer = window.setTimeout(() => {
- themeToggle.classList.remove('is-theme-entering');
- themeToggle.disabled = false;
- }, 260);
- }, 120);
- };
-
- themeToggle.addEventListener('click', switchTheme);
- systemTheme.addEventListener('change', () => {
- const theme = getTheme();
- if (theme.value === 'auto') applyTheme(theme);
- });
- applyTheme(getTheme());
- return true;
- };
-
- observeUntil(navbar, { childList: true, subtree: true }, enhanceThemeToggle);
- };
-
- const initializeExpandableSearch = () => {
- const form = document.querySelector('#navbar form.search');
- const input = form?.querySelector('#search-query');
- const nativeIcon = form?.querySelector(':scope > i');
- if (!form || !input || form.classList.contains('zenith-search')) return;
-
- const toggle = document.createElement('button');
- const icon = document.createElement('i');
- toggle.type = 'button';
- toggle.className = 'zenith-search-toggle';
- toggle.setAttribute('aria-controls', input.id);
- toggle.setAttribute('aria-expanded', 'false');
- toggle.setAttribute('aria-label', 'Open search');
- toggle.title = 'Search';
- icon.className = 'bi bi-search';
- icon.setAttribute('aria-hidden', 'true');
- toggle.append(icon);
- nativeIcon?.setAttribute('aria-hidden', 'true');
- form.prepend(toggle);
- form.classList.add('zenith-search');
- const searchPlaceholder = input.placeholder || 'Search';
-
- const updateAvailability = () => {
- const ready = !input.disabled;
- const expanded = form.classList.contains('is-expanded');
- form.classList.toggle('is-loading', !ready);
- form.setAttribute('aria-busy', String(!ready));
- toggle.setAttribute('aria-label', expanded ? 'Close search' : 'Open search');
- toggle.title = ready ? (expanded ? 'Close search' : 'Search') : 'Search is loading';
- icon.className = 'bi bi-search';
- input.placeholder = ready ? searchPlaceholder : 'Search is loading...';
- };
- const availabilityObserver = new MutationObserver(updateAvailability);
- availabilityObserver.observe(input, { attributes: true, attributeFilter: ['disabled'] });
- updateAvailability();
- let pendingFocusObserver;
-
- const setExpanded = (expanded, focusInput = false) => {
- if (!expanded) {
- pendingFocusObserver?.disconnect();
- pendingFocusObserver = undefined;
- }
- form.classList.toggle('is-expanded', expanded);
- toggle.setAttribute('aria-expanded', String(expanded));
- toggle.setAttribute('aria-label', expanded ? 'Close search' : 'Open search');
- toggle.title = input.disabled ? 'Search is loading' : (expanded ? 'Close search' : 'Search');
-
- if (!focusInput) return;
- const focusWhenReady = () => {
- if (input.disabled) return false;
- input.focus({ preventScroll: true });
- return true;
- };
- if (focusWhenReady()) return;
-
- pendingFocusObserver?.disconnect();
- pendingFocusObserver = new MutationObserver(() => {
- if (!form.classList.contains('is-expanded')) {
- pendingFocusObserver?.disconnect();
- pendingFocusObserver = undefined;
- return;
- }
- if (!focusWhenReady()) return;
- pendingFocusObserver?.disconnect();
- pendingFocusObserver = undefined;
- });
- pendingFocusObserver.observe(input, { attributes: true, attributeFilter: ['disabled'] });
- };
-
- const clearSearch = () => {
- if (!input.value) return;
- input.value = '';
- input.dispatchEvent(new Event('input', { bubbles: true }));
- };
-
- toggle.addEventListener('click', () => {
- const expanded = form.classList.contains('is-expanded');
- if (expanded) {
- clearSearch();
- setExpanded(false);
- toggle.focus({ preventScroll: true });
- } else {
- setExpanded(true, true);
- }
- });
-
- input.addEventListener('focus', () => setExpanded(true));
- input.addEventListener('keydown', event => {
- if (event.key !== 'Escape') return;
- event.preventDefault();
- clearSearch();
- setExpanded(false);
- toggle.focus({ preventScroll: true });
- });
- document.addEventListener('pointerdown', event => {
- if (!form.classList.contains('is-expanded') || form.contains(event.target)) return;
- if (!input.value && !document.body.hasAttribute('data-search')) setExpanded(false);
- });
-
- if (input.value || document.body.hasAttribute('data-search')) setExpanded(true);
- };
-
- const initializeReadingMotion = () => {
- const header = document.querySelector('body > header');
- if (!header) return;
-
- const progress = document.createElement('div');
- const progressValue = document.createElement('span');
- progress.className = 'zenith-reading-progress';
- progress.setAttribute('aria-hidden', 'true');
- progress.append(progressValue);
- header.append(progress);
-
- const hasScrollTimeline = window.CSS?.supports?.('animation-timeline: scroll()') === true;
-
- const updateScrollState = () => {
- const scrollRange = Math.max(1, document.documentElement.scrollHeight - window.innerHeight);
- const value = Math.min(1, Math.max(0, window.scrollY / scrollRange));
- if (!hasScrollTimeline) progressValue.style.transform = `scaleX(${value.toFixed(4)})`;
- header.classList.toggle('is-scrolled', window.scrollY > 8);
- };
-
- window.addEventListener('scroll', updateScrollState, { passive: true });
- window.addEventListener('resize', updateScrollState, { passive: true });
- updateScrollState();
- };
-
- const initializeOverlayScrollbars = () => {
- const initialized = new WeakSet();
- const hideTimers = new WeakMap();
- const updates = new WeakMap();
-
- const attach = (scroller, host) => {
- if (!scroller || !host) return;
- if (initialized.has(scroller)) {
- updates.get(scroller)?.();
- return;
- }
- initialized.add(scroller);
- host.classList.add('zenith-scroll-host');
-
- const track = document.createElement('span');
- const thumb = document.createElement('span');
- track.className = 'zenith-overlay-scrollbar';
- track.setAttribute('role', 'scrollbar');
- track.setAttribute('aria-label', host.id === 'toc'
- ? 'Table of contents scrollbar'
- : 'In this article scrollbar');
- track.setAttribute('aria-orientation', 'vertical');
- track.setAttribute('aria-valuemin', '0');
- if (!scroller.id) scroller.id = `${host.id || 'zenith'}-scroll-region`;
- track.setAttribute('aria-controls', scroller.id);
- track.append(thumb);
- host.append(track);
-
- const update = () => {
- const hostRect = host.getBoundingClientRect();
- const scrollerRect = scroller.getBoundingClientRect();
- const trackStyle = window.getComputedStyle(track);
- const scrollbarSize = Number.parseFloat(
- trackStyle.getPropertyValue('--zenith-scrollbar-size')
- ) || track.offsetWidth;
- const edgeGap = Number.parseFloat(
- trackStyle.getPropertyValue('--zenith-scrollbar-edge-gap')
- ) || (scrollbarSize / 2);
- const minThumbHeight = Number.parseFloat(
- trackStyle.getPropertyValue('--zenith-scrollbar-min-thumb')
- ) || (scrollbarSize * 4.5);
- const trackHeight = Math.max(0, scroller.clientHeight - (edgeGap * 2));
- const scrollRange = Math.max(0, scroller.scrollHeight - scroller.clientHeight);
- const thumbHeight = scrollRange
- ? Math.max(minThumbHeight, trackHeight * (scroller.clientHeight / scroller.scrollHeight))
- : trackHeight;
- const thumbRange = Math.max(0, trackHeight - thumbHeight);
- const thumbOffset = scrollRange ? (scroller.scrollTop / scrollRange) * thumbRange : 0;
-
- track.style.top = `${scrollerRect.top - hostRect.top + edgeGap}px`;
- track.style.height = `${trackHeight}px`;
- track.tabIndex = scrollRange > 1 ? 0 : -1;
- track.setAttribute('aria-valuemax', `${Math.round(scrollRange)}`);
- track.setAttribute('aria-valuenow', `${Math.round(scroller.scrollTop)}`);
- thumb.style.height = `${thumbHeight}px`;
- thumb.style.transform = `translateY(${thumbOffset}px)`;
- host.classList.toggle('has-scroll-range', scrollRange > 1);
- };
- updates.set(scroller, update);
-
- const showWhileScrolling = () => {
- host.classList.add('is-scrolling');
- window.clearTimeout(hideTimers.get(host));
- hideTimers.set(host, window.setTimeout(() => {
- host.classList.remove('is-scrolling');
- }, 700));
- update();
- };
-
- scroller.addEventListener('scroll', showWhileScrolling, { passive: true });
- window.addEventListener('resize', update, { passive: true });
- const resizeObserver = typeof ResizeObserver === 'undefined'
- ? null
- : new ResizeObserver(update);
- resizeObserver?.observe(scroller);
- resizeObserver?.observe(host);
- new MutationObserver(update).observe(scroller, { childList: true, subtree: true });
-
- track.addEventListener('keydown', event => {
- const lineStep = scroller.clientHeight / 10;
- const pageStep = scroller.clientHeight;
- const scrollRange = Math.max(0, scroller.scrollHeight - scroller.clientHeight);
- const target = {
- ArrowUp: scroller.scrollTop - lineStep,
- ArrowDown: scroller.scrollTop + lineStep,
- PageUp: scroller.scrollTop - pageStep,
- PageDown: scroller.scrollTop + pageStep,
- Home: 0,
- End: scrollRange
- }[event.key];
- if (target === undefined) return;
-
- event.preventDefault();
- scroller.scrollTop = Math.min(scrollRange, Math.max(0, target));
- });
-
- track.addEventListener('pointerdown', event => {
- if (!host.classList.contains('has-scroll-range')) return;
- event.preventDefault();
-
- const trackRect = track.getBoundingClientRect();
- const thumbRect = thumb.getBoundingClientRect();
- const startY = event.clientY;
- let startScrollTop = scroller.scrollTop;
- const thumbRange = Math.max(1, trackRect.height - thumbRect.height);
- const scrollRange = Math.max(0, scroller.scrollHeight - scroller.clientHeight);
-
- if (event.target !== thumb) {
- const targetOffset = Math.min(
- thumbRange,
- Math.max(0, event.clientY - trackRect.top - (thumbRect.height / 2))
- );
- scroller.scrollTop = (targetOffset / thumbRange) * scrollRange;
- startScrollTop = scroller.scrollTop;
- }
-
- track.classList.add('is-dragging');
- track.setPointerCapture(event.pointerId);
- const move = moveEvent => {
- scroller.scrollTop = startScrollTop
- + ((moveEvent.clientY - startY) / thumbRange) * scrollRange;
- };
- const stop = () => {
- track.classList.remove('is-dragging');
- track.removeEventListener('pointermove', move);
- track.removeEventListener('pointerup', stop);
- track.removeEventListener('pointercancel', stop);
- };
- track.addEventListener('pointermove', move);
- track.addEventListener('pointerup', stop);
- track.addEventListener('pointercancel', stop);
- });
- update();
- };
-
- const initialize = () => {
- const tocScroller = document.querySelector('#toc > .overflow-y-auto');
- attach(tocScroller, document.querySelector('#toc'));
-
- const affix = document.querySelector('#affix');
- attach(affix, affix?.parentElement);
- };
-
- const observer = new MutationObserver(initialize);
- for (const host of document.querySelectorAll('#toc, main > .affix')) {
- observer.observe(host, { childList: true, subtree: true });
- }
- initialize();
- };
-
- const copyText = async (text) => {
- try {
- await navigator.clipboard.writeText(text);
- return;
- } catch {
- const textarea = document.createElement('textarea');
- textarea.value = text;
- textarea.setAttribute('readonly', '');
- textarea.style.position = 'fixed';
- textarea.style.opacity = '0';
- document.body.append(textarea);
- textarea.select();
-
- const copied = document.execCommand('copy');
- textarea.remove();
- if (!copied) throw new Error('Copy failed');
- }
- };
-
- const copyResetTimers = new WeakMap();
- const setCopyButtonState = (button, state) => {
- const defaultLabel = button.dataset.copyLabel || button.getAttribute('aria-label') || 'Copy';
- const icon = button.querySelector('i');
- const feedback = {
- idle: { icon: 'bi bi-copy', label: defaultLabel },
- copied: { icon: 'bi bi-check2', label: 'Copied' },
- failed: { icon: 'bi bi-x-lg', label: 'Copy failed' }
- }[state];
-
- button.dataset.copyLabel = defaultLabel;
- button.classList.toggle('is-copied', state === 'copied');
- button.classList.toggle('is-failed', state === 'failed');
- button.setAttribute('aria-label', feedback.label);
- button.title = feedback.label;
- if (icon) icon.className = feedback.icon;
-
- const activeTimer = copyResetTimers.get(button);
- if (activeTimer) window.clearTimeout(activeTimer);
-
- if (state === 'idle') {
- copyResetTimers.delete(button);
- } else {
- copyResetTimers.set(button, window.setTimeout(() => {
- copyResetTimers.delete(button);
- setCopyButtonState(button, 'idle');
- }, 2000));
- }
- };
-
- const copyWithFeedback = async (button, text) => {
- try {
- await copyText(text);
- setCopyButtonState(button, 'copied');
- } catch {
- setCopyButtonState(button, 'failed');
- }
- };
-
- const brand = document.querySelector('.navbar-brand');
- if (brand && !brand.hasAttribute('aria-label')) {
- brand.setAttribute('aria-label', 'Zenith.NET home');
- }
- initializeThemeCycle();
- initializeExpandableSearch();
-
- const languageNames = {
- bash: 'Shell',
- csharp: 'C#',
- cs: 'C#',
- console: 'Console',
- json: 'JSON',
- powershell: 'PowerShell',
- shell: 'Shell',
- slang: 'Slang',
- xml: 'XML',
- yaml: 'YAML',
- yml: 'YAML'
- };
-
- const createCodeFrame = pre => {
- const code = pre.querySelector('code');
- if (!code) return null;
-
- const languageClass = [...code.classList].find(name => name.startsWith('language-') || name.startsWith('lang-'));
- const language = languageClass?.replace(/^language-|^lang-/, '').toLowerCase() || 'text';
- const existingFrame = pre.closest('.doc-code-frame');
- if (existingFrame) return { code, language };
- const frame = document.createElement('div');
- const toolbar = document.createElement('div');
- const languageLabel = document.createElement('span');
- const copy = document.createElement('button');
-
- frame.className = 'doc-code-frame';
- toolbar.className = 'doc-code-toolbar';
- languageLabel.className = 'doc-code-language';
- languageLabel.textContent = languageNames[language] || language.toUpperCase();
- copy.type = 'button';
- copy.className = 'doc-code-copy';
- copy.setAttribute('aria-label', 'Copy code block');
- copy.innerHTML = ' ';
- setCopyButtonState(copy, 'idle');
-
- pre.parentNode.insertBefore(frame, pre);
- frame.append(toolbar, pre);
- toolbar.append(languageLabel);
- toolbar.append(copy);
- pre.querySelector(':scope > .code-action')?.remove();
-
- copy.addEventListener('click', async () => {
- await copyWithFeedback(copy, code.textContent);
- });
-
- return { code, language };
- };
-
- for (const pre of document.querySelectorAll('body:not([data-layout="landing"]) article pre')) {
- const controls = createCodeFrame(pre);
- if (controls) void highlightCode(controls.code, controls.language);
- }
-
- for (const table of document.querySelectorAll('body:not([data-layout="landing"]) article table')) {
- if (table.closest('.doc-table-frame')) continue;
-
- const frame = document.createElement('div');
- frame.className = 'doc-table-frame';
- const responsive = table.closest('.table-responsive');
- const target = responsive || table;
- target.parentNode.insertBefore(frame, target);
- frame.append(target);
- }
-
- const initializeTableScrollCues = () => {
- const frames = [...document.querySelectorAll('.doc-table-frame')];
- if (!frames.length) return;
-
- const observedScrollers = new WeakSet();
- const updates = new WeakMap();
- const resizeObserver = typeof ResizeObserver === 'undefined'
- ? null
- : new ResizeObserver(entries => {
- for (const entry of entries) updates.get(entry.target)?.();
- });
-
- const initializeFrame = frame => {
- const scroller = frame.querySelector('.table-responsive');
- if (!scroller || observedScrollers.has(scroller)) return false;
- observedScrollers.add(scroller);
-
- const update = () => {
- const maxScroll = Math.max(0, scroller.scrollWidth - scroller.clientWidth);
- frame.classList.toggle('is-scrollable', maxScroll > 1);
- frame.classList.toggle('is-scroll-end', scroller.scrollLeft >= maxScroll - 1);
- };
-
- scroller.addEventListener('scroll', update, { passive: true });
- if (!resizeObserver) window.addEventListener('resize', update, { passive: true });
- updates.set(scroller, update);
- resizeObserver?.observe(scroller);
-
- const table = scroller.querySelector('table');
- if (table) {
- updates.set(table, update);
- resizeObserver?.observe(table);
- }
- update();
- return true;
- };
-
- const pendingFrames = new Set(frames);
- const initializePendingFrames = () => {
- for (const frame of pendingFrames) {
- if (initializeFrame(frame)) pendingFrames.delete(frame);
- }
- if (!pendingFrames.size) mutationObserver.disconnect();
- };
- const mutationObserver = new MutationObserver(initializePendingFrames);
-
- for (const frame of pendingFrames) {
- mutationObserver.observe(frame, { childList: true, subtree: true });
- }
- initializePendingFrames();
- };
-
- const initializeTutorialCarousel = () => {
- const carousel = document.querySelector('[data-tutorial-carousel]');
- if (!carousel) return;
-
- const slides = [...carousel.querySelectorAll('.render-carousel-slide')];
- const title = carousel.querySelector('[data-carousel-title]');
- const position = carousel.querySelector('[data-carousel-position]');
- const status = carousel.querySelector('[data-carousel-status]');
- const progress = carousel.querySelector('[data-carousel-progress]');
- const openLink = carousel.querySelector('[data-carousel-open]');
- const toggleButton = carousel.querySelector('[data-carousel-toggle]');
- const previousButton = carousel.querySelector('[data-carousel-prev]');
- const nextButton = carousel.querySelector('[data-carousel-next]');
- const mobileViewport = window.matchMedia('(max-width: 767.98px)');
- const autoPlayDuration = 6500;
- let activeIndex = Math.max(0, slides.findIndex(slide => slide.classList.contains('is-active')));
- let autoPlayTimer = 0;
- let autoPlayRemaining = autoPlayDuration;
- let autoPlayStartedAt = 0;
- let isAutoPlayPaused = false;
- let isVisible = true;
- let touchStartX = 0;
- let touchStartY = 0;
-
- if (slides.length < 2 || !title || !position || !progress || !openLink || !toggleButton || !previousButton || !nextButton) return;
-
- const prepareSlide = index => {
- const image = slides[index]?.querySelector('img[data-src]');
- if (!image?.dataset.src) return;
-
- image.src = image.dataset.src;
- image.removeAttribute('data-src');
- };
-
- const stopAutoPlay = () => {
- window.clearTimeout(autoPlayTimer);
- autoPlayTimer = 0;
- if (autoPlayStartedAt) {
- autoPlayRemaining = Math.max(0, autoPlayRemaining - (performance.now() - autoPlayStartedAt));
- autoPlayStartedAt = 0;
- }
- carousel.classList.remove('is-carousel-playing');
- };
-
- const canAutoPlay = () => !isAutoPlayPaused &&
- !reducedMotion.matches &&
- !mobileViewport.matches &&
- !document.hidden &&
- isVisible &&
- !carousel.matches(':hover') &&
- !carousel.matches(':focus-within');
-
- const scheduleAutoPlay = () => {
- if (!canAutoPlay()) {
- stopAutoPlay();
- return;
- }
- if (autoPlayTimer) return;
-
- autoPlayStartedAt = performance.now();
- carousel.classList.add('is-carousel-playing');
- autoPlayTimer = window.setTimeout(() => {
- autoPlayTimer = 0;
- autoPlayStartedAt = 0;
- autoPlayRemaining = autoPlayDuration;
- showSlide(activeIndex + 1);
- }, Math.max(16, autoPlayRemaining));
- };
-
- const showSlide = (index, announce = false) => {
- stopAutoPlay();
- activeIndex = (index + slides.length) % slides.length;
- prepareSlide(activeIndex);
- prepareSlide((activeIndex + 1) % slides.length);
-
- for (const [slideIndex, slide] of slides.entries()) {
- const isActive = slideIndex === activeIndex;
- slide.classList.toggle('is-active', isActive);
- slide.setAttribute('aria-hidden', String(!isActive));
- if (isActive) {
- slide.setAttribute('aria-current', 'true');
- } else {
- slide.removeAttribute('aria-current');
- }
- slide.tabIndex = isActive ? 0 : -1;
- }
-
- const activeSlide = slides[activeIndex];
- const activeTitle = activeSlide.dataset.carouselTitle || `Tutorial ${activeIndex + 1}`;
- title.textContent = activeTitle;
- position.textContent = `${activeIndex + 1} / ${slides.length}`;
- openLink.href = activeSlide.href;
- openLink.setAttribute('aria-label', `Open ${activeTitle} tutorial`);
- if (announce && status) {
- status.textContent = `${activeTitle}, slide ${activeIndex + 1} of ${slides.length}`;
- }
-
- autoPlayRemaining = autoPlayDuration;
- progress.classList.remove('is-timing');
- void progress.offsetWidth;
- progress.classList.add('is-timing');
- scheduleAutoPlay();
- };
-
- const updateToggleButton = () => {
- const label = isAutoPlayPaused ? 'Start automatic slide rotation' : 'Pause automatic slide rotation';
- toggleButton.setAttribute('aria-label', label);
- toggleButton.title = label;
- toggleButton.innerHTML = ` `;
- };
-
- toggleButton.addEventListener('click', () => {
- isAutoPlayPaused = !isAutoPlayPaused;
- updateToggleButton();
- if (isAutoPlayPaused) {
- stopAutoPlay();
- } else {
- autoPlayRemaining = autoPlayDuration;
- scheduleAutoPlay();
- }
- });
- previousButton.addEventListener('click', () => showSlide(activeIndex - 1, true));
- nextButton.addEventListener('click', () => showSlide(activeIndex + 1, true));
-
- carousel.addEventListener('keydown', event => {
- if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;
-
- event.preventDefault();
- showSlide(activeIndex + (event.key === 'ArrowRight' ? 1 : -1), true);
- });
- carousel.addEventListener('mouseenter', stopAutoPlay);
- carousel.addEventListener('mouseleave', scheduleAutoPlay);
- carousel.addEventListener('focusin', stopAutoPlay);
- carousel.addEventListener('focusout', () => window.requestAnimationFrame(scheduleAutoPlay));
- carousel.addEventListener('touchstart', event => {
- const touch = event.changedTouches[0];
- touchStartX = touch.clientX;
- touchStartY = touch.clientY;
- stopAutoPlay();
- }, { passive: true });
- carousel.addEventListener('touchend', event => {
- const touch = event.changedTouches[0];
- const deltaX = touch.clientX - touchStartX;
- const deltaY = touch.clientY - touchStartY;
- if (Math.abs(deltaX) >= 44 && Math.abs(deltaX) > Math.abs(deltaY)) {
- showSlide(activeIndex + (deltaX < 0 ? 1 : -1), true);
- } else {
- scheduleAutoPlay();
- }
- }, { passive: true });
-
- document.addEventListener('visibilitychange', scheduleAutoPlay);
- reducedMotion.addEventListener('change', scheduleAutoPlay);
- mobileViewport.addEventListener('change', scheduleAutoPlay);
-
- if ('IntersectionObserver' in window) {
- const observer = new IntersectionObserver(entries => {
- isVisible = entries[0]?.isIntersecting ?? true;
- scheduleAutoPlay();
- }, { threshold: 0.2 });
- observer.observe(carousel);
- }
-
- carousel.classList.add('is-carousel-ready');
- showSlide(activeIndex);
- };
-
- const initializeRevealMotion = () => {
- const selectors = document.body.matches('[data-layout="landing"]')
- ? [
- '.section-heading',
- '.feature-card',
- '.architecture-copy',
- '.resources-heading',
- '.resource-card',
- '.landing-footer-grid > *'
- ]
- : [
- 'article h1:first-of-type',
- 'article h2',
- 'article .alert',
- 'article .doc-code-frame',
- 'article .doc-table-frame',
- 'article .tabGroup'
- ];
- const targets = [...document.querySelectorAll(selectors.join(','))]
- .filter(element => element.getClientRects().length);
- if (!targets.length || reducedMotion.matches) {
- targets.forEach(element => element.classList.add('is-visible'));
- return;
- }
-
- for (const [index, target] of targets.entries()) {
- target.classList.add('zenith-reveal');
- target.style.setProperty('--zenith-reveal-delay', `${Math.min(index % 4, 3) * 45}ms`);
- if (document.body.matches('[data-layout="landing"]')) {
- const direction = target.matches('.architecture-grid > .architecture-copy:first-child')
- ? 'left'
- : target.matches('.architecture-grid > .architecture-copy:last-child')
- ? 'right'
- : 'up';
- target.dataset.revealFrom = direction;
- }
- }
- document.documentElement.classList.add('zenith-motion-ready');
-
- const pending = new Set(targets);
- const revealVisibleTargets = () => {
- const revealBoundary = window.innerHeight * 0.92;
- for (const target of pending) {
- const rect = target.getBoundingClientRect();
- if (rect.top >= revealBoundary || rect.bottom <= 0) continue;
-
- target.classList.add('is-visible');
- pending.delete(target);
- }
-
- if (!pending.size) {
- window.removeEventListener('scroll', revealVisibleTargets);
- window.removeEventListener('resize', revealVisibleTargets);
- }
- };
-
- window.addEventListener('scroll', revealVisibleTargets, { passive: true });
- window.addEventListener('resize', revealVisibleTargets, { passive: true });
- revealVisibleTargets();
- };
-
- initializeReadingMotion();
- initializeOverlayScrollbars();
- initializeTableScrollCues();
- initializeTutorialCarousel();
-
- const initializeAffix = () => {
- const links = [...document.querySelectorAll('#affix a[href^="#"]')];
- const targetGroups = new Map();
- const targetIndexes = new Map();
-
- for (const link of links) {
- const targetId = decodeURIComponent(link.hash.slice(1));
- if (!targetGroups.has(targetId)) {
- targetGroups.set(targetId, [...document.querySelectorAll(`[id="${CSS.escape(targetId)}"]`)]);
- }
- }
-
- const affixLinks = links
- .map(link => {
- const targetId = decodeURIComponent(link.hash.slice(1));
- const targetIndex = targetIndexes.get(targetId) || 0;
- const targets = targetGroups.get(targetId) || [];
- const target = targets[targetIndex] || targets[0];
- targetIndexes.set(targetId, targetIndex + 1);
-
- if (target && targetIndex > 0) {
- const uniqueId = `${targetId}--${targetIndex + 1}`;
- target.id = uniqueId;
- link.setAttribute('href', `#${uniqueId}`);
- }
-
- return { link, target };
- })
- .filter(item => {
- const isVisible = item.target?.getClientRects().length;
- if (!isVisible) item.link.closest('li')?.style.setProperty('display', 'none');
- return isVisible;
- });
- if (!affixLinks.length) return false;
-
- const updateAffix = () => {
- const scrollPaddingTop = Number.parseFloat(
- window.getComputedStyle(document.documentElement).scrollPaddingTop
- ) || 0;
- let current = affixLinks[0];
- let start = 0;
- let end = affixLinks.length - 1;
-
- while (start <= end) {
- const middle = Math.floor((start + end) / 2);
- const item = affixLinks[middle];
- const scrollMarginTop = Number.parseFloat(
- window.getComputedStyle(item.target).scrollMarginTop
- ) || 0;
- const targetOffset = scrollPaddingTop + scrollMarginTop + 1;
-
- if (item.target.getBoundingClientRect().top <= targetOffset) {
- current = item;
- start = middle + 1;
- } else {
- end = middle - 1;
- }
- }
-
- for (const item of affixLinks) {
- item.link.classList.toggle('is-active', item === current);
- }
- };
-
- window.addEventListener('scroll', updateAffix, { passive: true });
- updateAffix();
- return true;
- };
-
- const normalizePath = path => path.replace(/\/index\.html$/, '/');
- const siteRootPath = normalizePath(new URL(rootPath || './', window.location.href).pathname);
- const currentPath = normalizePath(window.location.pathname);
- for (const link of document.querySelectorAll('#navbar .nav-link')) {
- const linkPath = normalizePath(new URL(link.href, window.location.href).pathname);
- const isHome = linkPath === siteRootPath && currentPath === siteRootPath;
- const isSection = linkPath !== siteRootPath && currentPath.startsWith(linkPath);
- const isActive = isHome || isSection;
- link.classList.toggle('active', isActive);
- if (isActive) {
- link.setAttribute('aria-current', 'page');
- } else {
- link.removeAttribute('aria-current');
+ configureSyntax(hljs);
+ hljs.addPlugin({
+ 'before:highlightElement': preserveCodeReferences,
+ 'after:highlightElement': event => {
+ restoreCodeReferences(event);
+ linkSignatureType(event);
}
- }
-
- // Hide inherited type info sections (inheritance, implements, inheritedMembers, derivedClasses)
- for (const dl of document.querySelectorAll('dl.typelist.inheritance, dl.typelist.implements, dl.typelist.inheritedMembers, dl.typelist.derivedClasses')) {
- dl.style.display = 'none';
- }
-
- // Hide protected members from API pages
- for (const code of document.querySelectorAll('.codewrapper pre code')) {
- const text = code.textContent.trim();
- if (!/^(?:private\s+)?protected\b/.test(text)) continue;
-
- const wrapper = code.closest('.codewrapper');
- if (!wrapper) continue;
-
- const toHide = [];
-
- // Walk backward from codewrapper to collect h3 header and anchor
- let el = wrapper;
- while (el) {
- toHide.push(el);
- if (el.tagName === 'H3') {
- const prev = el.previousElementSibling;
- if (prev && prev.tagName === 'A' && prev.dataset.uid) {
- toHide.push(prev);
- }
- break;
- }
- el = el.previousElementSibling;
- }
-
- // Walk forward from codewrapper to collect parameters, returns, etc.
- let next = wrapper.nextElementSibling;
- while (next && next.tagName !== 'H3' && next.tagName !== 'H2' &&
- !(next.tagName === 'A' && next.dataset.uid)) {
- toHide.push(next);
- next = next.nextElementSibling;
- }
-
- toHide.forEach(e => e.style.display = 'none');
- }
-
- // Hide empty section headers (e.g. "Constructors" when all constructors are hidden)
- for (const h2 of document.querySelectorAll('article h2.section')) {
- let next = h2.nextElementSibling;
- let hasVisibleMember = false;
- while (next && next.tagName !== 'H2') {
- if (next.tagName === 'H3' && next.style.display !== 'none') {
- hasVisibleMember = true;
- break;
- }
- next = next.nextElementSibling;
- }
- if (!hasVisibleMember) {
- h2.style.display = 'none';
- }
- }
-
- observeUntil(
- document.querySelector('main > .affix'),
- { childList: true, subtree: true },
- initializeAffix
- );
-
- initializeRevealMotion();
-
- // Search results: navigate in same tab with back button support
- document.addEventListener('click', event => {
- const link = event.target instanceof Element
- ? event.target.closest('#search-results .sr-item a')
- : null;
- if (!link) return;
- if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey ||
- event.shiftKey || event.altKey || link.hasAttribute('download') ||
- (link.target && link.target !== '_self')) return;
-
- event.preventDefault();
- const query = document.getElementById('search-query')?.value || '';
- history.replaceState({ search: true, query, scrollY: window.scrollY }, '');
- window.location.href = link.href;
});
-
- const restoreSearchState = (state) => {
- if (!state?.search) return;
-
- const input = document.getElementById('search-query');
- if (!input) return;
-
- input.value = state.query || '';
- input.dispatchEvent(new Event('input', { bubbles: true }));
-
- if (!Number.isFinite(state.scrollY)) return;
-
- const restoreScroll = () => {
- if (!document.body.hasAttribute('data-search')) return false;
-
- const maxScroll = Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
- if (maxScroll < state.scrollY) return false;
-
- window.scrollTo(0, state.scrollY);
- return true;
- };
-
- if (restoreScroll()) return;
-
- const observer = new MutationObserver(() => {
- if (restoreScroll()) observer.disconnect();
- });
- observer.observe(document.body, { attributes: true, childList: true, subtree: true });
- window.setTimeout(() => {
- restoreScroll();
- observer.disconnect();
- }, 2000);
- };
-
- window.addEventListener('popstate', (e) => restoreSearchState(e.state));
-
- const restoreInitialSearchState = () => {
- if (!history.state?.search) return;
-
- const input = document.getElementById('search-query');
- if (!input) return;
-
- observeUntil(input, { attributes: true, attributeFilter: ['disabled'] }, () => {
- if (input.disabled) return;
- restoreSearchState(history.state);
- return true;
- });
- };
-
- restoreInitialSearchState();
+ },
+ mermaid: {
+ theme: 'base',
+ themeVariables: {
+ darkMode: true,
+ background: '#181b21',
+ primaryColor: '#262a32',
+ primaryTextColor: '#f2f3f5',
+ primaryBorderColor: '#626a78',
+ secondaryColor: '#181b21',
+ secondaryTextColor: '#f2f3f5',
+ secondaryBorderColor: '#626a78',
+ tertiaryColor: '#343943',
+ tertiaryTextColor: '#f2f3f5',
+ tertiaryBorderColor: '#626a78',
+ lineColor: '#a4abb7',
+ textColor: '#f2f3f5',
+ edgeLabelBackground: '#181b21',
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
+ }
+ },
+ start: async () => {
+ initializeSite();
+ await initializeLanguages();
+ initializeNavigation().catch(console.error);
+ initializeOutline();
+ initializeSearch();
+ initializeHomeScene();
+ initializeApi();
+ initializeMemberFinder();
+ initializeMotion();
}
-}
+};
diff --git a/documents/templates/public/member-finder.js b/documents/templates/public/member-finder.js
new file mode 100644
index 00000000..da6c6f4d
--- /dev/null
+++ b/documents/templates/public/member-finder.js
@@ -0,0 +1,144 @@
+import { t } from './resources.js';
+import { closeContents, closeNavigation, isApplePlatform, isEditing } from './site.js';
+import { bindDialogKeys, isBackdropClick } from './dialog.js';
+
+// A page-local symbol finder. Every result points to a stable member anchor.
+export function initializeMemberFinder() {
+ const dialog = document.getElementById('api-member-dialog');
+ if (!dialog) return;
+
+ const input = document.getElementById('api-member-query');
+ const results = document.getElementById('api-member-results');
+ const empty = document.getElementById('api-member-empty');
+ const clear = document.getElementById('api-member-clear');
+ const launchers = [...document.querySelectorAll('[data-member-find]')];
+ const groups = [...document.querySelectorAll('.api-reference > .api-group')].map(section => ({
+ name: section.querySelector('.api-group-heading h2')?.textContent.trim(),
+ members: [...section.querySelectorAll('.api-member-heading h3[id], .api-enum-value h3[id]')].map(heading => ({
+ heading,
+ name: heading.textContent.trim(),
+ signature: heading.closest('.api-member')?.querySelector(':scope > pre > code')?.textContent || heading.textContent
+ }))
+ })).filter(group => group.members.length);
+ if (!groups.length) empty.textContent = t('ui.members.undeclared');
+
+ let opener;
+ let restoreFocus = true;
+ const shortcut = isApplePlatform ? '⌥ M' : 'Alt M';
+ for (const launcher of launchers) {
+ launcher.hidden = false;
+ launcher.title = `${t('ui.members.title')} (${shortcut})`;
+ const key = launcher.querySelector('[data-member-shortcut]');
+ if (key) key.textContent = shortcut;
+ launcher.addEventListener('click', () => open(launcher));
+ }
+
+ function render() {
+ const terms = input.value.toLowerCase().trim().split(/\s+/).filter(Boolean);
+ const fragment = document.createDocumentFragment();
+ for (const group of groups) {
+ const matches = group.members.filter(member => {
+ const text = `${group.name} ${member.name} ${member.signature}`.toLowerCase();
+ return terms.every(term => text.includes(term));
+ });
+ if (!matches.length) continue;
+ const section = document.createElement('section');
+ const label = document.createElement('h3');
+ const list = document.createElement('ul');
+ label.textContent = group.name;
+ for (const member of matches) {
+ const item = document.createElement('li');
+ const link = document.createElement('a');
+ link.href = '#' + encodeURIComponent(member.heading.id);
+ link.dataset.memberTarget = member.heading.id;
+ link.textContent = member.name;
+ item.append(link);
+ list.append(item);
+ }
+ section.append(label, list);
+ fragment.append(section);
+ }
+ results.replaceChildren(fragment);
+ empty.hidden = results.childElementCount > 0;
+ clear.hidden = !input.value;
+ dialog.scrollTop = 0;
+ }
+
+ function isVisible(button) {
+ if (!button || !button.getClientRects().length || getComputedStyle(button).visibility !== 'visible') return false;
+ const drawer = button.closest('#tocOffcanvas');
+ if (drawer && window.matchMedia('(max-width: 767.98px)').matches && (!drawer.classList.contains('show') || drawer.classList.contains('hiding'))) return false;
+ const bounds = button.getBoundingClientRect();
+ return bounds.right > 0 && bounds.left < innerWidth && bounds.bottom > 0 && bounds.top < innerHeight;
+ }
+
+ function visibleLauncher() {
+ return launchers.find(isVisible);
+ }
+
+ function open(button) {
+ if (document.querySelector('dialog[open]')) return;
+ opener = button || visibleLauncher();
+ restoreFocus = true;
+ closeContents();
+ closeNavigation();
+ render();
+ dialog.showModal();
+ input.focus();
+ input.select();
+ }
+
+ document.getElementById('api-member-close').addEventListener('click', () => dialog.close());
+ dialog.addEventListener('cancel', event => {
+ event.preventDefault();
+ dialog.close();
+ });
+ dialog.addEventListener('close', () => {
+ if (restoreFocus && !document.querySelector('dialog[open]')) {
+ const menu = document.getElementById('menu-toggle');
+ const target = isVisible(opener) ? opener : visibleLauncher() || (isVisible(menu) ? menu : null);
+ target?.focus({ preventScroll: true });
+ }
+ });
+ dialog.addEventListener('click', event => {
+ if (event.target === dialog) {
+ if (isBackdropClick(event, dialog)) dialog.close();
+ return;
+ }
+ const link = event.target.closest('[data-member-target]');
+ if (!link || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
+ const target = document.getElementById(link.dataset.memberTarget);
+ restoreFocus = false;
+ dialog.close();
+ target.tabIndex = -1;
+ // Keep native anchor navigation/history; move keyboard focus after scrolling.
+ requestAnimationFrame(() => {
+ target.focus({ preventScroll: true });
+ target.scrollIntoView({ block: 'start', behavior: 'auto' });
+ });
+ });
+ input.addEventListener('input', render);
+ clear.addEventListener('click', () => {
+ input.value = '';
+ render();
+ input.focus();
+ });
+ document.getElementById('api-member-form').addEventListener('submit', event => {
+ event.preventDefault();
+ results.querySelector('a')?.click();
+ });
+ bindDialogKeys(dialog, { items: '#api-member-results a', close: () => dialog.close() });
+ document.addEventListener('keydown', event => {
+ if (event.isComposing || !event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.repeat || event.code !== 'KeyM' && event.key.toLowerCase() !== 'm') return;
+ if (isEditing(event.target)) return;
+ event.preventDefault();
+ if (dialog.open) dialog.close();
+ else open();
+ });
+ const dismissForNavigation = () => {
+ restoreFocus = false;
+ dialog.close();
+ };
+ window.addEventListener('pagehide', dismissForNavigation);
+ window.addEventListener('popstate', dismissForNavigation);
+}
diff --git a/documents/templates/public/motion.css b/documents/templates/public/motion.css
new file mode 100644
index 00000000..1691587f
--- /dev/null
+++ b/documents/templates/public/motion.css
@@ -0,0 +1,342 @@
+/* Shared illustration motion: a stable wireframe, moving light, then a soft highlight. */
+.card-art {
+ --diagram-line: #adc6e7;
+ --diagram-light: #edf6ff;
+ --diagram-duration: 1500ms;
+ --diagram-ease: cubic-bezier(.35, 0, .25, 1);
+ pointer-events: none;
+}
+
+.card-art .art-wire {
+ --art-rest-opacity: .7;
+ fill: none;
+ stroke: var(--diagram-line);
+ stroke-width: 1.35;
+ opacity: var(--art-rest-opacity);
+}
+
+.card-art .art-detail {
+ --art-rest-opacity: .22;
+ fill: none;
+ stroke: var(--diagram-line);
+ stroke-width: 1;
+ opacity: var(--art-rest-opacity);
+}
+
+.triangle-diagram .art-wire {
+ stroke-width: 2;
+}
+
+.triangle-diagram .art-detail {
+ --art-rest-opacity: .35;
+ stroke-width: 1.5;
+}
+
+.card-art .art-face {
+ fill: #b8d8ff;
+ stroke: none;
+ opacity: .055;
+}
+
+.card-art .art-node {
+ fill: var(--diagram-light);
+ stroke: none;
+ transform-box: fill-box;
+ transform-origin: center;
+}
+
+.card-art .art-code-base {
+ fill: none;
+ stroke: var(--diagram-line);
+ stroke-width: 3.5;
+ opacity: .45;
+}
+
+.card-art .art-flow {
+ fill: none;
+ stroke: var(--diagram-light);
+ stroke-width: 1.8;
+ stroke-linecap: round;
+ stroke-dasharray: 20 80;
+ stroke-dashoffset: 100;
+ opacity: 0;
+}
+
+.card-art .art-code-flow {
+ stroke-width: 3.5;
+}
+
+.card-art .art-cursor {
+ fill: none;
+ stroke: var(--diagram-light);
+ stroke-width: 2;
+ opacity: 0;
+}
+
+.illustrated-link:is(:hover, :focus-visible) .art-flow {
+ animation: diagram-flow var(--diagram-duration) var(--diagram-ease) var(--art-delay, 0ms) both;
+}
+
+.illustrated-link:is(:hover, :focus-visible) .art-node {
+ animation: diagram-node var(--diagram-duration) var(--diagram-ease) var(--art-delay, 0ms) both;
+}
+
+.illustrated-link:is(:hover, :focus-visible) .art-face {
+ animation: diagram-face var(--diagram-duration) var(--diagram-ease) 180ms both;
+}
+
+.illustrated-link:is(:hover, :focus-visible) .art-mark {
+ animation: diagram-mark var(--diagram-duration) var(--diagram-ease) 180ms both;
+}
+
+.illustrated-link:is(:hover, :focus-visible) .art-cursor {
+ animation: diagram-cursor var(--diagram-duration) var(--diagram-ease) 360ms both;
+}
+
+@keyframes diagram-flow {
+ 0% {
+ opacity: 0;
+ stroke-dashoffset: 100;
+ }
+
+ 18%, 65% {
+ opacity: .95;
+ }
+
+ 100% {
+ opacity: 0;
+ stroke-dashoffset: -100;
+ }
+}
+
+@keyframes diagram-node {
+ 0%, 100% {
+ transform: scale(1);
+ filter: none;
+ }
+
+ 35% {
+ transform: scale(1.18);
+ filter: drop-shadow(0 0 4px #c6e0ff);
+ }
+}
+
+@keyframes diagram-face {
+ 0%, 100% {
+ opacity: .055;
+ }
+
+ 48% {
+ opacity: .2;
+ }
+}
+
+@keyframes diagram-mark {
+ 0%, 100% {
+ opacity: var(--art-rest-opacity);
+ }
+
+ 48% {
+ opacity: .95;
+ }
+}
+
+@keyframes diagram-cursor {
+ 0%, 35%, 100% {
+ opacity: 0;
+ }
+
+ 50%, 78% {
+ opacity: .9;
+ }
+}
+
+/* Surface feedback: color and inset light, without moving hit targets. */
+#primary-navigation .nav-link,
+#toc li > a,
+.build-button,
+.path-card,
+.search-launcher,
+.language-menu > summary,
+.language-options a,
+.source-link,
+.contents-toggle,
+.contents-close,
+.menu-toggle,
+.scene-options button,
+#search-close,
+#api-member-close,
+.search-more,
+.learning-entry,
+.concept-list > a,
+.footer-links a,
+.search-shortcuts > a,
+#site-search-results a {
+ transition-property: background-color, border-color, color, box-shadow;
+ transition-duration: var(--zenith-state-duration);
+ transition-timing-function: var(--zenith-state-ease);
+}
+
+#primary-navigation .nav-link:active,
+#toc li > a:active,
+.build-button:active,
+.path-card:active,
+.search-launcher:active,
+.language-menu > summary:active,
+.language-options a:active,
+.source-link:active,
+.contents-toggle:active,
+.contents-close:active,
+.menu-toggle:active,
+.scene-options button:active,
+#search-close:active,
+#api-member-close:active,
+.search-more:active,
+.learning-entry:active,
+.concept-list > a:active,
+.search-shortcuts > a:active,
+#site-search-results a:active {
+ transition-duration: var(--zenith-press-duration);
+}
+
+#primary-navigation .nav-link:active, .build-button.secondary:active,
+.search-launcher:active, .source-link:active, .scene-options button:active {
+ color: #92afd3;
+}
+
+.build-button.primary:active {
+ background: #b9c6d8;
+ color: #15191f;
+ box-shadow: none;
+}
+
+.path-card:active {
+ background: #182536;
+ border-color: #647b99;
+}
+
+.learning-entry:active {
+ border-color: #586a82;
+}
+
+.concept-list > a:active, #toc li > a:active {
+ background: #1a2432;
+}
+
+.search-shortcuts > a:active, #site-search-results a:active {
+ background: #1c2838;
+}
+
+#search-close:hover, .search-more:hover {
+ color: #fff;
+}
+
+#search-close:active, .search-more:active {
+ color: #92afd3;
+}
+
+.menu-toggle:hover {
+ background: #ffffff0a;
+}
+
+.menu-toggle:active {
+ background: #ffffff12;
+}
+
+/* Shared light feedback, with stationary controls and illustrations. */
+:is(.build-button, .path-card, .learning-entry) {
+ position: relative;
+ overflow: hidden;
+ isolation: isolate;
+}
+
+:is(.build-button, .path-card, .learning-entry)::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ z-index: -1;
+ background: linear-gradient(110deg, transparent 20%, #e2edff26 48%, transparent 76%);
+ transform: translateX(-120%);
+ pointer-events: none;
+}
+
+@media (hover: hover) {
+ :is(.build-button, .path-card, .learning-entry):hover::before {
+ animation: zenith-sweep 620ms cubic-bezier(.2, .65, .3, 1);
+ }
+}
+
+.concept-list > a > i:first-child {
+ transition: background-color 240ms, border-color 240ms, color 240ms;
+}
+
+.concept-list > a:is(:hover, :focus-visible) > i:first-child {
+ color: #eef6ff;
+}
+
+.motion-enter {
+ --entrance-stagger: 55ms;
+ animation: zenith-reveal 480ms cubic-bezier(.16, 1, .3, 1) calc(var(--entrance-order, 0) * var(--entrance-stagger)) backwards;
+}
+
+@keyframes zenith-sweep {
+ to {
+ transform: translateX(120%);
+ }
+}
+
+@keyframes zenith-reveal {
+ from {
+ opacity: 0;
+ transform: translateY(12px);
+ }
+
+ to {
+ opacity: 1;
+ transform: none;
+ }
+}
+
+@keyframes zenith-panel-in {
+ from {
+ opacity: 0;
+ transform: translateY(-10px) scale(.98);
+ }
+
+ to {
+ opacity: 1;
+ transform: none;
+ }
+}
+
+@keyframes zenith-panel-out {
+ to {
+ opacity: 0;
+ transform: translateY(-6px) scale(.99);
+ }
+}
+
+@keyframes zenith-backdrop-in {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+@view-transition {
+ navigation: none;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html {
+ scroll-behavior: auto;
+ }
+
+ *, *::before, *::after, *::backdrop {
+ animation: none !important;
+ transition: none !important;
+ }
+}
diff --git a/documents/templates/public/motion.js b/documents/templates/public/motion.js
new file mode 100644
index 00000000..dabd46db
--- /dev/null
+++ b/documents/templates/public/motion.js
@@ -0,0 +1,43 @@
+// Reveal content once as it enters the viewport; navigation itself stays immediate.
+export function initializeMotion() {
+ const maxStaggeredItems = 4;
+ const preference = window.matchMedia('(prefers-reduced-motion: reduce)');
+ if (preference.matches) return;
+
+ const selector = document.querySelector('.home-page')
+ ? '.render-copy > *, .geometry-stage, .paths-intro, .path-grid'
+ : document.querySelector('.learn-hub')
+ ? '.learn-hub > h1, .page-intro, .learning-entries, .hub-section-title, .section-description, .concept-list > a'
+ : document.querySelector('.api-reference')
+ ? '.api-heading, .api-declaration, .api-start-grid, .api-group-heading'
+ : 'article > h1, article > p, article > section';
+ const observer = new IntersectionObserver(entries => {
+ let order = 0;
+ for (const { target, isIntersecting } of entries) {
+ if (!isIntersecting) continue;
+ observer.unobserve(target);
+ target.style.setProperty('--entrance-order', Math.min(order++, maxStaggeredItems - 1));
+ target.classList.add('motion-enter');
+ target.addEventListener('animationend', event => {
+ if (event.target === target && event.animationName === 'zenith-reveal') {
+ target.classList.remove('motion-enter');
+ target.style.removeProperty('--entrance-order');
+ }
+ });
+ }
+ }, { threshold: 0 });
+ document.querySelectorAll(selector).forEach(element => observer.observe(element));
+
+ const finish = () => {
+ observer.disconnect();
+ for (const element of document.querySelectorAll('.motion-enter')) {
+ element.classList.remove('motion-enter');
+ element.style.removeProperty('--entrance-order');
+ }
+ };
+ // Cached pages return in their completed state, without replaying an entrance.
+ window.addEventListener('pagehide', finish);
+ preference.addEventListener('change', event => {
+ if (event.matches) finish();
+ });
+}
diff --git a/documents/templates/public/navigation.js b/documents/templates/public/navigation.js
new file mode 100644
index 00000000..02d8ed2b
--- /dev/null
+++ b/documents/templates/public/navigation.js
@@ -0,0 +1,69 @@
+import { pageUrl, siteRoot } from './languages.js';
+import { t } from './resources.js';
+
+let navigationPromise;
+export function loadNavigation() {
+ return navigationPromise ??= fetch(new URL('toc.json', siteRoot), { cache: 'no-cache' }).then(response => {
+ if (!response.ok) throw new Error('Navigation unavailable');
+ return response.json();
+ }).then(model => model.items).catch(error => {
+ navigationPromise = undefined;
+ throw error;
+ });
+}
+
+export async function initializeNavigation() {
+ const items = await loadNavigation();
+ const currentPath = location.pathname.slice(siteRoot.pathname.length).replace(/\/$/, '/index.html');
+ const active = items.find(item => (item.topicHref || item.href)?.split('/')[0] === currentPath.split('/')[0]);
+ const label = item => item.resourceKey ? t(item.resourceKey) : item.name;
+ const linkFor = item => {
+ const link = document.createElement('a');
+ link.href = pageUrl(item.topicHref || item.href);
+ link.textContent = label(item);
+ return link;
+ };
+ const navbar = document.createElement('ul');
+ navbar.className = 'navbar-nav';
+ for (const item of items) {
+ const li = document.createElement('li');
+ li.className = 'nav-item';
+ const link = linkFor(item);
+ link.className = 'nav-link';
+ if (item === active) {
+ link.classList.add('active');
+ link.setAttribute('aria-current', 'true');
+ }
+ li.append(link); navbar.append(li);
+ }
+ document.getElementById('primary-navigation').replaceChildren(navbar);
+ const toc = document.getElementById('toc');
+ if (!toc) return;
+ const listFor = (items, level) => {
+ const list = document.createElement('ul');
+ list.className = `nav level${level}`;
+ for (const item of items) {
+ const li = document.createElement('li');
+ const href = item.topicHref || item.href;
+ if (href) {
+ const link = linkFor(item);
+ if (new URL(link.href).pathname === new URL(currentPath, siteRoot).pathname) {
+ li.classList.add('active');
+ link.setAttribute('aria-current', 'page');
+ }
+ li.append(link);
+ } else {
+ li.className = 'expander expanded';
+ const title = document.createElement('span');
+ title.className = 'toc-group-title'; title.textContent = label(item); li.append(title);
+ }
+ if (item.items?.length) li.append(listFor(item.items, level + 1));
+ list.append(li);
+ }
+ return list;
+ };
+ const scroll = document.createElement('div');
+ scroll.className = 'overflow-y-auto';
+ scroll.append(listFor(active ? [{ ...active, items: [] }, ...(active.items || [])] : [], 1));
+ toc.replaceChildren(scroll);
+}
diff --git a/documents/templates/public/outline.js b/documents/templates/public/outline.js
new file mode 100644
index 00000000..b101e38e
--- /dev/null
+++ b/documents/templates/public/outline.js
@@ -0,0 +1,92 @@
+import { t } from './resources.js';
+
+// Article navigation owns its markup and active state; links keep native hashes/history.
+export function initializeOutline() {
+ const nav = document.getElementById('article-outline');
+ if (!nav) return;
+ const headings = [...document.querySelectorAll('article h2[id], article h3[id]')];
+ if (!headings.length) {
+ nav.parentElement.hidden = true;
+ return;
+ }
+
+ const label = document.createElement('div');
+ label.className = 'outline-title';
+ label.textContent = t('ui.outline.title');
+ const list = document.createElement('ul');
+ list.className = 'outline-list';
+ let group;
+ const entries = headings.map(heading => {
+ const item = document.createElement('li');
+ const link = document.createElement('a');
+ link.className = 'outline-link';
+ link.href = '#' + encodeURIComponent(heading.id);
+ link.textContent = heading.textContent;
+ item.append(link);
+ if (heading.tagName === 'H2' || !group) {
+ list.append(item);
+ group = item;
+ } else {
+ let children = group.querySelector('ul');
+ if (!children) {
+ children = document.createElement('ul');
+ group.append(children);
+ }
+ children.append(item);
+ }
+ // Make the native fragment destination focusable for keyboard navigation.
+ heading.tabIndex = -1;
+ heading.classList.add('outline-target');
+ return { heading, link, group };
+ });
+ nav.append(label, list);
+
+ let active;
+ let frame;
+ let reveal = false;
+ const update = () => {
+ frame = undefined;
+ const forceReveal = reveal;
+ reveal = false;
+ if (!nav.getClientRects().length) return;
+ const headerBottom = document.querySelector('.site-header').getBoundingClientRect().bottom;
+ let current = entries[0];
+ for (const entry of entries) {
+ const threshold = headerBottom + Number.parseFloat(getComputedStyle(entry.heading).scrollMarginTop);
+ if (Math.round(entry.heading.getBoundingClientRect().top) > Math.round(threshold)) break;
+ current = entry;
+ }
+ const page = document.scrollingElement;
+ // The last section may never reach the header in a tall viewport.
+ if (page.scrollTop > 0 && Math.ceil(page.scrollTop + page.clientHeight) >= page.scrollHeight) current = entries.at(-1);
+ if (active === current && !forceReveal) return;
+ active?.link.removeAttribute('aria-current');
+ active?.group.classList.remove('outline-section-current');
+ current.link.setAttribute('aria-current', 'location');
+ current.group.classList.add('outline-section-current');
+ active = current;
+
+ // Follow reading progress inside the outline, without moving the article
+ // or interrupting someone browsing the outline with a pointer/keyboard.
+ if (forceReveal || !nav.matches(':hover, :focus-within')) {
+ const bounds = list.getBoundingClientRect();
+ const target = current.link.getBoundingClientRect();
+ if (target.top < bounds.top) list.scrollTop += target.top - bounds.top;
+ else if (target.bottom > bounds.bottom) list.scrollTop += target.bottom - bounds.bottom;
+ }
+ };
+ const schedule = (force = false) => {
+ if (force === true) reveal = true;
+ frame ??= requestAnimationFrame(update);
+ };
+ window.addEventListener('scroll', schedule, { passive: true });
+ window.addEventListener('resize', () => schedule(true));
+ window.addEventListener('hashchange', () => schedule(true));
+ window.addEventListener('pageshow', () => schedule(true));
+ window.addEventListener('pagehide', () => {
+ cancelAnimationFrame(frame);
+ frame = undefined;
+ });
+ new ResizeObserver(schedule).observe(document.querySelector('article'));
+ schedule();
+}
diff --git a/documents/templates/public/resource-format.js b/documents/templates/public/resource-format.js
new file mode 100644
index 00000000..882497cf
--- /dev/null
+++ b/documents/templates/public/resource-format.js
@@ -0,0 +1,20 @@
+// Shared by DocFX's template host and the browser. Values are text, never HTML.
+(function (api) {
+ api.parts = function (value) {
+ const parts = [];
+ let offset = 0;
+ for (const match of value.matchAll(/\{([a-zA-Z][a-zA-Z0-9]*)\}/g)) {
+ if (match.index > offset) parts.push({ text: value.slice(offset, match.index) });
+ parts.push({ slot: match[1] });
+ offset = match.index + match[0].length;
+ }
+ if (offset < value.length) parts.push({ text: value.slice(offset) });
+ return parts;
+ };
+ api.placeholders = value => [...new Set(api.parts(value).filter(part => part.slot).map(part => part.slot))].sort();
+ api.format = (value, slots) => api.parts(value).map(part => {
+ if (part.text !== undefined) return part.text;
+ if (!Object.prototype.hasOwnProperty.call(slots, part.slot)) throw new Error(`Missing resource placeholder: ${part.slot}`);
+ return slots[part.slot];
+ }).join('');
+})(typeof exports === 'object' ? exports : (globalThis.zenithResourceFormat = {}));
diff --git a/documents/templates/public/resources.js b/documents/templates/public/resources.js
new file mode 100644
index 00000000..087db608
--- /dev/null
+++ b/documents/templates/public/resources.js
@@ -0,0 +1,83 @@
+import './resource-format.js';
+
+export const resourceState = JSON.parse(document.getElementById('resource-state').textContent);
+let strings = resourceState.strings;
+export const getStrings = () => strings;
+const has = (dictionary, key) => Object.prototype.hasOwnProperty.call(dictionary, key);
+const format = globalThis.zenithResourceFormat;
+const slotTemplate = element => element.dataset.resourceSlots ? document.getElementById(element.dataset.resourceSlots) : null;
+const resourceAttributes = { label: 'aria-label', title: 'title', placeholder: 'placeholder', alt: 'alt' };
+
+// Validate before changing the DOM: an old or incomplete download must not mix languages.
+export function setStrings(value) {
+ if (!value || Array.isArray(value) || typeof value !== 'object' || Object.values(value).some(text => typeof text !== 'string' || !text.trim())) {
+ throw new Error('Invalid resource dictionary');
+ }
+ if (Object.keys(value).length !== Object.keys(resourceState.placeholders).length) {
+ throw new Error('Resource key set differs');
+ }
+ const requireKey = (key, slots) => {
+ if (!has(value, key)) throw new Error(`Missing text resource: ${key}`);
+ if (JSON.stringify(format.placeholders(value[key])) !== JSON.stringify([...slots].sort())) {
+ throw new Error(`Resource placeholders differ: ${key}`);
+ }
+ };
+ for (const [key, slots] of Object.entries(resourceState.placeholders)) requireKey(key, slots);
+ for (const element of document.querySelectorAll('[data-resource]')) {
+ const slots = slotTemplate(element)?.content.children || [];
+ requireKey(element.dataset.resource, [...slots].map(slot => slot.dataset.slot));
+ }
+ for (const attribute of Object.keys(resourceAttributes)) {
+ for (const element of document.querySelectorAll(`[data-resource-${attribute}]`)) requireKey(element.getAttribute(`data-resource-${attribute}`), []);
+ }
+ for (const meta of document.querySelectorAll('meta[name="resource:title"], meta[name="resource:description"]')) requireKey(meta.content, []);
+ strings = value;
+}
+
+export function t(key, values = {}) {
+ if (!has(strings, key)) throw new Error(`Missing text resource: ${key}`);
+ return format.format(strings[key], values);
+}
+
+function bind(element) {
+ const key = element.dataset.resource;
+ if (!has(strings, key)) return;
+ const template = slotTemplate(element);
+ const slots = new Map();
+ for (const slot of template?.content.children || []) {
+ const content = document.createDocumentFragment();
+ content.append(...[...slot.childNodes].map(child => child.cloneNode(true)));
+ for (const child of content.querySelectorAll('[data-resource]')) {
+ if (content.contains(child)) bind(child);
+ }
+ slots.set(slot.dataset.slot, content);
+ }
+ const content = document.createDocumentFragment();
+ for (const part of format.parts(strings[key])) {
+ if (part.text !== undefined) content.append(document.createTextNode(part.text));
+ else {
+ if (!slots.has(part.slot)) throw new Error(`Missing binding slot: ${key}.${part.slot}`);
+ content.append(slots.get(part.slot).cloneNode(true));
+ }
+ }
+ element.replaceChildren(content);
+}
+
+export function applyResources(code) {
+ document.documentElement.lang = code;
+ for (const element of document.querySelectorAll('[data-resource]')) {
+ if (element.isConnected) bind(element);
+ }
+ for (const [attribute, target] of Object.entries(resourceAttributes)) {
+ for (const element of document.querySelectorAll(`[data-resource-${attribute}]`)) {
+ const key = element.getAttribute(`data-resource-${attribute}`);
+ if (has(strings, key)) element.setAttribute(target, t(key));
+ }
+ }
+ const title = document.querySelector('meta[name="resource:title"]')?.content;
+ if (title) document.title = `${t(title)} | Zenith.NET`;
+ const description = document.querySelector('meta[name="resource:description"]')?.content;
+ if (description && has(strings, description)) document.querySelector('meta[name="description"]').content = t(description);
+ document.querySelector('meta[name="loc:copy"]').content = t('ui.action.copy');
+ for (const copy of document.querySelectorAll('.code-action[title]')) copy.title = t('ui.action.copy');
+}
diff --git a/documents/templates/public/search.css b/documents/templates/public/search.css
new file mode 100644
index 00000000..4f4aa744
--- /dev/null
+++ b/documents/templates/public/search.css
@@ -0,0 +1,309 @@
+.site-search {
+ --dialog-inset: min(12dvh, 100px);
+ --dialog-max-size: 720px;
+ width: min(var(--dialog-max-size), calc(100% - 32px));
+ max-width: none;
+ max-height: min(var(--dialog-max-size), calc(100dvh - 2 * var(--dialog-inset)));
+ margin: var(--dialog-inset) auto auto;
+ padding: 0;
+ overflow-y: auto;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+ border: 1px solid var(--zenith-control-border);
+ border-radius: 4px;
+ background: var(--zenith-surface);
+ color: var(--zenith-text);
+ box-shadow: 0 32px 120px #0009;
+}
+
+#search-close:hover, #api-member-close:hover {
+ color: var(--zenith-text);
+ background: var(--zenith-hover);
+}
+
+.site-search::backdrop {
+ background: #07090dbd;
+ backdrop-filter: blur(10px);
+ -webkit-backdrop-filter: blur(10px);
+}
+
+.site-search[open] {
+ display: block;
+ transform-origin: 50% 0;
+ animation: zenith-panel-in var(--zenith-panel-duration) cubic-bezier(.16, 1, .3, 1);
+}
+
+.site-search[open]::backdrop {
+ animation: zenith-backdrop-in var(--zenith-panel-duration) ease-out;
+}
+
+.site-search.is-closing {
+ animation: zenith-panel-out var(--zenith-panel-exit-duration) ease-in both;
+}
+
+.site-search.is-closing::backdrop {
+ animation: zenith-backdrop-in var(--zenith-panel-exit-duration) ease-in reverse both;
+}
+
+.search-input-row {
+ display: grid;
+ grid-template-columns: 20px minmax(0, 1fr) 36px;
+ align-items: center;
+ gap: 18px;
+ min-height: 88px;
+ padding: 20px 24px;
+ border-bottom: 1px solid var(--zenith-border);
+}
+
+.search-input-row > i {
+ color: #a6b2c4;
+ font-size: 20px;
+}
+
+.search-input-row:focus-within {
+ border-bottom-color: var(--zenith-focus);
+}
+
+#site-search-input, #api-member-query {
+ width: 100%;
+ min-width: 0;
+ padding: 0;
+ border: 0;
+ outline: none;
+ background: none;
+ color: var(--zenith-text);
+ font: 16px/1.6 var(--bs-body-font-family);
+}
+
+#site-search-input {
+ padding: 6px 0;
+ font: 400 21px/1.5 var(--bs-body-font-family);
+}
+
+#site-search-input::placeholder, #api-member-query::placeholder {
+ color: var(--zenith-muted);
+}
+
+#site-search-input::-webkit-search-cancel-button, #api-member-query::-webkit-search-cancel-button {
+ display: none;
+}
+
+#search-close, #api-member-close {
+ min-height: 32px;
+ padding: 4px 6px;
+ border: 0;
+ border-radius: 5px;
+ background: none;
+ color: #b4bfce;
+ font: 11px/1.5 var(--bs-font-monospace);
+ cursor: pointer;
+}
+
+.search-body {
+ padding: 18px 12px 12px;
+}
+
+#search-status {
+ margin: 0 12px 12px;
+ color: var(--zenith-muted);
+ font: 12px/1.5 var(--bs-body-font-family);
+}
+
+#site-search-results, #api-member-results ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.search-shortcuts > a,
+#site-search-results a {
+ display: block;
+ padding: 14px 16px;
+ border-radius: 3px;
+ color: var(--zenith-text);
+ text-decoration: none;
+ overflow-wrap: anywhere;
+}
+
+.search-shortcuts > a,
+.search-result-heading {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 20px;
+}
+
+.search-shortcuts > a:hover,
+.search-shortcuts > a:focus-visible,
+#site-search-results a:hover,
+#site-search-results a:focus-visible {
+ background: var(--zenith-hover);
+}
+
+.search-result-heading strong {
+ font-size: 16px;
+ font-weight: 500;
+}
+
+.site-search small {
+ flex-shrink: 0;
+ color: var(--zenith-muted);
+ font: 10px/1.5 var(--bs-font-monospace);
+ text-transform: uppercase;
+}
+
+#site-search-results p {
+ margin: 7px 0 0;
+ color: var(--zenith-muted);
+ font-size: 12px;
+ line-height: 1.6;
+}
+
+.search-more {
+ width: 100%;
+ padding: 12px;
+ border: 0;
+ background: none;
+ color: var(--zenith-text);
+ font-size: 13px;
+ cursor: pointer;
+}
+
+.search-footer {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 24px;
+ padding: 12px 24px;
+ border-top: 1px solid var(--zenith-border);
+ color: var(--zenith-muted);
+ font-size: 11px;
+}
+
+.search-footer kbd {
+ padding: 0 4px;
+ background: none;
+ color: #c2ccda;
+ font-size: 11px;
+}
+
+/* Member lookup shares the same dialog surface and controls. */
+.api-finder-heading {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 20px;
+ padding: 20px 24px 18px;
+}
+
+.api-finder-heading > div {
+ min-width: 0;
+}
+
+.api-finder-heading h2 {
+ margin: 0 0 7px;
+ color: var(--zenith-text);
+ font-size: 20px;
+ line-height: 1.35;
+ font-weight: 600;
+ letter-spacing: -.4px;
+}
+
+.api-finder-heading p {
+ margin: 0;
+ color: var(--zenith-muted);
+ font: 12px/1.6 var(--bs-font-monospace);
+}
+
+.api-member-filter {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 15px 24px;
+ border-block: 1px solid var(--zenith-border);
+ color: var(--zenith-muted);
+}
+
+.api-member-filter:focus-within {
+ border-color: var(--zenith-focus);
+}
+
+#api-member-clear {
+ flex-shrink: 0;
+ width: 30px;
+ height: 30px;
+ padding: 0;
+ border: 0;
+ border-radius: 4px;
+ background: none;
+ color: var(--zenith-muted);
+ font-size: 12px;
+ cursor: pointer;
+}
+
+#api-member-clear:hover {
+ background: var(--zenith-hover);
+ color: var(--zenith-text);
+}
+
+#api-member-results > section + section {
+ margin-top: 18px;
+}
+
+#api-member-results h3 {
+ margin: 5px 12px 8px;
+ color: var(--zenith-muted);
+ font: 500 11px/1.5 var(--bs-body-font-family);
+}
+
+#api-member-results a {
+ display: block;
+ padding: 11px 12px;
+ border-radius: 4px;
+ color: var(--zenith-text);
+ font: 12px/1.65 var(--bs-font-monospace);
+ text-decoration: none;
+ overflow-wrap: anywhere;
+}
+
+#api-member-results a:hover, #api-member-results a:focus-visible {
+ background: var(--zenith-hover);
+}
+
+#api-member-results a:focus-visible {
+ outline-offset: -2px;
+}
+
+.api-finder-empty {
+ margin: 18px 12px 28px;
+ color: var(--zenith-muted);
+ font-size: 13px;
+}
+
+@media (max-width: 767.98px) {
+ .api-finder-heading {
+ padding: 18px 16px 16px;
+ }
+
+ .api-member-filter {
+ padding: 13px 16px;
+ }
+
+ .site-search {
+ --dialog-inset: 16px;
+ width: calc(100% - 24px);
+ }
+
+ .search-input-row {
+ gap: 12px;
+ min-height: 76px;
+ padding: 16px;
+ }
+
+ #site-search-input {
+ font-size: 16px;
+ }
+
+ .search-footer {
+ padding: 12px 16px;
+ }
+}
diff --git a/documents/templates/public/search.js b/documents/templates/public/search.js
new file mode 100644
index 00000000..09c7834a
--- /dev/null
+++ b/documents/templates/public/search.js
@@ -0,0 +1,302 @@
+import { getStrings, t } from './resources.js';
+import { loadNavigation } from './navigation.js';
+import { closeContents, closeNavigation, isApplePlatform, isEditing } from './site.js';
+import { bindDialogKeys, isBackdropClick } from './dialog.js';
+import { edition, sourceLanguage, pageUrl, siteRoot } from './languages.js';
+
+const resultsPerPage = 30;
+const excerptLength = 160;
+
+// A directory index and an implicit current language identify the same document.
+function pageIdentity(href) {
+ const url = new URL(href, location.href);
+ url.pathname = url.pathname.replace(/\/$/, '/index.html');
+ if (!url.searchParams.has('lang')) url.searchParams.set('lang', edition.code);
+ url.searchParams.sort();
+ url.hash = '';
+ return url.href;
+}
+
+function pageTitles(items) {
+ const titles = new Map([['index.html', 'home.title'], ['api/index.html', 'reference.title']]);
+ const visit = item => {
+ const href = item.topicHref || item.href;
+ if (href && item.resourceKey?.endsWith('.title')) titles.set(href, item.resourceKey);
+ for (const child of item.items || []) visit(child);
+ };
+ items.forEach(visit);
+ return titles;
+}
+
+async function loadPageCatalog() {
+ const response = await fetch(new URL('index.json', siteRoot), { cache: 'no-cache' });
+ if (!response.ok) throw new Error('Page index unavailable');
+ const index = await response.json();
+ return Object.keys(index).sort().map(key => index[key]);
+}
+
+// Custom search presentation using DocFX's generated index and search worker.
+export function initializeSearch() {
+ const dialog = document.getElementById('search-dialog');
+ if (!dialog) return;
+
+ const trigger = document.getElementById('search-open');
+ if (!trigger) return;
+ const input = document.getElementById('site-search-input');
+ const status = document.getElementById('search-status');
+ const shortcuts = document.getElementById('search-shortcuts');
+ const results = document.getElementById('site-search-results');
+ const more = document.getElementById('search-more');
+ const pendingQueries = [];
+ let worker;
+ let ready = false;
+ let localEntries = [];
+ let loadingCatalog = false;
+ let hits = [];
+ let shown = 0;
+ let closeRequest;
+ const normalize = text => String(text || '').normalize('NFKD').replace(/\p{M}/gu, '').toLowerCase();
+
+ function rememberSearch(open = dialog.open) {
+ history.replaceState({
+ ...history.state,
+ zenithSearch: { open, query: input.value }
+ }, '');
+ }
+
+ const shortcut = isApplePlatform ? '⌘ K' : 'Ctrl K';
+ trigger.title = `${t('ui.search.title')} (${shortcut})`;
+ trigger.setAttribute('aria-keyshortcuts', shortcut === '⌘ K' ? 'Meta+K' : 'Control+K');
+
+ function runQuery() {
+ const query = input.value.trim();
+ results.replaceChildren();
+ more.hidden = true;
+ shortcuts.hidden = query !== '';
+ if (!query) {
+ status.textContent = t('ui.search.hint');
+ return;
+ }
+ status.textContent = ready ? t('ui.search.pending') : t('ui.search.loading');
+ if (!ready) return;
+ if (edition.code !== sourceLanguage) {
+ const normalizedQuery = normalize(query);
+ const terms = normalizedQuery.split(/\s+/).filter(Boolean);
+ const matches = localEntries
+ .filter(item => terms.every(term => item.body.includes(term)))
+ .map(item => ({ page: item.page, score: item.title === normalizedQuery ? terms.length + 1 : terms.filter(term => item.title.includes(term)).length }))
+ .sort((a, b) => b.score - a.score)
+ .map(item => item.page);
+ showResults(query, matches);
+ return;
+ }
+ // Use the same separators as DocFX's index for filenames and qualified API names.
+ const terms = query.split(/[\s\-.()]+/).filter(Boolean).map(term => '+' + term.replace(/[+\-:^~*\\]/g, '\\$&')).join(' ');
+ if (!terms) return showResults(query, []);
+ pendingQueries.push(query);
+ worker.postMessage({ q: terms });
+ }
+
+ function showResults(query, matches) {
+ hits = matches;
+ shown = 0;
+ results.replaceChildren();
+ status.textContent = hits.length ? t('ui.search.resultCount', { count: hits.length }) : t('ui.search.empty', { query });
+ appendResults();
+ }
+
+ function appendResults() {
+ const fragment = document.createDocumentFragment();
+ for (const hit of hits.slice(shown, shown + resultsPerPage)) {
+ const item = document.createElement('li');
+ const link = document.createElement('a');
+ const heading = document.createElement('span');
+ const title = document.createElement('strong');
+ const category = document.createElement('small');
+ const path = hit.href;
+ const apiResult = path.startsWith('api/');
+ link.href = pageUrl(hit.href);
+ link.setAttribute('data-search-item', '');
+ heading.className = 'search-result-heading';
+ title.textContent = hit.title.replace(/\s*\|\s*Zenith\.NET\s*$/, '');
+ category.textContent = apiResult ? 'API' : path === 'index.html' ? t('ui.navigation.home') : t('learning.title');
+ heading.append(title, category);
+ link.append(heading);
+ if (apiResult || hit.summary) {
+ const excerpt = document.createElement('p');
+ // API results identify the fully qualified type, without indexing UI labels into the preview.
+ let text = apiResult
+ ? path === 'api/index.html' ? t('ui.api.browse') : decodeURIComponent(new URL(hit.href, siteRoot).pathname.split('/').pop()).replace(/\.html$/, '')
+ : hit.summary.replace(/\s+/g, ' ').trim();
+ if (text.startsWith(title.textContent)) text = text.slice(title.textContent.length).trim();
+ if (text) {
+ excerpt.textContent = text.length > excerptLength ? text.slice(0, excerptLength) + '…' : text;
+ link.append(excerpt);
+ }
+ }
+ item.append(link);
+ fragment.append(item);
+ }
+ shown = Math.min(shown + resultsPerPage, hits.length);
+ results.append(fragment);
+ more.hidden = shown === hits.length;
+ }
+
+ function openSearch() {
+ document.getElementById('api-member-dialog')?.close();
+ closeRequest = undefined;
+ dialog.classList.remove('is-closing');
+ closeNavigation();
+ closeContents();
+ if (!dialog.open) dialog.showModal();
+ input.focus();
+ input.select();
+ rememberSearch();
+ if (ready) return runQuery();
+ if (edition.code !== sourceLanguage) {
+ if (!loadingCatalog) {
+ loadingCatalog = true;
+ Promise.all([loadPageCatalog(), loadNavigation()]).then(([index, navigation]) => {
+ const titles = pageTitles(navigation);
+ const strings = getStrings();
+ localEntries = index.map(original => {
+ const key = titles.get(original.href);
+ if (!key) return {
+ page: original,
+ title: normalize(original.title.replace(/\s*\|\s*Zenith\.NET\s*$/, '')),
+ body: normalize(`${original.href} ${original.title} ${original.keywords || ''} ${original.summary || ''}`)
+ };
+ const prefix = key.slice(0, -'.title'.length);
+ const values = Object.entries(strings).filter(([key]) => key.startsWith(prefix + '.')).map(([, value]) => value);
+ const description = strings[prefix + '.description'] || strings[prefix + '.meta.description'];
+ const page = { ...original, title: t(key), summary: description && !description.includes('{') ? description : '' };
+ // Keep shared code, filenames and API identifiers searchable in every language.
+ const body = `${values.join(' ')} ${original.href} ${original.keywords || ''} ${original.summary || ''}`;
+ return { page, title: normalize(page.title), body: normalize(body) };
+ });
+ ready = true;
+ runQuery();
+ }).catch(showUnavailable).finally(() => { loadingCatalog = false; });
+ }
+ return;
+ }
+ if (worker) return;
+ worker = new Worker(new URL('public/search-worker.min.js', siteRoot), { type: 'module' });
+ worker.addEventListener('message', event => {
+ if (event.data.e === 'index-ready') {
+ ready = true;
+ runQuery();
+ } else if (event.data.e === 'query-ready') {
+ const query = pendingQueries.shift();
+ if (query !== input.value.trim()) return;
+ showResults(query, event.data.d);
+ }
+ });
+ worker.addEventListener('error', () => {
+ worker.terminate();
+ worker = undefined;
+ ready = false;
+ pendingQueries.length = 0;
+ showUnavailable();
+ });
+ worker.postMessage({ init: {} });
+ }
+
+ function showUnavailable() {
+ results.replaceChildren();
+ more.hidden = true;
+ status.textContent = t('ui.search.unavailable');
+ shortcuts.hidden = false;
+ }
+
+ function closeSearch() {
+ if (!dialog.open || closeRequest) return closeRequest;
+ rememberSearch(false);
+ dialog.classList.add('is-closing');
+ const request = Promise.allSettled(dialog.getAnimations().map(animation => animation.finished)).then(() => {
+ // Reopening cancels this close, including when animations are disabled.
+ if (closeRequest !== request) return false;
+ dialog.close();
+ return true;
+ });
+ closeRequest = request;
+ return request;
+ }
+
+ trigger.addEventListener('click', openSearch);
+ input.addEventListener('input', () => {
+ rememberSearch();
+ runQuery();
+ });
+ document.getElementById('search-close').addEventListener('click', closeSearch);
+ dialog.addEventListener('close', () => {
+ if (dialog.open) return;
+ closeRequest = undefined;
+ dialog.classList.remove('is-closing');
+ rememberSearch();
+ trigger.focus({ preventScroll: true });
+ });
+ dialog.addEventListener('cancel', event => {
+ event.preventDefault();
+ closeSearch();
+ });
+ dialog.addEventListener('click', async event => {
+ const link = event.target.closest('a[data-search-item]');
+ if (link) {
+ rememberSearch();
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
+ if (pageIdentity(link.href) !== pageIdentity(location.href)) return;
+ event.preventDefault();
+ const url = new URL(link.href);
+ if (await closeSearch() && url.hash) {
+ location.hash = url.hash;
+ const target = document.getElementById(decodeURIComponent(url.hash.slice(1)));
+ if (target) requestAnimationFrame(() => {
+ target.tabIndex = -1;
+ target.focus({ preventScroll: true });
+ target.scrollIntoView({ block: 'start', behavior: 'auto' });
+ });
+ }
+ return;
+ }
+ if (isBackdropClick(event, dialog)) closeSearch();
+ });
+ document.getElementById('site-search-form').addEventListener('submit', event => {
+ event.preventDefault();
+ results.querySelector('a')?.click();
+ });
+ more.addEventListener('click', () => {
+ const next = shown;
+ appendResults();
+ results.children[next]?.querySelector('a').focus();
+ });
+ document.addEventListener('keydown', event => {
+ if (event.isComposing || event.repeat) return;
+ const command = (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k';
+ const slash = event.key === '/' && !event.metaKey && !event.ctrlKey && !event.altKey;
+ if (command || slash && !isEditing(event.target)) {
+ event.preventDefault();
+ if (dialog.open && command && !closeRequest) closeSearch();
+ else openSearch();
+ }
+ });
+ bindDialogKeys(dialog, { items: '[data-search-item]', close: closeSearch });
+
+ // History owns the open state and query, including same-document traversal.
+ function restoreSearch() {
+ const saved = history.state?.zenithSearch;
+ if (!saved?.open) {
+ closeRequest = undefined;
+ dialog.classList.remove('is-closing');
+ dialog.close();
+ return;
+ }
+ input.value = saved.query;
+ openSearch();
+ }
+ // Run after history traversal restores form controls, including Safari's search input.
+ const restoreAfterNavigation = () => setTimeout(restoreSearch, 0);
+ window.addEventListener('pageshow', restoreAfterNavigation);
+ window.addEventListener('popstate', restoreAfterNavigation);
+ restoreSearch();
+}
diff --git a/documents/templates/public/signature-links.js b/documents/templates/public/signature-links.js
new file mode 100644
index 00000000..826ef035
--- /dev/null
+++ b/documents/templates/public/signature-links.js
@@ -0,0 +1,119 @@
+import { collectCodeReferences, linkCodeRange } from './code-links.js';
+
+const escapeRegex = value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+const maskLiterals = text => text.replace(/@"(?:""|[^"])*"|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, match => ' '.repeat(match.length));
+
+// Generated declarations link local reference pages only. Authored xrefs are explicit.
+export function isLocalTypeReference(link) {
+ const href = link.getAttribute('href');
+ if (!href) return false;
+ const target = new URL(href, document.baseURI);
+ return target.origin === location.origin;
+}
+
+// Separate parameter declarations without splitting generic, tuple, or attribute syntax.
+function parameterTypeRanges(text, open, parameters) {
+ if (open < 0 || !parameters.length) return [];
+ const masked = maskLiterals(text);
+ const close = masked[open] === '[' ? ']' : ')';
+ const stack = [];
+ const ranges = [];
+ let start = open + 1;
+ let defaultStart = -1;
+ let index = 0;
+ for (let pos = start; pos < masked.length; pos++) {
+ const char = masked[pos];
+ if (!stack.length && (char === ',' || char === close)) {
+ const parameter = parameters[index++];
+ if (!parameter) break;
+ const declaration = text.slice(start, defaultStart < 0 ? pos : defaultStart);
+ const name = new RegExp(`(?' }[char]);
+ } else if (stack.length && char === stack[stack.length - 1]) {
+ stack.pop();
+ }
+ }
+ return ranges;
+}
+
+// Link each type within its own declaration range, using DocFX's resolved references.
+export function linkSignatureType({ el, text }) {
+ if (!el.matches('[data-api-symbol]')) return;
+ const references = el.parentElement.nextElementSibling;
+ if (!references?.matches('.api-signature-references')) return;
+
+ const symbol = el.dataset.apiSymbol.split('(')[0].split('[')[0];
+ const masked = maskLiterals(text);
+ const declaration = new RegExp(`(?:^|\\s)${escapeRegex(symbol)}(?=\\s*(?:[({\\[;=:]|$))`).exec(masked);
+ // The value type precedes the member name; conversion operators put it before "(".
+ const end = el.dataset.apiKind === 'operator'
+ ? text.indexOf('(', declaration ? declaration.index + declaration[0].length : 0)
+ : declaration ? declaration.index + declaration[0].length - symbol.length : -1;
+ const valueReferences = references.querySelector('[data-signature-value]');
+ if (end >= 0 && valueReferences) linkRange(el, 0, end, valueReferences);
+
+ if (!declaration) return;
+ const afterSymbol = declaration.index + declaration[0].length;
+ const baseReferences = [...references.querySelectorAll('[data-signature-base]')];
+ if (baseReferences.length) {
+ const constraint = /\bwhere\b/.exec(masked.slice(afterSymbol));
+ const limit = constraint ? afterSymbol + constraint.index : text.length;
+ const colon = masked.indexOf(':', afterSymbol);
+ if (colon >= 0 && colon < limit) {
+ for (const reference of baseReferences) linkRange(el, colon + 1, limit, reference);
+ }
+ }
+ const opening = /[([]/.exec(text.slice(afterSymbol));
+ const parameters = [...references.querySelectorAll('[data-signature-parameter]')];
+ const ranges = parameterTypeRanges(text, opening ? afterSymbol + opening.index : -1,
+ parameters.map(parameter => parameter.dataset.signatureParameter));
+ for (const range of ranges) linkRange(el, range.start, range.end, parameters[range.index]);
+}
+
+function linkRange(el, start, end, references) {
+ const links = collectCodeReferences(references).filter(reference => isLocalTypeReference(reference.link));
+ if (!links.length) return;
+ // Match the whole type expression. Equal spellings can refer to different symbols.
+ // Nullable annotations may be absent from DocFX's type display, but remain in copied code.
+ const tokenize = (text, offset = 0) => [...text.matchAll(/@?[\p{ID_Start}_][\p{ID_Continue}]*|[^\s]/gu)]
+ .filter(match => match[0] !== '?')
+ .map(match => ({ value: match[0].replace(/^@/, ''), start: offset + match.index, end: offset + match.index + match[0].length }));
+ const expected = tokenize(references.textContent);
+ const actual = tokenize(el.textContent.slice(start, end), start);
+ if (!expected.length) return;
+ let offset = -1;
+ for (let index = actual.length - expected.length; index >= 0; index--) {
+ if (expected.every((token, part) => token.value === actual[index + part].value)) {
+ offset = index;
+ break;
+ }
+ }
+ if (offset < 0) return;
+
+ let tupleDepth = 0;
+ for (let index = 0; index < expected.length; index++) {
+ const token = expected[index];
+ if (token.value === '(') tupleDepth++;
+ const previous = expected[index - 1]?.value;
+ const next = expected[index + 1]?.value;
+ // Tuple element names are labels, even when DocFX supplies a member xref for them.
+ token.label = tupleDepth > 0 && /^[\p{ID_Start}_]/u.test(token.value)
+ && [',', ')'].includes(next) && previous && !['(', ',', '<', '.'].includes(previous);
+ if (token.value === ')') tupleDepth--;
+ }
+ for (const reference of links.reverse()) {
+ const positions = expected.map((token, index) => ({ token, index }))
+ .filter(({ token }) => !token.label && token.start >= reference.start && token.end <= reference.end);
+ if (!positions.length) continue;
+ const first = actual[offset + positions[0].index];
+ const last = actual[offset + positions[positions.length - 1].index];
+ linkCodeRange(el, first.start, last.end, reference.link);
+ }
+}
diff --git a/documents/templates/public/site.js b/documents/templates/public/site.js
new file mode 100644
index 00000000..e10a4f38
--- /dev/null
+++ b/documents/templates/public/site.js
@@ -0,0 +1,67 @@
+export const isApplePlatform = /Mac|iPhone|iPad/.test(navigator.platform);
+
+export function closeHeaderMenu() {
+ document.getElementById('menu-toggle')?.setAttribute('aria-expanded', 'false');
+ document.querySelector('.site-header')?.classList.remove('menu-open');
+}
+
+export function closeNavigation() {
+ document.getElementById('language-menu')?.removeAttribute('open');
+ closeHeaderMenu();
+}
+
+export function closeContents() {
+ document.querySelector('#tocOffcanvas.show .contents-close')?.click();
+}
+
+export function isEditing(target) {
+ return Boolean(target.closest?.('input, textarea, select, [contenteditable]:not([contenteditable="false"])'));
+}
+
+export function initializeSite() {
+ const menu = document.getElementById('menu-toggle');
+ const header = document.querySelector('.site-header');
+ if (menu && header) {
+ menu.addEventListener('click', () => {
+ document.getElementById('language-menu')?.removeAttribute('open');
+ const open = menu.getAttribute('aria-expanded') !== 'true';
+ menu.setAttribute('aria-expanded', String(open));
+ header.classList.toggle('menu-open', open);
+ });
+ document.addEventListener('click', event => {
+ if (header.classList.contains('menu-open') && !header.contains(event.target)) closeNavigation();
+ });
+ document.addEventListener('keydown', event => {
+ if (event.key === 'Escape' && header.classList.contains('menu-open')) {
+ closeNavigation();
+ menu.focus();
+ }
+ });
+ window.matchMedia('(min-width: 768px)').addEventListener('change', event => {
+ if (event.matches) closeNavigation();
+ });
+ window.addEventListener('pagehide', closeNavigation);
+ }
+
+ // Preserve deep-link IDs while disabling DocFX's decorative heading anchors.
+ for (const heading of document.querySelectorAll('article h2, article h3, article h4')) heading.classList.add('no-anchor');
+ labelCodeBlocks();
+ // DocFX adds scrolling wrappers after this hook; the table retains its focus target.
+ for (const table of document.querySelectorAll('article table')) table.tabIndex = 0;
+}
+
+function labelCodeBlocks() {
+ const labels = {
+ bash: 'Shell', console: 'Console', cs: 'C#', csharp: 'C#', json: 'JSON',
+ powershell: 'PowerShell', sh: 'Shell', shell: 'Shell', slang: 'Slang', text: 'Text',
+ xml: 'XML', yaml: 'YAML', yml: 'YAML'
+ };
+ for (const code of document.querySelectorAll('article pre > code')) {
+ const language = [...code.classList].find(name => /^(?:lang|language)-/.test(name))
+ ?.replace(/^(?:lang|language)-/, '') || 'text';
+ if (language === 'mermaid') continue;
+ code.parentElement.dataset.language = labels[language] || language;
+ // Native keyboard scrolling for examples; API declarations already wrap.
+ if (!code.hasAttribute('data-api-symbol')) code.tabIndex = 0;
+ }
+}
diff --git a/documents/templates/public/syntax.js b/documents/templates/public/syntax.js
new file mode 100644
index 00000000..747ac72b
--- /dev/null
+++ b/documents/templates/public/syntax.js
@@ -0,0 +1,68 @@
+// Extend DocFX's bundled highlighter; coloring never creates navigation links.
+// Modes use highlight.js's public grammar API:
+// https://highlightjs.readthedocs.io/en/latest/mode-reference.html
+const configured = new WeakSet();
+
+function expressionModes(keywords) {
+ const reserved = new Set(Object.values(keywords).flatMap(words =>
+ (Array.isArray(words) ? words : words.split(/\s+/)).map(word => word.split('|')[0])));
+ return [
+ {
+ scope: 'title.function',
+ match: /(?)?\s*\()/,
+ relevance: 0,
+ 'on:begin': (match, response) => {
+ if (reserved.has(match[0])) response.ignoreMatch();
+ }
+ },
+ { scope: 'property', match: /(?<=\.)@?[A-Za-z_]\w*/, relevance: 0 },
+ { scope: 'attr', match: /\b[A-Za-z_]\w*(?=\s*:(?!:))/, relevance: 0 },
+ // Conventional type identifiers get lexical color, not semantic resolution.
+ { scope: 'type', match: /\b[A-Z][A-Za-z0-9_]*\b/, relevance: 0 },
+ { scope: 'operator', match: /=>|\?\?=?|\?\.|[=!<>+*/%&|^~?:-]+/, relevance: 0 }
+ ];
+}
+
+export function configureSyntax(hljs) {
+ const csharp = hljs.getLanguage('csharp');
+ if (!configured.has(csharp)) {
+ configured.add(csharp);
+ const expressions = expressionModes(csharp.keywords);
+ const visited = new Set();
+ const extend = mode => {
+ if (!mode || typeof mode !== 'object' || visited.has(mode)) return;
+ visited.add(mode);
+ for (const child of [...(mode.contains || []), ...(mode.variants || [])]) extend(child);
+ // Code contexts include declarations, parameter lists and interpolations.
+ // String/comment/attribute modes retain their own lexical boundaries.
+ if (mode.keywords === csharp.keywords) mode.contains = [...(mode.contains || []), ...expressions];
+ };
+ extend(csharp);
+ }
+ hljs.registerLanguage('slang', slang);
+ hljs.registerAliases(['console'], { languageName: 'shell' });
+}
+
+function slang(hljs) {
+ const keywords = {
+ keyword: 'struct class interface extension enum namespace using import public private internal static const uniform groupshared in out inout ref let var typedef typealias generic where __init __subscript if else for while do break continue return discard switch case default true false nullptr this sizeof reinterpret_cast no_diff differentiable',
+ type: 'void bool int uint half float double int8_t uint8_t int16_t uint16_t int32_t uint32_t int64_t uint64_t'
+ };
+ return {
+ name: 'Slang',
+ disableAutodetect: true,
+ keywords,
+ contains: [
+ hljs.C_LINE_COMMENT_MODE,
+ hljs.C_BLOCK_COMMENT_MODE,
+ hljs.QUOTE_STRING_MODE,
+ hljs.APOS_STRING_MODE,
+ { scope: 'meta', begin: /^\s*#/, end: /$/, contains: [hljs.QUOTE_STRING_MODE] },
+ hljs.C_NUMBER_MODE,
+ { scope: 'meta', match: /(?<=\[)(?:shader|numthreads|unroll|loop|branch|flatten|earlydepthstencil|outputtopology|mesh|domain|partitioning|patchconstantfunc|outputcontrolpoints)\b/ },
+ { scope: 'type', match: /\b(?:bool|int|uint|half|float|double)[1-4](?:x[1-4])?\b/ },
+ { scope: 'meta', match: /\b(?:SV_\w+|(?:POSITION|NORMAL|TEXCOORD|COLOR|TANGENT|BINORMAL)\d*)\b/ },
+ ...expressionModes(keywords)
+ ]
+ };
+}
diff --git a/documents/templates/public/theme.css b/documents/templates/public/theme.css
new file mode 100644
index 00000000..28f8a8f7
--- /dev/null
+++ b/documents/templates/public/theme.css
@@ -0,0 +1,213 @@
+/* Zenith.NET — a single graphite theme, based on the brand icon. */
+/* Tokens and DocFX / Bootstrap integration */
+:root {
+ color-scheme: dark;
+ scroll-behavior: auto;
+ --zenith-state-duration: 180ms;
+ --zenith-press-duration: 60ms;
+ --zenith-panel-duration: 280ms;
+ --zenith-panel-exit-duration: 160ms;
+ --zenith-state-ease: cubic-bezier(.2, 0, .2, 1);
+ --zenith-canvas: #101216;
+ --zenith-surface: #181b21;
+ --zenith-graphite: #262a32;
+ --zenith-border: #2d323b;
+ --zenith-text: #f2f3f5;
+ --zenith-body: #d0d4dc;
+ --zenith-muted: #a0a8b5;
+ --zenith-link: #dce5f2;
+ --zenith-code-link: #bedbfa;
+ --zenith-border-hover: #626a78;
+ --zenith-code: #0d0f13;
+ --zenith-focus: #cad5e5;
+ --zenith-action: #e1e8f2;
+ --zenith-control-bg: #1c2026;
+ --zenith-control-border: #343a44;
+ --zenith-hover: #272c34;
+ --zenith-radius: 8px;
+ --zenith-header-height: 72px;
+ --zenith-anchor-gap: 24px;
+ --zenith-width: 1920px;
+ --zenith-reading-width: 1080px;
+ --zenith-sidebar-width: 264px;
+ --zenith-outline-width: 208px;
+ --zenith-gap: 40px;
+ --zenith-gutter: clamp(40px, 3vw, 64px);
+ --bs-body-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ --bs-font-monospace: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
+ --bs-body-bg: var(--zenith-canvas);
+ --bs-body-bg-rgb: 16, 18, 22;
+ --bs-body-color: var(--zenith-body);
+ --bs-body-color-rgb: 208, 212, 220;
+ --bs-emphasis-color: var(--zenith-text);
+ --bs-emphasis-color-rgb: 242, 243, 245;
+ --bs-heading-color: var(--zenith-text);
+ --bs-secondary-color: var(--zenith-muted);
+ --bs-secondary-color-rgb: 160, 168, 181;
+ --bs-secondary-bg: var(--zenith-graphite);
+ --bs-secondary-bg-rgb: 38, 42, 50;
+ --bs-tertiary-color: var(--zenith-muted);
+ --bs-tertiary-bg: var(--zenith-surface);
+ --bs-tertiary-bg-rgb: 24, 27, 33;
+ --bs-link-color: var(--zenith-link);
+ --bs-link-color-rgb: 220, 229, 242;
+ --bs-link-hover-color: var(--zenith-text);
+ --bs-link-hover-color-rgb: 242, 243, 245;
+ --bs-border-color: var(--zenith-border);
+ --bs-border-color-translucent: var(--zenith-border);
+ --bs-border-radius: var(--zenith-radius);
+ --bs-code-color: var(--zenith-text);
+ --bs-highlight-color: var(--zenith-text);
+ --bs-highlight-bg: var(--zenith-graphite);
+}
+
+/* Page and typography */
+html {
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ background: var(--zenith-canvas);
+ overflow-y: auto;
+ /* Keep the centered shell stable when long and short pages switch scrollbars. */
+ scrollbar-gutter: stable;
+ overscroll-behavior-y: none;
+ scroll-padding-top: var(--zenith-header-height);
+ scrollbar-color: var(--zenith-border-hover) var(--zenith-canvas);
+ scrollbar-width: thin;
+}
+
+body.zenith-theme {
+ width: 100%;
+ min-width: 0;
+ min-height: 100dvh;
+ margin: 0;
+ padding-top: 0;
+ background: var(--zenith-canvas);
+ font-size: 16px;
+ line-height: 1.75;
+ -webkit-font-smoothing: antialiased;
+}
+
+::selection {
+ background: #414b5b;
+ color: var(--zenith-text);
+}
+
+:focus-visible {
+ outline: 2px solid var(--zenith-focus);
+ outline-offset: 3px;
+}
+
+a {
+ text-underline-offset: 3px;
+ text-decoration-color: #737f91;
+}
+
+a:hover {
+ text-decoration-color: currentColor;
+}
+
+article a.external[href]::after {
+ content: none;
+}
+
+article {
+ overflow-wrap: anywhere;
+}
+
+article :is(h1, h2, h3, h4, h5, h6) {
+ color: var(--zenith-text);
+ font-weight: 650;
+ line-height: 1.3;
+}
+
+body.zenith-theme > main > .content > article,
+body.zenith-theme > main > .content > article [id] {
+ scroll-margin-top: var(--zenith-anchor-gap);
+}
+
+article h1 {
+ margin: 0 0 24px;
+ font-size: clamp(32px, 3vw, 42px);
+ letter-spacing: -1.2px;
+}
+
+article h2 {
+ margin: 40px 0 18px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid var(--zenith-border);
+ font-size: 25px;
+ letter-spacing: -.5px;
+}
+
+article h3 {
+ margin: 30px 0 14px;
+ font-size: 20px;
+}
+
+article :is(h4, h5, h6) {
+ margin: 24px 0 12px;
+ font-size: 17px;
+}
+
+article :is(p, ul, ol, dl) {
+ margin-bottom: 20px;
+}
+
+article :is(ul, ol) {
+ padding-left: 24px;
+}
+
+article li {
+ margin-block: 6px;
+}
+
+article strong {
+ color: var(--zenith-text);
+ font-weight: 600;
+}
+
+article :is(img, video, svg) {
+ max-width: 100%;
+}
+
+article img {
+ height: auto;
+ border-radius: var(--zenith-radius);
+}
+
+article blockquote {
+ margin: 24px 0;
+ padding: 4px 0 4px 20px;
+ border-left: 2px solid var(--zenith-muted);
+ color: var(--zenith-muted);
+}
+
+article blockquote > :last-child {
+ margin-bottom: 0;
+}
+
+hr {
+ margin: 28px 0;
+ opacity: 1;
+ border-color: var(--zenith-border);
+}
+
+@media (max-width: 1199.98px) {
+ :root {
+ --zenith-sidebar-width: 220px;
+ --zenith-gap: 28px;
+ --zenith-gutter: 24px;
+ }
+}
+
+@media (max-width: 767.98px) {
+ :root {
+ --zenith-header-height: 64px;
+ --zenith-gutter: 20px;
+ }
+
+ article h2 {
+ font-size: 23px;
+ }
+}
diff --git a/documents/templates/resources.common.js b/documents/templates/resources.common.js
new file mode 100644
index 00000000..5833824d
--- /dev/null
+++ b/documents/templates/resources.common.js
@@ -0,0 +1,120 @@
+const format = require('./public/resource-format.js');
+const keyPattern = /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)+$/;
+const escape = value => String(value).replace(/[&<>"']/g, character => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[character]));
+const json = value => JSON.stringify(value).replace(/ model[key] ?? model._metadata?.[key];
+
+exports.dictionary = function (model) {
+ const result = {};
+ for (const key of Object.keys(model).filter(key => !key.startsWith('_'))) {
+ if (!keyPattern.test(key) || typeof model[key] !== 'string' || !model[key].trim()) {
+ throw new Error(`Invalid text resource: ${key} (${model._key})`);
+ }
+ result[key] = model[key];
+ }
+ return result;
+};
+
+exports.source = function (model) {
+ const code = setting(model, '_sourceLanguage');
+ const source = model.__global._shared[`~/locales/${code}/strings.yml`];
+ if (!source) throw new Error(`Missing source dictionary: locales/${code}/strings.yml`);
+ return exports.dictionary(source);
+};
+
+exports.get = function (dictionary, key) {
+ if (!Object.prototype.hasOwnProperty.call(dictionary, key)) throw new Error(`Missing text resource: ${key}`);
+ return dictionary[key];
+};
+
+exports.validate = function (model) {
+ const code = model._key.split('/').at(-2);
+ const languages = setting(model, '_languages');
+ if (!languages.some(language => language.code === code)) throw new Error(`Unregistered dictionary language: ${code}`);
+ const source = exports.source(model);
+ const dictionary = exports.dictionary(model);
+ for (const key of new Set([...Object.keys(source), ...Object.keys(dictionary)])) {
+ const expected = exports.get(source, key);
+ const value = exports.get(dictionary, key);
+ if (JSON.stringify(format.placeholders(expected)) !== JSON.stringify(format.placeholders(value))) {
+ throw new Error(`Resource placeholders differ: ${key} (${model._key})`);
+ }
+ }
+ return dictionary;
+};
+
+exports.prepare = function (model) {
+ const source = exports.source(model);
+ const interfaceEntries = Object.entries(source).filter(([key]) => key.startsWith('ui.') || key.endsWith('.title'));
+ model.r = Object.create(null);
+ for (const [key, value] of interfaceEntries) {
+ const path = key.split('.');
+ let scope = model.r;
+ for (const segment of path.slice(0, -1)) scope = scope[segment] ||= Object.create(null);
+ scope[path.at(-1)] = value;
+ }
+ for (const field of ['title', 'description']) {
+ if (model[field]?.startsWith('@')) {
+ model[`_resource${field[0].toUpperCase() + field.slice(1)}`] = model[field].slice(1);
+ model[field] = exports.get(source, model[field].slice(1));
+ }
+ }
+ model._languages = model._languages.map(language => ({
+ ...language,
+ available: Boolean(model.__global._shared[`~/locales/${language.code}/strings.yml`])
+ }));
+ model._resourceState = json({
+ sourceLanguage: model._sourceLanguage,
+ languages: model._languages,
+ placeholders: Object.fromEntries(Object.entries(source).map(([key, value]) => [key, format.placeholders(value)])),
+ strings: Object.fromEntries(interfaceEntries)
+ });
+ return source;
+};
+
+// Only the two authored binding tags are parsed. All other HTML is opaque shared markup.
+exports.bind = function (html, dictionary, templates) {
+ const root = { children: [] };
+ const stack = [root];
+ let offset = 0;
+ for (const match of html.matchAll(/<\/?(resource|slot)\b[^>]*>/g)) {
+ stack.at(-1).children.push(html.slice(offset, match.index));
+ if (match[0].startsWith('')) {
+ if (stack.length === 1 || stack.at(-1).type !== match[1]) throw new Error('Unbalanced resource binding tags');
+ stack.pop();
+ } else {
+ const name = /(?:key|name)="([^"]+)"/.exec(match[0])?.[1];
+ if (!name) throw new Error(`Missing binding name: ${match[0]}`);
+ const node = { type: match[1], name, children: [] };
+ stack.at(-1).children.push(node);
+ stack.push(node);
+ }
+ offset = match.index + match[0].length;
+ }
+ if (stack.length !== 1) throw new Error('Unclosed resource binding tag');
+ root.children.push(html.slice(offset));
+ const renderChildren = node => node.children.map(render).join('');
+ const render = node => {
+ if (typeof node === 'string') return node;
+ if (!node.type || node.type === 'slot') return renderChildren(node);
+ const slots = Object.create(null);
+ for (const child of node.children) {
+ if (typeof child === 'string' && !child.trim()) continue;
+ if (child.type !== 'slot' || child.name in slots) throw new Error(`Invalid slot in ${node.name}`);
+ slots[child.name] = renderChildren(child);
+ }
+ const value = exports.get(dictionary, node.name);
+ if (JSON.stringify(Object.keys(slots).sort()) !== JSON.stringify(format.placeholders(value))) {
+ throw new Error(`Binding slots differ: ${node.name}`);
+ }
+ const content = format.parts(value).map(part => part.text !== undefined ? escape(part.text) : slots[part.slot]).join('');
+ let reference = '';
+ if (Object.keys(slots).length) {
+ const id = `resource-slots-${templates.length}`;
+ templates.push(`${Object.entries(slots).map(([name, content]) => `${content} `).join('')} `);
+ reference = ` data-resource-slots="${id}"`;
+ }
+ return `${content} `;
+ };
+ return renderChildren(root).replace(/(aria-label|title|placeholder|alt)="@([^"]+)"/g, (_, attribute, key) => `${attribute}="${escape(exports.get(dictionary, key))}"`);
+};
diff --git a/documents/templates/schemas/Resources.schema.json b/documents/templates/schemas/Resources.schema.json
new file mode 100644
index 00000000..fc743021
--- /dev/null
+++ b/documents/templates/schemas/Resources.schema.json
@@ -0,0 +1,15 @@
+{
+ "$schema": "https://dotnet.github.io/docfx/schemas/v1.0/schema.json#",
+ "version": "1.0.0",
+ "title": "Resources",
+ "type": "object",
+ "metadata": "/_metadata",
+ "additionalProperties": {
+ "type": "string"
+ },
+ "properties": {
+ "_metadata": {
+ "type": "object"
+ }
+ }
+}
diff --git a/documents/templates/toc.extension.js b/documents/templates/toc.extension.js
new file mode 100644
index 00000000..c526c981
--- /dev/null
+++ b/documents/templates/toc.extension.js
@@ -0,0 +1,13 @@
+const resources = require('./resources.common.js');
+exports.postTransform = function (model) {
+ const strings = resources.source(model);
+ const visit = item => {
+ if (item.name?.startsWith('@')) {
+ item.resourceKey = item.name.slice(1);
+ item.name = resources.get(strings, item.resourceKey);
+ }
+ for (const child of item.items || []) visit(child);
+ };
+ visit(model);
+ return model;
+};
diff --git a/documents/toc.yml b/documents/toc.yml
index b1d527b5..1f6df69c 100644
--- a/documents/toc.yml
+++ b/documents/toc.yml
@@ -1,11 +1,21 @@
-- name: Docs
- href: docs/
- homepage: docs/index.md
-
-- name: Tutorials
- href: tutorials/
- homepage: tutorials/index.md
-
-- name: API
- href: api/
- homepage: api/index.md
+- name: '@learning.title'
+ href: learn/index.md
+ items:
+ - name: '@tutorial.title'
+ href: learn/first-triangle.md
+ - name: '@samples.title'
+ href: learn/samples.md
+ - name: '@ui.navigation.concepts'
+ items:
+ - name: '@concepts.execution.title'
+ href: learn/concepts/execution-model.md
+ - name: '@concepts.resources.title'
+ href: learn/concepts/resource-management.md
+ - name: '@concepts.shaders.title'
+ href: learn/concepts/shader-data-and-binding.md
+ - name: '@concepts.synchronization.title'
+ href: learn/concepts/synchronization.md
+ - name: '@concepts.platforms.title'
+ href: learn/concepts/platform-integration.md
+- name: '@ui.navigation.api'
+ href: api/index.md
diff --git a/documents/tutorials/getting-started/application-host.md b/documents/tutorials/getting-started/application-host.md
deleted file mode 100644
index a0e3a224..00000000
--- a/documents/tutorials/getting-started/application-host.md
+++ /dev/null
@@ -1,291 +0,0 @@
-# Application Host
-
-Every workload guide runs inside the same application host. The host owns the window and graphics context, creates the swap chain, records the outer frame transitions, and presents textures produced by compute workloads.
-
-This page explains that contract before the guides begin creating workload resources. Platform integration is host infrastructure and remains in the complete reference source rather than the walkthrough.
-
-## Responsibilities
-
-The shared host is responsible for:
-
-- selecting a supported graphics API for the current operating system;
-- creating a window surface and swap chain;
-- providing the active `GraphicsContext` and framebuffer dimensions;
-- obtaining one graphics command buffer per frame;
-- transitioning the swap-chain drawable into and out of attachment layout;
-- forwarding update, render, and resize events to the active workload;
-- submitting work, waiting for completion, and presenting the drawable;
-- loading shared assets and presenting offscreen textures.
-
-The workload renderer remains responsible for its own buffers, textures, pipelines, commands, transitions, and disposal.
-
-## Renderer contract
-
-Create `IRenderer.cs`:
-
-```csharp
-namespace ZenithTutorials;
-
-internal interface IRenderer : IDisposable
-{
- void Update(double deltaTime);
-
- void Render(CommandBuffer commandBuffer, Texture drawable);
-
- void Resize(uint width, uint height);
-}
-```
-
-The host calls `Update` before rendering, passes the current swap-chain texture to `Render`, and reports framebuffer size changes through `Resize`. Extending `IDisposable` makes each guide state its GPU ownership explicitly.
-
-## Create the graphics context
-
-`App` exposes one shared graphics context. Its static constructor selects DirectX 12 on Windows, Metal 4 on macOS, or Vulkan 1.4 on Linux.
-
-The complete `App.cs` contains the required imports and declares `App` as an `unsafe` static class because its upload helpers use pointers.
-
-```csharp
-static App()
-{
- if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsLinux())
- {
- throw new PlatformNotSupportedException("The tutorials support Windows, macOS, and Linux.");
- }
-
- if (OperatingSystem.IsWindows())
- {
- Context = GraphicsContext.CreateDirectX12(useValidationLayer: true);
- }
- else if (OperatingSystem.IsMacOS())
- {
- Context = GraphicsContext.CreateMetal(useValidationLayer: true);
- }
- else
- {
- Context = GraphicsContext.CreateVulkan(useValidationLayer: true);
- }
-
- Context.ValidationMessage += static (_, args) => Console.WriteLine($"[{args.Severity}] {args.Message}");
-}
-```
-
-Validation remains enabled throughout the guides. Validation messages are written to the terminal and should remain free of errors when a workload is running correctly.
-
-The host also publishes the shared color format and current framebuffer dimensions:
-
-```csharp
-public static GraphicsContext Context { get; }
-
-public static PixelFormat ColorFormat => PixelFormat.B8G8R8A8UNorm;
-
-public static uint Width => (uint)(window?.FramebufferSize.X ?? 0);
-
-public static uint Height => (uint)(window?.FramebufferSize.Y ?? 0);
-```
-
-Framebuffer dimensions, rather than logical window dimensions, are used for textures, viewports, and projection aspect ratios. This matters on high-density displays.
-
-## Create the window and swap chain
-
-Silk.NET creates a window without an OpenGL context because Zenith.NET owns the native graphics API:
-
-```csharp
-window = Window.Create(WindowOptions.Default with
-{
- API = GraphicsAPI.None,
- Title = "Zenith.NET Tutorials"
-});
-
-window.Initialize();
-window.Center();
-```
-
-The host creates a Zenith.NET `Surface` from the window. That platform integration is contained in `App.cs` and `CocoaHelper.cs`; workload renderers depend only on the resulting swap chain. The surface and shared color format define that swap chain:
-
-```csharp
-swapChain = Context.CreateSwapChain(new()
-{
- Surface = surface,
- Format = ColorFormat
-});
-```
-
-## Follow one frame
-
-The outer frame has the same shape for every workload:
-
-```mermaid
-flowchart LR
- A[Update workload] --> B[Acquire command buffer]
- B --> C[Drawable to ColorAttachment]
- C --> D[Record workload]
- D --> E[Drawable to Present]
- E --> F[Submit and wait]
- F --> G[Present swap chain]
-```
-
-The render callback records those operations in order:
-
-```csharp
-window.Render += _ =>
-{
- if (Width is 0 || Height is 0)
- {
- return;
- }
-
- CommandBuffer commandBuffer = Context.GraphicsQueue.CommandBuffer();
-
- commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.Undefined, TextureLayout.ColorAttachment);
-
- renderer.Render(commandBuffer, swapChain.Drawable);
-
- commandBuffer.Transition(swapChain.Drawable, default, TextureLayout.ColorAttachment, TextureLayout.Present);
-
- commandBuffer.Submit().Wait();
-
- swapChain.Present();
-};
-```
-
-The workload starts with a drawable already in `ColorAttachment` layout and must leave it in that layout. The host performs the final transition to `Present`.
-
-`Submit().Wait()` deliberately permits only one frame of work at a time. That makes resource lifetime and CPU uploads easier to follow, but it is not a production frame scheduler. Applications that keep multiple frames in flight need per-frame resources and explicit reuse synchronization.
-
-## Update and resize
-
-The update callback forwards elapsed seconds:
-
-```csharp
-window.Update += delta =>
-{
- if (Width is 0 || Height is 0)
- {
- return;
- }
-
- renderer.Update(delta);
-};
-```
-
-The resize callback lets the workload release size-dependent resources before resizing the swap chain:
-
-```csharp
-window.Resize += _ =>
-{
- if (Width is 0 || Height is 0)
- {
- return;
- }
-
- renderer.Resize(Width, Height);
- swapChain.Resize(Width, Height);
-};
-```
-
-Zero-size checks cover minimized windows. A workload normally sets a depth or output texture to `null` during `Resize`; its next `Render` call creates a replacement at the new framebuffer size.
-
-## Upload shared data
-
-The guides use one helper for small static and CPU-updated buffers:
-
-```csharp
-public static Buffer LoadBuffer(T[] data, BufferUsages usages) where T : unmanaged
-{
- Buffer buffer = Context.CreateBuffer(new()
- {
- SizeInBytes = (uint)(sizeof(T) * data.Length),
- StrideInBytes = (uint)sizeof(T),
- Usages = usages,
- Residency = MemoryResidency.CpuWriteOnly
- });
-
- fixed (T* pointer = data)
- {
- buffer.Upload(0, new()
- {
- Pointer = (nint)pointer,
- SizeInBytes = (uint)(sizeof(T) * data.Length)
- });
- }
-
- return buffer;
-}
-```
-
-`SizeInBytes` controls allocation size, while `StrideInBytes` describes one array element. `BufferUsages` states how later commands or shaders consume the buffer. The helper uses `CpuWriteOnly` residency because these compact guides favor direct uploads over a staging system.
-
-## Present offscreen textures
-
-Compute Shader and Ray Tracing do not render directly into the swap-chain drawable. They write an offscreen texture, transition it to `Sampled`, and pass it to `App.PresentTexture`.
-
-`TexturePresenter` owns a sampler, a two-handle constant buffer, and a fullscreen-triangle graphics pipeline:
-
-The complete presenter is also declared `unsafe` because it uploads a pointer to its local `Constants` value.
-
-| Resource | Role |
-| --- | --- |
-| Sampler | Samples the input texture with linear filtering and clamp addressing |
-| Constant buffer | Carries sampled-texture and sampler handles to Slang |
-| Graphics pipeline | Draws a fullscreen triangle into the current drawable |
-
-The presenter calculates a destination rectangle, uploads the two handles, and records a small render pass:
-
-```csharp
-Constants constants = new()
-{
- Image = texture.SampledHandle,
- Sampler = sampler.Handle
-};
-
-constantBuffer.Upload(0, new()
-{
- Pointer = (nint)(&constants),
- SizeInBytes = (uint)sizeof(Constants)
-});
-```
-
-```csharp
-commandBuffer.BeginRenderPass([ColorAttachment.Clear(drawable, new(0.04f, 0.055f, 0.075f, 1.0f))], null);
-
-commandBuffer.SetPipeline(pipeline);
-commandBuffer.SetViewports([new() { X = x, Y = y, Width = width, Height = height, MaxDepth = 1.0f }]);
-commandBuffer.SetScissors([new() { X = x, Y = y, Width = width, Height = height }]);
-commandBuffer.SetConstantBuffer(constantBuffer, 0);
-
-commandBuffer.Draw(3, 1, 0, 0);
-
-commandBuffer.EndRenderPass();
-```
-
-Its vertex shader derives a fullscreen triangle from `SV_VertexID`, so no vertex buffer is required. Workload guides treat this presenter as host infrastructure and focus on producing the sampled texture correctly.
-
-## Resource lifetime
-
-The `using TRenderer renderer = new();` declaration scopes the active renderer to the `try` body, so it is disposed when that body exits. The `finally` block then releases the host-owned presenter, swap chain, window, and graphics context:
-
-```csharp
-finally
-{
- texturePresenter?.Dispose();
- swapChain?.Dispose();
- window?.Dispose();
-
- Context.Dispose();
-}
-```
-
-The sample disposes temporary shader objects after pipeline creation. Buffers and textures remain alive until every submitted GPU command that references them has completed. A top-level acceleration structure is a stronger ownership example: keep every BLAS it instances alive until the TLAS is no longer used.
-
-## Complete source
-
-Use the complete host implementation as the shared starting point for the workload guides:
-
-- [App.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/App.cs)
-- [IRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/IRenderer.cs)
-- [CocoaHelper.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/CocoaHelper.cs)
-- [TexturePresenter.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/TexturePresenter.cs)
-- [PresentTexture.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/PresentTexture.slang)
-- [Program.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Program.cs)
-
-The reference `Program.cs` presents a menu for all six completed renderers. Continue with [Hello Triangle](../guides/hello-triangle.md) to create the first workload renderer and understand the first menu entry.
diff --git a/documents/tutorials/getting-started/project-setup.md b/documents/tutorials/getting-started/project-setup.md
deleted file mode 100644
index 065e9e12..00000000
--- a/documents/tutorials/getting-started/project-setup.md
+++ /dev/null
@@ -1,134 +0,0 @@
-# Project Setup
-
-This page creates the .NET project used throughout the guides. It installs the current Zenith.NET packages, enables the low-level C# features used for GPU uploads, and configures shader and texture assets for the build output.
-
-The runnable reference implementation is maintained separately in [ZenithTutorials](https://github.com/qian-o/ZenithTutorials). You do not need to clone that repository to follow the guides.
-
-## Requirements
-
-Install the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) and a current graphics driver. Verify the SDK from a terminal:
-
-```console
-dotnet --list-sdks
-```
-
-Zenith.NET selects a supported graphics API for the current operating system:
-
-| Operating system | Graphics API |
-| --- | --- |
-| Windows | DirectX 12 |
-| macOS | Metal 4 |
-| Linux | Vulkan 1.4 |
-
-Ray tracing and mesh shading are optional device capabilities. The corresponding guides check support at runtime before creating those resources.
-
-## Create the project
-
-Create a .NET 10 console application and enter its directory:
-
-```console
-dotnet new console --framework net10.0 --name ZenithTutorials
-cd ZenithTutorials
-```
-
-Add the windowing package, the Zenith.NET graphics packages, and the ImageSharp extension. No version is specified, so the CLI selects the latest compatible package available from the configured NuGet sources.
-
-```console
-dotnet package add Silk.NET.Windowing
-dotnet package add Zenith.NET
-dotnet package add Zenith.NET.DirectX12
-dotnet package add Zenith.NET.Metal
-dotnet package add Zenith.NET.Vulkan
-dotnet package add Zenith.NET.Extensions.ImageSharp
-```
-
-All three graphics packages can remain in one project. The application host creates only the graphics context selected for the current operating system.
-
-## Configure the project
-
-Open `ZenithTutorials.csproj`. Keep the package references generated by the CLI and make sure the main property group contains these settings:
-
-```xml
-
- Exe
- net10.0
- enable
- enable
- true
-
-```
-
-Several guides upload unmanaged structures by pointer, so `AllowUnsafeBlocks` is required. Nullable analysis remains enabled for resources that are created lazily or recreated after a resize.
-
-`AllowUnsafeBlocks` permits unsafe code to compile; the type or method containing each pointer expression must still be declared `unsafe`. The reference host and the renderers that upload structures by pointer include that declaration in their complete source files.
-
-Shaders and textures are opened at runtime from the application directory. Add this item group after the package references:
-
-```xml
-
-
- PreserveNewest
-
-
-```
-
-`PreserveNewest` copies changed assets without rewriting every file on every build.
-
-## Create the source layout
-
-Create the following folders and files as the guides introduce them:
-
-```text
-ZenithTutorials/
-├── Assets/
-│ ├── Shaders/
-│ └── Textures/
-├── Renderers/
-├── App.cs
-├── CocoaHelper.cs
-├── IRenderer.cs
-├── Program.cs
-├── TexturePresenter.cs
-├── Usings.cs
-└── ZenithTutorials.csproj
-```
-
-The application host is shared by every guide. Each workload adds one renderer under `Renderers` and one Slang source file under `Assets/Shaders`.
-
-These guides are walkthroughs of the current reference implementation rather than file-by-file listings. Each code fence is a contiguous excerpt from that implementation. Use the **Complete source** links at the end of a page when assembling the runnable file; the surrounding text explains why the excerpt exists and how it participates in the workload.
-
-## Add shared namespaces
-
-Create `Usings.cs`:
-
-```csharp
-global using System.Numerics;
-global using System.Runtime.CompilerServices;
-global using System.Runtime.InteropServices;
-global using Zenith.NET;
-global using Zenith.NET.Extensions.ImageSharp;
-global using Buffer = Zenith.NET.Buffer;
-```
-
-The alias on the final line resolves the name shared by `System.Buffer` and `Zenith.NET.Buffer`. `System.Numerics` supplies the vectors and matrices used by both the CPU data structures and the camera calculations.
-
-## Build the empty project
-
-Build once before adding the host:
-
-```console
-dotnet build
-```
-
-A successful build confirms that the SDK and packages restore correctly. No graphics device or window is created during `dotnet build`; those operations begin when the application runs.
-
-Continue with [Application Host](application-host.md) to add the shared window, graphics context, swap chain, and frame loop.
-
-## Reference project
-
-The finished project configuration and shared usings are available in the tutorial repository:
-
-- [ZenithTutorials.csproj](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/ZenithTutorials.csproj)
-- [Usings.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Usings.cs)
-
-The reference repository uses local project references so it can track Zenith.NET development. For a standalone reader project, keep the NuGet package references created above.
diff --git a/documents/tutorials/guides/compute-shader.md b/documents/tutorials/guides/compute-shader.md
deleted file mode 100644
index 66b1f54d..00000000
--- a/documents/tutorials/guides/compute-shader.md
+++ /dev/null
@@ -1,253 +0,0 @@
-# Compute Shader
-
-Compute Shader produces an image without a render pass. A compute pipeline reads a sampled texture, converts one pixel per thread to grayscale, and writes a storage texture. The host then presents that output with its shared fullscreen-triangle pipeline.
-
-The grayscale conversion makes the result easy to inspect, but the guide focuses on Zenith.NET resource handles, compute dispatch, and texture layout transitions rather than color-science derivations.
-
-## Result
-
-
-
-The source image is processed once at its original dimensions. It is displayed at those dimensions while the framebuffer is at least as large as the image. Threads outside the image bounds are discarded when the dimensions are not exact multiples of the thread-group size.
-
-## Frame overview
-
-```mermaid
-flowchart LR
- A{Already processed?} -- No --> B[Output to Storage]
- B --> C[Bind compute pipeline and constants]
- C --> D[Dispatch thread groups]
- D --> E[Output to Sampled]
- E --> F[Present texture]
- A -- Yes --> F
-```
-
-The output is static, so the compute pass runs only during the first frame. Later frames reuse the sampled result.
-
-## Resource map
-
-| Resource | Usage | Access |
-| --- | --- | --- |
-| Input texture | `Sampled` | Compute shader reads pixels |
-| Output texture | `Storage` and `Sampled` | Compute shader writes; presenter reads |
-| Constant buffer | `Constant` | Width, height, and two resource handles |
-| Compute shader | `CSMain` | One thread per output pixel |
-| Compute pipeline | Compute dispatch | Binds the shader executable |
-
-The output texture needs both usages at creation time because it changes roles after the dispatch.
-
-## Load the input texture
-
-Place an image at `Assets/Textures/shoko.png`. The project configuration from Project Setup copies it next to the executable. The ImageSharp extension loads it into a sampled Zenith.NET texture:
-
-```csharp
-inputTexture = App.LoadTexture("shoko.png", false);
-```
-
-The final argument disables mipmap generation because this workload reads exact source pixels with `Texture2D.Load`.
-
-`App.LoadTexture` completes the upload before returning and leaves the loaded mip in `Sampled` layout. The compute pass therefore needs no input-texture transition; only the output texture changes layout during `Render`.
-
-## Create the output texture
-
-Create a floating-point texture with the same dimensions as the source:
-
-```csharp
-outputTexture = App.Context.CreateTexture(new()
-{
- Type = TextureType.Texture2D,
- Format = PixelFormat.R32G32B32A32Float,
- Width = inputTexture.Desc.Width,
- Height = inputTexture.Desc.Height,
- Depth = 1,
- MipLevels = 1,
- ArrayLayers = 1,
- SampleCount = SampleCount.Count1,
- Usages = TextureUsages.Sampled | TextureUsages.Storage
-});
-```
-
-`R32G32B32A32Float` matches the shader's writable `float4` element type. `Storage` permits writes during compute; `Sampled` permits reads by `TexturePresenter` afterward.
-
-## Data contract
-
-The constant buffer passes image dimensions and descriptor handles:
-
-```slang
-struct Constants
-{
- uint Width;
-
- uint Height;
-
- DescriptorHandle Input;
-
- DescriptorHandle> Output;
-};
-
-ConstantBuffer constants;
-```
-
-The declaration after the structure binds that contract as the constant-buffer view read by `CSMain`.
-
-The C# layout matches the two four-byte integers followed by two eight-byte handles:
-
-```csharp
-[StructLayout(LayoutKind.Explicit, Size = 256)]
-file struct Constants
-{
- [FieldOffset(0)]
- public uint Width;
-
- [FieldOffset(4)]
- public uint Height;
-
- [FieldOffset(8)]
- public ResourceHandle Input;
-
- [FieldOffset(16)]
- public ResourceHandle Output;
-}
-```
-
-| Offset | C# value | Slang view |
-| ---: | --- | --- |
-| 0 | `inputTexture.Desc.Width` | `uint Width` |
-| 4 | `inputTexture.Desc.Height` | `uint Height` |
-| 8 | `inputTexture.SampledHandle` | `DescriptorHandle` |
-| 16 | `outputTexture.StorageHandle` | `DescriptorHandle>` |
-
-The structure reserves a 256-byte constant-buffer allocation while its semantic fields occupy the leading bytes. The offsets, rather than the C# field names, define the shared binary contract.
-
-Initialize and upload one value:
-
-```csharp
-Constants constants = new()
-{
- Width = inputTexture.Desc.Width,
- Height = inputTexture.Desc.Height,
- Input = inputTexture.SampledHandle,
- Output = outputTexture.StorageHandle
-};
-
-constantBuffer = App.LoadBuffer([constants], BufferUsages.Constant);
-```
-
-## Shader interface
-
-The shader declares a $16 \times 16$ thread group:
-
-```slang
-[shader("compute")]
-[numthreads(16, 16, 1)]
-void CSMain(uint3 dispatchThreadID: SV_DispatchThreadID)
-{
- uint2 pixel = dispatchThreadID.xy;
-
- if (pixel.x >= constants.Width || pixel.y >= constants.Height)
- {
- return;
- }
-
- float4 color = constants.Input.Load(int3(pixel, 0));
- float3 linear = pow(color.rgb, 2.2);
- float grayscale = dot(linear, float3(0.2126, 0.7152, 0.0722));
- grayscale = pow(grayscale, 1.0 / 2.2);
-
- constants.Output[pixel] = float4(grayscale, grayscale, grayscale, color.a);
-}
-```
-
-`SV_DispatchThreadID` is the global thread coordinate across all dispatched groups. The bounds check is essential because group counts round up to cover partial groups at the image edges.
-
-The middle lines approximate conversion to linear light, calculate luminance, and convert back for display. That effect can be replaced without changing the pipeline or resource flow; the important interface is one sampled read and one storage write at the same pixel coordinate.
-
-## Create the compute pipeline
-
-Compile the compute entry point and create its pipeline:
-
-```csharp
-using Shader computeShader = App.LoadShader("ComputeShader.slang", "CSMain");
-
-computePipeline = App.Context.CreateComputePipeline(new() { ComputeShader = computeShader });
-```
-
-A compute pipeline has no attachment formats or rasterizer state. It executes outside a render pass.
-
-## Dispatch the workload
-
-Before the first dispatch, transition the output from its undefined initial contents into writable storage layout:
-
-```csharp
-commandBuffer.Transition(outputTexture, default, TextureLayout.Undefined, TextureLayout.Storage);
-
-commandBuffer.SetPipeline(computePipeline);
-commandBuffer.SetConstantBuffer(constantBuffer, 0);
-commandBuffer.Dispatch((inputTexture.Desc.Width + ThreadGroupSize - 1) / ThreadGroupSize, (inputTexture.Desc.Height + ThreadGroupSize - 1) / ThreadGroupSize, 1);
-```
-
-For a dimension $D$ and group size $G$, integer ceiling division is:
-
-$$
-\left\lceil \frac{D}{G} \right\rceil = \frac{D + G - 1}{G}
-$$
-
-After all storage writes, transition the texture for sampled reads:
-
-```csharp
-commandBuffer.Transition(outputTexture, default, TextureLayout.Storage, TextureLayout.Sampled);
-
-processed = true;
-```
-
-The command buffer preserves this order: transition, dispatch, then transition. The presenter consumes the texture only after it reaches `Sampled` layout.
-
-## Present the output
-
-Every frame, including the first, hands the sampled result to the shared presenter:
-
-```csharp
-App.PresentTexture(commandBuffer, drawable, outputTexture, false);
-```
-
-Passing `false` preserves the texture's original size and centers it when the framebuffer is at least as large in both dimensions. In a smaller framebuffer, the presenter clamps each viewport dimension to the available size. The presenter records the graphics render pass that writes the swap-chain drawable.
-
-## Lifetime
-
-This image does not depend on the framebuffer size, so `Resize` is empty. Dispose each renderer-owned resource after the final submitted frame has completed:
-
-```csharp
-public void Dispose()
-{
- computePipeline.Dispose();
- constantBuffer.Dispose();
- outputTexture.Dispose();
- inputTexture.Dispose();
-}
-```
-
-## Inspect the result
-
-Run the host and select **Compute Shader**. Confirm that:
-
-- the complete source image appears in grayscale;
-- no unprocessed strip appears along the right or bottom edge;
-- resizing to a framebuffer at least as large as the image preserves its original display size;
-- validation reports no storage/sampled usage or layout errors.
-
-An unprocessed edge usually indicates floor division or a missing bounds check. A blank result often indicates an incorrect descriptor handle or a missing `Storage` to `Sampled` transition.
-
-## Explore further
-
-1. Replace the grayscale calculation with `constants.Output[pixel] = color` to verify a direct copy.
-2. Invert `color.rgb` while preserving alpha.
-3. Change the declared thread-group size and update the C# `ThreadGroupSize` constant to match.
-4. Set `fitToWindow` to `true` when presenting and compare the viewport behavior.
-
-## Complete source
-
-- [ComputeShaderRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/ComputeShaderRenderer.cs)
-- [ComputeShader.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/ComputeShader.slang)
-- [Input texture](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Textures/shoko.png)
-
-Continue with [Ray Tracing](ray-tracing.md) to reuse the compute-output flow for an image regenerated from inline ray queries every frame.
diff --git a/documents/tutorials/guides/hello-triangle.md b/documents/tutorials/guides/hello-triangle.md
deleted file mode 100644
index 002017d3..00000000
--- a/documents/tutorials/guides/hello-triangle.md
+++ /dev/null
@@ -1,216 +0,0 @@
-# Hello Triangle
-
-Hello Triangle is the smallest complete graphics workload in the series. It uploads three colored vertices, compiles one vertex and one fragment entry point, creates a graphics pipeline, and records a single draw into the swap-chain texture.
-
-This guide focuses on how vertex data moves from C# through a Zenith.NET pipeline into Slang. The shared window, command submission, and presentation loop come from [Application Host](../getting-started/application-host.md).
-
-## Result
-
-
-
-The final image is one triangle on a dark clear color. Red, green, and blue values are supplied per vertex and interpolated by the rasterizer across the covered fragments.
-
-## Frame overview
-
-The renderer creates its long-lived resources once. Each frame then records one render pass:
-
-```mermaid
-flowchart LR
- A[Drawable in ColorAttachment] --> B[Begin render pass and clear]
- B --> C[Bind graphics pipeline]
- C --> D[Bind vertex buffer]
- D --> E[Draw 3 vertices]
- E --> F[End render pass]
-```
-
-The host transitions the drawable into `ColorAttachment` before calling the renderer and transitions it to `Present` afterward.
-
-## Resource map
-
-| Resource | Created from | Consumed by |
-| --- | --- | --- |
-| Vertex buffer | Three `Vertex` values | Vertex input stage |
-| Vertex shader | `VSMain` in `HelloTriangle.slang` | Graphics pipeline |
-| Fragment shader | `FSMain` in `HelloTriangle.slang` | Graphics pipeline |
-| Graphics pipeline | Shaders, input layout, attachment format, render state | Draw command |
-| Swap-chain drawable | Application host | Color attachment |
-
-There is no index buffer, constant buffer, depth attachment, or descriptor handle in this first workload.
-
-## Data contract
-
-The CPU vertex stores a three-component position followed by a four-component color:
-
-```csharp
-[StructLayout(LayoutKind.Sequential)]
-file struct Vertex(Vector3 position, Vector4 color)
-{
- public Vector3 Position = position;
-
- public Vector4 Color = color;
-}
-```
-
-The corresponding Slang input uses matching attribute types and semantic names:
-
-```slang
-struct VSInput
-{
- float3 Position : POSITION0;
-
- float4 Color : COLOR0;
-};
-```
-
-An `InputLayout` connects the buffer fields to those semantics:
-
-```csharp
-InputLayout inputLayout = new();
-inputLayout.Add(new() { Format = ElementFormat.Float3, Semantic = ElementSemantic.Position });
-inputLayout.Add(new() { Format = ElementFormat.Float4, Semantic = ElementSemantic.Color });
-```
-
-The layout order matches the C# field order. `App.LoadBuffer` supplies `sizeof(Vertex)` as the buffer stride, so the GPU advances by one complete position-and-color record for each vertex.
-
-## Create the vertex buffer
-
-The renderer defines three positions directly in clip space. Their colors become the three corners visible in the result:
-
-```csharp
-Vertex[] vertices =
-[
- new(new(0.0f, 0.6f, 0.0f), new(1.0f, 0.2f, 0.15f, 1.0f)),
- new(new(0.6f, -0.5f, 0.0f), new(0.15f, 0.85f, 0.35f, 1.0f)),
- new(new(-0.6f, -0.5f, 0.0f), new(0.2f, 0.45f, 1.0f, 1.0f))
-];
-
-vertexBuffer = App.LoadBuffer(vertices, BufferUsages.Vertex);
-```
-
-`BufferUsages.Vertex` declares that later commands will bind this allocation as vertex input. Because the coordinates already lie in clip space, the workload does not need model, view, or projection matrices.
-
-## Shader interface
-
-The vertex shader converts each `float3` position into homogeneous clip-space coordinates and forwards the color:
-
-```slang
-struct FSInput
-{
- float4 Position : SV_POSITION;
-
- float4 Color : COLOR0;
-};
-
-[shader("vertex")]
-FSInput VSMain(VSInput input)
-{
- FSInput output;
- output.Position = float4(input.Position, 1.0);
- output.Color = input.Color;
-
- return output;
-}
-```
-
-`SV_POSITION` is consumed by rasterization. `COLOR0` is a user varying: the rasterizer interpolates it, then supplies the interpolated value to the fragment shader.
-
-```slang
-[shader("fragment")]
-float4 FSMain(FSInput input) : SV_TARGET
-{
- return input.Color;
-}
-```
-
-`SV_TARGET` marks the returned value as the color written to the active color attachment.
-
-## Create the pipeline
-
-Compile both named entry points and create the graphics pipeline:
-
-```csharp
-using Shader vertexShader = App.LoadShader("HelloTriangle.slang", "VSMain");
-using Shader fragmentShader = App.LoadShader("HelloTriangle.slang", "FSMain");
-
-pipeline = App.Context.CreateGraphicsPipeline(new()
-{
- VertexShader = vertexShader,
- FragmentShader = fragmentShader,
- InputLayouts = [inputLayout],
- PrimitiveTopology = PrimitiveTopology.TriangleList,
- AttachmentFormats = new()
- {
- ColorFormats = [App.ColorFormat],
- SampleCount = SampleCount.Count1
- },
- RenderState = new()
- {
- Rasterizer = RasterizerState.CullNone(),
- DepthStencil = DepthStencilState.DepthNone(),
- Blend = BlendState.Opaque()
- }
-});
-```
-
-The attachment format must match the swap chain. A triangle list consumes every group of three vertices as one triangle. Culling and depth testing are unnecessary because this workload has one two-dimensional primitive and no depth attachment.
-
-The `using` declarations limit the temporary `Shader` objects to pipeline creation.
-
-## Record the frame
-
-The render method clears the drawable, binds the two resources required by the draw, and emits one triangle:
-
-```csharp
-public void Render(CommandBuffer commandBuffer, Texture drawable)
-{
- commandBuffer.BeginRenderPass([ColorAttachment.Clear(drawable, new(0.04f, 0.055f, 0.075f, 1.0f))], null);
-
- commandBuffer.SetPipeline(pipeline);
- commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0);
-
- commandBuffer.Draw(3, 1, 0, 0);
-
- commandBuffer.EndRenderPass();
-}
-```
-
-The arguments to `Draw` request three vertices, one instance, first vertex zero, and first instance zero. No explicit viewport or scissor is needed here; the render pass uses the drawable dimensions.
-
-## Resource lifetime
-
-This renderer has no animated or size-dependent resources, so `Update` and `Resize` remain empty. Dispose both renderer-owned resources after the final submitted frame has completed:
-
-```csharp
-public void Dispose()
-{
- pipeline.Dispose();
- vertexBuffer.Dispose();
-}
-```
-
-## Inspect the result
-
-Run the tutorial host and select **Hello Triangle**. Confirm that:
-
-- the triangle appears centered on the dark background;
-- all three corner colors are visible and interpolate smoothly;
-- resizing the window preserves the workload without recreating resources;
-- the validation layer reports no errors.
-
-If the pipeline is rejected, first compare `App.ColorFormat` with `AttachmentFormats.ColorFormats`. If geometry is missing or corrupted, compare the C# field order, `InputLayout`, and Slang semantics.
-
-## Explore further
-
-Try these changes independently:
-
-1. Change the x and y components while keeping them in $[-1,1]$; leave z at 0 so the vertices remain in the visible depth range.
-2. Replace all three colors with one constant color and observe that interpolation no longer produces a gradient.
-3. Reverse two vertices and enable back-face culling to inspect how winding affects visibility.
-4. Add a fourth vertex and change the topology to explore how a triangle list groups input vertices.
-
-## Complete source
-
-- [HelloTriangleRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/HelloTriangleRenderer.cs)
-- [HelloTriangle.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/HelloTriangle.slang)
-
-Continue with [Spinning Cube](spinning-cube.md) to add indexed geometry, transformation constants, back-face culling, and a depth attachment.
diff --git a/documents/tutorials/guides/indirect-drawing.md b/documents/tutorials/guides/indirect-drawing.md
deleted file mode 100644
index af6ab622..00000000
--- a/documents/tutorials/guides/indirect-drawing.md
+++ /dev/null
@@ -1,289 +0,0 @@
-# Indirect Drawing
-
-Indirect Drawing reuses the depth-tested cube pipeline to render a $5 \times 5$ grid of animated instances. One structured buffer stores the model matrix and tint for each cube, while one indirect argument record supplies the indexed draw counts to the GPU.
-
-Two mechanisms are involved:
-
-- **instancing** draws the shared geometry multiple times and assigns each invocation an `SV_InstanceID`;
-- **indirect drawing** reads draw parameters from a buffer instead of C# method arguments.
-
-The CPU still updates instance data in this workload. The indirect buffer demonstrates the command format; it is not generated by a GPU culling pass.
-
-## Result
-
-
-
-Twenty-five cubes share one vertex buffer, one index buffer, one graphics pipeline, and one indirect draw record. Their positions, rotations, and colors vary through structured instance data.
-
-## Frame overview
-
-```mermaid
-flowchart LR
- A[Update 25 Instance records] --> B[Upload structured buffer]
- B --> C[Begin color and depth pass]
- C --> D[Bind geometry, constants, and pipeline]
- D --> E[DrawIndexedIndirect one record]
- E --> F[End render pass]
-```
-
-The indirect record is static in this example; only the instance buffer changes each frame.
-
-## Resource map
-
-| Resource | Usage | Contents |
-| --- | --- | --- |
-| Vertex buffer | `Vertex` | Shared cube corners |
-| Index buffer | `Index` | 36 cube indices |
-| Instance buffer | `StorageReadOnly` | 25 model matrices and colors |
-| Indirect buffer | `Indirect` | One `IndirectDrawIndexedArgs` record |
-| Constant buffer | `Constant` | View, projection, and instance-buffer handle |
-| Graphics pipeline | Indexed instanced rendering | Depth and back-face culling enabled |
-| Depth texture | Depth-stencil attachment | Recreated after resize |
-
-The shader reaches the structured buffer through a descriptor handle stored in the constant buffer. It does not bind the instance buffer as vertex input.
-
-## Define the indirect command
-
-The indirect structure contains the same values supplied to a direct indexed draw:
-
-```csharp
-IndirectDrawIndexedArgs arguments = new()
-{
- IndexCount = (uint)indices.Length,
- InstanceCount = GridSize * GridSize
-};
-```
-
-Unassigned fields remain zero, so this record means:
-
-| Field | Value | Meaning |
-| --- | ---: | --- |
-| `IndexCount` | 36 | Indices consumed per instance |
-| `InstanceCount` | 25 | Number of cube instances |
-| `FirstIndex` | 0 | Start of the index buffer |
-| `VertexOffset` | 0 | Added to each selected index |
-| `FirstInstance` | 0 | Starting system instance ID |
-
-Upload one record with `BufferUsages.Indirect`:
-
-```csharp
-indirectBuffer = App.LoadBuffer([arguments], BufferUsages.Indirect);
-```
-
-The final draw call requests one record from this buffer. It does not pass the instance count separately.
-
-## Instance data contract
-
-Each instance occupies 80 bytes in both C# and Slang:
-
-```csharp
-[StructLayout(LayoutKind.Explicit, Size = 80)]
-file struct Instance
-{
- [FieldOffset(0)]
- public Matrix4x4 Model;
-
- [FieldOffset(64)]
- public Vector4 Color;
-}
-```
-
-```slang
-struct Instance
-{
- float4x4 Model;
-
- float4 Color;
-};
-```
-
-The renderer allocates enough structured-buffer elements for the complete grid:
-
-```csharp
-instanceBuffer = App.LoadBuffer(new Instance[GridSize * GridSize], BufferUsages.StorageReadOnly);
-```
-
-`App.LoadBuffer` records `sizeof(Instance)` as the stride. The shader therefore indexes a sequence of 80-byte records through `StructuredBuffer`.
-
-## Constant-buffer contract
-
-The vertex shader also needs the camera matrices and a handle to the instance buffer:
-
-```slang
-struct Constants
-{
- float4x4 View;
-
- float4x4 Projection;
-
- DescriptorHandle> Instances;
-};
-```
-
-The corresponding C# offsets place the handle after two 64-byte matrices:
-
-```csharp
-[StructLayout(LayoutKind.Explicit, Size = 256)]
-file struct Constants
-{
- [FieldOffset(0)]
- public Matrix4x4 View;
-
- [FieldOffset(64)]
- public Matrix4x4 Projection;
-
- [FieldOffset(128)]
- public ResourceHandle Instances;
-}
-```
-
-On initialization and resize, upload `instanceBuffer.StorageReadOnlyHandle` with the current view and projection:
-
-```csharp
-Constants constants = new()
-{
- View = Matrix4x4.CreateLookAt(new(0.0f, 0.0f, 8.0f), Vector3.Zero, Vector3.UnitY),
- Projection = Matrix4x4.CreatePerspectiveFieldOfView(float.DegreesToRadians(45.0f), (float)width / height, 0.1f, 100.0f),
- Instances = instanceBuffer.StorageReadOnlyHandle
-};
-```
-
-## Select an instance in Slang
-
-`SV_InstanceID` is generated by the draw command. It is a system value, not an attribute in the C# `InputLayout`:
-
-```slang
-struct VSInput
-{
- float3 Position : POSITION0;
-
- float4 Color : COLOR0;
-
- uint InstanceID : SV_InstanceID;
-};
-```
-
-The vertex shader uses that ID to select one structured-buffer element:
-
-```slang
-[shader("vertex")]
-VSOutput VSMain(VSInput input)
-{
- Instance instance = constants.Instances[input.InstanceID];
- float4 worldPosition = mul(float4(input.Position, 1.0), instance.Model);
- float4 viewPosition = mul(worldPosition, constants.View);
-
- VSOutput output;
- output.Position = mul(viewPosition, constants.Projection);
- output.Color = input.Color * instance.Color;
-
- return output;
-}
-```
-
-Every invocation reads the same cube geometry. The instance record supplies only its world transform and tint.
-
-## Update instance records
-
-The CPU calculates one record for every grid coordinate:
-
-```csharp
-for (uint index = 0; index < GridSize * GridSize; index++)
-{
- uint x = index % GridSize;
- uint y = index / GridSize;
- float offsetX = (x - ((GridSize - 1) * 0.5f)) * 1.5f;
- float offsetY = (y - ((GridSize - 1) * 0.5f)) * 1.5f;
- float rotation = rotationAngle * (1.0f + (index * 0.1f));
-
- pointer[index] = new()
- {
- Model = Matrix4x4.CreateScale(0.4f) * Matrix4x4.CreateRotationY(rotation) * Matrix4x4.CreateRotationX(rotation * 0.5f) * Matrix4x4.CreateTranslation(offsetX, offsetY, 0.0f),
- Color = new((float)x / GridSize, (float)y / GridSize, 1.0f - ((float)x / GridSize), 1.0f)
- };
-}
-```
-
-After filling the array, upload all records in one contiguous copy:
-
-```csharp
-instanceBuffer.Upload(0, new()
-{
- Pointer = (nint)pointer,
- SizeInBytes = (uint)(sizeof(Instance) * instances.Length)
-});
-```
-
-This direct CPU update is intentionally simple. A larger renderer would normally avoid allocating a new managed array every frame and would coordinate dynamic-buffer reuse across frames in flight.
-
-The shared host waits for each submitted frame before the next update, so this single instance buffer is not overwritten while an earlier frame is reading it.
-
-## Record the indirect draw
-
-Create the depth attachment on first use and transition it from `Undefined` to `DepthStencilAttachment`. The texture remains in that layout until resize replaces it. Then begin a color/depth render pass, bind the shared geometry and constants, and execute one indirect record:
-
-```csharp
-if (depthTexture is null)
-{
- depthTexture = App.Context.CreateTexture(TextureDesc.DepthStencilAttachment(PixelFormat.D32FloatS8UInt, App.Width, App.Height, SampleCount.Count1));
-
- commandBuffer.Transition(depthTexture, default, TextureLayout.Undefined, TextureLayout.DepthStencilAttachment);
-}
-
-commandBuffer.BeginRenderPass([ColorAttachment.Clear(drawable, new(0.04f, 0.055f, 0.075f, 1.0f))], DepthStencilAttachment.Clear(depthTexture, 1.0f, 0));
-
-commandBuffer.SetPipeline(pipeline);
-commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0);
-commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32);
-commandBuffer.SetConstantBuffer(constantBuffer, 0);
-
-commandBuffer.DrawIndexedIndirect(indirectBuffer, 0, 1);
-
-commandBuffer.EndRenderPass();
-```
-
-The arguments mean indirect-buffer byte offset zero and draw count one. That one record expands to an indexed draw with 25 instances.
-
-The GPU does not read the indirect buffer through a shader descriptor. The command processor interprets it because the allocation was created with `BufferUsages.Indirect` and passed to `DrawIndexedIndirect`.
-
-## Resize and lifetime
-
-Resize invalidates the depth texture and uploads a projection for the new aspect ratio. Geometry, indirect arguments, and instance-buffer capacity remain unchanged.
-
-Release resources in dependency order:
-
-```csharp
-depthTexture?.Dispose();
-
-pipeline.Dispose();
-constantBuffer.Dispose();
-instanceBuffer.Dispose();
-indirectBuffer.Dispose();
-indexBuffer.Dispose();
-vertexBuffer.Dispose();
-```
-
-## Inspect the result
-
-Run the host and select **Indirect Drawing**. Confirm that:
-
-- 25 cubes appear in a centered grid;
-- each cube rotates and receives a distinct tint;
-- resize preserves the grid proportions and depth behavior;
-- validation reports no buffer-usage, structured-stride, or indirect-command errors.
-
-If every cube uses the same transform, inspect `SV_InstanceID` and the structured-buffer handle. If only one cube appears, inspect `InstanceCount` in the indirect record. If the draw is rejected, confirm `BufferUsages.Indirect` and the record layout.
-
-## Explore further
-
-1. Replace `DrawIndexedIndirect` temporarily with `DrawIndexed(36, GridSize * GridSize, 0, 0, 0)` and verify that the picture remains equivalent.
-2. Change `GridSize`, then update buffer capacity and camera distance together.
-3. Set `FirstInstance` to a nonzero value and account for it in the instance buffer.
-4. Create multiple indirect records and increase the final `drawCount` argument.
-
-## Complete source
-
-- [IndirectDrawingRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/IndirectDrawingRenderer.cs)
-- [IndirectDrawing.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/IndirectDrawing.slang)
-
-Continue with [Ray Tracing](ray-tracing.md) to see a different kind of GPU-readable scene structure, or [Mesh Shading](mesh-shading.md) to move geometry emission into programmable shader stages.
diff --git a/documents/tutorials/guides/mesh-shading.md b/documents/tutorials/guides/mesh-shading.md
deleted file mode 100644
index a8ae80a2..00000000
--- a/documents/tutorials/guides/mesh-shading.md
+++ /dev/null
@@ -1,454 +0,0 @@
-# Mesh Shading
-
-Mesh Shading replaces fixed vertex and index fetch with programmable task and mesh stages. This workload renders a $10 \times 10 \times 10$ sphere grid. Task threads test candidate instances against the camera frustum, compact visible IDs into a payload, and launch one mesh workgroup for each surviving sphere.
-
-This guide focuses on the Zenith.NET mesh-shading pipeline, structured geometry, C#/Slang layout, task payload, synchronization, and mesh dispatch. The UV-sphere construction and final diffuse-lighting calculation support the sample result but are not derived as general geometry or lighting algorithms.
-
-## Result
-
-
-
-The camera moves around 1,000 possible sphere instances. Only instances intersecting the view frustum produce mesh workgroups and rasterized geometry.
-
-> [!IMPORTANT]
-> Mesh shading is optional. Check `App.Context.Capabilities.MeshShadingSupported` before creating shaders, buffers, or a mesh-shading pipeline.
-
-## Frame overview
-
-```mermaid
-flowchart LR
- A[Update camera and frustum constants] --> B[Begin color and depth pass]
- B --> C[Bind mesh-shading pipeline]
- C --> D[Bind constants]
- D --> E[Dispatch 32 task groups]
- E --> F[Task groups compact visible IDs]
- F --> G[Mesh groups emit sphere geometry]
- G --> H[Rasterize and shade fragments]
-```
-
-No vertex or index buffer is bound through traditional command-buffer slots. The mesh shader reads source geometry through descriptor handles and writes pipeline outputs directly.
-
-## Pipeline stages
-
-```mermaid
-flowchart TD
- A[32 candidate IDs per task group] --> B[Frustum test]
- B --> C[groupshared payload]
- C --> D[DispatchMesh visible count]
- D --> E[One mesh group per visible ID]
- E --> F[Emit 62 vertices]
- E --> G[Emit 120 triangles]
- F --> H[Rasterization]
- G --> H
- H --> I[Fragment shader]
-```
-
-Zenith.NET calls the first stage `TaskShader`; Slang declares the corresponding entry point with `[shader("task")]`. Other APIs may call this an amplification or object stage.
-
-## Resource map
-
-| Resource | Usage | Role |
-| --- | --- | --- |
-| Vertex buffer | `StorageReadOnly` | Source sphere positions and normals |
-| Triangle buffer | `StorageReadOnly` | Source sphere index triplets |
-| Constant buffer | `Constant` | View-projection, frustum, light, and two handles |
-| Task shader | `ASMain` | Culls and compacts candidate instance IDs |
-| Mesh shader | `MSMain` | Emits transformed vertices and primitive indices |
-| Fragment shader | `FSMain` | Applies simple directional lighting |
-| Mesh-shading pipeline | Task, mesh, and fragment stages | Rasterized output |
-| Depth texture | Depth-stencil attachment | Recreated after resize |
-
-The source geometry remains in structured buffers for the renderer lifetime. Each visible instance reuses the same 62 vertices and 120 triangles.
-
-## Check device support
-
-Reject unsupported devices before generating or uploading the source mesh:
-
-```csharp
-if (!App.Context.Capabilities.MeshShadingSupported)
-{
- throw new PlatformNotSupportedException("Mesh Shading is not supported by the selected device.");
-}
-```
-
-The capability controls creation and execution of the task/mesh pipeline. It is independent of the traditional graphics pipeline used by earlier guides.
-
-## Create the source mesh
-
-The CPU generates one compact UV sphere with 62 vertices and 120 triangles. It creates a north pole, five latitude rings, a south pole, and indices for both caps and the ring quads.
-
-The complete generation loop is available in the renderer source. Its important output for this workload is two tightly defined arrays:
-
-```csharp
-vertexBuffer = App.LoadBuffer([.. sphereVertices], BufferUsages.StorageReadOnly);
-triangleBuffer = App.LoadBuffer([.. sphereTriangles], BufferUsages.StorageReadOnly);
-constantBuffer = App.LoadBuffer([new Constants()], BufferUsages.Constant);
-```
-
-These are structured shader inputs, not traditional vertex and index bindings. Their element strides come from the explicit C# structures described next.
-
-## Geometry data contract
-
-Three-component vectors can receive different alignment across shader targets. The C# vertex therefore places position and normal at separate 16-byte boundaries:
-
-```csharp
-[StructLayout(LayoutKind.Explicit, Size = 32)]
-file struct Vertex
-{
- [FieldOffset(0)]
- public Vector3 Position;
-
- [FieldOffset(16)]
- public Vector3 Normal;
-}
-```
-
-The Slang side uses two `float4` backing values:
-
-```slang
-struct Vertex
-{
- private float4 PositionAndPadding;
-
- private float4 NormalAndPadding;
-
- property float3 Position
- {
- get {
- return PositionAndPadding.xyz;
- }
- }
-
- property float3 Normal
- {
- get {
- return NormalAndPadding.xyz;
- }
- }
-};
-```
-
-Triangle elements follow the same pattern. C# stores three indices in a 16-byte record:
-
-```csharp
-[StructLayout(LayoutKind.Explicit, Size = 16)]
-file struct Triangle
-{
- [FieldOffset(0)]
- public uint Index0;
-
- [FieldOffset(4)]
- public uint Index1;
-
- [FieldOffset(8)]
- public uint Index2;
-}
-```
-
-Slang reads one `uint4` and exposes its first three lanes:
-
-```slang
-struct Triangle
-{
- private uint4 IndicesAndPadding;
-
- property uint3 Indices
- {
- get {
- return IndicesAndPadding.xyz;
- }
- }
-};
-```
-
-| Structured element | C# size | Slang backing storage |
-| --- | ---: | --- |
-| `Vertex` | 32 bytes | Two `float4` values |
-| `Triangle` | 16 bytes | One `uint4` value |
-
-Explicit backing storage keeps these strides consistent across the supported graphics APIs.
-
-## Frame constants
-
-The constant buffer supplies one view-projection matrix, six frustum planes, a light direction, and two structured-buffer handles:
-
-```csharp
-[StructLayout(LayoutKind.Explicit, Size = 256)]
-file struct Constants
-{
- [FieldOffset(0)]
- public Matrix4x4 ViewProjection;
-
- [FieldOffset(64)]
- public FrustumPlanes FrustumPlanes;
-
- [FieldOffset(160)]
- public Vector3 LightDirection;
-
- [FieldOffset(176)]
- public ResourceHandle Vertices;
-
- [FieldOffset(184)]
- public ResourceHandle Triangles;
-}
-```
-
-`FrustumPlanes` is an inline array of six `Vector4` values, occupying 96 bytes from offset 64 through 159. Slang backs `LightDirection` with a `float4`, so the handles begin at byte 176 rather than immediately after the 12-byte C# `Vector3`.
-
-```slang
-struct Constants
-{
- float4x4 ViewProjection;
-
- float4 FrustumPlanes[6];
-
- private float4 LightDirectionAndPadding;
-
- DescriptorHandle> Vertices;
-
- DescriptorHandle> Triangles;
-
- property float3 LightDirection
- {
- get {
- return LightDirectionAndPadding.xyz;
- }
- }
-};
-```
-
-The renderer extracts and normalizes the six planes from the current view-projection matrix. That matrix extraction is supporting camera math; the mesh-shading interface only requires six consistently oriented `float4` plane equations.
-
-## Create the pipeline
-
-Compile all three stage entry points:
-
-```csharp
-using Shader taskShader = App.LoadShader("MeshShading.slang", "ASMain");
-using Shader meshShader = App.LoadShader("MeshShading.slang", "MSMain");
-using Shader fragmentShader = App.LoadShader("MeshShading.slang", "FSMain");
-```
-
-Create a mesh-shading pipeline with the same color, depth, culling, and blend configuration used by the spinning cube:
-
-```csharp
-pipeline = App.Context.CreateMeshShadingPipeline(new()
-{
- TaskShader = taskShader,
- MeshShader = meshShader,
- FragmentShader = fragmentShader,
- PrimitiveTopology = PrimitiveTopology.TriangleList,
- AttachmentFormats = new()
- {
- ColorFormats = [App.ColorFormat],
- DepthStencilFormat = PixelFormat.D32FloatS8UInt,
- SampleCount = SampleCount.Count1
- },
- RenderState = new()
- {
- Rasterizer = RasterizerState.CullBack(),
- DepthStencil = DepthStencilState.DepthReadWrite(),
- Blend = BlendState.Opaque()
- }
-});
-```
-
-There is no `InputLayouts` field because fixed vertex fetch is not part of this pipeline. `TaskShader` is optional at the API level, but this workload uses it to generate mesh work dynamically.
-
-## Map instance IDs
-
-A linear ID maps into the $10 \times 10 \times 10$ grid:
-
-```slang
-void DecomposeInstanceID(uint id, out uint x, out uint y, out uint z)
-{
- x = id % GridSize;
- y = (id / GridSize) % GridSize;
- z = id / (GridSize * GridSize);
-}
-```
-
-Helper functions convert those coordinates into world positions and colors. This arithmetic controls sample placement; it does not alter the task or mesh pipeline contract.
-
-## Cull and compact in the task shader
-
-The task stage declares 32 threads and two groupshared values:
-
-```slang
-groupshared Payload s_payload;
-groupshared uint s_visibleCount;
-
-[shader("task")]
-[numthreads(ASGroupSize, 1, 1)]
-void ASMain(uint groupID: SV_GroupID, uint groupThreadID: SV_GroupThreadID)
-```
-
-Each thread derives one candidate instance and tests its bounding sphere against all six frustum planes:
-
-```slang
-uint instanceIndex = groupID * ASGroupSize + groupThreadID;
-
-bool visible = false;
-if (instanceIndex < GridSize * GridSize * GridSize)
-{
- float3 worldPos = InstancePosition(instanceIndex);
- visible = !IsFrustumCulled(worldPos, BoundingSphereRadius);
-}
-```
-
-Thread zero resets the shared counter, then all threads synchronize:
-
-```slang
-if (groupThreadID == 0)
-{
- s_visibleCount = 0;
-}
-
-GroupMemoryBarrierWithGroupSync();
-```
-
-Visible threads reserve unique payload slots with an atomic increment:
-
-```slang
-if (visible)
-{
- uint offset;
- InterlockedAdd(s_visibleCount, 1, offset);
- s_payload.InstanceIndices[offset] = instanceIndex;
-}
-
-GroupMemoryBarrierWithGroupSync();
-
-DispatchMesh(s_visibleCount, 1, 1, s_payload);
-```
-
-The second barrier ensures all payload writes are visible before the task group launches mesh work. A task group dispatches between zero and 32 mesh groups, one for each compacted instance ID.
-
-## Emit geometry in the mesh shader
-
-The mesh shader receives the payload, selects one instance, and declares its output capacity:
-
-```slang
-[shader("mesh")]
-[numthreads(120, 1, 1)]
-[outputtopology("triangle")]
-void MSMain(uint groupID: SV_GroupID, uint groupThreadID: SV_GroupThreadID, in payload Payload meshPayload, OutputVertices outVertices, OutputIndices outIndices)
-{
- uint instanceIndex = meshPayload.InstanceIndices[groupID];
- float3 instancePos = InstancePosition(instanceIndex);
- float3 color = InstanceColor(instanceIndex);
-
- SetMeshOutputCounts(SphereVertexCount, SphereTriangleCount);
-```
-
-The group has 120 threads because the source sphere contains 120 triangles. Threads zero through 61 also transform and emit the 62 vertices:
-
-```slang
-if (groupThreadID < SphereVertexCount)
-{
- Vertex v = constants.Vertices[groupThreadID];
- float3 worldPos = v.Position + instancePos;
-
- VertexOutput output;
- output.Position = mul(float4(worldPos, 1.0), constants.ViewProjection);
- output.WorldNormal = v.Normal;
- output.Color = color;
-
- outVertices[groupThreadID] = output;
-}
-
-if (groupThreadID < SphereTriangleCount)
-{
- outIndices[groupThreadID] = constants.Triangles[groupThreadID].Indices;
-}
-```
-
-`SetMeshOutputCounts` establishes the valid output ranges before writes occur. The fragment shader receives the emitted normal and color, then applies a compact ambient-plus-diffuse lighting calculation.
-
-## Update frame data
-
-Each update moves the camera, creates the view-projection matrix, extracts frustum planes, and supplies the two storage handles:
-
-```csharp
-Constants constants = new()
-{
- ViewProjection = viewProjection,
- FrustumPlanes = new(viewProjection),
- LightDirection = -Vector3.Normalize(cameraPosition),
- Vertices = vertexBuffer.StorageReadOnlyHandle,
- Triangles = triangleBuffer.StorageReadOnlyHandle
-};
-```
-
-The same matrix controls both emitted vertex positions and the planes used for culling, so visibility and rasterization share one camera definition.
-
-## Record the mesh dispatch
-
-Create the depth attachment on first use and transition it from `Undefined` to `DepthStencilAttachment`. Begin a color/depth render pass, bind the pipeline and constants, dispatch the task groups, and end the pass:
-
-```csharp
-if (depthTexture is null)
-{
- depthTexture = App.Context.CreateTexture(TextureDesc.DepthStencilAttachment(PixelFormat.D32FloatS8UInt, App.Width, App.Height, SampleCount.Count1));
-
- commandBuffer.Transition(depthTexture, default, TextureLayout.Undefined, TextureLayout.DepthStencilAttachment);
-}
-
-commandBuffer.BeginRenderPass([ColorAttachment.Clear(drawable, new(0.05f, 0.05f, 0.08f, 1.0f))], DepthStencilAttachment.Clear(depthTexture, 1.0f, 0));
-
-commandBuffer.SetPipeline(pipeline);
-commandBuffer.SetConstantBuffer(constantBuffer, 0);
-
-commandBuffer.DispatchMesh(32, 1, 1);
-
-commandBuffer.EndRenderPass();
-```
-
-There are 1,000 candidate instances and 32 task threads per group:
-
-$$
-\left\lceil \frac{1000}{32} \right\rceil = 32
-$$
-
-The bounds check discards candidate IDs at or above 1,000 in the final task group. Frustum testing then determines how many mesh groups each task group launches.
-
-## Resize and lifetime
-
-Only the depth attachment depends on framebuffer dimensions. Dispose it during `Resize` and let the next frame create a replacement. The source mesh, pipeline, and constant-buffer allocation remain valid.
-
-Release the optional depth texture, then the pipeline and its buffers:
-
-```csharp
-depthTexture?.Dispose();
-
-pipeline.Dispose();
-constantBuffer.Dispose();
-triangleBuffer.Dispose();
-vertexBuffer.Dispose();
-```
-
-## Inspect the result
-
-Run the host and select **Mesh Shading** on a supported device. Confirm that:
-
-- the camera moves around a colored sphere grid;
-- visible spheres remain complete as they approach the viewport edges;
-- resize preserves depth behavior and camera proportions;
-- validation reports no structured-stride, payload, output-count, or attachment errors.
-
-Missing spheres inside the view often indicate incorrect frustum-plane orientation or a missing groupshared barrier. Corrupted geometry points to a C#/Slang stride mismatch or output counts that disagree with the source mesh. A rejected pipeline usually indicates missing mesh-shading capability or mismatched shader stages.
-
-## Explore further
-
-1. Replace `IsFrustumCulled` with `false` and compare the number of generated mesh groups.
-2. Visualize visibility by assigning a constant color per task group.
-3. Change `InstanceSpacing` without modifying the source sphere buffers.
-4. Add a mesh entry point with no payload parameter that selects instance zero, create a pipeline with `TaskShader = null`, and dispatch one mesh group to isolate geometry emission.
-5. Replace the UV sphere with another mesh while keeping the vertex and triangle contracts unchanged.
-
-## Complete source
-
-- [MeshShadingRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/MeshShadingRenderer.cs)
-- [MeshShading.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/MeshShading.slang)
-
-This completes the guide set. Use the [Zenith.NET documentation](../../docs/index.md) and [API reference](../../api/index.md) to inspect the underlying resource and command types in more detail.
diff --git a/documents/tutorials/guides/ray-tracing.md b/documents/tutorials/guides/ray-tracing.md
deleted file mode 100644
index 73d43a2a..00000000
--- a/documents/tutorials/guides/ray-tracing.md
+++ /dev/null
@@ -1,441 +0,0 @@
-# Ray Tracing
-
-Ray Tracing reuses the compute-output flow from Compute Shader, but each thread now traces a camera ray through an acceleration structure. The scene contains a triangle floor and three procedural spheres represented by axis-aligned bounding boxes.
-
-This guide explains the Zenith.NET acceleration structures, resource handles, inline ray-query interface, output texture, and synchronization. The complete shader also implements soft shadows, rough reflections, checkerboard filtering, and tone mapping; those supporting effects are summarized rather than derived line by line.
-
-## Result
-
-
-
-The camera orbits a checkerboard floor and three colored spheres. Primary rays determine the visible surface, while additional queries produce shadows and reflections.
-
-> [!IMPORTANT]
-> Inline ray tracing is optional. Check `App.Context.Capabilities.RayTracingSupported` before creating any acceleration-structure resources.
-
-## Frame overview
-
-Acceleration structures are built once during renderer construction. Every frame updates the camera, regenerates the output texture, and presents it:
-
-```mermaid
-flowchart LR
- A[Update camera constants] --> B[Output to Storage]
- B --> C[Bind ray-query compute pipeline]
- C --> D[Dispatch one thread per pixel]
- D --> E[Output to Sampled]
- E --> F[Present texture]
-```
-
-The output is recreated after a framebuffer resize, but the scene geometry and acceleration structures remain valid.
-
-## Scene structure
-
-The acceleration structures form a two-level hierarchy:
-
-```mermaid
-flowchart TD
- A[TLAS] --> B[Floor instance]
- A --> C[Sphere instance]
- B --> D[Triangle BLAS]
- C --> E[AABB BLAS]
- D --> F[Floor vertex and index buffers]
- E --> G[AABB buffer]
- H[Sphere structured buffer] --> I[Inline sphere-intersection logic in CSMain]
-```
-
-- A bottom-level acceleration structure, or **BLAS**, describes geometry.
-- A top-level acceleration structure, or **TLAS**, instances one or more BLAS objects into the traced scene.
-- Triangle geometry can be intersected directly by traversal hardware.
-- An AABB narrows traversal to a candidate region; the shader must test and commit the exact procedural intersection.
-
-The sphere structured buffer is not part of the AABB geometry description. The shader reads it separately to recover each candidate's center, radius, and color.
-
-## Resource map
-
-| Resource | Usage | Role |
-| --- | --- | --- |
-| Floor vertex buffer | `StorageReadOnly` | Triangle BLAS positions |
-| Floor index buffer | `StorageReadOnly` | Triangle BLAS indices |
-| AABB buffer | `StorageReadOnly` | Procedural BLAS bounds |
-| Sphere buffer | `StorageReadOnly` | Exact intersection and material data |
-| Floor BLAS | Acceleration structure | Triangle geometry |
-| Sphere BLAS | Acceleration structure | Procedural AABB geometry |
-| TLAS | Acceleration structure | Scene instances supplied to ray queries |
-| Constant buffer | `Constant` | Camera and three resource handles |
-| Output texture | `Storage` and `Sampled` | Compute output and presenter input |
-| Compute pipeline | `CSMain` | Primary and secondary inline queries |
-
-Build-input buffers must remain alive until the BLAS build or update submission completes. The BLAS objects themselves must remain alive for as long as a TLAS that instances them is in use. The sphere buffer has an additional lifetime requirement because the shader reads it during every frame.
-
-## Check device support
-
-Fail before allocating workload resources when the selected device does not expose ray tracing:
-
-```csharp
-if (!App.Context.Capabilities.RayTracingSupported)
-{
- throw new PlatformNotSupportedException("Ray Tracing is not supported by the selected device.");
-}
-```
-
-Check the capability before creating the shaders and acceleration structures required by this workload.
-
-## Create scene geometry
-
-The floor uses four positions and six indices. Three `Sphere` values provide procedural geometry and shading data. Convert each sphere into an AABB:
-
-```csharp
-Aabb[] aabbs = new Aabb[spheres.Length];
-for (int index = 0; index < spheres.Length; index++)
-{
- aabbs[index] = new(spheres[index].Center - new Vector3(spheres[index].Radius), spheres[index].Center + new Vector3(spheres[index].Radius));
-}
-```
-
-Upload all four arrays:
-
-```csharp
-floorVertexBuffer = App.LoadBuffer(floorVertices, BufferUsages.StorageReadOnly);
-floorIndexBuffer = App.LoadBuffer(floorIndices, BufferUsages.StorageReadOnly);
-aabbBuffer = App.LoadBuffer(aabbs, BufferUsages.StorageReadOnly);
-sphereBuffer = App.LoadBuffer(spheres, BufferUsages.StorageReadOnly);
-constantBuffer = App.LoadBuffer([new Constants()], BufferUsages.Constant);
-```
-
-These buffers are read during acceleration-structure construction or shader execution. They are not vertex and index bindings in a graphics render pass.
-
-## Create the compute pipeline
-
-Load the `CSMain` entry point and create the compute pipeline used for every inline ray-query dispatch:
-
-```csharp
-using Shader computeShader = App.LoadShader("RayTracing.slang", "CSMain");
-
-rayTracingPipeline = App.Context.CreateComputePipeline(new() { ComputeShader = computeShader });
-```
-
-The acceleration structures and descriptor handles become inputs to this pipeline; no separate ray-tracing pipeline or intersection-shader stage is created.
-
-## Build the bottom-level structures
-
-Record acceleration-structure construction on a compute command buffer:
-
-```csharp
-CommandBuffer commandBuffer = App.Context.ComputeQueue.CommandBuffer();
-```
-
-The sample submits this one-time build and waits for it to complete before rendering begins.
-
-Describe the indexed triangle geometry for the floor:
-
-```csharp
-floorBlas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc()
-{
- Geometries =
- [
- RayTracingGeometry.Triangles(new()
- {
- VertexBuffer = floorVertexBuffer,
- VertexFormat = PixelFormat.R32G32B32Float,
- VertexCount = (uint)floorVertices.Length,
- VertexStrideInBytes = (uint)sizeof(Vector3),
- IndexBuffer = floorIndexBuffer,
- IndexFormat = IndexFormat.UInt32,
- IndexCount = (uint)floorIndices.Length,
- Transform = Matrix4x4.Identity
- }, true)
- ],
- BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace
-});
-```
-
-The vertex format and stride tell the builder how to read each `Vector3`. The final `true` marks the geometry opaque, allowing traversal to accept triangle hits without an any-hit stage.
-
-The procedural BLAS instead reads an array of AABBs:
-
-```csharp
-sphereBlas = commandBuffer.BuildAccelerationStructure(new BottomLevelAccelerationStructureDesc()
-{
- Geometries =
- [
- RayTracingGeometry.Aabbs(new()
- {
- Buffer = aabbBuffer,
- Count = (uint)spheres.Length,
- StrideInBytes = aabbBuffer.Desc.StrideInBytes
- }, true)
- ],
- BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace
-});
-```
-
-The AABBs accelerate traversal, but they do not turn boxes into visible surfaces. `CSMain` later performs the exact sphere intersection for each procedural candidate.
-
-## Build the top-level scene
-
-Create one identity-transform instance for each BLAS:
-
-```csharp
-tlas = commandBuffer.BuildAccelerationStructure(new TopLevelAccelerationStructureDesc()
-{
- Instances =
- [
- new()
- {
- AccelerationStructure = floorBlas,
- InstanceId = 0,
- VisibilityMask = 0xFF,
- Transform = Matrix4x4.Identity
- },
- new()
- {
- AccelerationStructure = sphereBlas,
- InstanceId = 1,
- VisibilityMask = 0xFF,
- Transform = Matrix4x4.Identity
- }
- ],
- BuildFlags = AccelerationStructureBuildFlags.PreferFastTrace
-});
-
-commandBuffer.Submit().Wait();
-```
-
-The CPU wait ensures both BLAS objects and the TLAS are complete before construction returns. The instance IDs distinguish scene entries; procedural primitive indices still identify individual AABBs inside the sphere BLAS.
-
-## Data contract
-
-Shared C#/Slang layout requires care around three-component vectors. The CPU sphere packs center and radius into the first 16 bytes, followed by color at byte 16:
-
-```csharp
-[StructLayout(LayoutKind.Explicit, Size = 32)]
-file struct Sphere
-{
- [FieldOffset(0)]
- public Vector3 Center;
-
- [FieldOffset(12)]
- public float Radius;
-
- [FieldOffset(16)]
- public Vector3 Color;
-}
-```
-
-Slang uses two `float4` backing fields and exposes semantic properties:
-
-```slang
-struct Sphere
-{
- private float4 CenterAndRadius;
-
- private float4 ColorAndPadding;
-
- property float3 Center
- {
- get {
- return CenterAndRadius.xyz;
- }
- }
-
- property float Radius
- {
- get {
- return CenterAndRadius.w;
- }
- }
-
- property float3 Color
- {
- get {
- return ColorAndPadding.xyz;
- }
- }
-};
-```
-
-This explicit storage keeps the structured-buffer stride at 32 bytes across the supported graphics APIs.
-
-The frame constants use the same technique for camera position, followed by three handles:
-
-| Offset | C# | Slang |
-| ---: | --- | --- |
-| 0 | `Vector3 Position` | `PositionAndPadding.xyz` |
-| 16 | `tlas.Handle` | `RaytracingAccelerationStructure Scene` |
-| 24 | `sphereBuffer.StorageReadOnlyHandle` | `StructuredBuffer Spheres` |
-| 32 | `outputTexture.StorageHandle` | `RWTexture2D OutputTexture` |
-
-## Inline ray-query interface
-
-Each compute thread reconstructs one camera ray and begins traversal through the TLAS:
-
-```slang
-RayDesc ray;
-ray.Origin = cameraPos;
-ray.Direction = rayDir;
-ray.TMin = RayEpsilon;
-ray.TMax = 1000.0;
-
-float3 sphereHitNormal = float3(0.0);
-float3 sphereHitColor = float3(0.0);
-
-RayQuery query;
-query.TraceRayInline(constants.Scene, RAY_FLAG_NONE, 0xFF, ray);
-```
-
-The two temporary values preserve the normal and material color produced by a procedural sphere candidate after that candidate is committed.
-
-Triangle intersections can become committed hits automatically. Procedural AABBs are reported as candidates and require an exact test:
-
-```slang
-while (query.Proceed())
-{
- if (query.CandidateType() == CANDIDATE_PROCEDURAL_PRIMITIVE)
- {
- uint sphereIndex = query.CandidatePrimitiveIndex();
- Sphere sphere = constants.Spheres[sphereIndex];
-
- float3 ro = query.CandidateObjectRayOrigin();
- float3 rd = query.CandidateObjectRayDirection();
-
- float t = IntersectSphere(ro, rd, sphere);
-
- if (t >= query.RayTMin() && t <= query.CommittedRayT())
- {
- float3 hitPoint = ro + rd * t;
-
- sphereHitNormal = normalize(hitPoint - sphere.Center);
- sphereHitColor = sphere.Color;
-
- query.CommitProceduralPrimitiveHit(t);
- }
- }
-}
-```
-
-`CandidatePrimitiveIndex` maps the AABB candidate back to its sphere record. The shader computes a positive intersection distance and commits it only when it lies inside the query's valid interval.
-
-After traversal, committed status selects the visible surface:
-
-```slang
-if (query.CommittedStatus() == COMMITTED_TRIANGLE_HIT)
-{
- float3 hitPoint = ray.Origin + ray.Direction * query.CommittedRayT();
- color = ShadeFloor(hitPoint, rayDir, cameraPos);
-}
-else if (query.CommittedStatus() == COMMITTED_PROCEDURAL_PRIMITIVE_HIT)
-{
- float3 hitPoint = ray.Origin + ray.Direction * query.CommittedRayT();
- color = ShadePrimarySphere(hitPoint, rayDir, cameraPos, sphereHitNormal, sphereHitColor);
-}
-else
-{
- color = SampleSky(rayDir);
-}
-```
-
-## Supporting shading flow
-
-The remaining shader functions improve the sample image but do not change the Zenith.NET resource interface:
-
-```mermaid
-flowchart TD
- A[CSMain primary query] --> B[Shade floor]
- A --> C[Shade sphere]
- A --> D[Sample sky]
- B --> E[Shadow queries]
- B --> F[Reflection query]
- C --> E
- C --> G[Rough reflection queries]
- B --> H[ACES tone mapping]
- C --> H
- D --> H
-```
-
-Shadow rays reuse the TLAS with `RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH`. Reflection rays use the same procedural-candidate loop as the primary query. Multiple jittered directions soften shadows and reflections. Finally, `ACESFilm` maps the accumulated HDR color into the output range.
-
-You can replace these helpers with simpler colors while preserving the acceleration structures, descriptors, pipeline, dispatch, and texture transitions described by this guide.
-
-## Create and update the output
-
-Create an `R32G32B32A32Float` texture with `Sampled | Storage` usage when the renderer first sees a valid framebuffer size. A new texture is initially established as sampled because every subsequent frame follows the same repeating cycle:
-
-```text
-Sampled -> Storage -> compute writes -> Sampled -> presenter reads
-```
-
-Update the camera and resource handles before dispatch:
-
-```csharp
-Constants constants = new()
-{
- Position = new(12.0f * MathF.Sin(totalTime * 0.3f), 4.0f + MathF.Sin(totalTime * 0.2f), -12.0f * MathF.Cos(totalTime * 0.3f)),
- Scene = tlas.Handle,
- Spheres = sphereBuffer.StorageReadOnlyHandle,
- OutputTexture = outputTexture.StorageHandle
-};
-```
-
-Then record the state changes and dispatch:
-
-```csharp
-commandBuffer.Transition(outputTexture, default, TextureLayout.Sampled, TextureLayout.Storage);
-
-commandBuffer.SetPipeline(rayTracingPipeline);
-commandBuffer.SetConstantBuffer(constantBuffer, 0);
-commandBuffer.Dispatch((App.Width + ThreadGroupSize - 1) / ThreadGroupSize, (App.Height + ThreadGroupSize - 1) / ThreadGroupSize, 1);
-
-commandBuffer.Transition(outputTexture, default, TextureLayout.Storage, TextureLayout.Sampled);
-
-App.PresentTexture(commandBuffer, drawable, outputTexture, true);
-```
-
-Unlike the static grayscale image, this workload dispatches every frame because the camera moves. Passing `true` to the presenter fits the output to the framebuffer.
-
-## Resize and lifetime
-
-Resize disposes only the framebuffer-sized output texture. The next frame recreates it and uploads its new storage handle.
-
-Dispose resources from users to dependencies:
-
-```csharp
-outputTexture?.Dispose();
-
-rayTracingPipeline.Dispose();
-constantBuffer.Dispose();
-tlas.Dispose();
-sphereBlas.Dispose();
-floorBlas.Dispose();
-sphereBuffer.Dispose();
-aabbBuffer.Dispose();
-floorIndexBuffer.Dispose();
-floorVertexBuffer.Dispose();
-```
-
-The TLAS is released before the BLAS objects it instances. The sphere buffer remains live because ray-query shading reads it every frame.
-
-## Inspect the result
-
-Run the host and select **Ray Tracing** on a supported device. Confirm that:
-
-- the floor, three spheres, sky, shadows, and reflections are visible;
-- the camera orbits continuously;
-- resize recreates an output that fills the new framebuffer;
-- validation reports no acceleration-structure lifetime, descriptor, or texture-layout errors.
-
-A sky-only image often indicates a missing TLAS handle or visibility-mask mismatch. Box-shaped procedural hits indicate that AABBs are being treated as bounds without committing the exact sphere intersection. A stale or blank image usually points to the output handle or `Sampled`/`Storage` transition sequence.
-
-## Explore further
-
-1. Return a constant color for each committed status to inspect traversal without the supporting shading functions.
-2. Set `ShadowSamples` and `ReflectionSamples` to `1` and compare cost and image quality.
-3. Disable reflections while leaving primary and shadow queries unchanged.
-4. Move one TLAS instance with its transform instead of changing the source geometry.
-5. Change a sphere radius and update both its structured-buffer record and AABB.
-
-## Complete source
-
-- [RayTracingRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/RayTracingRenderer.cs)
-- [RayTracing.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/RayTracing.slang)
-
-Continue with [Mesh Shading](mesh-shading.md) to explore another optional workload that replaces fixed vertex and index fetch with programmable geometry emission.
diff --git a/documents/tutorials/guides/spinning-cube.md b/documents/tutorials/guides/spinning-cube.md
deleted file mode 100644
index a9c41c9e..00000000
--- a/documents/tutorials/guides/spinning-cube.md
+++ /dev/null
@@ -1,265 +0,0 @@
-# Spinning Cube
-
-Spinning Cube extends the first graphics workload into three dimensions. It reuses vertex input and a graphics pipeline, then adds indexed geometry, model-view-projection constants, back-face culling, a depth attachment, animation, and size-dependent resource handling.
-
-The guide focuses on the Zenith.NET objects and their lifetime. Matrix construction uses `System.Numerics`; a full derivation of perspective projection is outside the workload boundary.
-
-## Result
-
-
-
-The cube rotates around two axes. Back faces are culled, and the depth attachment ensures that nearer fragments hide farther surfaces.
-
-## Frame overview
-
-Initialization uploads eight shared vertices and 36 indices, creates a constant buffer, and builds a depth-enabled pipeline. Per frame:
-
-```mermaid
-flowchart LR
- A[Update MVP constants] --> B{Depth texture exists?}
- B -- No --> C[Create and transition depth texture]
- B -- Yes --> D[Begin color and depth render pass]
- C --> D
- D --> E[Bind pipeline and buffers]
- E --> F[DrawIndexed 36]
- F --> G[End render pass]
-```
-
-On resize, the renderer disposes only the depth texture. The next frame recreates it with the new framebuffer dimensions.
-
-## Resource map
-
-| Resource | Usage | Lifetime |
-| --- | --- | --- |
-| Vertex buffer | Eight cube positions and colors | Renderer |
-| Index buffer | Twelve triangles sharing those vertices | Renderer |
-| Constant buffer | Model, view, and projection matrices | Renderer; updated each frame |
-| Graphics pipeline | Depth-tested indexed drawing | Renderer |
-| Depth texture | `D32FloatS8UInt` attachment | Recreated after resize |
-| Swap-chain drawable | Color attachment | Host-owned |
-
-The depth format appears in both the pipeline attachment declaration and the texture description. Those values must agree.
-
-## Geometry and indexed drawing
-
-Eight vertices describe the unique corners of the cube. The index buffer selects them to build six faces, two triangles per face:
-
-```csharp
-uint[] indices =
-[
- 0, 1, 2, 0, 2, 3,
- 5, 4, 7, 5, 7, 6,
- 4, 0, 3, 4, 3, 7,
- 1, 5, 6, 1, 6, 2,
- 3, 2, 6, 3, 6, 7,
- 4, 5, 1, 4, 1, 0
-];
-
-vertexBuffer = App.LoadBuffer(vertices, BufferUsages.Vertex);
-indexBuffer = App.LoadBuffer(indices, BufferUsages.Index);
-```
-
-Using indices avoids duplicating a complete position-and-color record for every triangle corner. The element type is `uint`, so the render method binds it as `IndexFormat.UInt32`.
-
-## Data contract
-
-The Slang constant buffer contains three consecutive matrices:
-
-```slang
-struct Constants
-{
- float4x4 Model;
-
- float4x4 View;
-
- float4x4 Projection;
-};
-
-ConstantBuffer constants;
-```
-
-The C# structure fixes each 64-byte matrix at the corresponding offset:
-
-```csharp
-[StructLayout(LayoutKind.Explicit, Size = 192)]
-file struct Constants
-{
- [FieldOffset(0)]
- public Matrix4x4 Model;
-
- [FieldOffset(64)]
- public Matrix4x4 View;
-
- [FieldOffset(128)]
- public Matrix4x4 Projection;
-}
-```
-
-| Offset | C# | Slang | Size |
-| ---: | --- | --- | ---: |
-| 0 | `Matrix4x4 Model` | `float4x4 Model` | 64 bytes |
-| 64 | `Matrix4x4 View` | `float4x4 View` | 64 bytes |
-| 128 | `Matrix4x4 Projection` | `float4x4 Projection` | 64 bytes |
-
-Explicit offsets keep the shared binary contract visible. The total structure size is 192 bytes.
-
-## Transform vertices
-
-The vertex shader composes the three matrices and transforms each position:
-
-```slang
-[shader("vertex")]
-FSInput VSMain(VSInput input)
-{
- float4x4 modelView = mul(constants.Model, constants.View);
- float4x4 mvp = mul(modelView, constants.Projection);
-
- FSInput output;
- output.Position = mul(float4(input.Position, 1.0), mvp);
- output.Color = input.Color;
-
- return output;
-}
-```
-
-`System.Numerics.Matrix4x4` and this shader use the same row-vector order: model, then view, then projection. The fragment shader simply returns the interpolated vertex color.
-
-## Enable depth and culling
-
-The graphics pipeline adds a depth format and enables both back-face culling and depth read/write:
-
-```csharp
-pipeline = App.Context.CreateGraphicsPipeline(new()
-{
- VertexShader = vertexShader,
- FragmentShader = fragmentShader,
- InputLayouts = [inputLayout],
- PrimitiveTopology = PrimitiveTopology.TriangleList,
- AttachmentFormats = new()
- {
- ColorFormats = [App.ColorFormat],
- DepthStencilFormat = PixelFormat.D32FloatS8UInt,
- SampleCount = SampleCount.Count1
- },
- RenderState = new()
- {
- Rasterizer = RasterizerState.CullBack(),
- DepthStencil = DepthStencilState.DepthReadWrite(),
- Blend = BlendState.Opaque()
- }
-});
-```
-
-Back-face culling removes triangles whose winding faces away from the camera. Depth testing compares overlapping fragments, while depth writes preserve the nearest accepted value for later triangles.
-
-## Update transformation constants
-
-`Update` advances a frame-rate-independent angle and constructs the three matrices:
-
-```csharp
-rotationAngle += (float)deltaTime;
-
-Constants constants = new()
-{
- Model = Matrix4x4.CreateRotationY(rotationAngle) * Matrix4x4.CreateRotationX(rotationAngle * 0.5f),
- View = Matrix4x4.CreateLookAt(new(0.0f, 0.0f, 3.0f), Vector3.Zero, Vector3.UnitY),
- Projection = Matrix4x4.CreatePerspectiveFieldOfView(float.DegreesToRadians(45.0f), (float)App.Width / App.Height, 0.1f, 100.0f)
-};
-```
-
-The framebuffer aspect ratio keeps the projection proportional after a resize. Upload exactly one `Constants` value:
-
-```csharp
-constantBuffer.Upload(0, new()
-{
- Pointer = (nint)(&constants),
- SizeInBytes = (uint)sizeof(Constants)
-});
-```
-
-The teaching host waits for each submitted frame, so overwriting this single constant buffer does not race a previous frame. A multi-frame application needs separate per-frame storage or equivalent synchronization.
-
-## Create the depth attachment
-
-The depth texture is created lazily after the window has a valid framebuffer size:
-
-```csharp
-if (depthTexture is null)
-{
- depthTexture = App.Context.CreateTexture(TextureDesc.DepthStencilAttachment(PixelFormat.D32FloatS8UInt, App.Width, App.Height, SampleCount.Count1));
-
- commandBuffer.Transition(depthTexture, default, TextureLayout.Undefined, TextureLayout.DepthStencilAttachment);
-}
-```
-
-The first transition establishes `DepthStencilAttachment` layout. The texture remains in that layout while reused by later frames.
-
-## Record the frame
-
-Begin a render pass with both color and depth attachments, bind all inputs, and issue an indexed draw:
-
-```csharp
-commandBuffer.BeginRenderPass([ColorAttachment.Clear(drawable, new(0.04f, 0.055f, 0.075f, 1.0f))], DepthStencilAttachment.Clear(depthTexture, 1.0f, 0));
-
-commandBuffer.SetPipeline(pipeline);
-commandBuffer.SetVertexBuffer(vertexBuffer, 0, 0);
-commandBuffer.SetIndexBuffer(indexBuffer, 0, IndexFormat.UInt32);
-commandBuffer.SetConstantBuffer(constantBuffer, 0);
-
-commandBuffer.DrawIndexed(36, 1, 0, 0, 0);
-
-commandBuffer.EndRenderPass();
-```
-
-Depth is cleared to `1.0` for each frame. `DrawIndexed` consumes all 36 indices for one instance, starting at index and vertex offsets zero.
-
-## Resize and lifetime
-
-The depth attachment dimensions must match the drawable. Dispose it on resize and recreate it during the next render:
-
-```csharp
-public void Resize(uint width, uint height)
-{
- depthTexture?.Dispose();
- depthTexture = null;
-}
-```
-
-Release the optional depth texture first, then the pipeline and its input resources:
-
-```csharp
-public void Dispose()
-{
- depthTexture?.Dispose();
-
- pipeline.Dispose();
- constantBuffer.Dispose();
- indexBuffer.Dispose();
- vertexBuffer.Dispose();
-}
-```
-
-## Inspect the result
-
-Run the host and select **Spinning Cube**. Confirm that:
-
-- the cube rotates smoothly and hidden surfaces do not bleed through;
-- resizing changes the projection without stretching the cube;
-- the depth texture is recreated after resize;
-- the validation layer reports no attachment-format or layout errors.
-
-If faces disappear unexpectedly, inspect index winding and culling. If distant faces draw over near faces, confirm that the pipeline enables depth read/write and that a depth attachment is supplied to the render pass.
-
-## Explore further
-
-1. Replace `CullBack` with `CullNone` and compare the result.
-2. Disable depth testing while leaving the depth attachment present.
-3. Change the near and far projection planes and observe depth precision at different distances.
-4. Give each face its own vertices and colors to see the tradeoff between shared indices and per-face attributes.
-
-## Complete source
-
-- [SpinningCubeRenderer.cs](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Renderers/SpinningCubeRenderer.cs)
-- [SpinningCube.slang](https://github.com/qian-o/ZenithTutorials/blob/master/ZenithTutorials/Assets/Shaders/SpinningCube.slang)
-
-Continue with [Indirect Drawing](indirect-drawing.md) to reuse this depth-tested cube for 25 instances driven by one indirect argument record, or branch to [Compute Shader](compute-shader.md) to produce an image without rasterized geometry.
diff --git a/documents/tutorials/index.md b/documents/tutorials/index.md
deleted file mode 100644
index 9d3b2af6..00000000
--- a/documents/tutorials/index.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# Tutorials
-
-The Zenith.NET tutorials examine complete rendering workloads built with C# and the Slang shader language. Each guide focuses on the resources, pipelines, commands, data contracts, and lifetime rules that connect a workload to Zenith.NET.
-
-Begin with Project Setup and Application Host. The six guides then move from a first graphics pipeline to compute, indirect drawing, inline ray queries, and programmable geometry.
-
-> [!NOTE]
-> The guides explain the Zenith.NET integration points in detail. Supporting graphics techniques such as sampling patterns, tone mapping, and procedural mesh generation are summarized when they are not essential to the API workflow. Their complete implementations remain available in the tutorial source.
-
-## Getting started
-
-- [Project Setup](getting-started/project-setup.md) creates the .NET 10 project and configures runtime assets.
-- [Application Host](getting-started/application-host.md) follows the cross-platform window, swap chain, frame loop, and offscreen texture presenter.
-
-## Guides
-
-| Guide | Result | Focus |
-| --- | --- | --- |
-| [Hello Triangle](guides/hello-triangle.md) | A vertex-colored triangle | Vertex input, graphics pipeline, render pass, and draw command |
-| [Spinning Cube](guides/spinning-cube.md) | A rotating depth-tested cube | Indexed geometry, constant buffers, transforms, depth, and resize |
-| [Compute Shader](guides/compute-shader.md) | A grayscale image | Storage textures, descriptor handles, dispatch, and layout transitions |
-| [Indirect Drawing](guides/indirect-drawing.md) | A grid of animated cubes | Structured instance data and indirect draw arguments |
-| [Ray Tracing](guides/ray-tracing.md) | Procedural spheres above a floor | Acceleration structures, inline ray queries, and compute output |
-| [Mesh Shading](guides/mesh-shading.md) | A culled sphere grid | Task and mesh shaders, payload compaction, and mesh dispatch |
-
-The complete, runnable implementations are maintained in the [ZenithTutorials repository](https://github.com/qian-o/ZenithTutorials). Code shown in these pages is static so it remains readable and searchable with the rest of the documentation; each guide links its complete renderer and shader at the end.
-
-## Suggested paths
-
-Complete [Hello Triangle](guides/hello-triangle.md) first. From there:
-
-- continue with [Spinning Cube](guides/spinning-cube.md) and [Indirect Drawing](guides/indirect-drawing.md) for the graphics path;
-- continue with [Compute Shader](guides/compute-shader.md) and [Ray Tracing](guides/ray-tracing.md) for compute-produced images;
-- read [Mesh Shading](guides/mesh-shading.md) after Spinning Cube when you are comfortable with graphics pipelines, depth attachments, and GPU thread groups.
-
-Ray tracing and mesh shading are optional device capabilities. Their guides show how to test support before creating workload-specific resources.
diff --git a/documents/tutorials/toc.yml b/documents/tutorials/toc.yml
deleted file mode 100644
index 0616191a..00000000
--- a/documents/tutorials/toc.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-- name: Tutorials
- href: index.md
-
-- name: Getting Started
- items:
- - name: Project Setup
- href: getting-started/project-setup.md
- - name: Application Host
- href: getting-started/application-host.md
-
-- name: Guides
- items:
- - name: Hello Triangle
- href: guides/hello-triangle.md
- - name: Spinning Cube
- href: guides/spinning-cube.md
- - name: Compute Shader
- href: guides/compute-shader.md
- - name: Indirect Drawing
- href: guides/indirect-drawing.md
- - name: Ray Tracing
- href: guides/ray-tracing.md
- - name: Mesh Shading
- href: guides/mesh-shading.md
diff --git a/sources/Directory.Packages.props b/sources/Directory.Packages.props
index 670823a4..31dc4160 100644
--- a/sources/Directory.Packages.props
+++ b/sources/Directory.Packages.props
@@ -9,7 +9,7 @@
-
+
@@ -19,9 +19,9 @@
-
+
-
+
\ No newline at end of file
diff --git a/sources/Experiments/CornellBox/App.cs b/sources/Experiments/CornellBox/App.cs
index 4fdbf5a2..af55a8aa 100644
--- a/sources/Experiments/CornellBox/App.cs
+++ b/sources/Experiments/CornellBox/App.cs
@@ -7,6 +7,7 @@
using Silk.NET.Windowing;
using Zenith.NET;
using Zenith.NET.DirectX12;
+using Zenith.NET.Extensions.ImGui;
using Zenith.NET.Metal;
using Zenith.NET.Vulkan;
@@ -140,7 +141,7 @@ public static void Run()
}
});
- ImGui.GetBackgroundDrawList().AddImage(imGui.Binding(renderer.Color), new(0, 0), new(Width / DpiScale.X, Height / DpiScale.Y));
+ ImGui.GetBackgroundDrawList().AddImage(renderer.Color.ImGuiBinding, new(0, 0), new(Width / DpiScale.X, Height / DpiScale.Y));
};
window.Render += static _ =>
diff --git a/sources/Experiments/FluidTank/App.cs b/sources/Experiments/FluidTank/App.cs
index b07b6bf4..27fc1ea9 100644
--- a/sources/Experiments/FluidTank/App.cs
+++ b/sources/Experiments/FluidTank/App.cs
@@ -7,6 +7,7 @@
using Silk.NET.Windowing;
using Zenith.NET;
using Zenith.NET.DirectX12;
+using Zenith.NET.Extensions.ImGui;
using Zenith.NET.Metal;
using Zenith.NET.Vulkan;
@@ -152,7 +153,7 @@ public static void Run()
}
});
- ImGui.GetBackgroundDrawList().AddImage(imGui.Binding(renderer.Color), new(0, 0), new(Width / DpiScale.X, Height / DpiScale.Y));
+ ImGui.GetBackgroundDrawList().AddImage(renderer.Color.ImGuiBinding, new(0, 0), new(Width / DpiScale.X, Height / DpiScale.Y));
};
window.Render += static _ =>
diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/Extensions.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/Extensions.cs
new file mode 100644
index 00000000..55d763ec
--- /dev/null
+++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/Extensions.cs
@@ -0,0 +1,16 @@
+using Hexa.NET.ImGui;
+
+namespace Zenith.NET.Extensions.ImGui;
+
+public static class Extensions
+{
+ extension(Texture texture)
+ {
+ public ImTextureRef ImGuiBinding => ImGuiController.Current?.Binding(texture) ?? default;
+ }
+
+ extension(TextureView textureView)
+ {
+ public ImTextureRef ImGuiBinding => ImGuiController.Current?.Binding(textureView) ?? default;
+ }
+}
diff --git a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs
index 0a9771d1..fea8d3be 100644
--- a/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs
+++ b/sources/Extensions/Zenith.NET.Extensions.ImGui/ImGuiController.cs
@@ -7,6 +7,9 @@ namespace Zenith.NET.Extensions.ImGui;
public unsafe class ImGuiController : DisposableObject
{
+ [ThreadStatic]
+ internal static ImGuiController? Current;
+
private readonly ImGuiRenderer renderer;
private readonly PlatformGetClipboardTextFn platformGetClipboardText;
private readonly PlatformSetClipboardTextFn platformSetClipboardText;
@@ -66,16 +69,6 @@ public ImGuiController(GraphicsContext context, AttachmentFormats attachmentForm
public IImGuiPlatformBindings? PlatformBindings { get; set; }
- public ImTextureRef Binding(Texture texture)
- {
- return new(null, renderer.Binding(texture));
- }
-
- public ImTextureRef Binding(TextureView textureView)
- {
- return new(null, renderer.Binding(textureView));
- }
-
public void Update(double delta, uint width, uint height)
{
HexaImGui.SetCurrentContext(Context);
@@ -138,6 +131,8 @@ public void Update(double delta, uint width, uint height)
HexaImGui.NewFrame();
frameBegun = true;
+
+ Current = this;
}
public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment)
@@ -152,6 +147,8 @@ public void Render(CommandBuffer commandBuffer, ColorAttachment colorAttachment)
frameBegun = false;
}
+
+ Current = null;
}
public void MouseDown(ImGuiMouseButton button)
@@ -202,6 +199,16 @@ protected override void Destroy()
HexaImGui.DestroyContext(Context);
}
+ internal ImTextureRef Binding(Texture texture)
+ {
+ return new(null, renderer.Binding(texture));
+ }
+
+ internal ImTextureRef Binding(TextureView textureView)
+ {
+ return new(null, renderer.Binding(textureView));
+ }
+
private byte* PlatformGetClipboardText(ImGuiContext* context)
{
clipboardScope?.Dispose();
diff --git a/sources/NuGet.Packaging.props b/sources/NuGet.Packaging.props
index e60169d2..932c0733 100644
--- a/sources/NuGet.Packaging.props
+++ b/sources/NuGet.Packaging.props
@@ -7,7 +7,7 @@
$(MSBuildThisFileDirectory)..\.nuget
- 1.0.0
+ 1.1.0-alpha
qian-o
Copyright (c) 2026 qian-o
Zenith.NET is a modern rendering hardware interface for .NET with one consistent C# API for graphics and compute across DirectX 12, Metal 4, and Vulkan 1.4.