Performance improvements

The old texture code was being bottle necked by the texture reads. We mirrored the real3d texture memory directly, including the mipmaps in a single large texture. I *think* most h/w has some sort of texture cache for a 2x2 or 4x4 block of pixels for a texture. What we were doing was reading the base texture, then reading the mipmap data from a totally separate part of the same texture which I can only assume flushed this cache. What I did was to create mipmap chains for the texture sheet, then copy the mipmap data there. Doing this basically doubles performance.
This commit is contained in:
Ian Curtis 2024-06-13 13:36:30 +01:00
parent a6466b353d
commit edb11dc223
14 changed files with 198 additions and 113 deletions

View file

@ -114,6 +114,7 @@ SRC_FILES = \
Src/Graphics/New3D/R3DShader.cpp \
Src/Graphics/New3D/R3DFloat.cpp \
Src/Graphics/New3D/R3DScrollFog.cpp \
Src/Graphics/New3D/TextureBank.cpp \
Src/Graphics/FBO.cpp \
Src/Graphics/Render2D.cpp \
Src/Graphics/SuperAA.cpp \

View file

@ -128,10 +128,16 @@ struct Mesh
enum TexWrapMode : int { repeat = 0, repeatClamp, mirror, mirrorClamp };
// texture
int format, x, y, width, height = 0;
int format = 0;
int x = 0;
int y = 0;
int width = 0;
int height = 0;
int page = 0;
bool inverted = false;
TexWrapMode wrapModeU;
TexWrapMode wrapModeV;
bool inverted = false;
// microtexture
bool microTexture = false;

View file

@ -21,7 +21,6 @@ CNew3D::CNew3D(const Util::Config::Node &config, const std::string& gameName) :
m_r3dShader(config),
m_r3dScrollFog(config),
m_gameName(gameName),
m_textureBuffer(0),
m_vao(0),
m_aaTarget(0)
{
@ -43,16 +42,6 @@ CNew3D::CNew3D(const Util::Config::Node &config, const std::string& gameName) :
m_r3dShader.LoadShader();
glUseProgram(0);
// setup our texture memory
glGenTextures(1, &m_textureBuffer);
glBindTexture(GL_TEXTURE_2D, m_textureBuffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R16UI, 2048, 2048, 0, GL_RED_INTEGER, GL_UNSIGNED_SHORT, nullptr); // allocate storage
// setup up our vertex buffer memory
glGenVertexArrays(1, &m_vao);
@ -87,11 +76,6 @@ CNew3D::~CNew3D()
m_vao = 0;
}
if (m_textureBuffer) {
glDeleteTextures(1, &m_textureBuffer);
m_textureBuffer = 0;
}
m_r3dShader.UnloadShader();
}
@ -102,6 +86,9 @@ void CNew3D::AttachMemory(const UINT32 *cullingRAMLoPtr, const UINT32 *cullingRA
m_polyRAM = polyRAMPtr;
m_vrom = vromPtr;
m_textureRAM = textureRAMPtr;
m_textureBank[0].AttachMemory(textureRAMPtr);
m_textureBank[1].AttachMemory(textureRAMPtr + (2048*1024));
}
void CNew3D::SetStepping(int stepping)
@ -143,12 +130,28 @@ bool CNew3D::Init(unsigned xOffset, unsigned yOffset, unsigned xRes, unsigned yR
void CNew3D::UploadTextures(unsigned level, unsigned x, unsigned y, unsigned width, unsigned height)
{
glBindTexture(GL_TEXTURE_2D, m_textureBuffer);
glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
// handle case of entire sheet invalidation
if (width == 2048 && height == 2048) {
for (unsigned i = 0; i < height; i++) {
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y + i, width, 1, GL_RED_INTEGER, GL_UNSIGNED_SHORT, m_textureRAM + ((y + i) * 2048) + x);
height = 1024;
const int mipXBase[] = { 0, 1024, 1536, 1792, 1920, 1984, 2016, 2032, 2040, 2044, 2046, 2047 };
const int mipYBase[] = { 0, 512, 768, 896, 960, 992, 1008, 1016, 1020, 1022, 1023 };
for (int i = 0; i < m_textureBank[0].GetNumberOfLevels(); i++) {
m_textureBank[0].UploadTextures(i, mipXBase[i], mipYBase[i], width, height);
m_textureBank[1].UploadTextures(i, mipXBase[i], mipYBase[i], width, height);
width = (width > 1) ? width / 2 : 1;
height = (height > 1) ? height / 2 : 1;
}
return;
}
int page;
TranslateTexture(x, y, width, height, page);
m_textureBank[page].UploadTextures(level, x, y, width, height);
}
void CNew3D::DrawScrollFog()
@ -280,7 +283,10 @@ void CNew3D::DrawAmbientFog()
bool CNew3D::RenderScene(int priority, bool renderOverlay, Layer layer)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_textureBuffer);
m_textureBank[0].Bind();
glActiveTexture(GL_TEXTURE1);
m_textureBank[1].Bind();
glActiveTexture(GL_TEXTURE0);
bool hasOverlay = false; // (high priority polys)
@ -1249,6 +1255,7 @@ void CNew3D::SetMeshValues(SortingMesh *currentMesh, PolyHeader &ph)
currentMesh->height = ph.TexHeight();
currentMesh->microTexture = ph.MicroTexture();
currentMesh->inverted = ph.TranslatorMapOffset() == 2;
currentMesh->page = ph.Page();
{
bool smoothU = ph.TexSmoothU();
@ -1592,6 +1599,14 @@ void CNew3D::CalcViewport(Viewport* vp)
}
}
void CNew3D::TranslateTexture(unsigned& x, unsigned& y, int width, int height, int& page)
{
page = y / 1024;
// remove page from y coordinate
y -= (page * 1024);
}
void CNew3D::SetSunClamp(bool enable)
{
m_sunClamp = enable;

View file

@ -43,6 +43,7 @@
#include "PolyHeader.h"
#include "R3DFrameBuffers.h"
#include <mutex>
#include "TextureBank.h"
namespace New3D {
@ -224,6 +225,7 @@ private:
void TranslateLosPosition(int inX, int inY, int& outX, int& outY);
bool ProcessLos(int priority);
void CalcViewport(Viewport* vp);
void TranslateTexture(unsigned& x, unsigned& y, int width, int height, int& page);
/*
* Data
@ -261,7 +263,6 @@ private:
UINT32 m_colorTableAddr = 0x400; // address of color table in polygon RAM
LODBlendTable* m_LODBlendTable;
GLuint m_textureBuffer;
NodeAttributes m_nodeAttribs;
Mat4 m_modelMat; // current modelview matrix
@ -281,6 +282,7 @@ private:
std::vector<FVertex> m_polyBufferRam; // dynamic polys
std::vector<FVertex> m_polyBufferRom; // rom polys
std::unordered_map<UINT32, std::shared_ptr<std::vector<Mesh>>> m_romMap; // a hash table for all the ROM models. The meshes don't have model matrices or tex offsets yet
TextureBank m_textureBank[2];
GLuint m_vao;
VBO m_vbo; // large VBO to hold our poly data, start of VBO is ROM data, ram polys follow

View file

@ -296,19 +296,11 @@ int PolyHeader::X()
int PolyHeader::Y()
{
//=======
//====
int y;
int page;
//=======
//====
if (Page()) {
page = 1024;
}
else {
page = 0;
}
y = (32 * (header[5] & 0x1F) + page); // if we hit 2nd page add 1024 to y coordinate
y = 32 * (header[5] & 0x1F); // if we hit 2nd page add 1024 to y coordinate
y &= 2047;
return y;

View file

@ -38,6 +38,7 @@ void R3DShader::Start()
m_specularValue = 0;
m_microTexScale = 0;
m_microTexID = -1;
m_texturePage = -1;
m_baseTexInfo[0] = -1;
m_baseTexInfo[1] = -1;
@ -100,7 +101,9 @@ bool R3DShader::LoadShader(const char* vertexShader, const char* fragmentShader)
PrintProgramResult(m_shaderProgram);
m_locTexture1 = glGetUniformLocation(m_shaderProgram, "tex1");
m_locTextureBank[0] = glGetUniformLocation(m_shaderProgram, "textureBank[0]");
m_locTextureBank[1] = glGetUniformLocation(m_shaderProgram, "textureBank[1]");
m_locTexturePage = glGetUniformLocation(m_shaderProgram, "texturePage");
m_locTexture1Enabled = glGetUniformLocation(m_shaderProgram, "textureEnabled");
m_locTexture2Enabled = glGetUniformLocation(m_shaderProgram, "microTexture");
m_locTextureAlpha = glGetUniformLocation(m_shaderProgram, "textureAlpha");
@ -202,7 +205,8 @@ void R3DShader::SetMeshUniforms(const Mesh* m)
}
if (m_dirtyMesh) {
glUniform1i(m_locTexture1, 0);
glUniform1i(m_locTextureBank[0], 0);
glUniform1i(m_locTextureBank[1], 1);
}
if (m_dirtyMesh || m->textured != m_textured1) {
@ -215,6 +219,11 @@ void R3DShader::SetMeshUniforms(const Mesh* m)
m_textured2 = m->microTexture;
}
if (m_dirtyMesh || (m->page ^ m_transPage) != m_texturePage) {
glUniform1i(m_locTexturePage, m->page ^ m_transPage);
m_texturePage = (m->page ^ m_transPage);
}
if (m_dirtyMesh || m->microTextureScale != m_microTexScale) {
glUniform1f(m_locMicroTexScale, m->microTextureScale);
m_microTexScale = m->microTextureScale;
@ -232,10 +241,7 @@ void R3DShader::SetMeshUniforms(const Mesh* m)
m_baseTexInfo[2] = m->width;
m_baseTexInfo[3] = m->height;
int translatedX, translatedY;
CalcTexOffset(m_transX, m_transY, m_transPage, m->x, m->y, translatedX, translatedY); // need to apply model translation
glUniform4i(m_locBaseTexInfo, translatedX, translatedY, m->width, m->height);
glUniform4i(m_locBaseTexInfo, (m->x + m_transX), (m->y + m_transY), m->width, m->height);
}
if (m_dirtyMesh || m_baseTexType != m->format) {
@ -426,21 +432,4 @@ void R3DShader::PrintProgramResult(GLuint program)
}
}
void R3DShader::CalcTexOffset(int offX, int offY, int page, int x, int y, int& newX, int& newY)
{
newX = (x + offX) & 2047; // wrap around 2048, shouldn't be required
int oldPage = y / 1024;
y -= (oldPage * 1024); // remove page from tex y
// calc newY with wrap around, wraps around in the same sheet, not into another memory sheet
newY = (y + offY) & 1023;
// add page to Y
newY += ((oldPage + page) & 1) * 1024; // max page 0-1
}
} // New3D

View file

@ -30,8 +30,6 @@ private:
void PrintShaderResult(GLuint shader);
void PrintProgramResult(GLuint program);
void CalcTexOffset(int offX, int offY, int page, int x, int y, int& newX, int& newY);
// run-time config
const Util::Config::Node &m_config;
@ -42,9 +40,10 @@ private:
GLuint m_fragmentShader;
// mesh uniform locations
GLint m_locTexture1;
GLint m_locTexture1Enabled;
GLint m_locTexture2Enabled;
GLint m_locTextureBank[2]; // 2 banks
GLint m_locTexture1Enabled; // base texture
GLint m_locTexture2Enabled; // micro texture
GLint m_locTexturePage;
GLint m_locTextureAlpha;
GLint m_locAlphaTest;
GLint m_locMicroTexScale;
@ -70,6 +69,7 @@ private:
bool m_fixedShading;
bool m_translatorMap;
bool m_polyAlpha;
int m_texturePage;
bool m_layered;
bool m_noLosReturn;

View file

@ -82,36 +82,13 @@ vec4 ExtractColour(int type, uint value)
return c;
}
int GetPage(int yCoord)
{
return yCoord / 1024;
}
int GetNextPage(int yCoord)
{
return (GetPage(yCoord) + 1) & 1;
}
int GetNextPageOffset(int yCoord)
{
return GetNextPage(yCoord) * 1024;
}
// wrapping tex coords would be super easy but we combined tex sheets so have to handle wrap around between sheets
// hardware testing would be useful because i don't know exactly what happens if you try to read outside the texture sheet
// wrap around is a good guess
ivec2 WrapTexCoords(ivec2 pos, ivec2 coordinate)
ivec2 WrapTexCoords(ivec2 pos, ivec2 coordinate, int level)
{
ivec2 newCoord;
newCoord.x = coordinate.x & 2047;
newCoord.y = coordinate.y;
int page = GetPage(pos.y);
newCoord.y -= (page * 1024); // remove page
newCoord.y &= 1023; // wrap around in the same sheet
newCoord.y += (page * 1024); // add page back
newCoord.x = coordinate.x & (2047 >> level);
newCoord.y = coordinate.y & (1023 >> level);
return newCoord;
}
@ -125,19 +102,11 @@ ivec2 GetTextureSize(int level, ivec2 size)
ivec2 GetTexturePosition(int level, ivec2 pos)
{
const int mipXBase[] = int[](0, 1024, 1536, 1792, 1920, 1984, 2016, 2032, 2040, 2044, 2046, 2047);
const int mipYBase[] = int[](0, 512, 768, 896, 960, 992, 1008, 1016, 1020, 1022, 1023);
int mipDivisor = 1 << level;
int page = pos.y / 1024;
pos.y -= (page * 1024); // remove page from tex y
ivec2 retPos;
retPos.x = mipXBase[level] + (pos.x / mipDivisor);
retPos.y = mipYBase[level] + (pos.y / mipDivisor);
retPos.y += (page * 1024); // add page back to tex y
retPos.x = pos.x / mipDivisor;
retPos.y = pos.y / mipDivisor;
return retPos;
}
@ -206,16 +175,16 @@ float LinearTexLocations(int wrapMode, float size, float u, out float u0, out fl
}
}
vec4 texBiLinear(usampler2D texSampler, ivec2 wrapMode, vec2 texSize, ivec2 texPos, vec2 texCoord)
vec4 texBiLinear(usampler2D texSampler, ivec2 wrapMode, vec2 texSize, ivec2 texPos, vec2 texCoord, int level)
{
float tx[2], ty[2];
float a = LinearTexLocations(wrapMode.s, texSize.x, texCoord.x, tx[0], tx[1]);
float b = LinearTexLocations(wrapMode.t, texSize.y, texCoord.y, ty[0], ty[1]);
vec4 p0q0 = ExtractColour(baseTexType,texelFetch(texSampler, WrapTexCoords(texPos,ivec2(vec2(tx[0],ty[0]) * texSize + texPos)), 0).r);
vec4 p1q0 = ExtractColour(baseTexType,texelFetch(texSampler, WrapTexCoords(texPos,ivec2(vec2(tx[1],ty[0]) * texSize + texPos)), 0).r);
vec4 p0q1 = ExtractColour(baseTexType,texelFetch(texSampler, WrapTexCoords(texPos,ivec2(vec2(tx[0],ty[1]) * texSize + texPos)), 0).r);
vec4 p1q1 = ExtractColour(baseTexType,texelFetch(texSampler, WrapTexCoords(texPos,ivec2(vec2(tx[1],ty[1]) * texSize + texPos)), 0).r);
vec4 p0q0 = ExtractColour(baseTexType,texelFetch(texSampler, WrapTexCoords(texPos,ivec2(vec2(tx[0],ty[0]) * texSize + texPos),level), level).r);
vec4 p1q0 = ExtractColour(baseTexType,texelFetch(texSampler, WrapTexCoords(texPos,ivec2(vec2(tx[1],ty[0]) * texSize + texPos),level), level).r);
vec4 p0q1 = ExtractColour(baseTexType,texelFetch(texSampler, WrapTexCoords(texPos,ivec2(vec2(tx[0],ty[1]) * texSize + texPos),level), level).r);
vec4 p1q1 = ExtractColour(baseTexType,texelFetch(texSampler, WrapTexCoords(texPos,ivec2(vec2(tx[1],ty[1]) * texSize + texPos),level), level).r);
if(alphaTest) {
if(p0q0.a > p1q0.a) { p1q0.rgb = p0q0.rgb; }
@ -254,15 +223,15 @@ vec4 textureR3D(usampler2D texSampler, ivec2 wrapMode, ivec2 texSize, ivec2 texP
ivec2 texSize0 = GetTextureSize(iLevel, texSize);
ivec2 texSize1 = GetTextureSize(iLevel+1, texSize);
vec4 texLevel0 = texBiLinear(texSampler, wrapMode, vec2(texSize0), texPos0, texCoord);
vec4 texLevel1 = texBiLinear(texSampler, wrapMode, vec2(texSize1), texPos1, texCoord);
vec4 texLevel0 = texBiLinear(texSampler, wrapMode, vec2(texSize0), texPos0, texCoord, iLevel);
vec4 texLevel1 = texBiLinear(texSampler, wrapMode, vec2(texSize1), texPos1, texCoord, iLevel+1);
return mix(texLevel0, texLevel1, fract(fLevel)); // linear blend between our mipmap levels
}
vec4 GetTextureValue()
{
vec4 tex1Data = textureR3D(tex1, textureWrapMode, ivec2(baseTexInfo.zw), ivec2(baseTexInfo.xy), fsTexCoord);
vec4 tex1Data = textureR3D(textureBank[texturePage], textureWrapMode, ivec2(baseTexInfo.zw), ivec2(baseTexInfo.xy), fsTexCoord);
if(textureInverted) {
tex1Data.rgb = vec3(1.0) - vec3(tex1Data.rgb);
@ -271,11 +240,8 @@ vec4 GetTextureValue()
if (microTexture) {
vec2 scale = (vec2(baseTexInfo.zw) / 128.0) * microTextureScale;
ivec2 pos = GetMicroTexturePos(microTextureID);
// add page offset to microtexture position
pos.y += GetNextPageOffset(baseTexInfo.y);
vec4 tex2Data = textureR3D(tex1, ivec2(0), ivec2(128), pos, fsTexCoord * scale);
vec4 tex2Data = textureR3D(textureBank[(texturePage+1)&1], ivec2(0), ivec2(128), pos, fsTexCoord * scale);
float lod = mip_map_level(fsTexCoord * scale * vec2(128.0));

View file

@ -175,7 +175,7 @@ static const char *fragmentShaderR3DQuads = R"glsl(
#version 450 core
uniform usampler2D tex1; // entire texture sheet
uniform usampler2D textureBank[2]; // entire texture sheet
// texturing
uniform bool textureEnabled;
@ -189,6 +189,7 @@ uniform bool textureAlpha;
uniform bool alphaTest;
uniform bool discardAlpha;
uniform ivec2 textureWrapMode;
uniform int texturePage;
// general
uniform vec3 fogColour;

View file

@ -66,7 +66,7 @@ static const char *fragmentShaderR3D = R"glsl(
#version 410 core
uniform usampler2D tex1; // entire texture sheet
uniform usampler2D textureBank[2]; // entire texture sheet
// texturing
uniform bool textureEnabled;
@ -80,6 +80,7 @@ uniform bool textureAlpha;
uniform bool alphaTest;
uniform bool discardAlpha;
uniform ivec2 textureWrapMode;
uniform int texturePage;
// general
uniform vec3 fogColour;

View file

@ -0,0 +1,71 @@
#include "TextureBank.h"
New3D::TextureBank::TextureBank()
{
glGenTextures(1, &m_texID);
glBindTexture(GL_TEXTURE_2D, m_texID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
int width = 2048;
int height = 1024;
int level = 0;
while (width>=1 && height>=1) {
glTexImage2D(GL_TEXTURE_2D, level, GL_R16UI, width, height, 0, GL_RED_INTEGER, GL_UNSIGNED_SHORT, nullptr); // allocate storage
width = (width > 1) ? width / 2 : 1;
height = (height > 1) ? height / 2 : 1;
level++;
if (width == 1 && height == 1) {
glTexImage2D(GL_TEXTURE_2D, level, GL_R16UI, width, height, 0, GL_RED_INTEGER, GL_UNSIGNED_SHORT, nullptr); // allocate storage
break;
}
}
m_numLevels = level;
}
New3D::TextureBank::~TextureBank()
{
if (m_texID) {
glDeleteTextures(1, &m_texID);
m_texID = 0;
}
}
void New3D::TextureBank::AttachMemory(const UINT16* textureRam)
{
m_textureRam = textureRam;
}
void New3D::TextureBank::Bind()
{
glBindTexture(GL_TEXTURE_2D, m_texID);
}
void New3D::TextureBank::UploadTextures(int level, int x, int y, int width, int height)
{
glBindTexture(GL_TEXTURE_2D, m_texID);
glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
const int mipXBase[] = { 0, 1024, 1536, 1792, 1920, 1984, 2016, 2032, 2040, 2044, 2046, 2047 };
const int mipYBase[] = { 0, 512, 768, 896, 960, 992, 1008, 1016, 1020, 1022, 1023 };
int subX = x - mipXBase[level];
int subY = y - mipYBase[level];
for (unsigned i = 0; i < height; i++) {
glTexSubImage2D(GL_TEXTURE_2D, level, subX, subY + i, width, 1, GL_RED_INTEGER, GL_UNSIGNED_SHORT, m_textureRam + ((y + i) * 2048) + x);
}
}
int New3D::TextureBank::GetNumberOfLevels()
{
return m_numLevels;
}

View file

@ -0,0 +1,33 @@
#pragma once
#ifndef _TEXTUREBANK_H_
#define _TEXTUREBANK_H_
#include "Types.h"
#include <GL/glew.h>
// texture banks are a fixed size
// 2048x1024 pixels, each pixel is 16bits in size
namespace New3D {
class TextureBank
{
public:
TextureBank();
~TextureBank();
void AttachMemory(const UINT16* textureRam);
void Bind();
void UploadTextures(int level, int x, int y, int width, int height);
int GetNumberOfLevels();
private:
GLuint m_texID = 0;
const UINT16* m_textureRam = nullptr;
int m_numLevels = 0;
};
}
#endif

View file

@ -324,6 +324,7 @@ xcopy /D /Y "$(ProjectDir)..\Assets\*" "$(TargetDir)Assets"</Command>
<ClCompile Include="..\Src\Graphics\New3D\R3DFrameBuffers.cpp" />
<ClCompile Include="..\Src\Graphics\New3D\R3DScrollFog.cpp" />
<ClCompile Include="..\Src\Graphics\New3D\R3DShader.cpp" />
<ClCompile Include="..\Src\Graphics\New3D\TextureBank.cpp" />
<ClCompile Include="..\Src\Graphics\New3D\VBO.cpp" />
<ClCompile Include="..\Src\Graphics\New3D\Vec.cpp" />
<ClCompile Include="..\Src\Graphics\Render2D.cpp" />
@ -503,6 +504,7 @@ xcopy /D /Y "$(ProjectDir)..\Assets\*" "$(TargetDir)Assets"</Command>
<ClInclude Include="..\Src\Graphics\New3D\R3DShaderCommon.h" />
<ClInclude Include="..\Src\Graphics\New3D\R3DShaderQuads.h" />
<ClInclude Include="..\Src\Graphics\New3D\R3DShaderTriangles.h" />
<ClInclude Include="..\Src\Graphics\New3D\TextureBank.h" />
<ClInclude Include="..\Src\Graphics\New3D\VBO.h" />
<ClInclude Include="..\Src\Graphics\New3D\Vec.h" />
<ClInclude Include="..\Src\Graphics\Render2D.h" />

View file

@ -473,6 +473,9 @@
<ClCompile Include="..\Src\Graphics\SuperAA.cpp">
<Filter>Source Files\Graphics</Filter>
</ClCompile>
<ClCompile Include="..\Src\Graphics\New3D\TextureBank.cpp">
<Filter>Source Files\Graphics\New</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<MASM Include="..\Src\CPU\68K\Turbo68K\Turbo68K.asm">
@ -862,6 +865,9 @@
<ClInclude Include="..\Src\Graphics\SuperAA.h">
<Filter>Header Files\Graphics</Filter>
</ClInclude>
<ClInclude Include="..\Src\Graphics\New3D\TextureBank.h">
<Filter>Header Files\Graphics\New</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="..\Src\Debugger\ReadMe.txt">