Merge pull request #920 from Jupeyy/master

VBO deletion after VAO deletion & viewport adjustment for dpi scaled displays at fullscreen
This commit is contained in:
Dennis Felsing 2017-10-24 07:58:37 +02:00 committed by GitHub
commit 2d889a0584
5 changed files with 110 additions and 26 deletions

View file

@ -1126,6 +1126,7 @@ endforeach()
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME ${PROJECT_NAME}) set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME ${PROJECT_NAME})
install(TARGETS ${TARGET_CLIENT} ${TARGET_SERVER} DESTINATION bin) install(TARGETS ${TARGET_CLIENT} ${TARGET_SERVER} DESTINATION bin)
install(DIRECTORY data DESTINATION share/DDNet) install(DIRECTORY data DESTINATION share/DDNet)
install(DIRECTORY shader DESTINATION share/DDNet)
set(CPACK_TARGETS set(CPACK_TARGETS
${TARGET_CLIENT} ${TARGET_CLIENT}
@ -1137,6 +1138,7 @@ set(CPACK_TARGETS
map_extract map_extract
) )
set(CPACK_DIRS data) set(CPACK_DIRS data)
set(CPACK_DIRS shader)
set(CPACK_FILES set(CPACK_FILES
license.txt license.txt
storage.cfg storage.cfg

View file

@ -763,6 +763,7 @@ void CCommandProcessorFragment_OpenGL3_3::Cmd_Init(const SCommand_Init *pCommand
glGenBuffers(1, &m_QuadDrawIndexBufferID); glGenBuffers(1, &m_QuadDrawIndexBufferID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID);
m_LastIndexBufferBound = m_QuadDrawIndexBufferID;
unsigned int Indices[CCommandBuffer::MAX_VERTICES/4 * 6]; unsigned int Indices[CCommandBuffer::MAX_VERTICES/4 * 6];
int Primq = 0; int Primq = 0;
@ -1011,7 +1012,11 @@ void CCommandProcessorFragment_OpenGL3_3::Cmd_Render(const CCommandBuffer::SComm
glDrawArrays(GL_LINES, 0, pCommand->m_PrimCount*2); glDrawArrays(GL_LINES, 0, pCommand->m_PrimCount*2);
break; break;
case CCommandBuffer::PRIMTYPE_QUADS: case CCommandBuffer::PRIMTYPE_QUADS:
if (m_LastIndexBufferBound != m_QuadDrawIndexBufferID)
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID);
m_LastIndexBufferBound = m_QuadDrawIndexBufferID;
}
glDrawElements(GL_TRIANGLES, pCommand->m_PrimCount*6, GL_UNSIGNED_INT, 0); glDrawElements(GL_TRIANGLES, pCommand->m_PrimCount*6, GL_UNSIGNED_INT, 0);
break; break;
default: default:
@ -1111,11 +1116,14 @@ void CCommandProcessorFragment_OpenGL3_3::DestroyVisualObjects(int Index)
{ {
SVisualObject& VisualObject = m_VisualObjects[Index]; SVisualObject& VisualObject = m_VisualObjects[Index];
if (VisualObject.m_VertArrayID != 0) glDeleteVertexArrays(1, &VisualObject.m_VertArrayID); if (VisualObject.m_VertArrayID != 0) glDeleteVertexArrays(1, &VisualObject.m_VertArrayID);
if(VisualObject.m_VertBufferID != 0) glDeleteBuffers(1, &VisualObject.m_VertBufferID);//this line should never be called
// this is required, due to a driver bug for AMD under windows
if(VisualObject.m_VertBufferID != 0) glDeleteBuffers(1, &VisualObject.m_VertBufferID);
VisualObject.m_NumElements = 0; VisualObject.m_NumElements = 0;
VisualObject.m_IsTextured = false; VisualObject.m_IsTextured = false;
VisualObject.m_VertBufferID = VisualObject.m_VertArrayID = 0; VisualObject.m_VertBufferID = VisualObject.m_VertArrayID = 0;
VisualObject.m_LastIndexBufferBound = 0;
} }
void CCommandProcessorFragment_OpenGL3_3::AppendIndices(unsigned int NewIndicesCount) void CCommandProcessorFragment_OpenGL3_3::AppendIndices(unsigned int NewIndicesCount)
@ -1135,7 +1143,6 @@ void CCommandProcessorFragment_OpenGL3_3::AppendIndices(unsigned int NewIndicesC
Primq+=4; Primq+=4;
} }
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_COPY_READ_BUFFER, m_QuadDrawIndexBufferID); glBindBuffer(GL_COPY_READ_BUFFER, m_QuadDrawIndexBufferID);
GLuint NewIndexBufferID; GLuint NewIndexBufferID;
glGenBuffers(1, &NewIndexBufferID); glGenBuffers(1, &NewIndexBufferID);
@ -1149,7 +1156,6 @@ void CCommandProcessorFragment_OpenGL3_3::AppendIndices(unsigned int NewIndicesC
glDeleteBuffers(1, &m_QuadDrawIndexBufferID); glDeleteBuffers(1, &m_QuadDrawIndexBufferID);
m_QuadDrawIndexBufferID = NewIndexBufferID; m_QuadDrawIndexBufferID = NewIndexBufferID;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID);
m_CurrentIndicesInBuffer = NewIndicesCount; m_CurrentIndicesInBuffer = NewIndicesCount;
delete[] Indices; delete[] Indices;
@ -1191,7 +1197,11 @@ void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderBorderTile(const CCommandBuf
pProgram->SetUniform(pProgram->m_LocJumpIndex, (int)pCommand->m_JumpIndex); pProgram->SetUniform(pProgram->m_LocJumpIndex, (int)pCommand->m_JumpIndex);
glBindVertexArray(VisualObject.m_VertArrayID); glBindVertexArray(VisualObject.m_VertArrayID);
if (VisualObject.m_LastIndexBufferBound != m_QuadDrawIndexBufferID)
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID);
VisualObject.m_LastIndexBufferBound = m_QuadDrawIndexBufferID;
}
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, pCommand->m_pIndicesOffset, pCommand->m_DrawNum); glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, pCommand->m_pIndicesOffset, pCommand->m_DrawNum);
} }
@ -1219,7 +1229,11 @@ void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderBorderTileLine(const CComman
pProgram->SetUniformVec2(pProgram->m_LocDir, 1, (float*)&pCommand->m_Dir); pProgram->SetUniformVec2(pProgram->m_LocDir, 1, (float*)&pCommand->m_Dir);
glBindVertexArray(VisualObject.m_VertArrayID); glBindVertexArray(VisualObject.m_VertArrayID);
if (VisualObject.m_LastIndexBufferBound != m_QuadDrawIndexBufferID)
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID);
VisualObject.m_LastIndexBufferBound = m_QuadDrawIndexBufferID;
}
glDrawElementsInstanced(GL_TRIANGLES, pCommand->m_IndexDrawNum, GL_UNSIGNED_INT, pCommand->m_pIndicesOffset, pCommand->m_DrawNum); glDrawElementsInstanced(GL_TRIANGLES, pCommand->m_IndexDrawNum, GL_UNSIGNED_INT, pCommand->m_pIndicesOffset, pCommand->m_DrawNum);
} }
@ -1252,9 +1266,11 @@ void CCommandProcessorFragment_OpenGL3_3::Cmd_RenderVertexArray(const CCommandBu
pProgram->SetUniformVec4(pProgram->m_LocColor, 1, (float*)&pCommand->m_Color); pProgram->SetUniformVec4(pProgram->m_LocColor, 1, (float*)&pCommand->m_Color);
glBindVertexArray(VisualObject.m_VertArrayID); glBindVertexArray(VisualObject.m_VertArrayID);
if (VisualObject.m_LastIndexBufferBound != m_QuadDrawIndexBufferID)
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_QuadDrawIndexBufferID);
//for some reasons this function seems not to be in the coreprofile for quite some gpus VisualObject.m_LastIndexBufferBound = m_QuadDrawIndexBufferID;
//glMultiDrawElements(GL_TRIANGLES, pCommand->m_pDrawCount, GL_UNSIGNED_INT, pCommand->m_pIndicesOffsets, pCommand->m_IndicesDrawNum); }
for (int i = 0; i < pCommand->m_IndicesDrawNum; ++i) for (int i = 0; i < pCommand->m_IndicesDrawNum; ++i)
{ {
glDrawElements(GL_TRIANGLES, pCommand->m_pDrawCount[i], GL_UNSIGNED_INT, pCommand->m_pIndicesOffsets[i]); glDrawElements(GL_TRIANGLES, pCommand->m_pDrawCount[i], GL_UNSIGNED_INT, pCommand->m_pIndicesOffsets[i]);
@ -1348,9 +1364,8 @@ void CCommandProcessorFragment_OpenGL3_3::Cmd_CreateVertArray(const CCommandBuff
glBindVertexArray(0); glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
//TODO: destroy the VBO here? it shouldn't be needed anymore // due to a driver bug for AMD hardware (https://stackoverflow.com/questions/41520764/should-i-delete-vertex-buffer-object-after-binding-it-to-vertex-array-objects)
glDeleteBuffers(1, &VisualObject.m_VertBufferID); // we don't delete the VBO bound to the VAO
VisualObject.m_VertBufferID = 0;
} }
// ------------ CCommandProcessorFragment_SDL // ------------ CCommandProcessorFragment_SDL
@ -1371,6 +1386,11 @@ void CCommandProcessorFragment_SDL::Cmd_Init(const SCommand_Init *pCommand)
glDepthMask(0); glDepthMask(0);
} }
void CCommandProcessorFragment_SDL::Cmd_Update_Viewport(const SCommand_Update_Viewport* pCommand)
{
glViewport(pCommand->m_X, pCommand->m_Y, pCommand->m_Width, pCommand->m_Height);
}
void CCommandProcessorFragment_SDL::Cmd_Shutdown(const SCommand_Shutdown *pCommand) void CCommandProcessorFragment_SDL::Cmd_Shutdown(const SCommand_Shutdown *pCommand)
{ {
SDL_GL_MakeCurrent(NULL, NULL); SDL_GL_MakeCurrent(NULL, NULL);
@ -1445,6 +1465,7 @@ bool CCommandProcessorFragment_SDL::RunCommand(const CCommandBuffer::SCommand *p
case CCommandBuffer::CMD_VIDEOMODES: Cmd_VideoModes(static_cast<const CCommandBuffer::SCommand_VideoModes *>(pBaseCommand)); break; case CCommandBuffer::CMD_VIDEOMODES: Cmd_VideoModes(static_cast<const CCommandBuffer::SCommand_VideoModes *>(pBaseCommand)); break;
case CMD_INIT: Cmd_Init(static_cast<const SCommand_Init *>(pBaseCommand)); break; case CMD_INIT: Cmd_Init(static_cast<const SCommand_Init *>(pBaseCommand)); break;
case CMD_SHUTDOWN: Cmd_Shutdown(static_cast<const SCommand_Shutdown *>(pBaseCommand)); break; case CMD_SHUTDOWN: Cmd_Shutdown(static_cast<const SCommand_Shutdown *>(pBaseCommand)); break;
case CMD_UPDATE_VIEWPORT: Cmd_Update_Viewport(static_cast<const SCommand_Update_Viewport *>(pBaseCommand)); break;
default: return false; default: return false;
} }
@ -1484,7 +1505,7 @@ void CCommandProcessor_SDL_OpenGL::RunBuffer(CCommandBuffer *pBuffer)
// ------------ CGraphicsBackend_SDL_OpenGL // ------------ CGraphicsBackend_SDL_OpenGL
int CGraphicsBackend_SDL_OpenGL::Init(const char *pName, int *Screen, int *pWidth, int *pHeight, int FsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight) int CGraphicsBackend_SDL_OpenGL::Init(const char *pName, int *Screen, int *pWidth, int *pHeight, int FsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight, int* pCurrentWidth, int* pCurrentHeight)
{ {
if(!SDL_WasInit(SDL_INIT_VIDEO)) if(!SDL_WasInit(SDL_INIT_VIDEO))
{ {
@ -1634,7 +1655,29 @@ int CGraphicsBackend_SDL_OpenGL::Init(const char *pName, int *Screen, int *pWidt
*pWidth = *pDesktopWidth; *pWidth = *pDesktopWidth;
*pHeight = *pDesktopHeight; *pHeight = *pDesktopHeight;
#else #else
//when we are at fullscreen, we really shouldn't allow window sizes, that aren't supported by the driver
bool SupportedResolution = false;
SDL_DisplayMode mode;
int maxModes = SDL_GetNumDisplayModes(g_Config.m_GfxScreen);
for (int i = 0; i < maxModes; i++)
{
if (SDL_GetDisplayMode(g_Config.m_GfxScreen, i, &mode) < 0)
{
dbg_msg("gfx", "unable to get display mode: %s", SDL_GetError());
continue;
}
if (*pWidth == mode.w && *pHeight == mode.h)
{
SupportedResolution = true;
break;
}
}
if(SupportedResolution)
SdlFlags |= SDL_WINDOW_FULLSCREEN; SdlFlags |= SDL_WINDOW_FULLSCREEN;
else
SdlFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
#endif #endif
} }
@ -1721,8 +1764,39 @@ int CGraphicsBackend_SDL_OpenGL::Init(const char *pName, int *Screen, int *pWidt
WaitForIdle(); WaitForIdle();
} }
*pCurrentWidth = *pWidth;
*pCurrentHeight = *pHeight;
SDL_ShowWindow(m_pWindow); SDL_ShowWindow(m_pWindow);
SetWindowScreen(g_Config.m_GfxScreen); if (SetWindowScreen(g_Config.m_GfxScreen))
{
// query the current displaymode, when running in fullscreen
// this is required if DPI scaling is active
if (SdlFlags&SDL_WINDOW_FULLSCREEN)
{
SDL_DisplayMode CurrentDisplayMode;
SDL_GetCurrentDisplayMode(g_Config.m_GfxScreen, &CurrentDisplayMode);
*pCurrentWidth = CurrentDisplayMode.w;
*pCurrentHeight = CurrentDisplayMode.h;
// since the window is centered, calculate how much the viewport has to be fixed
//int XOverflow = (*pWidth > *pCurrentWidth ? (*pWidth - *pCurrentWidth) : 0);
//int YOverflow = (*pHeight > *pCurrentHeight ? (*pHeight - *pCurrentHeight) : 0);
//TODO: current problem is, that the opengl driver knows about the scaled display,
//so the viewport cannot be adjusted for resolutions, that are higher than allowed by the display driver
CCommandProcessorFragment_SDL::SCommand_Update_Viewport CmdSDL;
CmdSDL.m_X = 0;
CmdSDL.m_Y = 0;
CmdSDL.m_Width = CurrentDisplayMode.w;
CmdSDL.m_Height = CurrentDisplayMode.h;
CmdBuffer.AddCommand(CmdSDL);
RunBuffer(&CmdBuffer);
WaitForIdle();
}
}
// return // return
return 0; return 0;

View file

@ -124,7 +124,7 @@ class CGLSLQuadProgram;
class CGLSLTileProgram; class CGLSLTileProgram;
class CGLSLBorderTileProgram; class CGLSLBorderTileProgram;
class CGLSLBorderTileLineProgram; class CGLSLBorderTileLineProgram;
// takes care of opengl 3.2 related rendering // takes care of opengl 3.3 related rendering
class CCommandProcessorFragment_OpenGL3_3 class CCommandProcessorFragment_OpenGL3_3
{ {
bool m_UseMultipleTextureUnits; bool m_UseMultipleTextureUnits;
@ -141,7 +141,6 @@ class CCommandProcessorFragment_OpenGL3_3
volatile int *m_pTextureMemoryUsage; volatile int *m_pTextureMemoryUsage;
CGLSLPrimitiveProgram* m_pPrimitiveProgram; CGLSLPrimitiveProgram* m_pPrimitiveProgram;
//CGLSLQuadProgram* m_QuadProgram;
CGLSLTileProgram* m_pTileProgram; CGLSLTileProgram* m_pTileProgram;
CGLSLTileProgram* m_pTileProgramTextured; CGLSLTileProgram* m_pTileProgramTextured;
CGLSLBorderTileProgram* m_pBorderTileProgram; CGLSLBorderTileProgram* m_pBorderTileProgram;
@ -151,6 +150,7 @@ class CCommandProcessorFragment_OpenGL3_3
GLuint m_PrimitiveDrawVertexID; GLuint m_PrimitiveDrawVertexID;
GLuint m_PrimitiveDrawBufferID; GLuint m_PrimitiveDrawBufferID;
GLuint m_LastIndexBufferBound;
GLuint m_QuadDrawIndexBufferID; GLuint m_QuadDrawIndexBufferID;
unsigned int m_CurrentIndicesInBuffer; unsigned int m_CurrentIndicesInBuffer;
@ -172,9 +172,10 @@ class CCommandProcessorFragment_OpenGL3_3
void AppendIndices(unsigned int NewIndicesCount); void AppendIndices(unsigned int NewIndicesCount);
struct SVisualObject{ struct SVisualObject{
SVisualObject() : m_VertArrayID(0), m_VertBufferID(0), m_NumElements(0), m_IsTextured(false) {} SVisualObject() : m_VertArrayID(0), m_VertBufferID(0), m_LastIndexBufferBound(0), m_NumElements(0), m_IsTextured(false) {}
GLuint m_VertArrayID; GLuint m_VertArrayID;
GLuint m_VertBufferID; GLuint m_VertBufferID;
GLuint m_LastIndexBufferBound;
int m_NumElements; //vertices and texture coordinates int m_NumElements; //vertices and texture coordinates
bool m_IsTextured; bool m_IsTextured;
}; };
@ -239,6 +240,7 @@ public:
enum enum
{ {
CMD_INIT = CCommandBuffer::CMDGROUP_PLATFORM_SDL, CMD_INIT = CCommandBuffer::CMDGROUP_PLATFORM_SDL,
CMD_UPDATE_VIEWPORT,
CMD_SHUTDOWN, CMD_SHUTDOWN,
}; };
@ -249,6 +251,15 @@ public:
SDL_GLContext m_GLContext; SDL_GLContext m_GLContext;
}; };
struct SCommand_Update_Viewport : public CCommandBuffer::SCommand
{
SCommand_Update_Viewport() : SCommand(CMD_UPDATE_VIEWPORT) {}
int m_X;
int m_Y;
int m_Width;
int m_Height;
};
struct SCommand_Shutdown : public CCommandBuffer::SCommand struct SCommand_Shutdown : public CCommandBuffer::SCommand
{ {
SCommand_Shutdown() : SCommand(CMD_SHUTDOWN) {} SCommand_Shutdown() : SCommand(CMD_SHUTDOWN) {}
@ -256,6 +267,7 @@ public:
private: private:
void Cmd_Init(const SCommand_Init *pCommand); void Cmd_Init(const SCommand_Init *pCommand);
void Cmd_Update_Viewport(const SCommand_Update_Viewport* pCommand);
void Cmd_Shutdown(const SCommand_Shutdown *pCommand); void Cmd_Shutdown(const SCommand_Shutdown *pCommand);
void Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand); void Cmd_Swap(const CCommandBuffer::SCommand_Swap *pCommand);
void Cmd_VSync(const CCommandBuffer::SCommand_VSync *pCommand); void Cmd_VSync(const CCommandBuffer::SCommand_VSync *pCommand);
@ -292,7 +304,7 @@ class CGraphicsBackend_SDL_OpenGL : public CGraphicsBackend_Threaded
bool m_UseOpenGL3_3; bool m_UseOpenGL3_3;
public: public:
virtual int Init(const char *pName, int *Screen, int *pWidth, int *pHeight, int FsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight); virtual int Init(const char *pName, int *Screen, int *pWidth, int *pHeight, int FsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight, int* pCurrentWidth, int* pCurrentHeight);
virtual int Shutdown(); virtual int Shutdown();
virtual int MemoryUsage() const; virtual int MemoryUsage() const;

View file

@ -1212,7 +1212,7 @@ int CGraphics_Threaded::IssueInit()
if(g_Config.m_GfxVsync) Flags |= IGraphicsBackend::INITFLAG_VSYNC; if(g_Config.m_GfxVsync) Flags |= IGraphicsBackend::INITFLAG_VSYNC;
if(g_Config.m_GfxResizable) Flags |= IGraphicsBackend::INITFLAG_RESIZABLE; if(g_Config.m_GfxResizable) Flags |= IGraphicsBackend::INITFLAG_RESIZABLE;
int r = m_pBackend->Init("DDNet Client", &g_Config.m_GfxScreen, &g_Config.m_GfxScreenWidth, &g_Config.m_GfxScreenHeight, g_Config.m_GfxFsaaSamples, Flags, &m_DesktopScreenWidth, &m_DesktopScreenHeight); int r = m_pBackend->Init("DDNet Client", &g_Config.m_GfxScreen, &g_Config.m_GfxScreenWidth, &g_Config.m_GfxScreenHeight, g_Config.m_GfxFsaaSamples, Flags, &m_DesktopScreenWidth, &m_DesktopScreenHeight, &m_ScreenWidth, &m_ScreenHeight);
m_UseOpenGL3_3 = m_pBackend->IsOpenGL3_3(); m_UseOpenGL3_3 = m_pBackend->IsOpenGL3_3();
return r; return r;
} }
@ -1286,10 +1286,6 @@ int CGraphics_Threaded::Init()
else else
m_pVertices = m_aVerticesOld; m_pVertices = m_aVerticesOld;
// fetch final resolution
m_ScreenWidth = g_Config.m_GfxScreenWidth;
m_ScreenHeight = g_Config.m_GfxScreenHeight;
// create command buffers // create command buffers
for(int i = 0; i < NUM_CMDBUFFERS; i++) for(int i = 0; i < NUM_CMDBUFFERS; i++)
m_apCommandBuffers[i] = new CCommandBuffer(256*1024, 2*1024*1024); m_apCommandBuffers[i] = new CCommandBuffer(256*1024, 2*1024*1024);

View file

@ -429,7 +429,7 @@ public:
virtual ~IGraphicsBackend() {} virtual ~IGraphicsBackend() {}
virtual int Init(const char *pName, int *Screen, int *pWidth, int *pHeight, int FsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight) = 0; virtual int Init(const char *pName, int *Screen, int *pWidth, int *pHeight, int FsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight, int* pCurrentWidth, int* pCurrentHeight) = 0;
virtual int Shutdown() = 0; virtual int Shutdown() = 0;
virtual int MemoryUsage() const = 0; virtual int MemoryUsage() const = 0;