Пишу фреймворк LDL на С++ с поддержкой старых систем

Приветствую хабравчане!

Буду рад советам и рекомендациям в комментах или на гитхабе в issue. Собственно пост в основном пилился для получения рекомендаций по проекту. Проект ещё в зачаточном состоянии. Документации пока нет, но есть тесты. Куцый функционал, мало поддерживаемых систем. Но работа идёт. Ну, что поехали!

В прошлой статье я негодовал о современном программном обеспечении.

Оборачивать своё негодование в смешную форму подачи это одно. Но есть и иной путь. Запилить свой велосипед как альтернативу. И таки я решил ступить на данную скользкую дорожку техно мытарств. Важное замечание, для более лучшей портируемости и обхвата операционных систем и других игровых устройств, я использую стандарт С++ 98.

5bb9ef8113eca5b1f0fe3ae6ad6abe64.png

Перед началом благословяза хочу передать привет разработчику с GameDev.ru

3ad5d1db3326fe98a2e102e0e3fd0821.png

Zefick привет от школяра!:)

Вашему вниманию представляю библиотеку Little DirectMedia Layer в аббревиатуре LDL. Все совпадения с текущими библиотеками случайны:)

Для большей информации тема на OldGames.ru

Основные фичи библиотеки:

  • Производительность. Да лукавые языки на i9 процессорах, рассказывают сказки, что производительность не важна. Время программиста дорого и вообще мы геи пишем на Electron. Я всё же придерживаюсь другого мировоззрения, производительность не менее важна, наряду с удобством в использовании ПО.

    Процитирую Стэна Трухильо из его книги «Графика для Windows средствами DirectDraw»

Быстродействие никогда не выйдет из моды. Чтобы обеспечить максимальное быстродействие программы, ее необходимо оптимизировать. Об этом знают все программисты, занимающиеся разработкой аркадных игр. Их игры должны работать быстро, иначе они будут плохо продаваться. С каждым годом игроки желают иметь все более высокое быстродействие. Каждая новая игра-бестселлер устанавливает новые стандарты и поднимает планку еще выше — несомненно, в будущем эта тенденция только усилится.

  • Кроссплатформенность — обеспечение работы как на старых так и на новых системах. Это и все версии Windows 95, 98, Me. А так же поддержка старых систем Linux начиная с 2000-го года. В будущем планирую обеспечить поддержку Dos, Android, iOS, macOS.

  • Поддержка всевозможных графических API OpenGL, Glide, Vulkan и DirectX начиная с 5.0 по 12.0 Для максимального покрытия видеокарт.

  • Удобный и по возможности высокоуровневый API. С++ дает возможность не только эффективно работать устраивать тех порно с байтами, но и скрывать низкоуровневые вещи под высокоуровневыми абстракциями.

  • Открытый исходный код по лицензии Boost Software License.

На данный момент готов следующий функционал.

  • Поддерживаются все версии Windows начиная с Windows 95.

  • Реализован функционал по выводу и рисованию 2D графики. (Вывод картинок, рисование графических примитивов)

  • По умолчанию для GPU рендера реализована поддержка OpenGL 1.2

  • Пока отсутствует документация, но она планируется после стабилизации графического API.

Некоторые особенности в реализации.

  • Использование сырых указателей. В стандарте С++ 98 нет ни shared не unique. Радует, что при использовании библиотеки не требуется делать new. Байтоложество с uint8_t на месте:)

    5034e9b315e42f45ac68be442f6c25d3.png
  • Выбор реализации на уровне header файлов.

  • Поддержка только статического связывания.

  • Поддержка старых и новых систем.

  • Отсутствие зависимости от внешних dll, все зависимости включены физически в проект.

  • Минимальное использование наследования и виртуальных методов.

  • Часть команды, часть корабля:)

Пример

Данный пример загружает картинку и выводит её на экран.

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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;
}

Не так и страшен С++ 98 года выпуска!

Архитектура

ebc7ae45a35e7ae5cf467a1eb23e7453.png

Для каждой реализуемой операционной системы есть свой каталог для исходников и заголовочных файлов. Каждой системе требуется реализовать свой класс MainWindow инкапсулирующий очередь событий ОС, а так же переводящий события в общий класс LDL: Events: Event.

Для поддержки 2D графики, каждая система включает в себя общий класс BaseRender и свою реализацию основанную на графическом API, к примеру ниже показан пример OpenGL.

#ifndef LDL_Graphics_GL1Render_hpp
#define LDL_Graphics_GL1Render_hpp

#include 
#include 
#include 
#include 

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  

Вся библиотека устроена похожим образом.

Я так и не смог разработать единый API для GPU и CPU версии библиотеки. Поэтому принял решение разделить реализацию и поддерживать две версии.

При компиляции через объявленный define, выбирается указанная реализация.

#ifndef LDL_Graphics_GpuRender_hpp
#define LDL_Graphics_GpuRender_hpp

#if defined(LDL_GPU_SUPPORT_OPENGL1)
#include 
namespace LDL
{
	namespace Graphics
	{
		typedef LDL::Graphics::GL1Render GpuRender;
	}
}
#elif defined(LDL_GPU_SUPPORT_DIRECTX9)
#include 
namespace LDL
{
	namespace Graphics
	{
		typedef LDL::Graphics::DX9Render GpuRender;
	}
}
#elif defined(LDL_GPU_SUPPORT_DIRECTX5)
#include 
namespace LDL
{
	namespace Graphics
	{
		typedef LDL::Graphics::DX5Render GpuRender;
	}
}
#else
#error Not implementation: Graphics::GpuRender
#endif

#endif 

Планы на будущее.

  • Портирование на Linux (xlib следом wayland)

  • Дописать тесты на готовый функционал.

  • Начать готовить документацию.

Ответы на часто задаваемые вопросы:

  • В конце 2022 года пилить свой костыль с поддержкой Windows 95? Ты серьезно?

    Таки да!

  • У вас есть справка из психдиспансера?

    Да, но это не точно:)

  • Почему не на Rust?

    Примерно потому:

    45f6377660f4dd5556643f39591132cb.png

Вопросы требующие решения:

  1. Реализация Fast Pimpl’a для поддержки старых компиляторов.

  2. Как реализовать юникод, что бы и старые системы работали.

  3. Как более модульно разбить проект.

  4. Имеет ли смысл выпилить исключения и есть ли альтернатива.

  5. Как лучше организовать тестирование связанное с графикой.

Примеры:

По клику рисуем картинку.

Работа с картинками из ОЗУ.

Вывод картинки с указанный цветом для альфы.

© Habrahabr.ru