在上一篇文章中我们已经利用 SDL 的日志接口实现了简单的字符串输出,实际上是解决了开发环境搭建问题,接下来我们将在已有代码的基础上继续开发,实现第一个窗口的创建和背景色绘制。

初始化

首先设置日志输出级别:

SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);

因为还是开发阶段所以我们将输出日志级别设置为最低的 VERBOSE,这样所有的日志都会输出,有助于我们观察 SDL 的运行情况,出现错误时可以得到尽量详细的出错信息,有助于我们快速定位问题。

接下来初始化 SDL 库,参数 SDL_INIT_VIDEO 指定初始化的子系统为视频系统:

if (SDL_Init(SDL_INIT_VIDEO) < 0) {
SDL_Log("SDL_Init failed: %s", SDL_GetError());
return -1;
}

实际上这一步可以省略,因为在调用 SDL API 时其内部会自行检查和初始化所需使用的子系统。比如接下来要使用的 SDL_CreateWindow 函数,内部有这样的代码:

if (!_this) {
/* Initialize the video system if needed */
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
return NULL;
} ...
}

其中 _this 是视频子系统初始化完成后设置的全局变量,声明如下:

static SDL_VideoDevice *_this = NULL;

虽然不是必须的,但是我们仍然建议调用 SDL_Init 对主要用到的子系统进行显式初始化,目的有两个:

  1. 清晰完整的展示出执行过程,有助于理解代码;
  2. 如果执行失败,可以在第一时间定位出错位置,提高排障效率。

创建窗口和渲染器

创建一个 800x600 大小的窗口,然后移动到屏幕居中的位置:

SDL_Window* window = SDL_CreateWindow("Hello, SDL3!", 800, 600, SDL_WINDOW_RESIZABLE);
if (!window) {
SDL_Log("Could not create a window: %s", SDL_GetError());
return -1;
} SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);

创建和窗口关联的渲染器:

SDL_Renderer* renderer = SDL_CreateRenderer(window, nullptr);
if (!renderer) {
SDL_Log("Create renderer failed: %s", SDL_GetError());
return -1;
}

所有图形图像都是通过渲染器绘制到窗口,以背景色绘制为例,代码如下:

SDL_SetRenderDrawColor(renderer, 16, 0, 16, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);

这里使用的背景色是 RGB(16, 0, 16),像我这样曾经用过 DirectDraw 渲染视频的老家伙们对这个颜色值应该很熟悉,不过这里用这个颜色只是为了和 SDL 窗口默认的黑色区分以便更好的观察渲染结果,没有其他特殊效果。

事件循环

SDL 有两种方式获取事件队列中的事件:

  1. SDL_PollEvent 类似 Win32 API 中的 PeekMessage,无论队列中有无事件都会立即返回,区别只是返回值不同
  2. SDL_WaitEvent 类似 Win32 API 中的 WaitMessage,如果队列中没有事件会阻塞等待,直到收到第一个事件才返回

我们使用第二种方式实现事件循环:

SDL_Event event{};
bool keep_going = true; while (keep_going) {
SDL_WaitEvent(&event); switch (event.type) {
case SDL_EVENT_QUIT: {
keep_going = false;
break;
} case SDL_EVENT_KEY_DOWN: {
keep_going = keep_going && (event.key.keysym.sym != SDLK_ESCAPE);
break;
} case SDL_EVENT_WINDOW_EXPOSED: {
SDL_SetRenderDrawColor(renderer, 16, 0, 16, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
break;
}
}
}

SDL_EVENT_QUIT 表示点击了关闭窗口按钮所以收到该事件后跳出循环;

SDL_EVENT_KEY_DOWN 键盘按下事件,这里我们实现了按 'Esc' 键退出的能力;

SDL_EVENT_WINDOW_EXPOSED 表示需要对窗口进行重绘,所以我们把绘制背景色的代码放在这个事件中执行。

可以在 while 循环中增加一行日志来观察收到了哪些事件:

SDL_Log("Event: %d", event.type);

注意上面这个事件循环的写法和大多数 SDL 的示例不同,实际上在这里我们是把 SDL 当作一个正经的窗口系统在使用,而不是当作一个游戏引擎。两者一个重要的区别是使用游戏引擎时一般是按照固定帧率持续进行窗口重绘,而一般的 GUI 软件使用窗口系统时只进行必要的重绘,以最大程度节省 CPU 和 GPU 的使用。视频虽然也有帧率的概念,但是采用的是第二种方式,只在视频帧刷新时执行重绘。

完整代码

添加退出前清理资源的代码后,完整的代码如下:

#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h> int main(int argc, char* argv[])
{
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE); if (SDL_Init(SDL_INIT_VIDEO) < 0) {
SDL_Log("SDL_Init failed: %s", SDL_GetError());
return -1;
} SDL_Window* window =
SDL_CreateWindow("Hello, SDL3!", 800, 600, SDL_WINDOW_RESIZABLE);
if (!window) {
SDL_Log("Could not create a window: %s", SDL_GetError());
return -1;
} SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); SDL_Renderer* renderer = SDL_CreateRenderer(window, nullptr);
if (!renderer) {
SDL_Log("Create renderer failed: %s", SDL_GetError());
return -1;
} SDL_Event event{};
bool keep_going = true; while (keep_going) {
SDL_WaitEvent(&event); switch (event.type) {
case SDL_EVENT_QUIT: {
keep_going = false;
break;
} case SDL_EVENT_KEY_DOWN: {
keep_going = keep_going && (event.key.keysym.sym != SDLK_ESCAPE);
break;
} case SDL_EVENT_WINDOW_EXPOSED: {
SDL_SetRenderDrawColor(renderer, 16, 0, 16, 255);
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
break;
}
}
SDL_Log("Event: %d", event.type);
} SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit(); return 0;
}

输出效果如图:

SDL3 入门(2):第一个窗口的更多相关文章

  1. Storm入门之第一章

    Storm入门之第一章 1.名词 spout龙卷,读取原始数据为bolt提供数据 bolt雷电,从spout或者其他的bolt接收数据,并处理数据,处理结果可作为其他bolt的数据源或最终结果 nim ...

  2. SFML从入门到放弃(1) 窗口和交互

    SFML从入门到放弃(1) 窗口和交互 创建一个新窗口: sf::RenderWindow window(sf::VideoMode(,),"new window"); 但是光创建 ...

  3. delphi程序向另一个可执行程序发消息(使用GetForegroundWindow; 找出当前操作系统中活动的第一个窗口)

    function FindWindowThroughWindowText(WindowText: string): THandle;var  hCurrentWindow: THandle;  cnt ...

  4. Entity Framework 6.0 入门系列 第一篇

    Entity Framework 6.0 入门系列 第一篇 好几年前接触过一些ef感觉不是很好用,废弃.但是 Entity Framework 6.0是经过几个版本优化过的产物,性能和功能不断完善,开 ...

  5. Elasticsearch7.X 入门学习第一课笔记----基本概念

    原文:Elasticsearch7.X 入门学习第一课笔记----基本概念 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https: ...

  6. vue入门的第一天:v-clock、v-text、v-html的使用

    vue入门的第一天 1. v-cloak v-cloak可以解决插值闪烁问题(防止代码被人看见),在元素里加入 v-cloak即可 html: <p v-cloak>{{msg}}< ...

  7. Newbe.Claptrap 框架入门,第一步 —— 开发环境准备

    Newbe.Claptrap 框架依托于一些关键性的基础组件和一些可选的辅助组件.本篇我们来介绍一下如何准备一个开发环境. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如 ...

  8. 《进击吧!Blazor!》系列入门教程 第一章 8.部署

    <进击吧!Blazor!>是本人与张善友老师合作的Blazor零基础入门教程视频,此教程能让一个从未接触过Blazor的程序员掌握开发Blazor应用的能力. 视频地址:https://s ...

  9. 学习WCF入门的第一个实例

    一.概述 WCF说白了就是一个基于终结点的通信手段!就是Service端提供一定的功能实现,然后暴露出一个或多个终结点,Client端调用Service端的功能(可以理解为调用一个函数),那么Clie ...

  10. CodeIgniter框架入门教程——第一课 Hello World!

    本文转载自:http://www.softeng.cn/?p=45 今天开始,我将在这里连载由我自己编写的<CodeIgniter框架入门教程>,首先,这篇教程的读着应该是有PHP基础的编 ...

随机推荐

  1. 函数计算GB镜像秒级启动:下一代软硬件架构协同优化揭秘

    简介: 优化镜像加速冷启动大致分为两种做法:降低绝对延迟和降低冷启动概率.自容器镜像上线以来我们已经通过镜像加速技术,分阶段降低了绝对延迟.本文在此基础上,介绍借助函数计算下一代IaaS底座神龙裸金属 ...

  2. Quick Audience 营销活动功能一期上线

    ​简介: 营销活动为Quick Audience(QA)用户洞察下的一个功能模块,通过这个模块,可以将QA侧生成的受众以及营销渠道全部关联起来,从营销活动的视角,一站式完成活动目标制定.活动计划制定到 ...

  3. Quick BI的可视分析之路

    简介: Quick BI是专为云上用户量身打造的智能数据分析和可视化BI产品,帮助企业快速完成从传统的数据分析到数据云化+分析云化的转变,将企业的业务数据产出后以最快的速度被推送到各组织侧消费使用.本 ...

  4. Forrester云原生开发者洞察白皮书,低代码概念缔造者又提出新的开发范式

    简介: 云原生时代的到来为开发者群体带来了前所未有的机遇,让开发者可以更加专注业务价值创造与创新,并使得人人成为开发者成为现实.广大开发者如何转型成为云原生开发者?运维等专业人员在云原生时代如何避免边 ...

  5. [ELK] Elasticsearch 安装/配置、启动/停止、加节点/重启

    Elasticsearch 在不同环境上支持的安装方式很多,有源码安装.二进制安装.docker安装.rpm包等管理器安装. 具体的,根据应用的实际情况选择即可. 完成可测试开发环境的建立后,需要进一 ...

  6. dotnet 警惕 ConcurrentDictionary 使用 FirstOrDefault 获取到非预期的首项

    在 dotnet 里面的 ConcurrentDictionary 是一个支持并发读写的线程安全字典,在这个字典里面有一些行为会出现随机性,即多次执行相同的代码返回的结果可能不相同.本文记录在 Con ...

  7. clickhouse数据操常见执行语句

    1.清空本地表数据 truncate table 数据库名.表名 :) select * from test_local; SELECT * FROM test_local Query id: ab1 ...

  8. Nats集群部署

    环境: 3台机器采用同样的目录名字和文件名称 服务器 192.168.10.30 192.168.10.31 192.168.10.32 nats版本2.9.15 配置文件 # 192.168.10. ...

  9. Jenkins 简述及其搭建

    什么是持续集成? 持续集成(CI)是在软件开发过程中自动化和集成许多团队成员的代码更改和更新的过程.在 CI 中,自动化工具在集成之前确认软件代码是有效且无错误的,这有助于检测错误并加快新版本的发布. ...

  10. 【进阶篇】基于 Redis 实现分布式锁的全过程

    目录 前言 一.关于分布式锁 二.RedLock 红锁(不推荐) 三.基于 setIfAbsent() 方法 四.使用示例 4.1RedLock 使用 4.2setIfAbsent() 方法使用 五. ...