I am writing an LDL framework in C++ with support for older systems
Greetings habravchan!
I will be glad to advice and recommendations in the comments or on the github in issue. Actually the post was mostly sawn to get recommendations on the project. The project is still in its infancy. There is no documentation yet, but there are tests. Little functionality, few supported systems. But the work is going on. Here we go!
In the last article, I resented modern software.
Turning your indignation into a funny form of presentation is one thing. But there is another way. File down your bike as an alternative. And yet I decided to step on this slippery path of techno ordeals. An important note, for better portability and coverage of operating systems and other gaming devices, I use the C ++ 98 standard.

Before starting the blessing, I want to say hello to the developer from GameDev.ru
Zefick hello from a student! 🙂
I present to your attention the library Little DirectMedia Layer in abbreviation LDL. All matches with current libraries are random 🙂
For more information, the topic OldGames.ru
The main features of the library:
-
Performance. Yes, crafty tongues on i9 processors tell tales that performance is not important. A programmer’s time is expensive, and in general we write in Electron. I still adhere to a different worldview, performance is no less important, along with ease of use of the software.
I will quote Stan Trujillo from his book “Graphics for Windows with DirectDraw”
Speed will never go out of style. To ensure maximum performance of the program, it must be optimized. All programmers involved in the development of arcade games know this. Their games need to run fast or they won’t sell well. Every year, players want to have ever higher performance. Each new best-selling game sets new standards and raises the bar even higher, a trend that will no doubt only intensify in the future.
-
Cross-platform – ensuring work on both old and new systems. This and all versions of Windows 95, 98, Me. As well as support for older Linux systems since 2000. I plan to provide support in the future DosAndroid, iOS, macOS.
-
Support for all kinds of graphics APIs OpenGL, Glide, Vulkan and DirectX from 5.0 to 12.0 For maximum coverage of graphics cards.
-
Convenient and possibly high-level API. C++ makes it possible not only to work efficiently
arrange those pornwith bytes, but also to hide low-level things under high-level abstractions. -
Open source under the Boost Software License.
At the moment, the following functionality is ready.
-
All versions of Windows starting from Windows 95 are supported.
-
Implemented functionality for displaying and drawing 2D graphics. (Output pictures, drawing graphic primitives)
-
Support for OpenGL 1.2 is implemented by default for GPU rendering
-
There is no documentation yet, but it is planned after the stabilization of the graphics API.
Some features in the implementation.
-
Using raw pointers. In the C++98 standard, there is neither shared nor unique. I am glad that when using the library, you do not need to do new. Bytologic with uint8_t in place 🙂
-
The choice of implementation at the level of header files.
-
Support for static linking only.
-
Support for old and new systems.
-
No dependency on external dlls, all dependencies are physically included in the project.
-
Minimal use of inheritance and virtual methods.
-
Part of the team, part of the ship 🙂
Example
This example loads an image and displays it on the screen.
#include <iostream>
#include <LDL/Graphics/Gpu/GpuWindow.hpp>
#include <LDL/Graphics/Gpu/GpuImage.hpp>
#include <LDL/Graphics/Gpu/GpuRender.hpp>
#include <LDL/Core/RuntimeError.hpp>
#include <LDL/Loaders/ImageLoader.hpp>
#include <LDL/Time/FpsCounter.hpp>
#include <LDL/Core/IntegerToString.hpp>
#include <LDL/Allocators/FixedLinear.hpp>
int main()
{
try
{
// Создание окна
LDL::Graphics::GpuWindow window(LDL::Graphics::Point2u(0, 0), LDL::Graphics::Point2u(800, 600), "Window!");
// Создание рендера
LDL::Graphics::GpuRender render(&window);
LDL::Events::Event report;
// Создаём линейный фиксированный аллокатор
// и выделяем 4 мб памяти
LDL::Allocators::FixedLinear allocator(LDL::Allocators::Allocator::Mb * 4);
// Инициализируем загрузчик картинок аллокатором
LDL::Loaders::ImageLoader loader(&allocator);
// Загружаем картинку, теперь вся память для работы
// загрузчика берётся из аллокатора
loader.Load("trehmachtovyiy-korabl-kartina-maslom-60x50_512x.jpg");
// Создаём текстуру из загруженных данных
LDL::Graphics::GpuImage image(loader.Size(), loader.BytesPerPixel(), loader.Pixels());
// Создаём счётчик FPS
LDL::Time::FpsCounter fpsCounter;
//Создаём класс конвертера из числа в строку
LDL::Core::IntegerToString convert;
// Главный цикл приложения
while (window.GetEvent(report))
{
fpsCounter.Start();
render.Begin();
render.Color(LDL::Graphics::Color(0, 162, 232));
render.Clear();
if (report.Type == LDL::Events::IsQuit)
{
window.StopEvent();
}
render.Draw(&image, window.Pos(), window.Size());
render.End();
if (fpsCounter.Calc())
{
if (convert.Convert(fpsCounter.Fps()))
{
window.Title(convert.Result());
}
fpsCounter.Clear();
}
}
}
catch (const LDL::Core::RuntimeError& error)
{
std::cout << error.what() << '\n';
}
return 0;
}
C++ 98 is not so scary!
Architecture
Each implemented operating system has its own source and header directory. Each system needs to implement its own MainWindow class that encapsulates the OS event queue, as well as translating events into the general LDL::Events::Event class.
To support 2D graphics, each system includes a generic BaseRender class and its implementation based on the graphics API, for example below is an OpenGL example.
#ifndef LDL_Graphics_GL1Render_hpp
#define LDL_Graphics_GL1Render_hpp
#include <LDL/Graphics/Base/BaseRender.hpp>
#include <LDL/Graphics/GL1/GL1Window.hpp>
#include <LDL/Graphics/GL1/GL1Image.hpp>
#include <LDL/Graphics/GL1/GL1Screen.hpp>
namespace LDL
{
namespace Graphics
{
class GL1Render
{
public:
GL1Render(LDL::Graphics::GL1Window* window);
void Begin();
void End();
const LDL::Graphics::Point2u& Size();
const LDL::Graphics::Color& Color();
void Clear();
void Color(const LDL::Graphics::Color& color);
void Pixel(const LDL::Graphics::Point2u& pos);
void Fill(const LDL::Graphics::Point2u& pos, const LDL::Graphics::Point2u& size);
void Line(const LDL::Graphics::Point2u& pos1, const LDL::Graphics::Point2u& pos2);
void Draw(LDL::Graphics::GL1Image* image, const LDL::Graphics::Point2u& pos, const LDL::Graphics::Point2u& size);
void Draw(LDL::Graphics::GL1Image* image, const LDL::Graphics::Point2u& pos);
void Draw(LDL::Graphics::CpuImage* image, const LDL::Graphics::Point2u& pos, const LDL::Graphics::Point2u& size);
void Draw(LDL::Graphics::CpuImage* image, const LDL::Graphics::Point2u& pos);
private:
LDL::Graphics::GL1Window* _Window;
LDL::Graphics::BaseRender _BaseRender;
LDL::Graphics::GL1Screen _Screen;
};
}
}
#endif
The entire library is organized in a similar way.
I was never able to develop a single API for the GPU and CPU versions of the library. Therefore, I decided to separate the implementation and support two versions.
When compiled through a declared define, the specified implementation is chosen.
#ifndef LDL_Graphics_GpuRender_hpp
#define LDL_Graphics_GpuRender_hpp
#if defined(LDL_GPU_SUPPORT_OPENGL1)
#include <LDL/Graphics/GL1/GL1Render.hpp>
namespace LDL
{
namespace Graphics
{
typedef LDL::Graphics::GL1Render GpuRender;
}
}
#elif defined(LDL_GPU_SUPPORT_DIRECTX9)
#include <LDL/Graphics/DX9/DX9Render.hpp>
namespace LDL
{
namespace Graphics
{
typedef LDL::Graphics::DX9Render GpuRender;
}
}
#elif defined(LDL_GPU_SUPPORT_DIRECTX5)
#include <LDL/Graphics/DX5/DX5Render.hpp>
namespace LDL
{
namespace Graphics
{
typedef LDL::Graphics::DX5Render GpuRender;
}
}
#else
#error Not implementation: Graphics::GpuRender
#endif
#endif
Future plans.
-
Porting to Linux (xlib followed by wayland)
-
Add tests for the finished functionality.
-
Start preparing documentation.
Answers to frequently asked questions:
-
At the end of 2022, cut your crutch with support for Windows 95? Are you serious?
So yes!
-
Do you have a certificate from the psychiatric clinic?
Yes, but not sure 🙂
-
Why not in Rust?
Roughly because:
Issues to be resolved:
-
Implementation of Fast Pimpl to support older compilers.
-
How to implement unicode so that older systems would work.
-
How to break up a project more modularly.
-
Does it make sense to cut exceptions and is there an alternative.
-
What is the best way to organize testing related to graphics.