Irrlicht Software Renderer + Autoscale

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
Noiecity
Posts: 324
Joined: Wed Aug 23, 2023 7:22 pm
Contact:

Irrlicht Software Renderer + Autoscale

Post by Noiecity »

Image

It works well and consumes very little CPU.

Code: Select all

#include <irrlicht.h>
#include <windows.h>
#pragma comment(lib, "Irrlicht.lib")

// Use common Irrlicht namespaces
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace gui;

// --- Global variables for Win32 and double buffering ---
static HWND g_hWnd;             // Main application window handle
static HWND g_hIrrlichtWnd;     // Child window for Irrlicht rendering
static HBRUSH g_bgBrush = NULL; // Background brush for clearing
static HDC g_hMemDC = NULL;     // Memory Device Context (off-screen drawing)
static HBITMAP g_hMemBitmap = NULL; // Off-screen bitmap
static HBITMAP g_hOldBitmap = NULL; // Original bitmap to restore after drawing

// --- Irrlicht-related global pointers ---
static IrrlichtDevice* device = nullptr;
static IVideoDriver* driver = nullptr;
static ISceneManager* smgr = nullptr;
static ITexture* renderTexture = nullptr; // Off-screen render target

// --- Windows bitmap info for drawing Irrlicht output ---
static BITMAPINFO bmi = {};
static HBITMAP hBitmap = nullptr;
static void* bmpBits = nullptr; // Pointer to pixel data

// --- Window size & fullscreen/maximized tracking ---
static RECT windowRectInitial = {};
static bool isFullscreen = false;
static bool isMaximized = false;
static RECT windowRectBeforeFullscreen = {};

// --- Scene objects ---
static IAnimatedMeshSceneNode* node2 = nullptr;
static ISceneNode* node3 = nullptr;
static ISceneNode* node4 = nullptr;
static ICameraSceneNode* camera = nullptr;

// --- Camera control variables ---
f32 yaw = 0.0f;         // Horizontal rotation (left/right)
f32 pitch = 0.0f;       // Vertical rotation (up/down)
f32 distance = 100.0f;  // Distance from camera to its target

position2di lastMousePos;

// Update the camera's target position based on yaw and pitch
void updateCameraTarget() {
  f32 radYaw = yaw * core::DEGTORAD;
  f32 radPitch = pitch * core::DEGTORAD;

  // Convert spherical coordinates to Cartesian
  f32 x = distance * cosf(radPitch) * cosf(radYaw);
  f32 y = distance * sinf(radPitch);
  f32 z = distance * cosf(radPitch) * sinf(radYaw);

  vector3df cameraPos = camera->getPosition();
  vector3df newTarget = cameraPos + vector3df(x, y, z);
  camera->setTarget(newTarget);
}

// Forward declaration of main loop function
void loop_func();

// Frame count and FPS update tracking
static unsigned int frameCount = 0;
static DWORD lastFpsUpdate = 0;

// --- Create bitmap surface for drawing Irrlicht output ---
void CreateBitmapSurface() {
  ZeroMemory(&bmi, sizeof(BITMAPINFO));
  bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bmi.bmiHeader.biWidth = 256;
  bmi.bmiHeader.biHeight = -256; // Negative for top-down bitmap
  bmi.bmiHeader.biPlanes = 1;
  bmi.bmiHeader.biBitCount = 24;
  bmi.bmiHeader.biCompression = BI_RGB;

  HDC hdc = GetDC(NULL);
  hBitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &bmpBits, NULL, 0);
  ReleaseDC(NULL, hdc);
}

// Copy pixels from Irrlicht's texture to Windows bitmap
void UpdateBitmapFromTexture() {
  if (!driver || !renderTexture || !bmpBits) return;

  IImage* image = driver->createImage(renderTexture, position2d<s32>(0, 0),
                                      dimension2d<u32>(256, 256));
  if (!image) return;

  u8* dst = (u8*)bmpBits;
  for (u32 y = 0; y < 256; ++y) {
    for (u32 x = 0; x < 256; ++x) {
      SColor c = image->getPixel(x, y);
      int offset = (y * 256 + x) * 3;
      dst[offset]     = c.getBlue();
      dst[offset + 1] = c.getGreen();
      dst[offset + 2] = c.getRed();
    }
  }
  image->drop();
}

// --- Main Windows procedure (handles events and double buffering) ---
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
  switch (msg) {
    case WM_CREATE:
      g_hMemDC = CreateCompatibleDC(NULL);
      return 0;

    case WM_SIZE: {
      if (g_hMemBitmap) DeleteObject(g_hMemBitmap);
      HDC hdc = GetDC(hWnd);
      int width = LOWORD(lParam);
      int height = HIWORD(lParam);
      g_hMemBitmap = CreateCompatibleBitmap(hdc, width, height);
      ReleaseDC(hWnd, hdc);
      g_hOldBitmap = (HBITMAP)SelectObject(g_hMemDC, g_hMemBitmap);
	  InvalidateRect(hWnd, NULL, TRUE);
	  UpdateWindow(hWnd);
      return 0;
    }

    case WM_ERASEBKGND:
      return 1; // Prevent flickering by skipping background erase

    case WM_PAINT: {
      PAINTSTRUCT ps;
      HDC hdc = BeginPaint(hWnd, &ps);
      RECT rc;
      GetClientRect(hWnd, &rc);
      FillRect(g_hMemDC, &rc, g_bgBrush);
      if (hBitmap && bmpBits) {
        int winWidth = rc.right - rc.left;
        int winHeight = rc.bottom - rc.top;
        int size = min(winWidth, winHeight);
        int x = (winWidth - size) / 2;
        int y = (winHeight - size) / 2;
        SetStretchBltMode(g_hMemDC, COLORONCOLOR);
        StretchDIBits(g_hMemDC, x, y, size, size, 0, 0, 256, 256, bmpBits, &bmi,
                      DIB_RGB_COLORS, SRCCOPY);
      }
      BitBlt(hdc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, g_hMemDC, 0, 0,
             SRCCOPY);
      EndPaint(hWnd, &ps);
      return 0;
    }

    case WM_DESTROY:
      SelectObject(g_hMemDC, g_hOldBitmap);
      DeleteObject(g_hMemBitmap);
      DeleteDC(g_hMemDC);
      PostQuitMessage(0);
      return 0;

    default:
      return DefWindowProc(hWnd, msg, wParam, lParam);
  }
}

// Toggle between fullscreen and windowed mode
void ToggleFullscreen(HWND hWnd) {
  if (!isFullscreen) {
    GetWindowRect(hWnd, &windowRectBeforeFullscreen);
    HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
    MONITORINFO mi = {sizeof(mi)};
    GetMonitorInfo(hMonitor, &mi);
    SetWindowLong(hWnd, GWL_STYLE, WS_POPUP);
    SetWindowPos(hWnd, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top,
                 mi.rcMonitor.right - mi.rcMonitor.left,
                 mi.rcMonitor.bottom - mi.rcMonitor.top,
                 SWP_FRAMECHANGED | SWP_SHOWWINDOW);
    isFullscreen = true;
  } else {
    SetWindowLong(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
    SetWindowPos(hWnd, HWND_NOTOPMOST, windowRectBeforeFullscreen.left,
                 windowRectBeforeFullscreen.top,
                 windowRectBeforeFullscreen.right - windowRectBeforeFullscreen.left,
                 windowRectBeforeFullscreen.bottom - windowRectBeforeFullscreen.top,
                 SWP_FRAMECHANGED | SWP_SHOWWINDOW);
    isFullscreen = false;
  }
  // Force repaint
  InvalidateRect(hWnd, NULL, TRUE);
  UpdateWindow(hWnd);
  RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN);
}

// Toggle between maximized and normal window size
void ToggleMaximizedWindow(HWND hWnd) {
  if (!isMaximized) {
    ShowWindow(hWnd, SW_MAXIMIZE);
    isMaximized = true;
  } else {
    SetWindowLong(hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
    SetWindowPos(hWnd, HWND_NOTOPMOST, windowRectInitial.left,
                 windowRectInitial.top,
                 windowRectInitial.right - windowRectInitial.left,
                 windowRectInitial.bottom - windowRectInitial.top,
                 SWP_FRAMECHANGED | SWP_SHOWWINDOW);
    isMaximized = false;
  }
  // Force repaint
  InvalidateRect(hWnd, NULL, TRUE);
  UpdateWindow(hWnd);
  RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN);
}
// -------------------
// WinMain - Entry point of the application
// -------------------
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) {
  // Create a solid black background brush
  g_bgBrush = CreateSolidBrush(RGB(0, 0, 0));

  // Register the window class
  WNDCLASSEX wc = {
    sizeof(WNDCLASSEX),                 // Size of the structure
    CS_HREDRAW | CS_VREDRAW,           // Redraw on horizontal/vertical resize
    WndProc,                           // Window procedure function
    0, 0,                              // No extra memory
    hInst,                             // Application instance
    NULL,                              // No icon
    LoadCursor(NULL, IDC_ARROW),       // Default arrow cursor
    NULL, NULL,                        // No background brush or menu
    "TestWindow",                      // Class name
    NULL                               // No small icon
  };
  RegisterClassEx(&wc);

  // Create the main window
  g_hWnd = CreateWindowEx(0, "TestWindow", "CPU Render - Irrlicht Engine",
                          WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
                          512, 512, NULL, NULL, hInst, NULL);
  GetWindowRect(g_hWnd, &windowRectInitial);

  // Create a child window that Irrlicht will render into
  g_hIrrlichtWnd = CreateWindowEx(0, "STATIC", NULL, WS_CHILD, 0, 0, 256, 256,
                                  g_hWnd, NULL, hInst, NULL);

  // --- Setup Irrlicht Device Parameters ---
  SIrrlichtCreationParameters params;
  params.DriverType = EDT_SOFTWARE; // Use software renderer
  params.WindowId = g_hIrrlichtWnd; // Render into the child window
  params.WindowSize = dimension2d<u32>(256, 256);
  params.Bits = 8; // Color depth

  // Create the Irrlicht device
  device = createDeviceEx(params);
  if (!device) return 1; // Failed to initialize

  driver = device->getVideoDriver();
  smgr = device->getSceneManager();
  ShowWindow(g_hIrrlichtWnd, SW_HIDE); // Hide the child window (we draw manually)

  // Create a render target texture to draw into
  renderTexture = driver->addRenderTargetTexture(dimension2d<u32>(256, 256),
                                                 "RTT1", ECF_R8G8B8);
  //driver->setTextureCreationFlag(ETCF_CREATE_MIP_MAPS, false); // Disable mipmaps

  // ----------------------------
  // Load 3D assets into the scene
  // ----------------------------

  // Mesh 1 - Animated character
  IAnimatedMesh* mesh2 = smgr->getMesh("../../media/Monster3_forirrlicht.md2");
  node2 = smgr->addAnimatedMeshSceneNode(mesh2);
  if (node2) {
    node2->setMaterialType(EMT_SOLID);
    node2->setMaterialFlag(EMF_LIGHTING, false);
    node2->setMaterialTexture(0, driver->getTexture("../../media/Monster3_Albedo.jpg"));
    node2->setMD2Animation("run");
    node2->setAnimationSpeed(64);
    node2->setScale(vector3df(4, 4, 4));
    node2->setPosition(vector3df(0, 0, -15));
    //node2->setMaterialFlag(EMF_USE_MIP_MAPS, false);
    node2->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
  }

  // Mesh 2 - Scene environment
  IAnimatedMesh* mesh3 = smgr->getMesh("../../media/Zone1test.md2");
  node3 = smgr->addMeshSceneNode(mesh3);
  if (node3) {
    node3->setMaterialType(EMT_SOLID);
    node3->setMaterialFlag(EMF_LIGHTING, false);
    node3->setMaterialTexture(0, driver->getTexture("../../media/zone1Albedo.jpg"));
    node3->setScale(vector3df(5, 5, 5));
    node3->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
  }

  // Mesh 3 - A shadow/flat model
  IAnimatedMesh* mesh4 = smgr->getMesh("../../media/shadow.obj");
  node4 = smgr->addMeshSceneNode(mesh4);
  if (node4) {
    node4->setMaterialType(EMT_SOLID);
    node4->setMaterialFlag(EMF_LIGHTING, false);
    node4->setMaterialTexture(0, driver->getTexture("../../media/shadow.jpg"));
    node4->setScale(vector3df(2, 2, 2));
    node4->setPosition(vector3df(1, 0, -15));
    node4->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
  }

  // Add camera to the scene
  camera = smgr->addCameraSceneNode();
  camera->setTarget(vector3df(0, 0, 0));
  camera->setAspectRatio(1.0);
  camera->setFOV(1.0f);
  camera->setPosition(vector3df(0, 7, 0));

  // Hide cursor (FPS style control)
  gui::ICursorControl* cursorControl = device->getCursorControl();
  cursorControl->setVisible(false);

  // Create Windows bitmap surface to copy Irrlicht's render
  CreateBitmapSurface();

  // Show main window
  ShowWindow(g_hWnd, nShow);
  UpdateWindow(g_hWnd);

  bool state;

  // -------------------
  // Main Application Loop
  // -------------------
MainLoop:
  state = device->run() ? 1 : 0;
  switch (state) {
    case 1: {
      // Center of Irrlicht render target
      dimension2d<u32> screenSize = device->getVideoDriver()->getScreenSize();
      position2di center(screenSize.Width / 2, screenSize.Height / 2);

      // Mouse delta movement
      position2di currentPos = device->getCursorControl()->getPosition();
      position2di delta = currentPos - center;

      if (delta.X != 0 || delta.Y != 0) {
        f32 sensitivity = 0.2f;
        yaw -= delta.X * sensitivity;
        pitch -= delta.Y * sensitivity;
        if (pitch > 89.0f) pitch = 89.0f;
        if (pitch < -89.0f) pitch = -89.0f;
        updateCameraTarget();
        device->getCursorControl()->setPosition(center);
      }

      // Render scene to texture
      driver->beginScene(false, false);
      driver->setRenderTarget(renderTexture, true, true, SColor(255, 30, 30, 30));
      smgr->drawAll();
      driver->setRenderTarget(0);
      driver->endScene();


      // Camera up/down movement
      if (GetAsyncKeyState(VK_SPACE) & 0x8000)
        camera->setPosition(camera->getPosition() + vector3df(0, 0.4f, 0));
      if (GetAsyncKeyState(VK_LCONTROL) & 0x8000)
        camera->setPosition(camera->getPosition() - vector3df(0, 0.4f, 0));

      // WASD movement
      f32 radYaw = yaw * core::DEGTORAD;
      vector3df forward(cosf(radYaw), 0.0f, sinf(radYaw));
      forward.normalize();
      vector3df right(-forward.Z, 0.0f, forward.X);
      right.normalize();

      vector3df camPos = camera->getPosition();
      f32 speed = 0.4f;

      if (GetAsyncKeyState('W') & 0x8000) camPos += forward * speed;
      if (GetAsyncKeyState('S') & 0x8000) camPos -= forward * speed;
      if (GetAsyncKeyState('A') & 0x8000) camPos += right * speed;
      if (GetAsyncKeyState('D') & 0x8000) camPos -= right * speed;
      if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) break;

      camera->setPosition(camPos);
      updateCameraTarget();

      // Toggle fullscreen / maximized modes
      bool ctrlPressed = (GetAsyncKeyState(VK_CONTROL) & 0x8000);
      bool altPressed = (GetAsyncKeyState(VK_MENU) & 0x8000);
      bool enterPressed = (GetAsyncKeyState(VK_RETURN) & 0x8000);

      if (ctrlPressed && altPressed && enterPressed) {
        ToggleMaximizedWindow(g_hWnd);
        Sleep(64); // Avoid multiple triggers
      } else if (altPressed && enterPressed) {
        ToggleFullscreen(g_hWnd);
        Sleep(64);
      }
	  // Copy render texture to Windows bitmap
      UpdateBitmapFromTexture();
      InvalidateRect(g_hWnd, NULL, FALSE);

      // FPS counter in window title
      ++frameCount;
      DWORD now = GetTickCount();
      if (now - lastFpsUpdate >= 1000) {
        char title[64];
        wsprintfA(title, "CPU Render - Irrlicht Engine | FPS: %u", frameCount);
        SetWindowTextA(g_hWnd, title);
        frameCount = 0;
        lastFpsUpdate = now;
      }

      // Continue looping
      goto MainLoop;
    }

    case 0:
      break; // Exit
  }

  // Cleanup
  DeleteObject(hBitmap);
  device->drop();
}
You can move with WASD, press Ctrl to go down and Space to go up, press Escape to close, press Alt Enter to go full screen, and press Ctrl + Alt + Enter to maximize the window.

Inside the code, it shows how to move the ICameraSceneNode.

You have to consider that it was compiled with Irrlicht 1.9.0.
For Irrlicht to work well with culling(with software renderer), you have to add this line(not in your main program, in the dll compilation):

Code: Select all

#define __SOFTWARE_CLIPPING_PROBLEM__
In this:
irrlicht-1.9.0_withss\source\Irrlicht\CSoftwareDriver.cpp

Download:
https://drive.google.com/file/d/1CbAaaR ... sp=sharing

8)
Last edited by Noiecity on Mon Jul 28, 2025 2:17 am, edited 1 time in total.
Irrlicht is love, Irrlicht is life, long live to Irrlicht
n00bc0de
Posts: 123
Joined: Tue Oct 04, 2022 1:21 am

Re: Irrlicht Software Renderer + Autoscale

Post by n00bc0de »

What are you using windows.h for?
Noiecity
Posts: 324
Joined: Wed Aug 23, 2023 7:22 pm
Contact:

Re: Irrlicht Software Renderer + Autoscale

Post by Noiecity »

n00bc0de wrote: Mon Jul 28, 2025 12:27 am What are you using windows.h for?
Irrlicht loads everything related to 3D and gives me a 256x256 image. I create a double buffer (to avoid flickering without that) and windows.h scales that image, this way, I keep Irrlicht rendering at a very small resolution, since it maintains its size in the window even when I scale it, and windows.h does the rest

Image

In linux you can use x11
Irrlicht is love, Irrlicht is life, long live to Irrlicht
Post Reply