CRM64Pro GDK v0.80.0
A free cross-platform game development kit built on top of SDL 3.0
Loading...
Searching...
No Matches
Tutorial 06: GFX drawing

Overview

This tutorial extends Tutorial 05 with immediate GFX drawing, image filters, pixel access and additive glow helpers. It mixes these operations with normal Image and Font rendering in one Screen callback.

Tutorial 06 does not add resources to Tutorial.cdc; it reuses the background, ship and cursor created by earlier tutorials.

Final result

CRM64Pro Tutorial 06 GFX samples

Runtime controls

  • Cursor keys: Move the ship image.
  • G: Read the pixel color under the cursor and move the marker.
  • Mouse click: Read the clicked pixel and move the marker.
  • D: Show or hide the debug window.
  • Grave key: Show or hide the built-in console.
  • Q or ESC: Exit the tutorial.

Prerequisites

  • CRM64Pro GDK installed and configured with a supported C++17 compiler.
  • Tutorial package downloaded and fully extracted, preserving its folder structure.
  • Run Tutorials 02 and 04 first so Tutorial.cdc contains the background, ship and cursor resources.
  • Complete Tutorial 05 or understand its callback and built-in Font usage.

What you will learn

  • Access the immediate drawing interface through Main::gfx().
  • Draw lines, rectangles, rounded rectangles, circles and ellipses.
  • Draw outlined and filled polygons with alpha.
  • Duplicate Images and apply filters.
  • Read rendered pixels with GFX::getPixel().
  • Combine Images with glow, radial-glow and flash helpers.

Step by step

1. Load the shared resources

The tutorial adds no new CDC blocks. ImageMgr and CursorMgr load the existing resources by the stable names established earlier in the series.

// Tutorial 06 does not add new resources. It uses the CDC built by previous tutorials.
mLog.msg(LL_INFO, " Load background image from CDC ... ");
state.idBgImage = mC64.imageMgr().load(OUTPUT_CDC, RESOURCE_BG_CDC_NAME);
if(state.idBgImage < 0)
{
logTaskFailed(mLog, state.idBgImage);
Main::terminate();
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, " Load ship image from CDC ... ");
state.idPlayerImage = mC64.imageMgr().load(OUTPUT_CDC, RESOURCE_PLAYER_CDC_NAME);
if(state.idPlayerImage < 0)
{
logTaskFailed(mLog, state.idPlayerImage);
Main::terminate();
return -1;
}
logTaskOk(mLog);

2. Draw primitive rows

GFX exposes immediate helpers for common shapes. The same function draws one opaque row and one translucent row by changing only the alpha argument.

// Draw one row of graphics primitive samples.
static void renderPrimitiveRow(float fOffsetY, Uint8 iAlpha)
{
GFX& mGFX = Main::instance().gfx();
// Lines.
mGFX.lineH(40, 240, 60 + fOffsetY, 255, 220, 80, iAlpha);
mGFX.lineV(40, 60 + fOffsetY, 170 + fOffsetY, 255, 220, 80, iAlpha);
mGFX.line(40, 170 + fOffsetY, 240, 60 + fOffsetY, 255, 255, 255, iAlpha);
mGFX.line(40, 60 + fOffsetY, 240, 170 + fOffsetY, 0, 210, 255, iAlpha);
// Rectangles.
mGFX.rect(290, 55 + fOffsetY, 405, 145 + fOffsetY, 0, 230, 255, iAlpha);
mGFX.rectFilled(315, 80 + fOffsetY, 380, 120 + fOffsetY, 0, 170, 90, iAlpha);
mGFX.rectRound(430, 55 + fOffsetY, 555, 145 + fOffsetY, 18, 255, 120, 80, iAlpha);
mGFX.rectFilledRound(460, 85 + fOffsetY, 525, 125 + fOffsetY, 14, 255, 120, 80, iAlpha);
// Circles and ellipses.
mGFX.circle(665, 100 + fOffsetY, 50, 150, 255, 120, iAlpha);
mGFX.circleFilled(665, 100 + fOffsetY, 24, 150, 255, 120, iAlpha);
mGFX.ellipse(790, 100 + fOffsetY, 58, 35, 255, 255, 120, iAlpha);
mGFX.ellipseFilled(790, 100 + fOffsetY, 28, 16, 255, 255, 120, iAlpha);
}

3. Add polygons, a marker and glow helpers

Polygon helpers receive parallel coordinate arrays. The marker combines a filled circle, lines and a pixel, while the decorative effects use additive glow helpers.

// Polygon outline and filled polygon.
float fPolyX[5] = { 80.0f, 130.0f, 210.0f, 180.0f, 95.0f };
float fPolyY[5] = { 405.0f, 335.0f, 375.0f, 475.0f, 470.0f };
float fFillX[4] = { 310.0f, 430.0f, 500.0f, 360.0f };
float fFillY[4] = { 395.0f, 330.0f, 455.0f, 490.0f };
mGFX.polygon(fPolyX, fPolyY, 5, 255, 255, 255, 255);
mGFX.polygonFilled(fFillX, fFillY, 4, 0, 210, 255, 140);
// Marker uses pixel and circle drawing. Move it by clicking or pressing G.
mGFX.circleFilled(rState.fMarkerX, rState.fMarkerY, 6, 255, 80, 80, 255);
mGFX.lineH(rState.fMarkerX - 14.0f, rState.fMarkerX + 14.0f, rState.fMarkerY, 255, 80, 80, 255);
mGFX.lineV(rState.fMarkerX, rState.fMarkerY - 14.0f, rState.fMarkerY + 14.0f, 255, 80, 80, 255);
mGFX.setPixel(rState.fMarkerX, rState.fMarkerY, 255, 255, 255, 255);
// Additive helpers draw decorative light effects over the scene.
mGFX.drawGlow(610, 340, 58, 80, 180, 255, 160, 0.80f);
mGFX.drawRadialGlow(735, 340, 12, 68, 255, 100, 220, 170, 0.85f);
mGFX.drawFlash(860, 340, 54, 255, 235, 120, 210, 1.0f);

4. Create filtered Image copies

ImageMgr::duplicate() keeps the source ship unchanged. Each duplicate applies one filter, and a failed filter closes its temporary Image before returning the actual error.

// Create an image resource with the selected filter.
static bool createFilteredImage(Main& mC64, Log& mLog, Sint32 idSource, const char* szName, eImageFilter eFilter, Sint32 iValue, Sint32& rID)
{
mLog.msg(LL_INFO, " Create %s filter sample ... ", szName);
rID = mC64.imageMgr().duplicate(idSource, szName);
Image* pImage = mC64.imageMgr().get(rID);
if(rID < 0 || pImage == nullptr)
{
logTaskFailed(mLog, rID);
return false;
}
const Sint32 iRet = pImage->applyFilter(eFilter, iValue);
if(iRet < 0)
{
logTaskFailed(mLog, iRet);
mC64.imageMgr().close(rID);
rID = -1;
return false;
}
logTaskOk(mLog);
return true;
}
mLog.msg(LL_INFO, "\nFILTERS: duplicate ship and apply filters\n");
if(!createFilteredImage(mC64, mLog, state.idPlayerImage, "TutorialFilterGrey", IF_GREY, 0, state.idFilterGrey) ||
!createFilteredImage(mC64, mLog, state.idPlayerImage, "TutorialFilterNegative", IF_NEGATIVE, 0, state.idFilterNegative) ||
!createFilteredImage(mC64, mLog, state.idPlayerImage, "TutorialFilterPixelate", IF_PIXELATE, 8, state.idFilterPixelate))
{
Main::terminate();
return -1;
}

5. Select the custom cursor

Select the loaded Cursor and make it visible.

mLog.msg(LL_INFO, " Select cursor ... ");
if(mC64.cursorMgr().select(state.idCursor) < 0 ||
!mC64.cursorMgr().show())
{
logTaskFailed(mLog);
Main::terminate();
return -1;
}
logTaskOk(mLog);

6. Mix immediate and resource rendering

The render callback first draws the background and immediate primitives. It then adds a glow behind the ship, renders the ship and finishes with filtered Images and text.

// Draw the current frame. CursorMgr draws the selected cursor automatically.
static void renderFrame(Image* pBgImage, Image* pPlayerImage, Font* pBuiltInFont, const TutorialState& rState)
{
if(pBgImage) pBgImage->render();
renderGFX(rState);
if(pPlayerImage)
{
SDL_FRect rDst = {
rState.fPlayerX,
rState.fPlayerY,
static_cast<float>(pPlayerImage->getWidth()),
static_cast<float>(pPlayerImage->getHeight())
};
Main::instance().gfx().drawGlowImage(rState.idPlayerImage, rState.fPlayerX + 32.0f, rState.fPlayerY + 32.0f, 1.35f, 255, 180, 70, 130, 0.70f);
pPlayerImage->render(0, nullptr, &rDst);
}
renderFilterSamples(pBuiltInFont, rState);
renderRates(pBuiltInFont, rState);
}

7. Read pixels at current coordinates

The G key queries CursorMgr immediately, avoiding stale stored coordinates. Mouse clicks already carry their exact coordinates in the SDL event.

else if(event.key.key == SDLK_G)
{
state.fMarkerX = mC64.cursorMgr().getX();
state.fMarkerY = mC64.cursorMgr().getY();
state.iLastPixel = mC64.gfx().getPixel(state.fMarkerX, state.fMarkerY);
mLog.msg(LL_INFO, " Pixel at %.0f, %.0f: %#08x\n", state.fMarkerX, state.fMarkerY, state.iLastPixel);
}
else if(event.key.key == SDLK_D)
{
toggleDebugWindow(pDebug);
}
else if(event.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
{
state.fMarkerX = event.button.x;
state.fMarkerY = event.button.y;
state.iLastPixel = mC64.gfx().getPixel(state.fMarkerX, state.fMarkerY);
mLog.msg(LL_INFO, " Mouse clicked: button %d at %d, %d; pixel %#08x\n", event.button.button, (Sint32)event.button.x, (Sint32)event.button.y, state.iLastPixel);
}

8. Complete logic and cleanup

A quit request exits before the final state update. Cleanup closes Fonts and Cursors before Images because those resources can own Images.

if(!bRunning) break;
state.fMouseX = mC64.cursorMgr().getX();
state.fMouseY = mC64.cursorMgr().getY();
state.iCurrentRenderRate = static_cast<Sint32>(mC64.timer().getCurrentRFR());
state.iCurrentLogicRate = static_cast<Sint32>(mC64.timer().getCurrentLFR());
updateLogic(state, pPlayerImage, 1.0f / static_cast<float>(iLogicRate));
mLog.msg(LL_INFO, " Close fonts ... ");
mC64.fontMgr().close(0);
logTaskOk(mLog);
mLog.msg(LL_INFO, " Close cursors ... ");
mC64.cursorMgr().close(0);
logTaskOk(mLog);
mLog.msg(LL_INFO, " Close images ... ");
mC64.imageMgr().close(0);
logTaskOk(mLog);
mLog.msg(LL_INFO, " Close CDC archives ... ");
mC64.archiveMgr().close(0);
logTaskOk(mLog);
Main::terminate();

Complete source

The tutorial source is authoritative. The following listing is included directly from validation/source/Tutorial_06_GFX.cpp.

/* ----------------------------------------------------------------------
CRM64Pro GDK - Roberto Prieto
Copyright (C) 2013-2026 MegaStorm Systems
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
------------------------------------------------------------------------
Tutorial 06: GFX
------------------------------------------------------------------------
Overview:
This tutorial extends Tutorial 05 Font with GFX drawing primitives,
image filters and glow helpers.
It keeps the runtime resources loaded from Tutorial.cdc and uses a built-in
font only for the render and logic rate overlay.
Key concepts:
- Access the GFX interface through Main::gfx().
- Draw lines, rectangles, circles, ellipses and polygons.
- Apply Image filters to duplicate images.
- Draw additive glow helpers.
- Mix image rendering and immediate GFX drawing in the same render callback.
- Read a pixel color at the mouse position.
- Keep the debug window available but hidden by default.
Controls:
- Cursor Keys: Move the ship image.
- G: Read the pixel color under the cursor.
- D: Toggle debug window.
- Grave: Toggle console.
- Any Key/Mouse: Logs events.
- Q / ESC: Quit.
Loop order:
- Process input events.
- Run one fixed logic update.
- Let Main::update() call the screen render callback.
------------------------------------------------------------------------ */
#include "CRM64Pro.h"
#include "RunParameter.h"
using namespace CRM64Pro;
#ifdef CRM64PRO_PLATFORM_WINDOWS
#define OUTPUTDIR "Win64/"
#elif defined(CRM64PRO_PLATFORM_LINUX)
#define OUTPUTDIR "Linux/"
#elif defined(CRM64PRO_PLATFORM_MACOS)
#define OUTPUTDIR "macOS/"
#endif
#define RESOURCE_BG_CDC_NAME "Tutorial_bg"
#define RESOURCE_PLAYER_CDC_NAME "Tutorial_ship"
#define RESOURCE_CURSOR_CDC_NAME "Tutorial_cursor"
#define OUTPUT_CDC OUTPUTDIR"Tutorial.cdc"
// Fixed-rate timing.
// Render rate 0 lets the screen render as often as possible. Logic rate controls movement.
static const Sint32 iLogicRate = 20;
static const Sint32 iRenderRate = 0;
// Window size and movement speed.
static const Sint32 iScreenW = 960;
static const Sint32 iScreenH = 540;
static const float fMoveSpeed = 220.0f;
// Values changed while the tutorial runs.
struct TutorialState
{
Sint32 idBgImage = -1;
Sint32 idPlayerImage = -1;
Sint32 idFilterGrey = -1;
Sint32 idFilterNegative = -1;
Sint32 idFilterPixelate = -1;
Sint32 idCursor = -1;
Sint32 idBuiltInFont = -1;
Sint32 idDebugWindow = -1;
float fPlayerX = 386.0f;
float fPlayerY = 186.0f;
float fMouseX = 0.0f;
float fMouseY = 0.0f;
float fMarkerX = 820.0f;
float fMarkerY = 250.0f;
Sint32 iCurrentRenderRate = 0;
Sint32 iCurrentLogicRate = 0;
Sint32 iLogicFrames = 0;
Uint32 iLastPixel = 0;
bool bMoveLeft = false;
bool bMoveRight = false;
bool bMoveUp = false;
bool bMoveDown = false;
};
// Keep a value inside the given range.
static float clampFloat(float fValue, float fMin, float fMax)
{
if(fValue < fMin) return fMin;
if(fValue > fMax) return fMax;
return fValue;
}
// Helper functions used by the setup log messages.
static void logTaskOk(Log& mLog)
{
mLog.msg(LL_DEBUG, "OK\n");
}
// Record a failed tutorial task.
static void logTaskFailed(Log& mLog, Sint32 iCode)
{
mLog.msg(LL_ERROR, "FAILED (Code %d)\n", iCode);
}
// Record a failed tutorial task.
static void logTaskFailed(Log& mLog)
{
mLog.msg(LL_ERROR, "FAILED\n");
}
// Move the ship image one fixed logic step.
static void updateLogic(TutorialState& rState, Image* pPlayerImage, float fDeltaTime)
{
if(!pPlayerImage) return;
float fDX = 0.0f;
float fDY = 0.0f;
if(rState.bMoveLeft) fDX -= fMoveSpeed * fDeltaTime;
if(rState.bMoveRight) fDX += fMoveSpeed * fDeltaTime;
if(rState.bMoveUp) fDY -= fMoveSpeed * fDeltaTime;
if(rState.bMoveDown) fDY += fMoveSpeed * fDeltaTime;
const float fMaxX = static_cast<float>(iScreenW - pPlayerImage->getWidth());
const float fMaxY = static_cast<float>(iScreenH - pPlayerImage->getHeight());
rState.fPlayerX = clampFloat(rState.fPlayerX + fDX, 0.0f, fMaxX);
rState.fPlayerY = clampFloat(rState.fPlayerY + fDY, 0.0f, fMaxY);
rState.iLogicFrames++;
}
// Draw one row of graphics primitive samples.
static void renderPrimitiveRow(float fOffsetY, Uint8 iAlpha)
{
GFX& mGFX = Main::instance().gfx();
// Lines.
mGFX.lineH(40, 240, 60 + fOffsetY, 255, 220, 80, iAlpha);
mGFX.lineV(40, 60 + fOffsetY, 170 + fOffsetY, 255, 220, 80, iAlpha);
mGFX.line(40, 170 + fOffsetY, 240, 60 + fOffsetY, 255, 255, 255, iAlpha);
mGFX.line(40, 60 + fOffsetY, 240, 170 + fOffsetY, 0, 210, 255, iAlpha);
// Rectangles.
mGFX.rect(290, 55 + fOffsetY, 405, 145 + fOffsetY, 0, 230, 255, iAlpha);
mGFX.rectFilled(315, 80 + fOffsetY, 380, 120 + fOffsetY, 0, 170, 90, iAlpha);
mGFX.rectRound(430, 55 + fOffsetY, 555, 145 + fOffsetY, 18, 255, 120, 80, iAlpha);
mGFX.rectFilledRound(460, 85 + fOffsetY, 525, 125 + fOffsetY, 14, 255, 120, 80, iAlpha);
// Circles and ellipses.
mGFX.circle(665, 100 + fOffsetY, 50, 150, 255, 120, iAlpha);
mGFX.circleFilled(665, 100 + fOffsetY, 24, 150, 255, 120, iAlpha);
mGFX.ellipse(790, 100 + fOffsetY, 58, 35, 255, 255, 120, iAlpha);
mGFX.ellipseFilled(790, 100 + fOffsetY, 28, 16, 255, 255, 120, iAlpha);
}
// Draw the graphics tutorial samples.
static void renderGFX(const TutorialState& rState)
{
GFX& mGFX = Main::instance().gfx();
// First row: opaque primitives. Second row: same primitives with alpha.
renderPrimitiveRow(0.0f, 255);
renderPrimitiveRow(145.0f, 120);
// Polygon outline and filled polygon.
float fPolyX[5] = { 80.0f, 130.0f, 210.0f, 180.0f, 95.0f };
float fPolyY[5] = { 405.0f, 335.0f, 375.0f, 475.0f, 470.0f };
float fFillX[4] = { 310.0f, 430.0f, 500.0f, 360.0f };
float fFillY[4] = { 395.0f, 330.0f, 455.0f, 490.0f };
mGFX.polygon(fPolyX, fPolyY, 5, 255, 255, 255, 255);
mGFX.polygonFilled(fFillX, fFillY, 4, 0, 210, 255, 140);
// Marker uses pixel and circle drawing. Move it by clicking or pressing G.
mGFX.circleFilled(rState.fMarkerX, rState.fMarkerY, 6, 255, 80, 80, 255);
mGFX.lineH(rState.fMarkerX - 14.0f, rState.fMarkerX + 14.0f, rState.fMarkerY, 255, 80, 80, 255);
mGFX.lineV(rState.fMarkerX, rState.fMarkerY - 14.0f, rState.fMarkerY + 14.0f, 255, 80, 80, 255);
mGFX.setPixel(rState.fMarkerX, rState.fMarkerY, 255, 255, 255, 255);
// Additive helpers draw decorative light effects over the scene.
mGFX.drawGlow(610, 340, 58, 80, 180, 255, 160, 0.80f);
mGFX.drawRadialGlow(735, 340, 12, 68, 255, 100, 220, 170, 0.85f);
mGFX.drawFlash(860, 340, 54, 255, 235, 120, 210, 1.0f);
}
// Draw text at the requested screen position.
static void renderText(Font* pBuiltInFont, Sint32 iX, Sint32 iY, const char* szText)
{
if(!pBuiltInFont) return;
pBuiltInFont->setPosition(iX, iY);
pBuiltInFont->render(szText);
}
// Draw the image filtering examples.
static void renderFilterSamples(Font* pBuiltInFont, const TutorialState& rState)
{
Main& mC64 = Main::instance();
const Sint32 iFilters[3] = { rState.idFilterGrey, rState.idFilterNegative, rState.idFilterPixelate };
const char* szLabels[3] = { "Grey", "Negative", "Pixelate" };
for(Sint32 i = 0; i < 3; ++i)
{
Image* pImage = mC64.imageMgr().get(iFilters[i]);
if(!pImage) continue;
SDL_FRect rDst = {
600.0f + static_cast<float>(i * 115),
450.0f,
80.0f,
80.0f
};
pImage->render(0, nullptr, &rDst);
renderText(pBuiltInFont, static_cast<Sint32>(rDst.x), 432, szLabels[i]);
}
}
// Draw the current timing information.
static void renderRates(Font* pBuiltInFont, const TutorialState& rState)
{
if(!pBuiltInFont) return;
char szRates[96];
snprintf(szRates, sizeof(szRates), "Render: %d fps Logic: %d fps", rState.iCurrentRenderRate, rState.iCurrentLogicRate);
renderText(pBuiltInFont, 10, 10, szRates);
}
// Draw the current frame. CursorMgr draws the selected cursor automatically.
static void renderFrame(Image* pBgImage, Image* pPlayerImage, Font* pBuiltInFont, const TutorialState& rState)
{
if(pBgImage) pBgImage->render();
renderGFX(rState);
if(pPlayerImage)
{
SDL_FRect rDst = {
rState.fPlayerX,
rState.fPlayerY,
static_cast<float>(pPlayerImage->getWidth()),
static_cast<float>(pPlayerImage->getHeight())
};
Main::instance().gfx().drawGlowImage(rState.idPlayerImage, rState.fPlayerX + 32.0f, rState.fPlayerY + 32.0f, 1.35f, 255, 180, 70, 130, 0.70f);
pPlayerImage->render(0, nullptr, &rDst);
}
renderFilterSamples(pBuiltInFont, rState);
renderRates(pBuiltInFont, rState);
}
// Create an image resource with the selected filter.
static bool createFilteredImage(Main& mC64, Log& mLog, Sint32 idSource, const char* szName, eImageFilter eFilter, Sint32 iValue, Sint32& rID)
{
mLog.msg(LL_INFO, " Create %s filter sample ... ", szName);
rID = mC64.imageMgr().duplicate(idSource, szName);
Image* pImage = mC64.imageMgr().get(rID);
if(rID < 0 || pImage == nullptr)
{
logTaskFailed(mLog, rID);
return false;
}
const Sint32 iRet = pImage->applyFilter(eFilter, iValue);
if(iRet < 0)
{
logTaskFailed(mLog, iRet);
mC64.imageMgr().close(rID);
rID = -1;
return false;
}
logTaskOk(mLog);
return true;
}
// Screen render callback.
// Main::update() calls this when the screen is ready to render.
static Sint32 renderWrapper(Sint32, void* pObj)
{
TutorialState* pState = static_cast<TutorialState*>(pObj);
if(!pState) return 0;
Main& mC64 = Main::instance();
Image* pBgImage = mC64.imageMgr().get(pState->idBgImage);
Image* pPlayerImage = mC64.imageMgr().get(pState->idPlayerImage);
Font* pBuiltInFont = mC64.fontMgr().get(pState->idBuiltInFont);
renderFrame(pBgImage, pPlayerImage, pBuiltInFont, *pState);
return 0;
}
// Show or hide the debug window.
static void toggleDebugWindow(DebugWindow* pDebug)
{
if(!pDebug) return;
if(pDebug->baseWidget().status() == GS_SHOWN) pDebug->baseWidget().hide();
else pDebug->baseWidget().show();
}
// Run the tutorial application and release its resources.
int main(int argc, char* argv[])
{
if(!runParameterReadRender(argc, argv, eRenderer)) return 1;
// Start CRM64Pro memory tracking before creating engine resources.
// atexit() releases memory tracking data when the program exits.
atexit(CMem::destroy);
// Main gives access to the CRM64Pro managers.
Main& mC64 = Main::instance();
Log& mLog = *mC64.logMgr().get();
TutorialState state;
// Write log output to stdout and to a file under validation/bin.
mLog.init("Tutorial_06_GFX", LL_DEBUG, LM_FILE | LM_STDOUT, OUTPUTDIR"Tutorial_06_GFX.log");
mLog.msg(LL_INFO, "\nSETUP: screen and timing\n");
// Initialize the timer before calling Main::update().
// setRate() receives render frames per second, then logic frames per second.
mLog.msg(LL_INFO, " Initialize timer ... ");
if(!mC64.timer().init())
{
logTaskFailed(mLog);
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, " Set frame rates (render=%d, logic=%d) ... ", iRenderRate, iLogicRate);
if(!mC64.timer().setRate(iRenderRate, iLogicRate))
{
logTaskFailed(mLog);
return -1;
}
logTaskOk(mLog);
// Add a 1 ms idle delay inside Main::update().
// Configure and show the default screen.
Screen* pScreen = mC64.configMgr().get();
pScreen->setSize(iScreenW, iScreenH);
pScreen->setDriver(eRenderer);
pScreen->setTitle("CRM64Pro - Tutorial 06: GFX");
mLog.msg(LL_INFO, " Create screen (%dx%d) ... ", iScreenW, iScreenH);
if(pScreen->show() < 0)
{
mLog.msg(LL_CRITICAL, "FAILED\n");
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, " Set render callback ... ");
if(!pScreen->setRenderCallback([&state](Sint32 iMode) { return renderWrapper(iMode, &state); }))
{
logTaskFailed(mLog);
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, "\nRESOURCES: load from CDC\n");
// Tutorial 06 does not add new resources. It uses the CDC built by previous tutorials.
mLog.msg(LL_INFO, " Load background image from CDC ... ");
state.idBgImage = mC64.imageMgr().load(OUTPUT_CDC, RESOURCE_BG_CDC_NAME);
if(state.idBgImage < 0)
{
logTaskFailed(mLog, state.idBgImage);
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, " Load ship image from CDC ... ");
state.idPlayerImage = mC64.imageMgr().load(OUTPUT_CDC, RESOURCE_PLAYER_CDC_NAME);
if(state.idPlayerImage < 0)
{
logTaskFailed(mLog, state.idPlayerImage);
return -1;
}
logTaskOk(mLog);
Image* pPlayerImage = mC64.imageMgr().get(state.idPlayerImage);
if(pPlayerImage == nullptr)
{
logTaskFailed(mLog);
return -1;
}
mLog.msg(LL_INFO, "\nFILTERS: duplicate ship and apply filters\n");
if(!createFilteredImage(mC64, mLog, state.idPlayerImage, "TutorialFilterGrey", IF_GREY, 0, state.idFilterGrey) ||
!createFilteredImage(mC64, mLog, state.idPlayerImage, "TutorialFilterNegative", IF_NEGATIVE, 0, state.idFilterNegative) ||
!createFilteredImage(mC64, mLog, state.idPlayerImage, "TutorialFilterPixelate", IF_PIXELATE, 8, state.idFilterPixelate))
{
return -1;
}
mLog.msg(LL_INFO, " Load cursor from CDC ... ");
state.idCursor = mC64.cursorMgr().load(OUTPUT_CDC, RESOURCE_CURSOR_CDC_NAME);
Cursor* pCursor = mC64.cursorMgr().get(state.idCursor);
if(state.idCursor < 0 || pCursor == nullptr)
{
logTaskFailed(mLog, state.idCursor);
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, " Select cursor ... ");
if(mC64.cursorMgr().select(state.idCursor) < 0 ||
!mC64.cursorMgr().show())
{
logTaskFailed(mLog);
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, " Load built-in font ... ");
state.idBuiltInFont = mC64.fontMgr().getBuiltin("CourierNew10White");
Font* pBuiltInFont = mC64.fontMgr().get(state.idBuiltInFont);
if(state.idBuiltInFont < 0 || pBuiltInFont == nullptr)
{
logTaskFailed(mLog, state.idBuiltInFont);
return -1;
}
logTaskOk(mLog);
mLog.msg(LL_INFO, "\nTOOLS: GFX, glow helpers, debug window and console\n");
// Main::gfx() returns primitives, pixel helpers and glow helpers. No resource creation is required.
mLog.msg(LL_INFO, " Access GFX interface ... ");
GFX& mGFX = mC64.gfx();
(void)mGFX;
logTaskOk(mLog);
// The DebugWindow is available, but this tutorial starts with it hidden.
mLog.msg(LL_INFO, " Create debug window ... ");
state.idDebugWindow = mC64.guiMgr().create("Debug", CRM64PRO_GUI_DEBUGWINDOW);
DebugWindow* pDebug = mC64.guiMgr().getDebugWindow(state.idDebugWindow);
if(!pDebug)
{
logTaskFailed(mLog, state.idDebugWindow);
return -1;
}
pDebug->addWatch("Player X", nullptr, &state.fPlayerX);
pDebug->addWatch("Player Y", nullptr, &state.fPlayerY);
pDebug->addWatch("Mouse X", nullptr, &state.fMouseX);
pDebug->addWatch("Mouse Y", nullptr, &state.fMouseY);
pDebug->addWatch("Marker X", nullptr, &state.fMarkerX);
pDebug->addWatch("Marker Y", nullptr, &state.fMarkerY);
pDebug->addWatch("Render fps", &state.iCurrentRenderRate);
pDebug->addWatch("Logic fps", &state.iCurrentLogicRate);
pDebug->addWatch("Logic frames", &state.iLogicFrames);
pDebug->baseWidget().hide();
logTaskOk(mLog);
// Main creates the default Console. Log output is also sent there.
Console* pConsole = mC64.guiMgr().getConsole();
if(pConsole) pConsole->print("Tutorial 06: GFX ready. Press ` to toggle the console.\n");
mLog.msg(LL_INFO, "\nCONTROLS\n");
mLog.msg(LL_INFO, " [Q] / [ESC] Quit\n");
mLog.msg(LL_INFO, " [Arrow Keys] Move the ship image\n");
mLog.msg(LL_INFO, " [G] Read pixel color under the cursor\n");
mLog.msg(LL_INFO, " [D] Toggle debug window\n");
mLog.msg(LL_INFO, " [`] Toggle console\n");
mLog.msg(LL_INFO, " Any key or mouse button logs the input event\n");
mLog.msg(LL_INFO, "\nRUNNING\n");
SDL_Event event;
bool bRunning = true;
while(bRunning)
{
// Main::update() processes input, runs internal GUI logic and calls the
// screen render callback when a render frame is ready.
while(mC64.update(&event))
{
if(event.type == SDL_EVENT_QUIT)
{
bRunning = false;
}
else if(event.type == SDL_EVENT_KEY_DOWN)
{
// SDL3 stores the key value in event.key.key.
mLog.msg(LL_INFO, " Key pressed: %s (%d)\n", SDL_GetKeyName(event.key.key), event.key.key);
if(event.key.key == SDLK_Q || event.key.key == SDLK_ESCAPE)
{
bRunning = false;
}
else if(event.key.key == SDLK_LEFT)
{
state.bMoveLeft = true;
}
else if(event.key.key == SDLK_RIGHT)
{
state.bMoveRight = true;
}
else if(event.key.key == SDLK_UP)
{
state.bMoveUp = true;
}
else if(event.key.key == SDLK_DOWN)
{
state.bMoveDown = true;
}
else if(event.key.key == SDLK_G)
{
state.fMarkerX = mC64.cursorMgr().getX();
state.fMarkerY = mC64.cursorMgr().getY();
state.iLastPixel = mC64.gfx().getPixel(state.fMarkerX, state.fMarkerY);
mLog.msg(LL_INFO, " Pixel at %.0f, %.0f: %#08x\n", state.fMarkerX, state.fMarkerY, state.iLastPixel);
}
else if(event.key.key == SDLK_D)
{
toggleDebugWindow(pDebug);
}
}
else if(event.type == SDL_EVENT_KEY_UP)
{
if(event.key.key == SDLK_LEFT) state.bMoveLeft = false;
else if(event.key.key == SDLK_RIGHT) state.bMoveRight = false;
else if(event.key.key == SDLK_UP) state.bMoveUp = false;
else if(event.key.key == SDLK_DOWN) state.bMoveDown = false;
}
else if(event.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
{
state.fMarkerX = event.button.x;
state.fMarkerY = event.button.y;
state.iLastPixel = mC64.gfx().getPixel(state.fMarkerX, state.fMarkerY);
mLog.msg(LL_INFO, " Mouse clicked: button %d at %d, %d; pixel %#08x\n", event.button.button, (Sint32)event.button.x, (Sint32)event.button.y, state.iLastPixel);
}
}
if(!bRunning) break;
state.fMouseX = mC64.cursorMgr().getX();
state.fMouseY = mC64.cursorMgr().getY();
state.iCurrentRenderRate = static_cast<Sint32>(mC64.timer().getCurrentRFR());
state.iCurrentLogicRate = static_cast<Sint32>(mC64.timer().getCurrentLFR());
updateLogic(state, pPlayerImage, 1.0f / static_cast<float>(iLogicRate));
}
// Close resources owned by this tutorial.
mC64.gfx().info();
mLog.msg(LL_INFO, "\nCLEANUP\n");
mLog.msg(LL_INFO, " Close debug window ... ");
mC64.guiMgr().close(state.idDebugWindow);
logTaskOk(mLog);
mLog.msg(LL_INFO, " Close fonts ... ");
mC64.fontMgr().close(0);
logTaskOk(mLog);
mLog.msg(LL_INFO, " Close cursors ... ");
mC64.cursorMgr().close(0);
logTaskOk(mLog);
mLog.msg(LL_INFO, " Close images ... ");
mC64.imageMgr().close(0);
logTaskOk(mLog);
mLog.msg(LL_INFO, " Close CDC archives ... ");
mC64.archiveMgr().close(0);
logTaskOk(mLog);
return 0;
}
Sint32 close(Sint32 idCDC) override
Close and destroy a CDC object.
Definition CoreArchive.cpp:2476
Screen * get(Sint32 idScreen=0)
Get a pointer to the screen using its handle.
Definition CoreConfig.cpp:1497
void setMTFriendly(Uint8 iDelayMS)
Set the multithread-friendly scheduler yield.
Definition CoreConfig.cpp:1183
Console Object class.
Definition CRM64Pro.h:9678
Cursor Object class.
Definition CRM64Pro.h:3734
float getY() const
Get the mouse cursor Y position.
Definition GraphicsCursor.cpp:1041
Sint32 select(Sint32 idCursor)
Select a mouse cursor.
Definition GraphicsCursor.cpp:968
bool show()
Make visible the selected mouse cursor.
Definition GraphicsCursor.cpp:1001
Sint32 load(const string &sCDCFile, const string &sName)
Load a cursor stored in a CDC file.
Definition GraphicsCursor.cpp:1115
Sint32 close(Sint32 idCursor) override
Close and destroy a cursor.
Definition GraphicsCursor.cpp:897
Cursor * get(Sint32 idCursor)
Get a pointer to the cursor using its handle.
Definition GraphicsCursor.cpp:962
float getX() const
Get the mouse cursor X position.
Definition GraphicsCursor.cpp:1035
DebugWindow Object class.
Definition CRM64Pro.h:9810
Font Object class.
Definition CRM64Pro.h:4741
Sint32 render(const string &sText, bool bShowCursor=false, Sint32 iCurPos=-1, Sint32 idRes=0)
Render a text using this font.
Definition GraphicsFont.cpp:1095
bool setPosition(const Position &rPosX, const Position &rPosY, bool bInterpolate=false)
Set the font position.
Definition GraphicsFont.cpp:1037
Sint32 getBuiltin(const string &sName)
Get a built-in embedded font id using its name.
Definition GraphicsFont.cpp:1436
Sint32 close(Sint32 idFont) override
Close and destroy a font.
Definition GraphicsFont.cpp:1363
Font * get(const string &sName)
Get a pointer to the font using its name.
Definition GraphicsFont.cpp:1235
GFX class.
Definition CRM64Pro.h:5083
Image Object class.
Definition CRM64Pro.h:3356
Sint32 applyFilter(eImageFilter eFilter, Sint32 iValue=0)
Apply a filter to this image.
Definition GraphicsImage.cpp:1589
Sint32 getHeight() const
Get the image height.
Definition GraphicsImage.cpp:1839
Sint32 getWidth() const
Get the image width.
Definition GraphicsImage.cpp:1833
Sint32 render(Sint32 idRes=0, SDL_FRect *pSrc=nullptr, SDL_FRect *pDst=nullptr)
Render the image.
Definition GraphicsImage.cpp:1575
Image * get(Sint32 idImg)
Get a pointer to the image using its handle.
Definition GraphicsImage.cpp:2279
Sint32 load(const string &sCDCFile, const string &sName)
Load an image stored in a CDC file.
Definition GraphicsImage.cpp:2476
Sint32 duplicate(Sint32 idImg, const string &sName)
Duplicate an image creating a new, identical and independent one.
Definition GraphicsImage.cpp:2285
Sint32 close(Sint32 idImg) override
Close and destroy an image.
Definition GraphicsImage.cpp:2178
Log Object class.
Definition CRM64Pro.h:771
CRM64Pro GDK Main class.
Definition CRM64Pro.h:502
Screen Object class.
Definition CRM64Pro.h:1055
Sint32 show()
Show the screen.
Definition CoreConfig.cpp:362
Sint32 setDriver(eConfigRendererDriver eCRD)
Set the renderer driver.
Definition CoreConfig.cpp:454
bool setRenderCallback(RenderCallback fnCallback)
Set a callback function for rendering graphics at Render Frame Rate.
Definition CoreConfig.cpp:765
Sint32 setSize(Sint32 iWidth, Sint32 iHeight)
Set the size of the screen.
Definition CoreConfig.cpp:526
bool setTitle(const string &sName)
Set the title.
Definition CoreConfig.cpp:674
void setStatsLevel(eMemStatsLevel eMSL)
Set the memory stats gathering level.
Definition CMemoryManager.cpp:618
void destroy()
Releases the internal heap and displays final stats.
Definition CMemoryManager.cpp:954
@ MSL_NORMAL
Collect allocations per module (CRM64Pro and user). Default for debug builds.
Definition CRM64Pro.h:386
eConfigRendererDriver
Screen renderer drivers.
Definition CRM64Pro.h:1013
@ CRD_SOFTWARE
Screen::setDriver(): software renderer driver. Slow but works on all platforms.
Definition CRM64Pro.h:1015
bool setPixel(float fX, float fY, Uint32 iColor, Sint32 idRes=0)
Draw a pixel.
Definition GraphicsGFX.cpp:2102
bool rectFilledRound(float fX1, float fY1, float fX2, float fY2, float fRad, Uint32 iColor, Sint32 idRes=0)
Draw a round filled rectangle.
Definition GraphicsGFX.cpp:2501
bool drawRadialGlow(float fX, float fY, float fInnerRadius, float fOuterRadius, Uint32 iColor, float fIntensity=1.0f, Sint32 idRes=0)
Draw a soft additive radial glow with a solid inner radius.
Definition GraphicsGFX.cpp:2881
bool rect(float fX1, float fY1, float fX2, float fY2, Uint32 iColor, Sint32 idRes=0)
Draw an outlined rectangle.
Definition GraphicsGFX.cpp:2287
bool ellipse(float fX, float fY, float fRadX, float fRadY, Uint32 iColor, Sint32 idRes=0)
Draw an outlined ellipse.
Definition GraphicsGFX.cpp:2984
bool line(float fX1, float fY1, float fX2, float fY2, Uint32 iColor, Sint32 idRes=0)
Draw a line.
Definition GraphicsGFX.cpp:2245
bool ellipseFilled(float fX, float fY, float fRadX, float fRadY, Uint32 iColor, Sint32 idRes=0)
Draw a filled ellipse.
Definition GraphicsGFX.cpp:3037
bool polygon(const float *fVX, const float *fVY, Sint32 iNV, Uint32 iColor, Sint32 idRes=0)
Draw an outlined polygon.
Definition GraphicsGFX.cpp:3117
bool lineH(float fX1, float fX2, float fY, Uint32 iColor, Sint32 idRes=0)
Draw a horizontal line.
Definition GraphicsGFX.cpp:2191
bool rectFilled(float fX1, float fY1, float fX2, float fY2, Uint32 iColor, Sint32 idRes=0)
Draw a filled rectangle.
Definition GraphicsGFX.cpp:2349
Uint32 getPixel(float fX, float fY, Sint32 idRes=0) const
Get a pixel color.
Definition GraphicsGFX.cpp:2143
bool lineV(float fX, float fY1, float fY2, Uint32 iColor, Sint32 idRes=0)
Draw a vertical line.
Definition GraphicsGFX.cpp:2218
bool circleFilled(float fX, float fY, float fRad, Uint32 iColor, Sint32 idRes=0)
Draw a filled circle.
Definition GraphicsGFX.cpp:2729
bool circle(float fX, float fY, float fRad, Uint32 iColor, Sint32 idRes=0)
Draw an outlined circle.
Definition GraphicsGFX.cpp:2716
bool info(Sint32 iMode=0) override
Request GFX module information.
Definition GraphicsGFX.cpp:1952
bool drawGlow(float fX, float fY, float fRadius, Uint32 iColor, float fIntensity=1.0f, Sint32 idRes=0)
Draw a soft additive glow.
Definition GraphicsGFX.cpp:2868
bool drawFlash(float fX, float fY, float fRadius, Uint32 iColor, float fIntensity=1.0f, Sint32 idRes=0)
Draw a short hot-center additive flash.
Definition GraphicsGFX.cpp:2900
bool rectRound(float fX1, float fY1, float fX2, float fY2, float fRad, Uint32 iColor, Sint32 idRes=0)
Draw a round outlined rectangle.
Definition GraphicsGFX.cpp:2413
bool polygonFilled(const float *fVX, const float *fVY, Sint32 iNV, Uint32 iColor, Sint32 idRes=0)
Draw a filled polygon.
Definition GraphicsGFX.cpp:3179
Sint32 create(const string &sName, Uint32 iType=CRM64PRO_GUI_PANEL) override
Creates a new GUI object.
Definition GUI.cpp:1051
Sint32 addWatch(const string &sName, Sint32 *iIntegerVar, float *fFloatVar=nullptr)
Add a watch.
Definition GUIDebugWindow.cpp:275
#define CRM64PRO_GUI_DEBUGWINDOW
Definition CRM64Pro.h:9195
Widget & baseWidget()
Get the reference to base Widget for this object.
Definition GUIDebugWindow.cpp:269
Sint32 print(const char *format,...)
Print a formatted message to the console.
Definition GUIConsole.cpp:403
Console * getConsole(Sint32 idGUI=CRM64PRO_GUI_CONSOLE)
Get a pointer to a Console using its handle.
Definition GUI.cpp:1366
DebugWindow * getDebugWindow(Sint32 idGUI)
Get a pointer to a DebugWindow using its handle.
Definition GUI.cpp:1375
Sint32 close(Sint32 idGUI) override
Close and destroy a GUI object.
Definition GUI.cpp:1168
eGeneralStatus status() const
Get the widget status.
Definition GUIWidget.cpp:1772
bool hide()
Hide a widget.
Definition GUIWidget.cpp:1729
bool show()
Show a widget.
Definition GUIWidget.cpp:1671
Sint32 setPosition(const Position &rPosX=Position(PH_CENTER), const Position &rPosY=Position(PH_CENTER))
Set the position.
Definition GUIWidget.cpp:1923
eImageFilter
Image filters.
Definition CRM64Pro.h:3342
@ IF_GREY
'eFilter' parameter in Image::applyFilter. Apply a grey effect.
Definition CRM64Pro.h:3346
@ IF_PIXELATE
'eFilter' parameter in Image::applyFilter. Apply a pixelate effect that can be animated using variabl...
Definition CRM64Pro.h:3349
@ IF_NEGATIVE
'eFilter' parameter in Image::applyFilter. Apply a negative effect.
Definition CRM64Pro.h:3345
Log * get(Sint32 idLog=0)
Get a pointer to the log object.
Definition CoreLog.cpp:921
bool init(const string &sAppname, eLogLevel eLL, eLogMode eLM, const string &sFilename="", const string &sAuthor="", const string &sEmail="", const string &sWeb="")
Initialize the log object.
Definition CoreLog.cpp:338
@ LM_FILE
Log output mode for the 'lm' parameter in CRM64Pro::Log::init(). Output to a file and overwrite exist...
Definition CRM64Pro.h:748
@ LM_STDOUT
Log output mode for the 'lm' parameter in CRM64Pro::Log::init(). Output to standard output (stdout).
Definition CRM64Pro.h:750
@ LL_INFO
Runtime events (startup/shutdown). Messages without timestamp.
Definition CRM64Pro.h:764
@ LL_DEBUG
Detailed information for flow control.
Definition CRM64Pro.h:765
@ LL_CRITICAL
Fatal errors, application crash, or assertions.
Definition CRM64Pro.h:761
@ LL_ERROR
Runtime errors that do not require immediate termination.
Definition CRM64Pro.h:762
GUIMgr & guiMgr()
Get reference to GUI Manager.
Definition CoreMain.cpp:808
Timer & timer()
Get reference to Timer module.
Definition CoreMain.cpp:730
Sint32 update(SDL_Event *evUser=nullptr)
Manage the GDK update cycle with a fixed virtual logic frame rate.
Definition CoreMain.cpp:451
ImageMgr & imageMgr()
Get reference to Image Manager.
Definition CoreMain.cpp:754
CursorMgr & cursorMgr()
Get reference to Cursor Manager.
Definition CoreMain.cpp:760
GFX & gfx()
Get reference to GFX module.
Definition CoreMain.cpp:784
static void terminate()
Main explicit destructor.
Definition CoreMain.cpp:266
static Main & instance()
Access the Main singleton.
Definition CoreMain.cpp:252
LogMgr & logMgr()
Get reference to Log Manager.
Definition CoreMain.cpp:724
ArchiveMgr & archiveMgr()
Get reference to Archive Manager.
Definition CoreMain.cpp:736
FontMgr & fontMgr()
Get reference to Font Manager.
Definition CoreMain.cpp:778
ConfigMgr & configMgr()
Get reference to Config Manager.
Definition CoreMain.cpp:712
@ GS_SHOWN
General status: enabled and shown.
Definition CRM64Pro.h:300
@ PH_RIGHT
Widget/Font: right with optional offset. Scene: align to map right/end (MapWidth - ViewportWidth)....
Definition CRM64Pro.h:340
@ PH_TOP
Widget/Font: top with optional offset. Scene: align to map top/begin (0.0). Valid for Y axis.
Definition CRM64Pro.h:337
bool init(eTimerState eState=TS_INIT)
Initialize the timer system.
Definition CoreTimer.cpp:543
bool setRate(Uint16 iR, Uint16 iL)
Set preferred Render and Logic Frame Rate.
Definition CoreTimer.cpp:556
CRM64Pro GDK.
Definition AudioTrack.cpp:36
Absolute coordinate or helper-relative position used by layout APIs.
Definition CRM64Pro.h:346

Navigation

Previous: Tutorial 05 - Fonts

Back to the tutorials index

The next tutorial creates and renders a Tileset map.

Continue with Tutorial 07: Tileset maps