refactor(render): extract typed retail pview passes
This commit is contained in:
parent
6d6e5b5fa5
commit
85239fb373
13 changed files with 1888 additions and 841 deletions
576
src/AcDream.App/Rendering/RetailPViewPassExecutor.cs
Normal file
576
src/AcDream.App/Rendering/RetailPViewPassExecutor.cs
Normal file
|
|
@ -0,0 +1,576 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Sky;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.Core.Rendering;
|
||||
using AcDream.Core.Vfx;
|
||||
using AcDream.Core.World;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
internal readonly record struct RetailPViewFramebufferSize(int Width, int Height);
|
||||
|
||||
internal interface IRetailPViewFramebufferSource
|
||||
{
|
||||
RetailPViewFramebufferSize Capture();
|
||||
}
|
||||
|
||||
internal sealed class SilkRetailPViewFramebufferSource(IWindow window) :
|
||||
IRetailPViewFramebufferSource
|
||||
{
|
||||
private readonly IWindow _window = window
|
||||
?? throw new ArgumentNullException(nameof(window));
|
||||
|
||||
public RetailPViewFramebufferSize Capture()
|
||||
{
|
||||
var size = _window.FramebufferSize;
|
||||
return new RetailPViewFramebufferSize(size.X, size.Y);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RetailPViewCellSource : IRetailPViewCellSource
|
||||
{
|
||||
private readonly CellVisibility _cells;
|
||||
|
||||
public RetailPViewCellSource(CellVisibility cells) =>
|
||||
_cells = cells ?? throw new ArgumentNullException(nameof(cells));
|
||||
|
||||
public LoadedCell? Find(uint cellId) =>
|
||||
_cells.TryGetCell(cellId, out LoadedCell? cell) ? cell : null;
|
||||
}
|
||||
|
||||
internal sealed class RetailPViewParticleClassifications
|
||||
{
|
||||
private readonly HashSet<uint> _outdoor = [];
|
||||
private readonly HashSet<uint> _visible = [];
|
||||
private readonly HashSet<uint> _dynamics = [];
|
||||
|
||||
public IReadOnlySet<uint> Outdoor => _outdoor;
|
||||
public HashSet<uint> Visible => _visible;
|
||||
public HashSet<uint> Dynamics => _dynamics;
|
||||
|
||||
public void BeginFrame()
|
||||
{
|
||||
_outdoor.Clear();
|
||||
_visible.Clear();
|
||||
_dynamics.Clear();
|
||||
}
|
||||
|
||||
public void ReplaceOutdoor(IReadOnlyList<WorldEntity> owners)
|
||||
{
|
||||
_outdoor.Clear();
|
||||
foreach (WorldEntity owner in owners)
|
||||
_outdoor.Add(owner.Id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Concrete GL implementation of the named passes ordered by
|
||||
/// <see cref="RetailPViewRenderer"/>. It owns reusable pass-local particle
|
||||
/// classifications but borrows every renderer and world source.
|
||||
/// The order it implements is retail <c>PView::DrawCells @ 0x005A4840</c>:
|
||||
/// landscape, delayed-alpha flush, optional interior depth clear, exit masks,
|
||||
/// cell shells/objects, then surviving dynamics. Landscape sky/terrain/weather
|
||||
/// placement follows <c>LScape::draw @ 0x00506330</c>.
|
||||
/// </summary>
|
||||
internal sealed class RetailPViewPassExecutor : IRetailPViewPassExecutor
|
||||
{
|
||||
private readonly GL _gl;
|
||||
private readonly IRetailPViewFramebufferSource _framebuffer;
|
||||
private readonly ClipFrame _clipFrame;
|
||||
private readonly TerrainModernRenderer? _terrain;
|
||||
private readonly EnvCellRenderer _envCells;
|
||||
private readonly WbDrawDispatcher _entities;
|
||||
private readonly SkyRenderer? _sky;
|
||||
private readonly ParticleSystem? _particles;
|
||||
private readonly ParticleRenderer? _particleRenderer;
|
||||
private readonly PortalDepthMaskRenderer? _portalDepthMask;
|
||||
private readonly RetailAlphaQueue _alpha;
|
||||
private readonly WorldRenderDiagnostics _diagnostics;
|
||||
private readonly TerrainDrawDiagnosticsController _terrainDiagnostics;
|
||||
private readonly RetailPViewParticleClassifications _particleClassifications = new();
|
||||
private readonly HashSet<uint> _noSceneParticleEntityIds = [];
|
||||
|
||||
/// <summary>
|
||||
/// Borrowed until the next late landscape pass. The outdoor-root post-world
|
||||
/// particle pass consumes this synchronously before another PView frame.
|
||||
/// </summary>
|
||||
public IReadOnlySet<uint> OutdoorSceneParticleEntityIds =>
|
||||
_particleClassifications.Outdoor;
|
||||
|
||||
public RetailPViewPassExecutor(
|
||||
GL gl,
|
||||
IRetailPViewFramebufferSource framebuffer,
|
||||
ClipFrame clipFrame,
|
||||
TerrainModernRenderer? terrain,
|
||||
EnvCellRenderer envCells,
|
||||
WbDrawDispatcher entities,
|
||||
SkyRenderer? sky,
|
||||
ParticleSystem? particles,
|
||||
ParticleRenderer? particleRenderer,
|
||||
PortalDepthMaskRenderer? portalDepthMask,
|
||||
RetailAlphaQueue alpha,
|
||||
WorldRenderDiagnostics diagnostics,
|
||||
TerrainDrawDiagnosticsController terrainDiagnostics)
|
||||
{
|
||||
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
|
||||
_framebuffer = framebuffer
|
||||
?? throw new ArgumentNullException(nameof(framebuffer));
|
||||
_clipFrame = clipFrame ?? throw new ArgumentNullException(nameof(clipFrame));
|
||||
_terrain = terrain;
|
||||
_envCells = envCells ?? throw new ArgumentNullException(nameof(envCells));
|
||||
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
|
||||
_sky = sky;
|
||||
_particles = particles;
|
||||
_particleRenderer = particleRenderer;
|
||||
_portalDepthMask = portalDepthMask;
|
||||
_alpha = alpha ?? throw new ArgumentNullException(nameof(alpha));
|
||||
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
|
||||
_terrainDiagnostics = terrainDiagnostics
|
||||
?? throw new ArgumentNullException(nameof(terrainDiagnostics));
|
||||
}
|
||||
|
||||
public void BeginFrame()
|
||||
{
|
||||
_particleClassifications.BeginFrame();
|
||||
}
|
||||
|
||||
public ClipFrameAssembly AssembleClipFrame(
|
||||
PortalVisibilityFrame portalFrame,
|
||||
ClipFrameAssembly reuseAssembly) =>
|
||||
ClipFrameAssembler.Assemble(_clipFrame, portalFrame, reuseAssembly);
|
||||
|
||||
public void PrepareClipFrame(int terrainUploadCount)
|
||||
{
|
||||
// Allocate every terrain record before issuing the first draw. BufferData
|
||||
// must not replace the arena while an earlier slice can reference it.
|
||||
_clipFrame.ReserveTerrainUploads(_gl, terrainUploadCount);
|
||||
_clipFrame.UploadRegions(_gl);
|
||||
_entities.SetClipRegionSsbo(_clipFrame.RegionSsbo);
|
||||
_envCells.SetClipRegionSsbo(_clipFrame.RegionSsbo);
|
||||
UploadTerrainClip();
|
||||
}
|
||||
|
||||
public void SetTerrainClip(ReadOnlySpan<Vector4> planes)
|
||||
{
|
||||
_clipFrame.SetTerrainClip(planes);
|
||||
UploadTerrainClip();
|
||||
}
|
||||
|
||||
public void ClearClipRouting() => _entities.ClearClipRouting();
|
||||
|
||||
public void UseIndoorMembershipOnlyRouting()
|
||||
{
|
||||
// Retail viewcone-checks meshes and draws whole cell shells. This clears
|
||||
// any terrain-slice routing before the indoor membership passes.
|
||||
_envCells.SetClipRouting(null);
|
||||
_entities.ClearClipRouting();
|
||||
}
|
||||
|
||||
public void PrepareCellBatches(
|
||||
RetailPViewFrameInput frame,
|
||||
HashSet<uint> visibleCellIds) =>
|
||||
_envCells.PrepareRenderBatches(
|
||||
frame.ViewProjection,
|
||||
frame.CameraWorldPosition,
|
||||
filter: visibleCellIds,
|
||||
centerLbX: frame.RenderCenterLbX,
|
||||
centerLbY: frame.RenderCenterLbY,
|
||||
renderRadius: frame.RenderRadius);
|
||||
|
||||
public void DrawOpaqueCellShells(HashSet<uint> cellIds) =>
|
||||
_envCells.Render(WbRenderPass.Opaque, cellIds);
|
||||
|
||||
public bool CellHasTransparentShell(uint cellId) =>
|
||||
_envCells.CellHasTransparent(cellId);
|
||||
|
||||
public void DrawTransparentCellShells(HashSet<uint> cellIds) =>
|
||||
_envCells.Render(WbRenderPass.Transparent, cellIds);
|
||||
|
||||
public void DrawTransparentCellShellsOrdered(IReadOnlyList<uint> cellIds) =>
|
||||
_envCells.RenderTransparentOrdered(cellIds);
|
||||
|
||||
public void DrawEntityBucket(
|
||||
RetailPViewFrameInput frame,
|
||||
IReadOnlyList<WorldEntity> entities,
|
||||
HashSet<uint>? visibleCellIds)
|
||||
{
|
||||
uint landblockId = frame.PlayerLandblockId ?? 0u;
|
||||
var entry = (
|
||||
landblockId,
|
||||
Vector3.Zero,
|
||||
Vector3.Zero,
|
||||
entities,
|
||||
(IReadOnlyDictionary<uint, WorldEntity>?)null);
|
||||
|
||||
_entities.Draw(
|
||||
frame.Camera,
|
||||
new[] { entry },
|
||||
frame.Frustum,
|
||||
neverCullLandblockId: frame.PlayerLandblockId,
|
||||
visibleCellIds: visibleCellIds,
|
||||
animatedEntityIds: frame.AnimatedEntityIds);
|
||||
}
|
||||
|
||||
public void EmitClipRouteProbe(
|
||||
ClipFrameAssembly clipAssembly,
|
||||
ClipViewSlice slice,
|
||||
int sliceIndex) =>
|
||||
_diagnostics.EmitClipRouteProbe(
|
||||
RenderingDiagnostics.ProbeClipRouteEnabled,
|
||||
_clipFrame,
|
||||
clipAssembly,
|
||||
slice,
|
||||
sliceIndex);
|
||||
|
||||
public void EmitOutStageOwner(
|
||||
WorldEntity entity,
|
||||
Vector3 sphereCenter,
|
||||
float sphereRadius,
|
||||
int sliceIndex,
|
||||
bool passed) =>
|
||||
_diagnostics.EmitOutStageOwner(
|
||||
RenderingDiagnostics.ProbeOutStageEnabled,
|
||||
RenderingDiagnostics.DumpEntitySourceIds,
|
||||
entity,
|
||||
sphereCenter,
|
||||
sphereRadius,
|
||||
sliceIndex,
|
||||
passed);
|
||||
|
||||
public void EmitOutStageRouting(
|
||||
int sliceIndex,
|
||||
IReadOnlyList<WorldEntity> entities,
|
||||
ViewconeCuller viewcone) =>
|
||||
_diagnostics.EmitOutStageRouting(
|
||||
RenderingDiagnostics.ProbeOutStageEnabled,
|
||||
sliceIndex,
|
||||
entities,
|
||||
viewcone);
|
||||
|
||||
public void EmitPhantomObjects(uint cellId, int survivorCount) =>
|
||||
_diagnostics.EmitPhantomObjects(
|
||||
RenderingDiagnostics.ProbePhantomEnabled,
|
||||
cellId,
|
||||
survivorCount);
|
||||
|
||||
public void DrawLandscapeSlice(
|
||||
RetailPViewFrameInput frame,
|
||||
RetailPViewLandscapeSliceContext context)
|
||||
{
|
||||
ClipViewSlice slice = context.Slice;
|
||||
bool scissor = BeginDoorwayScissor(slice.NdcAabb);
|
||||
_diagnostics.EmitClipRouteScissorProbe(
|
||||
RenderingDiagnostics.ProbeClipRouteEnabled,
|
||||
scissor,
|
||||
slice.NdcAabb);
|
||||
|
||||
_clipFrame.BindTerrainClip(_gl);
|
||||
EnableClipDistances();
|
||||
if (frame.RenderSky)
|
||||
{
|
||||
_sky?.RenderSky(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
frame.DayFraction,
|
||||
frame.ActiveDayGroup,
|
||||
frame.SkyKeyframe,
|
||||
frame.EnvironOverrideActive);
|
||||
}
|
||||
|
||||
DisableClipDistances();
|
||||
if (frame.RenderSky && _particles is not null && _particleRenderer is not null)
|
||||
{
|
||||
_particleRenderer.Draw(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
ParticleRenderPass.SkyPreScene);
|
||||
}
|
||||
|
||||
EnableClipDistances();
|
||||
_terrainDiagnostics.Begin();
|
||||
_terrain?.Draw(
|
||||
frame.Camera,
|
||||
frame.Frustum,
|
||||
neverCullLandblockId: frame.PlayerLandblockId,
|
||||
clipPlanes: slice.Planes,
|
||||
ndcClipAabb: slice.NdcAabb);
|
||||
_terrainDiagnostics.Complete();
|
||||
|
||||
DisableClipDistances();
|
||||
if (context.OutdoorEntities.Count > 0)
|
||||
{
|
||||
var sceneryEntry = (
|
||||
frame.PlayerLandblockId ?? 0u,
|
||||
Vector3.Zero,
|
||||
Vector3.Zero,
|
||||
context.OutdoorEntities,
|
||||
(IReadOnlyDictionary<uint, WorldEntity>?)null);
|
||||
_entities.Draw(
|
||||
frame.Camera,
|
||||
new[] { sceneryEntry },
|
||||
frame.Frustum,
|
||||
neverCullLandblockId: frame.PlayerLandblockId,
|
||||
visibleCellIds: null,
|
||||
animatedEntityIds: frame.AnimatedEntityIds);
|
||||
}
|
||||
|
||||
if (scissor)
|
||||
_gl.Disable(EnableCap.ScissorTest);
|
||||
DisableClipDistances();
|
||||
}
|
||||
|
||||
public void DrawLandscapeSliceLate(
|
||||
RetailPViewFrameInput frame,
|
||||
RetailPViewLandscapeLateSliceContext context)
|
||||
{
|
||||
ClipViewSlice slice = context.Slice;
|
||||
bool scissor = BeginDoorwayScissor(slice.NdcAabb);
|
||||
_clipFrame.BindTerrainClip(_gl);
|
||||
|
||||
DisableClipDistances();
|
||||
if (context.Dynamics.Count > 0)
|
||||
{
|
||||
var dynamicsEntry = (
|
||||
frame.PlayerLandblockId ?? 0u,
|
||||
Vector3.Zero,
|
||||
Vector3.Zero,
|
||||
context.Dynamics,
|
||||
(IReadOnlyDictionary<uint, WorldEntity>?)null);
|
||||
_entities.Draw(
|
||||
frame.Camera,
|
||||
new[] { dynamicsEntry },
|
||||
frame.Frustum,
|
||||
neverCullLandblockId: frame.PlayerLandblockId,
|
||||
visibleCellIds: null,
|
||||
animatedEntityIds: frame.AnimatedEntityIds);
|
||||
}
|
||||
|
||||
_particleClassifications.ReplaceOutdoor(context.ParticleOwners);
|
||||
|
||||
_diagnostics.EmitOutStageParticles(
|
||||
RenderingDiagnostics.ProbeOutStageEnabled,
|
||||
_particles,
|
||||
_particleClassifications.Outdoor);
|
||||
|
||||
if (!frame.RootCell.IsOutdoorNode
|
||||
&& _particleClassifications.Outdoor.Count > 0
|
||||
&& _particles is not null
|
||||
&& _particleRenderer is not null)
|
||||
{
|
||||
_particleRenderer.DrawForOwners(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
ParticleRenderPass.Scene,
|
||||
_particleClassifications.Outdoor);
|
||||
}
|
||||
|
||||
EnableClipDistances();
|
||||
if (frame.RenderSky && frame.RenderWeather)
|
||||
{
|
||||
_sky?.RenderWeather(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
frame.DayFraction,
|
||||
frame.ActiveDayGroup,
|
||||
frame.SkyKeyframe,
|
||||
frame.EnvironOverrideActive);
|
||||
DisableClipDistances();
|
||||
if (_particles is not null && _particleRenderer is not null)
|
||||
{
|
||||
_particleRenderer.Draw(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
ParticleRenderPass.SkyPostScene);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DisableClipDistances();
|
||||
}
|
||||
|
||||
if (scissor)
|
||||
_gl.Disable(EnableCap.ScissorTest);
|
||||
DisableClipDistances();
|
||||
}
|
||||
|
||||
public void ClearInteriorDepth()
|
||||
{
|
||||
_gl.Disable(EnableCap.ScissorTest);
|
||||
_gl.DepthMask(true);
|
||||
_gl.Clear(ClearBufferMask.DepthBufferBit);
|
||||
}
|
||||
|
||||
public void DrawExitPortalMask(
|
||||
RetailPViewFrameInput frame,
|
||||
RetailPViewCellSliceContext context) =>
|
||||
DrawPortalDepthWrite(context, frame, forceFarZ: frame.RootCell.IsOutdoorNode);
|
||||
|
||||
public void DrawLookInPortalPunch(
|
||||
RetailPViewFrameInput frame,
|
||||
RetailPViewCellSliceContext context) =>
|
||||
DrawPortalDepthWrite(context, frame, forceFarZ: true);
|
||||
|
||||
public void DrawUnattachedSceneParticles(RetailPViewFrameInput frame)
|
||||
{
|
||||
if (_particles is null || _particleRenderer is null)
|
||||
return;
|
||||
|
||||
DisableClipDistances();
|
||||
_particleRenderer.DrawForOwners(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
ParticleRenderPass.Scene,
|
||||
_noSceneParticleEntityIds,
|
||||
includeUnattached: true);
|
||||
}
|
||||
|
||||
public void FlushLandscapeAlpha() => _alpha.Flush();
|
||||
|
||||
public void DrawCellParticles(
|
||||
RetailPViewFrameInput frame,
|
||||
RetailPViewCellSliceContext context)
|
||||
{
|
||||
if (_particles is null
|
||||
|| _particleRenderer is null
|
||||
|| context.CellEntities.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<uint> visible = _particleClassifications.Visible;
|
||||
visible.Clear();
|
||||
foreach (WorldEntity entity in context.CellEntities)
|
||||
visible.Add(entity.Id);
|
||||
if (visible.Count == 0)
|
||||
return;
|
||||
|
||||
DisableClipDistances();
|
||||
_particleRenderer.DrawForOwners(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
ParticleRenderPass.Scene,
|
||||
visible);
|
||||
DisableClipDistances();
|
||||
}
|
||||
|
||||
public void DrawDynamicsParticles(
|
||||
RetailPViewFrameInput frame,
|
||||
IReadOnlyList<WorldEntity> survivors)
|
||||
{
|
||||
if (_particles is null || _particleRenderer is null || survivors.Count == 0)
|
||||
return;
|
||||
|
||||
HashSet<uint> dynamics = _particleClassifications.Dynamics;
|
||||
dynamics.Clear();
|
||||
foreach (WorldEntity entity in survivors)
|
||||
dynamics.Add(entity.Id);
|
||||
if (dynamics.Count == 0)
|
||||
return;
|
||||
|
||||
DisableClipDistances();
|
||||
_particleRenderer.DrawForOwners(
|
||||
frame.Camera,
|
||||
frame.CameraWorldPosition,
|
||||
ParticleRenderPass.Scene,
|
||||
dynamics);
|
||||
DisableClipDistances();
|
||||
}
|
||||
|
||||
public void EmitDiagnostics(
|
||||
RetailPViewFrameInput frame,
|
||||
RetailPViewFrameResult result) =>
|
||||
_diagnostics.EmitRetailPViewDiagnostics(
|
||||
RenderingDiagnostics.ProbeViewerEnabled,
|
||||
RenderingDiagnostics.ProbeVisibilityEnabled,
|
||||
RenderingDiagnostics.ProbeFlapEnabled,
|
||||
result,
|
||||
frame.RootCell,
|
||||
frame.ViewerCellId,
|
||||
frame.PlayerCellId,
|
||||
frame.CameraWorldPosition,
|
||||
frame.PlayerViewPosition,
|
||||
frame.CameraView,
|
||||
frame.CameraCellResolution);
|
||||
|
||||
private void UploadTerrainClip()
|
||||
{
|
||||
TerrainClipBufferBinding binding = _clipFrame.UploadTerrainClip(_gl);
|
||||
_terrain?.SetClipUbo(binding);
|
||||
}
|
||||
|
||||
private void DrawPortalDepthWrite(
|
||||
RetailPViewCellSliceContext context,
|
||||
RetailPViewFrameInput frame,
|
||||
bool forceFarZ)
|
||||
{
|
||||
// Retail D3DPolyRender::DrawPortalPolyInternal @ 0x0059BC90.
|
||||
// Main interior roots stamp true depth (seal); outdoor and look-in
|
||||
// apertures stamp far depth (punch). The renderer owns that choice.
|
||||
if (_portalDepthMask is null)
|
||||
return;
|
||||
LoadedCell? cell = frame.Cells.Find(context.CellId);
|
||||
if (cell is null)
|
||||
return;
|
||||
|
||||
Span<Vector3> world = stackalloc Vector3[32];
|
||||
for (int index = 0; index < cell.Portals.Count; index++)
|
||||
{
|
||||
if (cell.Portals[index].OtherCellId != 0xFFFF)
|
||||
continue;
|
||||
if (index >= cell.PortalPolygons.Count)
|
||||
break;
|
||||
Vector3[] localVertices = cell.PortalPolygons[index];
|
||||
if (localVertices.Length < 3)
|
||||
continue;
|
||||
|
||||
int count = Math.Min(localVertices.Length, world.Length);
|
||||
for (int vertex = 0; vertex < count; vertex++)
|
||||
{
|
||||
world[vertex] = Vector3.Transform(
|
||||
localVertices[vertex],
|
||||
cell.WorldTransform);
|
||||
world[vertex].Z += PortalVisibilityBuilder.ShellDrawLiftZ;
|
||||
}
|
||||
|
||||
_diagnostics.EmitSeamMask(
|
||||
RenderingDiagnostics.ProbeSeamDrawEnabled,
|
||||
RenderingDiagnostics.SeamDrawTargetCells,
|
||||
context.CellId,
|
||||
index,
|
||||
forceFarZ,
|
||||
world[..count]);
|
||||
|
||||
_portalDepthMask.DrawDepthFan(
|
||||
world[..count],
|
||||
frame.ViewProjection,
|
||||
context.Slice.Planes,
|
||||
forceFarZ);
|
||||
}
|
||||
}
|
||||
|
||||
private bool BeginDoorwayScissor(Vector4 ndcAabb)
|
||||
{
|
||||
RetailPViewFramebufferSize framebuffer = _framebuffer.Capture();
|
||||
var box = NdcScissorRect.ToPixels(
|
||||
ndcAabb,
|
||||
framebuffer.Width,
|
||||
framebuffer.Height);
|
||||
_gl.Enable(EnableCap.ScissorTest);
|
||||
_gl.Scissor(box.X, box.Y, (uint)box.Width, (uint)box.Height);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void EnableClipDistances()
|
||||
{
|
||||
for (int index = 0; index < ClipFrame.MaxPlanes; index++)
|
||||
_gl.Enable(EnableCap.ClipDistance0 + index);
|
||||
}
|
||||
|
||||
private void DisableClipDistances()
|
||||
{
|
||||
for (int index = 0; index < ClipFrame.MaxPlanes; index++)
|
||||
_gl.Disable(EnableCap.ClipDistance0 + index);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue