@ major change in the way sector surfaces are allocated and rendered for optimization

@ white texture has been moved to the DataManager and is now also available to plugins
This commit is contained in:
codeimp 2009-06-04 20:21:31 +00:00
parent 6144b9b7ad
commit 45ad5f5943
9 changed files with 864 additions and 246 deletions

View file

@ -676,6 +676,9 @@
<Compile Include="General\ErrorLogger.cs" />
<Compile Include="IO\DoomColormapReader.cs" />
<Compile Include="Map\SelectionType.cs" />
<Compile Include="Rendering\SurfaceBufferSet.cs" />
<Compile Include="Rendering\SurfaceEntry.cs" />
<Compile Include="Rendering\SurfaceManager.cs" />
<Compile Include="Windows\ErrorsForm.cs">
<SubType>Form</SubType>
</Compile>

View file

@ -81,6 +81,7 @@ namespace CodeImp.DoomBuilder.Data
private ImageData crosshairbusy;
private Dictionary<string, ImageData> internalsprites;
private ImageData thingbox;
private ImageData whitetexture;
// Used images
private Dictionary<long, long> usedimages;
@ -113,6 +114,7 @@ namespace CodeImp.DoomBuilder.Data
public ImageData Crosshair3D { get { return crosshair; } }
public ImageData CrosshairBusy3D { get { return crosshairbusy; } }
public ImageData ThingBox { get { return thingbox; } }
public ImageData WhiteTexture { get { return whitetexture; } }
public List<ThingCategory> ThingCategories { get { return thingcategories; } }
public ICollection<ThingTypeInfo> ThingTypes { get { return thingtypes.Values; } }
internal ICollection<MatchingTextureSet> TextureSets { get { return texturesets; } }
@ -157,8 +159,12 @@ namespace CodeImp.DoomBuilder.Data
crosshairbusy.LoadImage();
thingbox = new ResourceImage("ThingBox.png");
thingbox.LoadImage();
whitetexture = new ResourceImage("White.png");
whitetexture.UseColorCorrection = false;
whitetexture.LoadImage();
whitetexture.CreateTexture();
}
// Disposer
internal void Dispose()
{
@ -179,6 +185,8 @@ namespace CodeImp.DoomBuilder.Data
crosshairbusy = null;
thingbox.Dispose();
thingbox = null;
whitetexture.Dispose();
whitetexture = null;
// Done
isdisposed = true;

View file

@ -1392,6 +1392,9 @@ namespace CodeImp.DoomBuilder
// Can't have a selection in an old map set
map.ClearAllSelected();
// Reset surfaces
renderer2d.Surfaces.Reset();
// Apply
map.Dispose();
map = newmap;

View file

@ -891,9 +891,18 @@ namespace CodeImp.DoomBuilder.Map
{
// Update all linedefs
if(dolines) foreach(Linedef l in linedefs) l.UpdateCache();
// Update all sectors
if(dosectors) foreach(Sector s in sectors) s.UpdateCache();
if(dosectors)
{
foreach(Sector s in sectors) s.Triangulate();
General.Map.CRenderer2D.Surfaces.AllocateBuffers();
foreach(Sector s in sectors) s.CreateSurfaces();
General.Map.CRenderer2D.Surfaces.UnlockBuffers();
}
}
/// <summary>

View file

@ -33,7 +33,7 @@ using SlimDX;
namespace CodeImp.DoomBuilder.Map
{
public sealed class Sector : SelectableElement, ID3DResource
public sealed class Sector : SelectableElement
{
#region ================== Constants
@ -74,8 +74,7 @@ namespace CodeImp.DoomBuilder.Map
private Triangulation triangles;
private FlatVertex[] flatvertices;
private ReadOnlyCollection<LabelPositionInfo> labels;
private VertexBuffer flatceilingbuffer;
private VertexBuffer flatfloorbuffer;
private SurfaceEntry surfaceentry;
#endregion
@ -103,8 +102,6 @@ namespace CodeImp.DoomBuilder.Map
internal int SerializedIndex { get { return serializedindex; } set { serializedindex = value; } }
public Triangulation Triangles { get { return triangles; } }
public FlatVertex[] FlatVertices { get { return flatvertices; } }
internal VertexBuffer FlatCeilingBuffer { get { return flatceilingbuffer; } }
internal VertexBuffer FlatFloorBuffer { get { return flatfloorbuffer; } }
public ReadOnlyCollection<LabelPositionInfo> Labels { get { return labels; } }
#endregion
@ -125,8 +122,6 @@ namespace CodeImp.DoomBuilder.Map
this.longceiltexname = MapSet.EmptyLongName;
this.triangulationneeded = true;
General.Map.Graphics.RegisterResource(this);
// We have no destructor
GC.SuppressFinalize(this);
}
@ -141,8 +136,6 @@ namespace CodeImp.DoomBuilder.Map
this.triangulationneeded = true;
ReadWrite(stream);
General.Map.Graphics.RegisterResource(this);
// We have no destructor
GC.SuppressFinalize(this);
@ -167,13 +160,10 @@ namespace CodeImp.DoomBuilder.Map
// because a sidedef cannot exist without reference to its sector.
foreach(Sidedef sd in sidedefs) sd.Dispose();
General.Map.Graphics.UnregisterResource(this);
// Free surface entry
General.Map.CRenderer2D.Surfaces.FreeSurfaces(surfaceentry);
// Clean up
if(flatceilingbuffer != null) flatceilingbuffer.Dispose();
if(flatfloorbuffer != null) flatfloorbuffer.Dispose();
flatceilingbuffer = null;
flatfloorbuffer = null;
mainlistitem = null;
sidedefs = null;
map = null;
@ -296,10 +286,9 @@ namespace CodeImp.DoomBuilder.Map
}
}
// This updates the sector when changes have been made
public void UpdateCache()
// This triangulates the sector geometry
internal void Triangulate()
{
// Update if needed
if(updateneeded)
{
// Triangulate again?
@ -308,11 +297,23 @@ namespace CodeImp.DoomBuilder.Map
// Triangulate sector
triangles = Triangulation.Create(this);
triangulationneeded = false;
updateneeded = true;
// Make label positions
labels = Array.AsReadOnly<LabelPositionInfo>(Tools.FindLabelPositions(this).ToArray());
// Number of vertices changed?
if((surfaceentry != null) && (triangles.Vertices.Count != surfaceentry.numvertices))
General.Map.CRenderer2D.Surfaces.FreeSurfaces(surfaceentry);
}
}
}
// This makes new vertices as well as floor and ceiling surfaces
internal void CreateSurfaces()
{
if(updateneeded)
{
// Brightness color (alpha is opaque)
byte clampedbright = 0;
if((brightness >= 0) && (brightness <= 255)) clampedbright = (byte)brightness;
@ -335,120 +336,71 @@ namespace CodeImp.DoomBuilder.Map
// Create bounding box
bbox = CreateBBox();
// Make a dummy entry if we don't have one yet
if(surfaceentry == null) surfaceentry = new SurfaceEntry(-1, -1, -1);
// Create floor vertices
FlatVertex[] floorvertices = new FlatVertex[flatvertices.Length];
flatvertices.CopyTo(floorvertices, 0);
General.Plugins.OnSectorFloorSurfaceUpdate(this, ref floorvertices);
surfaceentry.floorvertices = floorvertices;
surfaceentry.floortexture = longfloortexname;
// Create ceiling vertices
FlatVertex[] ceilvertices = new FlatVertex[flatvertices.Length];
flatvertices.CopyTo(ceilvertices, 0);
General.Plugins.OnSectorCeilingSurfaceUpdate(this, ref ceilvertices);
surfaceentry.ceilvertices = ceilvertices;
surfaceentry.ceiltexture = longceiltexname;
// Update entry
surfaceentry = General.Map.CRenderer2D.Surfaces.UpdateSurfaces(surfaceentry);
// Updated
updateneeded = false;
// Update buffers
UpdateFloorSurface();
UpdateCeilingSurface();
}
}
// This updates the buffer with flat vertices
// This updates the floor surface
public void UpdateFloorSurface()
{
// Trash buffer, if any
if(flatfloorbuffer != null)
{
flatfloorbuffer.Dispose();
flatfloorbuffer = null;
}
// Not updated?
if(updateneeded)
{
// Make sure the sector is up-to-date
// This will automatically call this function again
UpdateCache();
}
// Any vertices?
else if(flatvertices.Length > 0)
{
if(General.Map.Graphics.CheckAvailability())
{
FlatVertex[] buffervertices = new FlatVertex[triangles.Vertices.Count];
flatvertices.CopyTo(buffervertices, 0);
// Raise event to allow plugins to modify this data
General.Plugins.OnSectorFloorSurfaceUpdate(this, ref buffervertices);
// Make the buffer
flatfloorbuffer = new VertexBuffer(General.Map.Graphics.Device, FlatVertex.Stride * buffervertices.Length,
Usage.WriteOnly | Usage.Dynamic, VertexFormat.None, Pool.Default);
// Fill it
DataStream bufferstream = flatfloorbuffer.Lock(0, FlatVertex.Stride * buffervertices.Length, LockFlags.Discard);
bufferstream.WriteRange<FlatVertex>(buffervertices);
flatfloorbuffer.Unlock();
bufferstream.Dispose();
}
}
// Create floor vertices
FlatVertex[] floorvertices = new FlatVertex[flatvertices.Length];
flatvertices.CopyTo(floorvertices, 0);
General.Plugins.OnSectorFloorSurfaceUpdate(this, ref floorvertices);
surfaceentry.floorvertices = floorvertices;
surfaceentry.floortexture = longfloortexname;
// Update entry
surfaceentry = General.Map.CRenderer2D.Surfaces.UpdateSurfaces(surfaceentry);
General.Map.CRenderer2D.Surfaces.UnlockBuffers();
}
// This updates the buffer with flat vertices
// This updates the ceiling surface
public void UpdateCeilingSurface()
{
// Trash buffer, if any
if(flatceilingbuffer != null)
{
flatceilingbuffer.Dispose();
flatceilingbuffer = null;
}
// Create ceiling vertices
FlatVertex[] ceilvertices = new FlatVertex[flatvertices.Length];
flatvertices.CopyTo(ceilvertices, 0);
General.Plugins.OnSectorCeilingSurfaceUpdate(this, ref ceilvertices);
surfaceentry.ceilvertices = ceilvertices;
surfaceentry.ceiltexture = longceiltexname;
// Not updated?
// Update entry
surfaceentry = General.Map.CRenderer2D.Surfaces.UpdateSurfaces(surfaceentry);
General.Map.CRenderer2D.Surfaces.UnlockBuffers();
}
// This updates the sector when changes have been made
public void UpdateCache()
{
// Update if needed
if(updateneeded)
{
// Make sure the sector is up-to-date
// This will automatically call this function again
UpdateCache();
Triangulate();
CreateSurfaces();
}
// Any vertices?
else if(flatvertices.Length > 0)
{
if(General.Map.Graphics.CheckAvailability())
{
FlatVertex[] buffervertices = new FlatVertex[triangles.Vertices.Count];
flatvertices.CopyTo(buffervertices, 0);
// Raise event to allow plugins to modify this data
General.Plugins.OnSectorCeilingSurfaceUpdate(this, ref buffervertices);
// Make the buffer
flatceilingbuffer = new VertexBuffer(General.Map.Graphics.Device, FlatVertex.Stride * buffervertices.Length,
Usage.WriteOnly | Usage.Dynamic, VertexFormat.None, Pool.Default);
// Fill it
DataStream bufferstream = flatceilingbuffer.Lock(0, FlatVertex.Stride * buffervertices.Length, LockFlags.Discard);
bufferstream.WriteRange<FlatVertex>(buffervertices);
flatceilingbuffer.Unlock();
bufferstream.Dispose();
}
}
}
// Unload unstable resources
public void UnloadResource()
{
// Trash buffer, if any
if(flatfloorbuffer != null)
{
flatfloorbuffer.Dispose();
flatfloorbuffer = null;
}
// Trash buffer, if any
if(flatceilingbuffer != null)
{
flatceilingbuffer.Dispose();
flatceilingbuffer = null;
}
}
// Reload unstable resources
public void ReloadResource()
{
UpdateFloorSurface();
UpdateCeilingSurface();
}
// Selected

View file

@ -106,9 +106,11 @@ namespace CodeImp.DoomBuilder.Rendering
private bool thingsfront;
private int vertexsize;
private RenderLayers renderlayer = RenderLayers.None;
// Surfaces
private SurfaceManager surfaces;
// Images
private ResourceImage whitetexture;
private ResourceImage[] thingtexture;
// View settings (world coordinates)
@ -139,6 +141,7 @@ namespace CodeImp.DoomBuilder.Rendering
public float Scale { get { return scale; } }
public int VertexSize { get { return vertexsize; } }
public ViewMode ViewMode { get { return viewmode; } }
public SurfaceManager Surfaces { get { return surfaces; } }
#endregion
@ -157,11 +160,8 @@ namespace CodeImp.DoomBuilder.Rendering
thingtexture[i].CreateTexture();
}
// Load white texture
whitetexture = new ResourceImage("White.png");
whitetexture.UseColorCorrection = false;
whitetexture.LoadImage();
whitetexture.CreateTexture();
// Create surface manager
surfaces = new SurfaceManager();
// Create rendertargets
CreateRendertargets();
@ -179,7 +179,9 @@ namespace CodeImp.DoomBuilder.Rendering
// Destroy rendertargets
DestroyRendertargets();
foreach(ResourceImage i in thingtexture) i.Dispose();
whitetexture.Dispose();
// Dispose surface manager
surfaces.Dispose();
// Done
base.Dispose();
@ -1138,12 +1140,33 @@ namespace CodeImp.DoomBuilder.Rendering
// Set transformations
UpdateTransformations();
// Render what must be rendered
// Set states
graphics.Device.SetRenderState(RenderState.CullMode, Cull.None);
graphics.Device.SetRenderState(RenderState.ZEnable, false);
graphics.Device.SetRenderState(RenderState.AlphaBlendEnable, false);
graphics.Device.SetRenderState(RenderState.AlphaTestEnable, false);
graphics.Device.SetRenderState(RenderState.TextureFactor, -1);
graphics.Device.SetRenderState(RenderState.FogEnable, false);
SetWorldTransformation(true);
graphics.Shaders.Display2D.SetSettings(1f, 1f, 0f, 1f, General.Settings.ClassicBilinear);
// Prepare for rendering
switch(viewmode)
{
case ViewMode.Brightness: RenderSectorBrightness(General.Map.Map.Sectors); break;
case ViewMode.FloorTextures: RenderSectorFloors(General.Map.Map.Sectors); break;
case ViewMode.CeilingTextures: RenderSectorCeilings(General.Map.Map.Sectors); break;
case ViewMode.Brightness:
surfaces.RenderSectorBrightness();
surfaces.RenderSectorSurfaces(graphics);
break;
case ViewMode.FloorTextures:
surfaces.RenderSectorFloors();
surfaces.RenderSectorSurfaces(graphics);
break;
case ViewMode.CeilingTextures:
surfaces.RenderSectorCeilings();
surfaces.RenderSectorSurfaces(graphics);
break;
}
}
}
@ -1152,112 +1175,6 @@ namespace CodeImp.DoomBuilder.Rendering
Finish();
}
// This renders all sector floors
private void RenderSectorFloors(ICollection<Sector> sectors)
{
// Set states
graphics.Device.SetRenderState(RenderState.CullMode, Cull.None);
graphics.Device.SetRenderState(RenderState.ZEnable, false);
graphics.Device.SetRenderState(RenderState.AlphaBlendEnable, false);
graphics.Device.SetRenderState(RenderState.AlphaTestEnable, false);
graphics.Device.SetRenderState(RenderState.TextureFactor, -1);
graphics.Device.SetRenderState(RenderState.FogEnable, false);
SetWorldTransformation(true);
graphics.Shaders.Display2D.SetSettings(1f, 1f, 0f, 1f, General.Settings.ClassicBilinear);
// Render all sectors
foreach(Sector s in sectors)
RenderSectorSurface(s, s.FlatFloorBuffer, s.LongFloorTexture);
}
// This renders all sector ceilings
private void RenderSectorCeilings(ICollection<Sector> sectors)
{
// Set states
graphics.Device.SetRenderState(RenderState.CullMode, Cull.None);
graphics.Device.SetRenderState(RenderState.ZEnable, false);
graphics.Device.SetRenderState(RenderState.AlphaBlendEnable, false);
graphics.Device.SetRenderState(RenderState.AlphaTestEnable, false);
graphics.Device.SetRenderState(RenderState.TextureFactor, -1);
graphics.Device.SetRenderState(RenderState.FogEnable, false);
SetWorldTransformation(true);
graphics.Shaders.Display2D.SetSettings(1f, 1f, 0f, 1f, General.Settings.ClassicBilinear);
// Render all sectors
foreach(Sector s in sectors)
RenderSectorSurface(s, s.FlatCeilingBuffer, s.LongCeilTexture);
}
// This renders all sector brightness levels
private void RenderSectorBrightness(ICollection<Sector> sectors)
{
// Set states
graphics.Device.SetRenderState(RenderState.CullMode, Cull.None);
graphics.Device.SetRenderState(RenderState.ZEnable, false);
graphics.Device.SetRenderState(RenderState.AlphaBlendEnable, false);
graphics.Device.SetRenderState(RenderState.AlphaTestEnable, false);
graphics.Device.SetRenderState(RenderState.TextureFactor, -1);
graphics.Device.SetRenderState(RenderState.FogEnable, false);
SetWorldTransformation(true);
graphics.Shaders.Display2D.SetSettings(1f, 1f, 0f, 1f, General.Settings.ClassicBilinear);
// Render all sectors
foreach(Sector s in sectors)
RenderSectorSurface(s, s.FlatFloorBuffer, 0);
}
// This renders the geometry and tecture of the sector
private void RenderSectorSurface(Sector s, VertexBuffer buffer, long longimagename)
{
Texture t = null;
if((buffer != null) && (s.FlatVertices != null) && (s.FlatVertices.Length > 0))
{
if(longimagename == 0)
{
t = whitetexture.Texture;
}
else
{
ImageData img = General.Map.Data.GetFlatImage(longimagename);
if(img != null)
{
// Texture unknown?
if(img is UnknownImage)
{
t = General.Map.Data.UnknownTexture3D.Texture;
}
// Is the texture loaded?
else if(img.IsImageLoaded && !img.LoadFailed)
{
if(img.Texture == null) img.CreateTexture();
t = img.Texture;
}
else
{
t = whitetexture.Texture;
}
}
else
{
t = whitetexture.Texture;
}
}
// Set renderstates for rendering
graphics.Shaders.Display2D.Texture1 = t;
graphics.Device.SetTexture(0, t);
graphics.Device.SetStreamSource(0, buffer, 0, FlatVertex.Stride);
// Draw
graphics.Shaders.Display2D.Begin();
graphics.Shaders.Display2D.BeginPass(1);
graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, 0, s.FlatVertices.Length / 3);
graphics.Shaders.Display2D.EndPass();
graphics.Shaders.Display2D.End();
}
}
#endregion
#region ================== Overlay
@ -1279,7 +1196,7 @@ namespace CodeImp.DoomBuilder.Rendering
}
else
{
t = whitetexture.Texture;
t = General.Map.Data.WhiteTexture.Texture;
}
// Set renderstates for rendering
@ -1385,8 +1302,8 @@ namespace CodeImp.DoomBuilder.Rendering
graphics.Device.SetRenderState(RenderState.TextureFactor, -1);
graphics.Device.SetRenderState(RenderState.FogEnable, false);
SetWorldTransformation(false);
graphics.Device.SetTexture(0, whitetexture.Texture);
graphics.Shaders.Display2D.Texture1 = whitetexture.Texture;
graphics.Device.SetTexture(0, General.Map.Data.WhiteTexture.Texture);
graphics.Shaders.Display2D.Texture1 = General.Map.Data.WhiteTexture.Texture;
graphics.Shaders.Display2D.SetSettings(1f, 1f, 0f, 1f, General.Settings.ClassicBilinear);
// Draw
@ -1424,8 +1341,8 @@ namespace CodeImp.DoomBuilder.Rendering
graphics.Device.SetRenderState(RenderState.TextureFactor, -1);
graphics.Device.SetRenderState(RenderState.FogEnable, false);
SetWorldTransformation(false);
graphics.Device.SetTexture(0, whitetexture.Texture);
graphics.Shaders.Display2D.Texture1 = whitetexture.Texture;
graphics.Device.SetTexture(0, General.Map.Data.WhiteTexture.Texture);
graphics.Shaders.Display2D.Texture1 = General.Map.Data.WhiteTexture.Texture;
graphics.Shaders.Display2D.SetSettings(1f, 1f, 0f, 1f, General.Settings.ClassicBilinear);
// Draw
@ -1478,8 +1395,8 @@ namespace CodeImp.DoomBuilder.Rendering
graphics.Device.SetRenderState(RenderState.TextureFactor, -1);
graphics.Device.SetRenderState(RenderState.FogEnable, false);
SetWorldTransformation(false);
graphics.Device.SetTexture(0, whitetexture.Texture);
graphics.Shaders.Display2D.Texture1 = whitetexture.Texture;
graphics.Device.SetTexture(0, General.Map.Data.WhiteTexture.Texture);
graphics.Shaders.Display2D.Texture1 = General.Map.Data.WhiteTexture.Texture;
graphics.Shaders.Display2D.SetSettings(1f, 1f, 0f, 1f, General.Settings.ClassicBilinear);
// Draw

View file

@ -0,0 +1,59 @@

#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
using System.Drawing;
using System.ComponentModel;
using CodeImp.DoomBuilder.Map;
using SlimDX.Direct3D9;
using SlimDX;
using CodeImp.DoomBuilder.Geometry;
using System.Drawing.Imaging;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Editing;
using Configuration = CodeImp.DoomBuilder.IO.Configuration;
#endregion
namespace CodeImp.DoomBuilder.Rendering
{
internal struct SurfaceBufferSet
{
// The number of vertices per sector that this set is for
public int numvertices;
// These are the vertex buffers. They are hashed by an integer key which
// is the number of vertices per sector geometry the buffer if meant for.
public List<VertexBuffer> buffers;
public List<int> buffersizes;
// These are the entries that contain information for the contents of the buffers.
public List<SurfaceEntry> entries;
// These are the empty entries in buferrs that are available.
public List<SurfaceEntry> holes;
}
}

View file

@ -0,0 +1,83 @@
#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
using System.Drawing;
using System.ComponentModel;
using CodeImp.DoomBuilder.Map;
using SlimDX.Direct3D9;
using SlimDX;
using CodeImp.DoomBuilder.Geometry;
using System.Drawing.Imaging;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Editing;
using Configuration = CodeImp.DoomBuilder.IO.Configuration;
#endregion
namespace CodeImp.DoomBuilder.Rendering
{
// This is an entry is the surface manager and contains the information
// needed for a sector to place it's ceiling and floor surface geometry
// in a vertexbuffer. Sectors keep a reference to this entry to tell the
// surface manager to remove them if needed.
internal class SurfaceEntry
{
// Number of vertices in the geometry and index of the buffer
// This tells the surface manager which vertexbuffer this is in.
public int numvertices;
public int bufferindex;
// Offset in the buffer (in number of vertices)
public int vertexoffset;
// Sector geometry (local copy used to quickly refill buffers)
// The sector must set these!
public FlatVertex[] floorvertices;
public FlatVertex[] ceilvertices;
// Sector images
// The sector must set these!
public long floortexture;
public long ceiltexture;
// Constructor
internal SurfaceEntry(int numvertices, int bufferindex, int vertexoffset)
{
this.numvertices = numvertices;
this.bufferindex = bufferindex;
this.vertexoffset = vertexoffset;
}
// Constructor that copies the entry, but does not copy the vertices
internal SurfaceEntry(SurfaceEntry oldentry)
{
this.numvertices = oldentry.numvertices;
this.bufferindex = oldentry.bufferindex;
this.vertexoffset = oldentry.vertexoffset;
}
}
}

View file

@ -0,0 +1,584 @@
#region ================== Copyright (c) 2007 Pascal vd Heiden
/*
* Copyright (c) 2007 Pascal vd Heiden, www.codeimp.com
* This program is released under GNU General Public License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#endregion
#region ================== Namespaces
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
using System.Drawing;
using System.ComponentModel;
using CodeImp.DoomBuilder.Map;
using SlimDX.Direct3D9;
using SlimDX;
using CodeImp.DoomBuilder.Geometry;
using System.Drawing.Imaging;
using CodeImp.DoomBuilder.Data;
using CodeImp.DoomBuilder.Editing;
using Configuration = CodeImp.DoomBuilder.IO.Configuration;
#endregion
namespace CodeImp.DoomBuilder.Rendering
{
internal class SurfaceManager : ID3DResource
{
#region ================== Constants
// The true maximum lies at 65535 if I remember correctly, but that
// is a scary big number for a vertexbuffer.
private const int MAX_VERTICES_PER_BUFFER = 30000;
#endregion
#region ================== Variables
// Set of buffers for a specific number of vertices per sector
private Dictionary<int, SurfaceBufferSet> sets;
// List of buffers that are locked
// This is null when not in the process of updating
private List<VertexBuffer> lockedbuffers;
// Surface to be rendered.
// Each BinaryHeap in the Dictionary contains all geometry that needs
// to be rendered with the associated ImageData.
// The BinaryHeap sorts the geometry by sector to minimize stream switchs.
// This is null when not in the process of rendering
private Dictionary<ImageData, List<SurfaceEntry>> surfaces;
// This is 1 to add the number of vertices to the offset
// (effectively rendering the ceiling vertices instead of floor vertices)
private int surfacevertexoffsetmul;
// This is set to true when the resources have been unloaded
private bool resourcesunloaded;
#endregion
#region ================== Properties
#endregion
#region ================== Constructor / Disposer
// Constructor
public SurfaceManager()
{
sets = new Dictionary<int, SurfaceBufferSet>();
lockedbuffers = new List<VertexBuffer>();
General.Map.Graphics.RegisterResource(this);
}
// Disposer
public void Dispose()
{
if(sets != null)
{
General.Map.Graphics.UnregisterResource(this);
// Dispose all sets
foreach(KeyValuePair<int, SurfaceBufferSet> set in sets)
{
// Dispose vertex buffers
foreach(VertexBuffer vb in set.Value.buffers)
vb.Dispose();
}
sets = null;
}
}
#endregion
#region ================== Management
// Called when all resource must be unloaded
public void UnloadResource()
{
resourcesunloaded = true;
foreach(KeyValuePair<int, SurfaceBufferSet> set in sets)
{
foreach(VertexBuffer vb in set.Value.buffers)
vb.Dispose();
set.Value.buffers.Clear();
}
}
// Called when all resource must be reloaded
public void ReloadResource()
{
foreach(KeyValuePair<int, SurfaceBufferSet> set in sets)
{
// Rebuild vertex buffers
for(int i = 0; i < set.Value.buffersizes.Count; i++)
{
// Make the new buffer!
VertexBuffer b = new VertexBuffer(General.Map.Graphics.Device, FlatVertex.Stride * set.Value.buffersizes[i],
Usage.None, VertexFormat.None, Pool.Default);
// Start refilling the buffer with sector geometry
int vertexoffset = 0;
DataStream bstream = b.Lock(0, FlatVertex.Stride * set.Value.buffersizes[i], LockFlags.Discard);
foreach(SurfaceEntry e in set.Value.entries)
{
if(e.bufferindex == i)
{
// Fill buffer
bstream.Seek(e.vertexoffset * FlatVertex.Stride, SeekOrigin.Begin);
bstream.WriteRange(e.floorvertices);
bstream.WriteRange(e.ceilvertices);
}
}
// Unlock buffer
b.Unlock();
bstream.Dispose();
// Add to list
set.Value.buffers.Add(b);
}
}
resourcesunloaded = false;
}
// This resets all buffers and requires all sectors to get new entries
public void Reset()
{
// Clear all items
foreach(KeyValuePair<int, SurfaceBufferSet> set in sets)
{
foreach(SurfaceEntry entry in set.Value.entries)
{
entry.numvertices = -1;
entry.bufferindex = -1;
}
foreach(SurfaceEntry entry in set.Value.holes)
{
entry.numvertices = -1;
entry.bufferindex = -1;
}
foreach(VertexBuffer vb in set.Value.buffers)
vb.Dispose();
}
// New dictionary
sets = new Dictionary<int, SurfaceBufferSet>();
}
// Updating sector surface geometry should go in this order;
// - Triangulate sectors
// - Call FreeSurfaces to remove entries that have changed number of vertices
// - Call AllocateBuffers
// - Call UpdateSurfaces to add/update entries
// - Call UnlockBuffers
// This (re)allocates the buffers based on an analysis of the map
// The map must be updated (triangulated) before calling this
public void AllocateBuffers()
{
// Make analysis of sector geometry
Dictionary<int, int> sectorverts = new Dictionary<int, int>();
foreach(Sector s in General.Map.Map.Sectors)
{
if(s.Triangles != null)
{
// We count the number of sectors that have specific number of vertices
if(!sectorverts.ContainsKey(s.Triangles.Vertices.Count))
sectorverts.Add(s.Triangles.Vertices.Count, 0);
sectorverts[s.Triangles.Vertices.Count]++;
}
}
// Now (re)allocate the needed buffers
foreach(KeyValuePair<int, int> sv in sectorverts)
{
// Zero vertices can't be drawn
if(sv.Key > 0)
{
SurfaceBufferSet set = GetSet(sv.Key);
// Calculte how many free entries we need
int neededentries = sv.Value;
int freeentriesneeded = neededentries - set.entries.Count;
// Allocate the space needed
EnsureFreeBufferSpace(set, freeentriesneeded);
}
}
}
// This ensures there is enough space for a given number of free entries (also adds new bufers if needed)
private void EnsureFreeBufferSpace(SurfaceBufferSet set, int freeentries)
{
DataStream bstream;
// Check if we have to add entries
int addentries = freeentries - set.holes.Count;
// Begin resizing buffers starting with the last in this set
int bufferindex = set.buffers.Count - 1;
// Calculate the maximum number of entries we can put in a new buffer
// Note that verticesperentry is the number of vertices multiplied by 2, because
// we have to store both the floor and ceiling
int verticesperentry = set.numvertices * 2;
int maxentriesperbuffer = MAX_VERTICES_PER_BUFFER / verticesperentry;
while(addentries > 0)
{
// Create a new buffer?
if((bufferindex == -1) || (bufferindex > (set.buffers.Count - 1)))
{
// Determine the number of entries we will be making this buffer for
int bufferentries = (addentries > maxentriesperbuffer) ? maxentriesperbuffer : addentries;
// Calculate the number of vertices that will be
int buffernumvertices = bufferentries * verticesperentry;
// Make the new buffer!
VertexBuffer b = new VertexBuffer(General.Map.Graphics.Device, FlatVertex.Stride * buffernumvertices,
Usage.None, VertexFormat.None, Pool.Default);
// Add it. Also add available entries as holes, because they are not used yet.
set.buffers.Add(b);
set.buffersizes.Add(buffernumvertices);
for(int i = 0; i < bufferentries; i++)
set.holes.Add(new SurfaceEntry(set.numvertices, set.buffers.Count - 1, i * verticesperentry));
// Done
addentries -= bufferentries;
}
// Reallocate a buffer
else
{
// Trash the old buffer
if(set.buffers[bufferindex].Tag != null)
{
bstream = (DataStream)set.buffers[bufferindex].Tag;
set.buffers[bufferindex].Unlock();
bstream.Dispose();
set.buffers[bufferindex].Tag = null;
}
set.buffers[bufferindex].Dispose();
// Get the entries that are in this buffer only
List<SurfaceEntry> theseentries = new List<SurfaceEntry>();
foreach(SurfaceEntry e in set.entries)
{
if(e.bufferindex == bufferindex)
theseentries.Add(e);
}
// Determine the number of entries we will be making this buffer for
int bufferentries = ((theseentries.Count + addentries) > maxentriesperbuffer) ? maxentriesperbuffer : (theseentries.Count + addentries);
// Calculate the number of vertices that will be
int buffernumvertices = bufferentries * verticesperentry;
// Make the new buffer!
VertexBuffer b = new VertexBuffer(General.Map.Graphics.Device, FlatVertex.Stride * buffernumvertices,
Usage.None, VertexFormat.None, Pool.Default);
// Start refilling the buffer with sector geometry
int vertexoffset = 0;
bstream = b.Lock(0, FlatVertex.Stride * theseentries.Count * verticesperentry, LockFlags.Discard);
foreach(SurfaceEntry e in theseentries)
{
// Fill buffer
bstream.WriteRange(e.floorvertices);
bstream.WriteRange(e.ceilvertices);
// Set the new location in the buffer
e.vertexoffset = vertexoffset;
// Move on
vertexoffset += verticesperentry;
}
// Unlock buffer
b.Unlock();
bstream.Dispose();
// Set the new buffer and add available entries as holes, because they are not used yet.
set.buffers[bufferindex] = b;
set.buffersizes[bufferindex] = buffernumvertices;
set.holes.Clear();
for(int i = 0; i < bufferentries - theseentries.Count; i++)
set.holes.Add(new SurfaceEntry(set.numvertices, bufferindex, i * verticesperentry + vertexoffset));
// Done
addentries -= bufferentries;
}
// Always continue in next (new) buffer
bufferindex = set.buffers.Count;
}
}
// This adds or updates sector geometry into a buffer.
// Always specify the entry when a previous entry was already given for that sector!
// Sector must set the floorvertices and ceilvertices members on the entry.
// Returns the new surface entry for the stored geometry, floorvertices and ceilvertices will be preserved.
public SurfaceEntry UpdateSurfaces(SurfaceEntry entry)
{
if(entry.floorvertices.Length != entry.ceilvertices.Length)
General.Fail("Floor vertices has different length from ceiling vertices!");
int numvertices = entry.floorvertices.Length;
// Free entry when number of vertices have changed
if((entry.numvertices != numvertices) && (entry.numvertices != -1))
FreeSurfaces(entry);
// Check if we can render this at all
if(numvertices > 0)
{
SurfaceBufferSet set = GetSet(numvertices);
// Check if we need a new entry
if(entry.numvertices == -1)
{
EnsureFreeBufferSpace(set, 1);
SurfaceEntry nentry = set.holes[set.holes.Count - 1];
set.holes.RemoveAt(set.holes.Count - 1);
nentry.ceilvertices = entry.ceilvertices;
nentry.floorvertices = entry.floorvertices;
nentry.floortexture = entry.floortexture;
nentry.ceiltexture = entry.ceiltexture;
set.entries.Add(nentry);
entry = nentry;
}
// Lock the buffer
DataStream bstream;
VertexBuffer vb = set.buffers[entry.bufferindex];
if(vb.Tag == null)
{
bstream = vb.Lock(0, set.buffersizes[entry.bufferindex] * FlatVertex.Stride, LockFlags.None);
vb.Tag = bstream;
lockedbuffers.Add(vb);
}
else
{
bstream = (DataStream)vb.Tag;
}
// Write the vertices to buffer
bstream.Seek(entry.vertexoffset * FlatVertex.Stride, SeekOrigin.Begin);
bstream.WriteRange(entry.floorvertices);
bstream.WriteRange(entry.ceilvertices);
}
return entry;
}
// This frees the given surface entry
public void FreeSurfaces(SurfaceEntry entry)
{
if((entry.numvertices > 0) && (entry.bufferindex > -1))
{
SurfaceBufferSet set = sets[entry.numvertices];
set.entries.Remove(entry);
SurfaceEntry newentry = new SurfaceEntry(entry);
set.holes.Add(newentry);
}
entry.numvertices = -1;
entry.bufferindex = -1;
}
// This unlocks the locked buffers
public void UnlockBuffers()
{
foreach(VertexBuffer vb in lockedbuffers)
{
if(vb.Tag != null)
{
DataStream bstream = (DataStream)vb.Tag;
vb.Unlock();
bstream.Dispose();
vb.Tag = null;
}
}
// Clear list
lockedbuffers = new List<VertexBuffer>();
}
// This gets or creates a set for a specific number of vertices
private SurfaceBufferSet GetSet(int numvertices)
{
SurfaceBufferSet set;
// Get or create the set
if(!sets.ContainsKey(numvertices))
{
set = new SurfaceBufferSet();
set.numvertices = numvertices;
set.buffers = new List<VertexBuffer>();
set.buffersizes = new List<int>();
set.entries = new List<SurfaceEntry>();
set.holes = new List<SurfaceEntry>();
sets.Add(numvertices, set);
}
else
{
set = sets[numvertices];
}
return set;
}
#endregion
#region ================== Rendering
// This renders all sector floors
internal void RenderSectorFloors()
{
surfaces = new Dictionary<ImageData, List<SurfaceEntry>>();
surfacevertexoffsetmul = 0;
// Go for all surfaces as they are sorted in the buffers, so that
// they are automatically already sorted by vertexbuffer
foreach(KeyValuePair<int, SurfaceBufferSet> set in sets)
{
foreach(SurfaceEntry entry in set.Value.entries)
AddSurfaceEntryForRendering(entry, entry.floortexture);
}
}
// This renders all sector ceilings
internal void RenderSectorCeilings()
{
surfaces = new Dictionary<ImageData, List<SurfaceEntry>>();
surfacevertexoffsetmul = 1;
// Go for all surfaces as they are sorted in the buffers, so that
// they are automatically already sorted by vertexbuffer
foreach(KeyValuePair<int, SurfaceBufferSet> set in sets)
{
foreach(SurfaceEntry entry in set.Value.entries)
AddSurfaceEntryForRendering(entry, entry.ceiltexture);
}
}
// This renders all sector brightness levels
internal void RenderSectorBrightness()
{
surfaces = new Dictionary<ImageData, List<SurfaceEntry>>();
surfacevertexoffsetmul = 0;
// Go for all surfaces as they are sorted in the buffers, so that
// they are automatically already sorted by vertexbuffer
foreach(KeyValuePair<int, SurfaceBufferSet> set in sets)
{
foreach(SurfaceEntry entry in set.Value.entries)
AddSurfaceEntryForRendering(entry, 0);
}
}
// This adds a surface entry to the list of surfaces
private void AddSurfaceEntryForRendering(SurfaceEntry entry, long longimagename)
{
// Determine texture to use
ImageData img;
if(longimagename == 0)
{
img = General.Map.Data.WhiteTexture;
}
else
{
img = General.Map.Data.GetFlatImage(longimagename);
if(img != null)
{
// Texture unknown?
if(img is UnknownImage)
{
img = General.Map.Data.UnknownTexture3D;
}
// Is the texture loaded?
else if(img.IsImageLoaded && !img.LoadFailed)
{
if(img.Texture == null) img.CreateTexture();
}
else
{
img = General.Map.Data.WhiteTexture;
}
}
else
{
img = General.Map.Data.WhiteTexture;
}
}
// Store by texture
if(!surfaces.ContainsKey(img))
surfaces.Add(img, new List<SurfaceEntry>());
surfaces[img].Add(entry);
}
// This renders the sorted sector surfaces
internal void RenderSectorSurfaces(D3DDevice graphics)
{
if(!resourcesunloaded)
{
graphics.Shaders.Display2D.Begin();
foreach(KeyValuePair<ImageData, List<SurfaceEntry>> imgsurfaces in surfaces)
{
// Set texture
graphics.Shaders.Display2D.Texture1 = imgsurfaces.Key.Texture;
if(!graphics.Shaders.Enabled) graphics.Device.SetTexture(0, imgsurfaces.Key.Texture);
graphics.Shaders.Display2D.BeginPass(1);
// Go for all surfaces
VertexBuffer lastbuffer = null;
foreach(SurfaceEntry entry in imgsurfaces.Value)
{
// Set the vertex buffer
SurfaceBufferSet set = sets[entry.numvertices];
if(set.buffers[entry.bufferindex] != lastbuffer)
{
lastbuffer = set.buffers[entry.bufferindex];
graphics.Device.SetStreamSource(0, lastbuffer, 0, FlatVertex.Stride);
}
// Draw
graphics.Device.DrawPrimitives(PrimitiveType.TriangleList, entry.vertexoffset + (entry.numvertices * surfacevertexoffsetmul), entry.numvertices / 3);
}
graphics.Shaders.Display2D.EndPass();
}
graphics.Shaders.Display2D.End();
}
}
#endregion
}
}