[SDL3] SDL Audio Wav

SDL, raylib 2025. 12. 16. 18:30 |
반응형

SDL3를 이용해 wav 파일을 간단히 플레이 해 보자.

 

#define SDL_MAIN_USE_CALLBACKS 1  /* use the callbacks instead of main() */
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>

static SDL_AudioStream* stream = NULL;
static Uint8* wav_data = NULL;
static Uint32 wav_data_len = 0;

/* This function runs once at startup. */
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
	SDL_AudioSpec spec;
	char* wav_path = NULL;

	SDL_SetAppMetadata("Example Audio Load Wave", "1.0", "Sean");

	if (!SDL_Init(SDL_INIT_AUDIO)) {
		SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
		return SDL_APP_FAILURE;
	}

	/* Load the .wav file from wherever the app is being run from. */
	//SDL_asprintf(&wav_path, "%smusic.wav", SDL_GetBasePath());  /* allocate a string of the full file path */
	SDL_asprintf(&wav_path, "music.wav");  // assume current working directory
	if (!SDL_LoadWAV(wav_path, &spec, &wav_data, &wav_data_len)) {
		SDL_Log("Couldn't load .wav file: %s", SDL_GetError());
		return SDL_APP_FAILURE;
	}

	SDL_free(wav_path);  /* done with this string. */

	// Print out some info about the .wav file we loaded.
	int bitDepth = SDL_AUDIO_BITSIZE(spec.format);
	int bytesPerSecond = spec.freq * spec.channels * (bitDepth / 8);
	float duration = (float)wav_data_len / (float)bytesPerSecond;
	SDL_Log("Duration: %d:%d\n", (int)duration / 60, (int)duration % 60);

	/* Create our audio stream in the same format as the .wav file. It'll convert to what the audio hardware wants. */
	stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL);
	if (!stream) {
		SDL_Log("Couldn't create audio stream: %s", SDL_GetError());
		return SDL_APP_FAILURE;
	}

	/* SDL_OpenAudioDeviceStream starts the device paused. You have to tell it to start! */
	SDL_ResumeAudioStreamDevice(stream);

	return SDL_APP_CONTINUE;  /* carry on with the program! */
}

/* This function runs when a new event (mouse input, keypresses, etc) occurs. */
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
	switch (event->type) {
	case SDL_EVENT_QUIT:
		return SDL_APP_SUCCESS;  /* end the program, reporting success to the OS. */
	case SDL_EVENT_KEY_DOWN:
		// Since there's no window, this won't work, but if there were, it would log key presses.
		SDL_Log("Key down: %s\n", SDL_GetKeyName(event->key.key));
		if (event->key.key == SDLK_ESCAPE) {
			return SDL_APP_SUCCESS;  /* end the program, reporting success to the OS. */
		}
		break;
	default:
		break;
	}

	return SDL_APP_CONTINUE;  /* carry on with the program! */
}

/* This function runs once per frame, and is the heart of the program. */
SDL_AppResult SDL_AppIterate(void* appstate)
{
	/* see if we need to feed the audio stream more data yet.
	   We're being lazy here, but if there's less than the entire wav file left to play,
	   just shove a whole copy of it into the queue, so we always have _tons_ of
	   data queued for playback. */
	if (SDL_GetAudioStreamQueued(stream) < (int)wav_data_len) {
		/* feed more data to the stream. It will queue at the end, and trickle out as the hardware needs more data. */
		SDL_PutAudioStreamData(stream, wav_data, wav_data_len);
		// 이렇게 한 번만 넣어주면 끝까지 재생됨.
		// 이 경우, wav_data_len 만큼의 데이터를 계속 넣어주게 되므로 재생이 끝난 후에도 계속 반복 재생됨.
	}

	return SDL_APP_CONTINUE;  /* carry on with the program! */
}

/* This function runs once at shutdown. */
void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
	SDL_free(wav_data);
	SDL_DestroyAudioStream(stream);

	SDL_Quit();
}

 

플레이 시간이 표시되고 wav 파일이 플레이 된다.

 

반응형
Posted by J-sean
:
반응형

파티클은 아니지만 차일드 윈도우로 화면에 내리는 눈을 만들어 보자.

 

// 차일드 윈도우가 100개 정도만 되어도 엄청 느려지고 잘 안된다.
// 그러다 그 이상되면 101번째 윈도우 생성시(정확히는 렌더러 생성시) 아래와 같은 Access Violation이 발생한다.
// Exception thrown at 0x00007FF8EE239D68 (nvd3dumx.dll) in SDLTest.exe: 0xC0000005 : Access violation reading location 0x0000000000000038.
// SDL3가 문제인지, 그래픽 드라이버가 문제인지... 정확히는 모르겠지만 아무래도 그래픽 드라이버 문제같다.

#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <stdio.h>
#include <windows.h>

SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;

const int number_flakes = 10;
int screen_width;
int screen_height;

SDL_Window* subwindow[number_flakes]; // 서브 윈도우
SDL_Renderer* subrenderer[number_flakes]; // 서브 윈도우 렌더러

typedef struct position {
	float x, y;
	float vx, vy;
} s_position;
s_position subwindow_pos[number_flakes]; // 서브 윈도우 위치

// 파티클 이미지 텍스쳐와 렉트
SDL_Texture* texture[number_flakes];
SDL_FRect rect;

SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
	ShowWindow(GetConsoleWindow(), SW_HIDE);
	// 콘솔 창 숨김. Project - Property Pages - Linker - System - SubSystem - Windows로 바꿔서 컴파일해도 된다.

	SDL_SetAppMetadata("Example", "1.0", "sean");

	if (!SDL_Init(SDL_INIT_VIDEO)) {
		SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
		return SDL_APP_FAILURE;
	}

	// 부모	윈도우 생성하고 최소화
	if (!SDL_CreateWindowAndRenderer("Press ESC to Quit", 320, 32, SDL_WINDOW_MINIMIZED, &window, &renderer)) {
		SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
		return SDL_APP_FAILURE;
	}

	// 비트맵 로드
	SDL_Surface* bmpSurface = SDL_LoadBMP("snow.bmp");
	if (!bmpSurface) {
		SDL_Log("Couldn't load BMP: %s", SDL_GetError());
		return SDL_APP_FAILURE;
	}

	// 컬러키(투명) 설정
	SDL_SetSurfaceColorKey(bmpSurface, true, SDL_MapSurfaceRGB(bmpSurface, 0x00, 0x00, 0x00));
	//SDL_SetSurfaceColorKey(bmpSurface, true, SDL_MapRGB(SDL_GetPixelFormatDetails(bmpSurface->format), NULL, 0x00, 0x00, 0x00));
	rect = { 0, 0, (float)bmpSurface->w, (float)bmpSurface->h };

	// 현재 디스플레이 정보 가져오기(해상도, 주사율 등)
	const SDL_DisplayMode* displaymode = SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay());
	screen_width = displaymode->w / 3;
	screen_height = displaymode->h / 3;

	for (int i = 0; i < number_flakes; i++) {
		subwindow_pos[i].x = (float)SDL_rand(screen_width);
		subwindow_pos[i].y = (float)SDL_rand(screen_height); // 서브 윈도우 위치 설정
		subwindow_pos[i].vx = SDL_randf() * 0.5f;
		subwindow_pos[i].vy = SDL_randf() + 0.8f;

		/*
		// 서브 윈도우 생성
		SDL_PropertiesID props = SDL_CreateProperties(); // Create Properties
		SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_TRANSPARENT_BOOLEAN, true);
		SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_BORDERLESS_BOOLEAN, true);
		SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_ALWAYS_ON_TOP_BOOLEAN, true);
		SDL_GetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_MENU_BOOLEAN, true);
		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, rect.w);
		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, rect.h);
		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, subwindow_pos[i].x);
		SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, subwindow_pos[i].y);
		SDL_SetPointerProperty(props, SDL_PROP_WINDOW_CREATE_PARENT_POINTER, window);

		subwindow[i] = SDL_CreateWindowWithProperties(props);
		if (!subwindow[i]) {
			SDL_Log("Couldn't create subwindow: %s", SDL_GetError());
			return SDL_APP_FAILURE;
		}

		subrenderer[i] = SDL_CreateRenderer(subwindow[i], NULL);
		if (!subrenderer[i]) {
			SDL_Log("Couldn't create subwindow renderer: %s", SDL_GetError());
			return SDL_APP_FAILURE;
		}
		*/

		// 위 주석의 코드와 동일
		if (!SDL_CreateWindowAndRenderer("Sub Window", (int)rect.w, (int)rect.h, SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALWAYS_ON_TOP | SDL_WINDOW_TRANSPARENT, &subwindow[i], &subrenderer[i])) {
			SDL_Log("Couldn't create subwindow and subrenderer: %s", SDL_GetError());
			return SDL_APP_FAILURE;
		}
		SDL_SetWindowParent(subwindow[i], window); // 서브 윈도우 부모 설정		
		SDL_SetWindowPosition(subwindow[i], (int)subwindow_pos[i].x, (int)subwindow_pos[i].y);

		SDL_SetRenderDrawBlendMode(subrenderer[i], SDL_BLENDMODE_BLEND);
	}

	for (int i = 0; i < number_flakes; i++) {
		texture[i] = SDL_CreateTextureFromSurface(subrenderer[i], bmpSurface);
		if (!texture[i]) {
			SDL_Log("Couldn't create texture: %s", SDL_GetError());
			return SDL_APP_FAILURE;
		}
	}

	SDL_DestroySurface(bmpSurface);

	return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
	switch (event->type) {
	case SDL_EVENT_QUIT:
		return SDL_APP_SUCCESS;
	case SDL_EVENT_KEY_DOWN:
		printf("Key pressed: %s\n", SDL_GetKeyName(event->key.key));
		if (event->key.key == SDLK_ESCAPE)
			return SDL_APP_SUCCESS;
		break;
	default:
		break;
	}

	return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void* appstate)
{
	SDL_Delay(1000 / 60); // 60 FPS

	SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
	SDL_RenderClear(renderer);

	SDL_RenderPresent(renderer);

	for (int i = 0; i < number_flakes; i++) {
		SDL_SetRenderDrawColor(subrenderer[i], 0, 0, 0, SDL_ALPHA_TRANSPARENT);
		SDL_RenderClear(subrenderer[i]);
		SDL_RenderTexture(subrenderer[i], texture[i], NULL, &rect);
		SDL_RenderPresent(subrenderer[i]);

		// 서브 윈도우 위치 업데이트
		subwindow_pos[i].x += subwindow_pos[i].vx; // 오른쪽으로 이동
		subwindow_pos[i].y += subwindow_pos[i].vy; // 아래로 이동

		if (subwindow_pos[i].x > screen_width) {
			subwindow_pos[i].x = 0; // 화면 왼쪽으로 다시 이동
		}
		if (subwindow_pos[i].y > screen_height) {
			subwindow_pos[i].y = 0; // 화면 위로 다시 이동
		}
		SDL_SetWindowPosition(subwindow[i], (int)subwindow_pos[i].x, (int)subwindow_pos[i].y);
	}

	return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
	for (int i = 0; i < number_flakes; i++) {
		SDL_DestroyTexture(texture[i]);
		SDL_DestroyRenderer(subrenderer[i]);
		SDL_DestroyWindow(subwindow[i]);
	}

	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window);

	SDL_Quit();
}

 

 

 

하지만 위 예처럼 눈의 수 만큼 차일드 윈도우를 생성하는건 부담스럽다.

특별한 비활성화 투명 윈도우를 생성하는 방식으로 바꿔보자.

 

#define SDL_MAIN_USE_CALLBACKS 1
#include "SDL3/SDL.h"
#include "SDL3/SDL_main.h"
#include <stdio.h>

#include <windows.h>
#include <WinUser.h>

SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;

int screen_width = GetSystemMetrics(SM_CXSCREEN);
int screen_height = GetSystemMetrics(SM_CYSCREEN);

Uint64 start;
Uint64 end;
float fps;

typedef struct {
	float x, y;
	float vx, vy;
} s_particle;

const int number_particles = 2000;
s_particle particles[number_particles];

// 파티클 이미지 텍스쳐와 렉트
SDL_Texture* texture;
SDL_FRect rect;

void spawn_particle(s_particle* pt) {
	pt->x = (SDL_randf() * 2 - 1) * screen_width * 10;
	pt->y = SDL_randf() * screen_height * -2;
	// 넓은 범위에서 눈이 생성되게 해서 실행 초기에 눈이 쏟아져 내리는걸 방지.
	pt->vx = SDL_randf() * 0.5f; // 눈은 오른쪽으로만 흩날린다.
	pt->vy = SDL_randf() + 0.8f;
}

void draw_particle(s_particle pt[]) {
	for (int i = 0; i < number_particles; i++) {
		s_particle* particle = &pt[i];
		particle->x += particle->vx;
		particle->y += particle->vy;
		// 눈이 화면 왼쪽에서도 바람에 날려 올 수 있도록 생존 범위 조정.
		if (particle->x > screen_width || particle->x < -screen_width || particle->y > screen_height)
			spawn_particle(particle);

		// 텍스쳐 그리기
		rect.x = particle->x - rect.w / 2;
		rect.y = particle->y - rect.h / 2;
		SDL_RenderTexture(renderer, texture, NULL, &rect);

		//SDL_SetRenderDrawColor(renderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
		//SDL_RenderPoint(renderer, particle->x, particle->y);
	}
}

SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
	SDL_SetAppMetadata("Example", "1.0", "sean");

	if (!SDL_Init(SDL_INIT_VIDEO)) {
		SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
		return SDL_APP_FAILURE;
	}

	SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
	// A variable controlling whether updates to the SDL screen surface should be synchronized
	// with the vertical refresh, to avoid tearing.

	// 디스플레이 스케일 정보 확인
	SDL_DisplayID display = SDL_GetPrimaryDisplay(); // Return the primary display
	SDL_Log("DisplayID: %u", display);
	float display_scale = SDL_GetDisplayContentScale(display);
	SDL_Log("Display Scale: %f", display_scale);
	SDL_Log("Original Display Size: (%d X %d)", screen_width, screen_height);

	// 스케일 적용
	screen_width = int(screen_width * display_scale);
	screen_height = int(screen_height * display_scale);

	SDL_Log("Scaled Display Size: (%d X %d)", screen_width, screen_height);

	if (!SDL_CreateWindowAndRenderer("example", screen_width, screen_height, SDL_WINDOW_TRANSPARENT | SDL_WINDOW_BORDERLESS | SDL_WINDOW_FULLSCREEN, &window, &renderer)) {
		SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
		return SDL_APP_FAILURE;
	}

	// 투명 배경 설정을 위해 블렌드 모드 설정
	SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

	// 윈도우 핸들 가져오기
	SDL_PropertiesID props = SDL_GetWindowProperties(window);
	HWND hwnd = (HWND)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
	// 윈도우 투명 및 클릭 무시 설정
	SetWindowLongPtr(hwnd, GWL_EXSTYLE, GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT);
	// 항상 위에 표시, 사이즈, 위치 변경, 활성화 금지 
	SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

	for (int i = 0; i < number_particles; i++) {
		spawn_particle(&particles[i]);
	}

	// 비트맵 로드
	SDL_Surface* bmpSurface = SDL_LoadBMP("snow.bmp");
	if (!bmpSurface) {
		SDL_Log("Couldn't load BMP: %s", SDL_GetError());
		return SDL_APP_FAILURE;
	}

	// 컬러키(투명) 설정
	SDL_SetSurfaceColorKey(bmpSurface, true, SDL_MapSurfaceRGB(bmpSurface, 0x00, 0x00, 0x00));
	//SDL_SetSurfaceColorKey(bmpSurface, true, SDL_MapRGB(SDL_GetPixelFormatDetails(bmpSurface->format), NULL, 0x00, 0x00, 0x00));
	rect = { 0, 0, (float)bmpSurface->w, (float)bmpSurface->h };
	texture = SDL_CreateTextureFromSurface(renderer, bmpSurface);

	SDL_DestroySurface(bmpSurface);

	return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
	switch (event->type) {
	case SDL_EVENT_QUIT:
		return SDL_APP_SUCCESS;
	case SDL_EVENT_KEY_DOWN:
		printf("Key pressed: %s\n", SDL_GetKeyName(event->key.key));
		if (event->key.key == SDLK_ESCAPE)
			return SDL_APP_SUCCESS;
		break;
	default:
		break;
	}

	return SDL_APP_CONTINUE;
}

SDL_AppResult SDL_AppIterate(void* appstate)
{
	// FPS 계산. 사용하지는 않음.
	start = SDL_GetPerformanceCounter();

	{
		// 반투명 배경으로 클리어
		SDL_SetRenderDrawColor(renderer, 0, 0, 0, 128);
		SDL_RenderClear(renderer);

		draw_particle(particles);

		SDL_RenderPresent(renderer);
	}

	end = SDL_GetPerformanceCounter();
	fps = 1.0f / ((end - start) / (float)SDL_GetPerformanceFrequency());

	return SDL_APP_CONTINUE;
}

void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
	SDL_DestroyTexture(texture);

	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window);
	SDL_Quit();
}

 

※ 참고

위 코드에서는 화면 배율이 1이 아닌 경우를 위해 screen_width, screen_height 등의 정보를 Windows API를 이용해 확인하고, 화면 배율(display_scale)을 적용해 정확한 스크린 사이즈를 결정한다. 화면 배율이 1이 아닌 경우 정확한 스크린 사이즈 확인은 아래 링크와 같이 하는게 더 낫다.

 

2025.06.02 - [SDL, raylib] - [SDL3] Display Information 디스플레이 정보 가져오기

 

살짝 어두운 배경으로 바뀌고 눈이 내린다. 동시에 다른 프로그램을 사용할 수 있다.

 

 

※ 참고

2025.04.18 - [SDL, raylib] - [SDL3] Particle 파티클 (Fireworks/Snow)

 

반응형
Posted by J-sean
:
반응형

부모 윈도우와 자식 윈도우의 입력을 구분해 처리해 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#define SDL_MAIN_USE_CALLBACKS 1
 
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <stdio.h>
#include <Windows.h>
 
static SDL_Window* window = NULL;
static SDL_Renderer* renderer = NULL;
 
static SDL_Window* childWindow = NULL;
static SDL_Renderer* childRenderer = NULL;
 
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[])
{
    SDL_SetAppMetadata("Example""1.0""Sean");
 
    // Initialize SDL with video subsystem
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    // 메인 윈도우 및 렌더러 생성
    if (!SDL_CreateWindowAndRenderer("Parent Window"6404800&window, &renderer)) {
        SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    printf("Main window ID: %d\n", SDL_GetWindowID(window));
 
    // 자식 윈도우 및 렌더러 생성
    if (!SDL_CreateWindowAndRenderer("Child Window"6404800&childWindow, &childRenderer)) {
        SDL_Log("Couldn't create child window/renderer: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    printf("Child window ID: %d\n", SDL_GetWindowID(childWindow));
 
    // 메인 윈도우를 자식 윈도우의 부모 윈도우로 설정
    if (!SDL_SetWindowParent(childWindow, window)) {
        SDL_Log("Couldn't set child window parent: %s", SDL_GetError());
        return SDL_APP_FAILURE;
    }
 
    // 윈도우 핸들 구하기
    SDL_PropertiesID prop = SDL_GetWindowProperties(window);
    HWND hwnd = (HWND)SDL_GetPointerProperty(prop, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
    if (hwnd)
        printf("Main window handle: %p\n", hwnd);
 
    prop = SDL_GetWindowProperties(childWindow);
    HWND hpwnd = (HWND)SDL_GetPointerProperty(prop, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
    if (hpwnd)
        printf("Popup window handle: %p\n", hpwnd);
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
{
    switch (event->type) {
    case SDL_EVENT_QUIT:
        return SDL_APP_SUCCESS;
    case SDL_EVENT_KEY_DOWN:
        printf("Key pressed window: %d, Key: %s\n", event->key.windowID, SDL_GetKeyName(event->key.key));
        if (event->key.key == SDLK_ESCAPE)
            if (event->key.windowID == SDL_GetWindowID(window))
                return SDL_APP_SUCCESS; // Exit on Escape key in main window
            else if (event->key.windowID == SDL_GetWindowID(childWindow)) {
                // 자식 윈도우에서 Escape 키를 누르면 자식 윈도우 닫음
                SDL_DestroyRenderer(childRenderer);
                childRenderer = NULL;
                SDL_DestroyWindow(childWindow);
                childWindow = NULL;
            }
        break;
    case SDL_EVENT_MOUSE_BUTTON_DOWN:
        printf("Mouse button pressed window: %d, Button index: %d\n", event->button.windowID, event->button.button);
        printf("Mouse position: (%d, %d)\n", (int)event->button.x, (int)event->button.y);
        break;
    default:
        break;
    }
 
    return SDL_APP_CONTINUE;
}
 
SDL_AppResult SDL_AppIterate(void* appstate)
{
    SDL_SetRenderDrawColor(renderer, 255255255, SDL_ALPHA_OPAQUE);
    SDL_RenderClear(renderer);
    SDL_RenderPresent(renderer);
 
    if (childRenderer)
    {
        SDL_SetRenderDrawColor(childRenderer, 00255, SDL_ALPHA_OPAQUE);
        SDL_RenderClear(childRenderer);
        SDL_RenderPresent(childRenderer);
    }
 
    return SDL_APP_CONTINUE;
}
 
void SDL_AppQuit(void* appstate, SDL_AppResult result)
{
    if (childRenderer)
        SDL_DestroyRenderer(childRenderer);
    if (childWindow)
        SDL_DestroyWindow(childWindow);
 
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
 
    SDL_Quit();
}
 

 

자식 윈도우에서 ESC키를 누르면 자식 윈도우만 종료된다.

 

반응형
Posted by J-sean
:
반응형

Display number, name, format, width, height, refresh rate 등의 정보를 확인해 보자.

 

#include "SDL3/SDL.h"

int main() {
	if (!SDL_Init(SDL_INIT_VIDEO)) {
		SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
		return SDL_APP_FAILURE;
	}

	// 모든 디스플레이 정보 가져오기(해상도, 주사율 등)
	int num_displays;
	SDL_DisplayID* displays = SDL_GetDisplays(&num_displays);
	SDL_Log("Found %d display(s)\n\n", num_displays);

	for (int i = 0; i < num_displays; i++) {
		const SDL_DisplayMode* displaymode = SDL_GetCurrentDisplayMode(displays[i]);
		SDL_Log("Display %d: %s(width: %d, height: %d)", i, SDL_GetDisplayName(displays[i]),
			displaymode->w, displaymode->h);
	}

	SDL_free(displays);

	// 위 코드보다 간단히 현재 디스플레이 정보만 가져오기(해상도, 주사율 등)
	const SDL_DisplayMode* displaymode = SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay());
	SDL_Log("\nPrimary Display: %s(width: %d, height: %d)", SDL_GetDisplayName(SDL_GetPrimaryDisplay()),
		displaymode->w, displaymode->h);

	SDL_Quit();

	return 0;
}

 

코드를 작성하고 빌드한다.

SDL_DisplayMode 구조체는 아래와 같이 구성되어 있다.

■ SDL_DisplayID displayID; /* the display this mode is associated with */
■ SDL_PixelFormat format; /* pixel format */
■ int w; /* width */
■ int h; /* height */
■ float pixel_density; /* scale converting size to pixels (e.g. a 1920x1080 mode with 2.0 scale would have 3840x2160 pixels) */
■ float refresh_rate; /* refresh rate (or 0.0f for unspecified) */
■ int refresh_rate_numerator; /* precise refresh rate numerator (or 0 for unspecified) */
■ int refresh_rate_denominator; /* precise refresh rate denominator */

 

실행하면 디스플레이 정보가 표시된다.

 

반응형
Posted by J-sean
:
반응형

raylib에서 윈도우 핸들을 구해 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define _CRT_SECURE_NO_WARNINGS
 
#include "raylib.h"
#include <stdio.h>
 
int main(void)
{
    InitWindow(640480"raylib example");
 
    void* p = GetWindowHandle();
    printf("Window Handle: %p\n", p);
    //printf("%016llX\n", (long long)p);
 
    char str[17];
    sprintf(str, "%016llX", (long long)p);
 
    while (!WindowShouldClose())
    {
        BeginDrawing();
 
        ClearBackground(RAYWHITE);
        DrawText(str, 22023020, GRAY);
 
        EndDrawing();
    }
 
    CloseWindow();
 
    return 0;
}
 

 

소스를 입력하고 빌드한다.

 

마지막 줄에 윈도우 핸들이 출력된다.

 

윈도우 핸들이 출력된다.

하지만 raylib는 child window를 만들 수 없다.

 

※ 참고

2025.03.30 - [SDL, raylib] - [SDL3] Simple DirectMedia Layer 3 Setup and Getting Started - SDL3 설정 및 초기화

 

반응형
Posted by J-sean
:
반응형

raylib로 간단한 파티클 시스템을 만들어 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include "raylib.h"
 
const int screenWidth = 640;
const int screenHeight = 480;
 
Texture2D texture;
 
class Particles {
private:
    Vector2 position;
    Vector2 velocity;
public:
    Particles() {
        Reset();
    }
 
    void Draw_particle() {
        //DrawPixel(position.x, position.y, WHITE);
        //DrawCircle(position.x, position.y, 10, WHITE);
        DrawTexture(texture, position.x, position.y, WHITE);
        position.x += velocity.x;
        position.y += velocity.y;
        if (position.x > screenWidth || position.y > screenHeight)
            Reset();
    }
 
    void Reset() {
        position.x = GetRandomValue(-screenWidth * 0.4f, screenWidth);
        position.y = GetRandomValue(-screenHeight * 20);
        float max = 10000.0f;
        velocity.x = (GetRandomValue(1, (int)max) / max) * 0.5f;
        velocity.y = (GetRandomValue(1, (int)max) / max) + 0.8f;
    }
};
 
int main(void)
{
    //SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_UNDECORATED);
    InitWindow(screenWidth, screenHeight, "raylib example");
    // InitWindow() 실행시 내부에서 SetRandomSeed()를 time(NULL)로 초기화 한다.
    // particle 생성시 GetRandomValue()를 사용하므로 InitWindow() 이후에 생성한다.
    SetTargetFPS(60);
 
    texture = LoadTexture("snow.png");
    // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
 
    const int number_particles = 400;
    Particles particles[number_particles];
 
    while (!WindowShouldClose())
    {
        BeginDrawing();
 
        ClearBackground(BLACK);
 
        for (int i = 0; i < number_particles; i++)
            particles[i].Draw_particle();
 
        DrawFPS(00);
 
        EndDrawing();
    }
 
    UnloadTexture(texture);
 
    CloseWindow();
 
    return 0;
}
 

 

특별한 설정 없이도 png 파일의 알파 채널을 인식한다.

소스를 입력하고 빌드한다.

 

snow.png
0.00MB

 

 

DrawPixel(position.x, position.y, WHITE) 실행

 

DrawTexture(texture, position.x, position.y, WHITE) 실행


실제 실행하면 텍스쳐가 많이 깜빡인다. (아래 동영상 참고)

DrawPixel() 을 실행할 때는 크게 눈에 띄지 않지만 DrawTexture() 를 실행하면 눈에 띈다.

 

윈도우 사이즈를 크게 할수록 깜빡이는 현상이 줄어드는거 같다. 아니면 snow.png 파일을 LoadImage()로 로드하고 ImageResize()로 크게 바꾼 다음 LoadTextureFromImage()로 로드하면 깜빡임이 약간 줄어드는거 같다. (ImageResize()로 이미지 확대가 핵심)

Vector2로 정의된 위치값을 사용하는 DrawTextureV()를 이용해도 차이가 없다.

정확한 원인을 모르겠다.

 

 

RenerTexture2D를 사용하니까 조금 덜 깜빡이는거 같기도 한데.. 아닌거 같다.

더보기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include "raylib.h"
 
const int screenWidth = 640;
const int screenHeight = 480;
 
Texture2D texture;
 
class Particles {
private:
    Vector2 position;
    Vector2 velocity;
public:
    Particles() {
        Reset();
    }
 
    void Draw_particle() {
        //DrawPixel(position.x, position.y, WHITE);
        //DrawCircle(position.x, position.y, 10, WHITE);
        DrawTexture(texture, position.x, position.y, WHITE);
        position.x += velocity.x;
        position.y += velocity.y;
        if (position.x > screenWidth || position.y > screenHeight)
            Reset();
    }
 
    void Reset() {
        position.x = GetRandomValue(-screenWidth * 0.4f, screenWidth);
        position.y = GetRandomValue(-screenHeight * 20);
        float max = 10000.0f;
        velocity.x = (GetRandomValue(1, (int)max) / max) * 0.5f;
        velocity.y = (GetRandomValue(1, (int)max) / max) + 0.8f;
    }
};
 
int main(void)
{
    //SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_UNDECORATED);
    InitWindow(screenWidth, screenHeight, "raylib example");
    // InitWindow() 실행시 내부에서 SetRandomSeed()를 time(NULL)로 초기화 한다.
    // particle 생성시 GetRandomValue()를 사용하므로 InitWindow() 이후에 생성한다.
    SetTargetFPS(60);
 
    RenderTexture2D target = LoadRenderTexture(screenWidth, screenHeight);
 
    texture = LoadTexture("snow.png");
    // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
 
    const int number_particles = 400;
    Particles particles[number_particles];
 
    while (!WindowShouldClose())
    {
        BeginTextureMode(target);
        {
            ClearBackground(BLACK);
 
            for (int i = 0; i < number_particles; i++)
                particles[i].Draw_particle();
 
            DrawFPS(00);
        }
        EndTextureMode();
 
        BeginDrawing();
        {
            //ClearBackground(BLACK);
            //DrawTexture(target.texture, 0, 0, WHITE); // 위아래가 뒤집혀 보인다.
            DrawTextureRec(target.texture, { 00, screenWidth, -screenHeight }, { 00 }, WHITE);
            // OpenGL의 좌표 체계가 다르기 때문에 위아래를 뒤집어야 제대로 표시된다.
        }
        EndDrawing();
    }
 
    UnloadTexture(texture);
    UnloadRenderTexture(target);
 
    CloseWindow();
 
    return 0;
}
 

 

 

Raylib 파티클 예제에 나오는 것 처럼 구조체를 이용해 봐도 별 차이가 없다.

Raylib Particles  

더보기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include "raylib.h"
 
const int screenWidth = 640;
const int screenHeight = 480;
 
Texture2D texture;
 
typedef struct {
    Vector2 position;
    Vector2 velocity;
} s_particle;
 
const int number_particles = 400;
s_particle particles[number_particles];
 
void spawn_particle(s_particle* pt) {
    pt->position.x = GetRandomValue(-screenWidth * 0.4f, screenWidth);
    pt->position.y = GetRandomValue(-screenHeight * 20);
    float max = 10000.0f;
    pt->velocity.x = (GetRandomValue(1, (int)max) / max) * 0.5f;
    pt->velocity.y = (GetRandomValue(1, (int)max) / max) + 0.8f;
}
 
void draw_particle(s_particle pt[]) {
    for (int i = 0; i < number_particles; i++) {
        s_particle* particle = &pt[i];
        particle->position.x += particle->velocity.x;
        particle->position.y += particle->velocity.y;
 
        if (particle->position.x > screenWidth || particle->position.y > screenHeight)
            spawn_particle(particle);
 
        DrawTexture(texture, particle->position.x, particle->position.y, WHITE);
    }
}
 
int main(void)
{
    //SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_UNDECORATED);
    InitWindow(screenWidth, screenHeight, "raylib example");
    // InitWindow() 실행시 내부에서 SetRandomSeed()를 time(NULL)로 초기화 한다.
    // particle 생성시 GetRandomValue()를 사용하므로 InitWindow() 이후에 생성한다.
    SetTargetFPS(60);
 
    texture = LoadTexture("snow.png");
    // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
 
    for (int i = 0; i < number_particles; i++) {
        spawn_particle(&particles[i]);
    }
 
    while (!WindowShouldClose())
    {
        BeginDrawing();
        {
            ClearBackground(BLACK);
 
            draw_particle(particles);
 
            DrawFPS(00);
        }
        EndDrawing();
    }
 
    UnloadTexture(texture);
 
    CloseWindow();
 
    return 0;
}
 

 

한 가지 확인한 것은 velocity->x, velocity->y의 값을 임의의 소수가 아닌 (1이나 2같은)정수로 바꾸면 깜빡임이 없다.

Raylib 렌더링 함수에서 소수 처리 부분에 문제가 있는게 아닐까 싶다.

 

매 프레임마다 눈 이동 거리를 delta time으로 계산해도 별 의미가 없는거 같다.

더보기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include "raylib.h"
 
const int screenWidth = 640;
const int screenHeight = 480;
 
Texture2D texture;
 
typedef struct {
    Vector2 position;
    Vector2 velocity;
} s_particle;
 
const int number_particles = 400;
s_particle particles[number_particles];
 
void spawn_particle(s_particle* pt) {
    pt->position.x = GetRandomValue(-screenWidth * 0.4f, screenWidth);
    pt->position.y = GetRandomValue(-screenHeight * 20);
    //float max = 10000.0f;
    //pt->velocity.x = (float)(GetRandomValue(1, (int)max) / max) * 0.5f;
    //pt->velocity.y = (float)(GetRandomValue(1, (int)max) / max) + 0.8f;
 
    pt->velocity.x = (float)GetRandomValue(030+ 10;
    // 눈이 오른쪽으로 이동하는 속도를 초당 10~40 픽셀로 설정
    pt->velocity.y = (float)GetRandomValue(2080+ 40;
    // 눈이 아래로 떨어지는 속도를 초당 60~120 픽셀로 설정    
}
 
void draw_particle(s_particle pt[], float delta) {
    for (int i = 0; i < number_particles; i++) {
        s_particle* particle = &pt[i];
        particle->position.x += particle->velocity.x * delta;
        particle->position.y += particle->velocity.y * delta;
 
        if (particle->position.x > screenWidth || particle->position.y > screenHeight)
            spawn_particle(particle);
 
        DrawTexture(texture, particle->position.x, particle->position.y, WHITE);
    }
}
 
int main(void)
{
    //SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_UNDECORATED);
    InitWindow(screenWidth, screenHeight, "raylib example");
    // InitWindow() 실행시 내부에서 SetRandomSeed()를 time(NULL)로 초기화 한다.
    // particle 생성시 GetRandomValue()를 사용하므로 InitWindow() 이후에 생성한다.
    SetTargetFPS(60);
 
    texture = LoadTexture("snow.png");
    // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
 
    for (int i = 0; i < number_particles; i++) {
        spawn_particle(&particles[i]);
    }
 
    while (!WindowShouldClose())
    {
        BeginDrawing();
        {
            ClearBackground(BLACK);
 
            draw_particle(particles, GetFrameTime());
            // GetFrameTime() : Get time in seconds for last frame drawn (delta time)
 
            DrawFPS(00);
        }
        EndDrawing();
    }
 
    UnloadTexture(texture);
 
    CloseWindow();
 
    return 0;
}
 

 

 

반응형
Posted by J-sean
:
반응형

투명한 윈도우를 만들어 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "raylib.h"
 
int main(void)
{
    const int screenWidth = 450;
    const int screenHeight = 500;
 
    SetConfigFlags(FLAG_WINDOW_TRANSPARENT | FLAG_WINDOW_UNDECORATED);
    InitWindow(screenWidth, screenHeight, "raylib example");
    SetTargetFPS(60);
 
    Image rayimage = LoadImage("palvin.jpg");
    // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
    Texture2D texture = LoadTextureFromImage(rayimage);
 
    //Texture2D texture = LoadTexture("palvin.jpg");
    // Image 사용 없이 LoadTexture("palvin.jpg")로 해도 된다.
    // 이 경우, 아래 UnloadImage(rayimage);는 삭제한다.
 
    while (!WindowShouldClose())
    {
        BeginDrawing();
 
        ClearBackground(BLANK);
 
        DrawTexture(texture, 1010, WHITE);
 
        EndDrawing();
    }
 
    UnloadImage(rayimage);
    UnloadTexture(texture);
 
    CloseWindow();
 
    return 0;
}
 

 

소스를 입력하고 빌드한다.

 

타이틀바가 없고 배경이 투병한 윈도우에 이미지가 출력된다.

 

Window Flags

 

반응형

'SDL, raylib' 카테고리의 다른 글

[raylib] Window Handle 윈도우 핸들  (0) 2025.05.04
[raylib] Particle 파티클 (Snow)  (0) 2025.05.04
[raylib] raylib with opencv  (0) 2025.05.03
[raylib] raylib 설정하고 사용하기  (0) 2025.05.02
[SDL3] Framerate Per Second FPS  (0) 2025.04.22
Posted by J-sean
:
반응형

raylib와 opencv를 사용해 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include "raylib.h"
#include "opencv2/opencv.hpp"
 
using namespace cv;
 
int main(void)
{
    Mat cvimage = imread("palvin.jpg", IMREAD_COLOR_RGB);
 
   Image rayimage{};
    rayimage.width = cvimage.cols;
    rayimage.height = cvimage.rows;
    rayimage.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8;
    rayimage.mipmaps = 1;
    rayimage.data = cvimage.ptr(); //(void*)(cvimage.data);
 
    const int screenWidth = 450;
    const int screenHeight = 500;
    InitWindow(screenWidth, screenHeight, "raylib example");
    SetTargetFPS(60);
 
    // NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
    Texture2D texture = LoadTextureFromImage(rayimage);
 
    while (!WindowShouldClose())
    {
        BeginDrawing();
 
        ClearBackground(RAYWHITE);
 
        DrawTexture(texture, 1010, WHITE);
 
        EndDrawing();
    }
 
    //UnloadImage(rayimage);
    // LoadImage()로 로드한 이미지에만 사용.
    UnloadTexture(texture);
 
    CloseWindow();
 
    return 0;
}
 

 

raylib에서 texture는 윈도우가 생성된 이후 로드되어야 한다.

 

 

반응형
Posted by J-sean
: