잡담

CMake 사용기

여태 엔진 만들던거 Visual Studio C++ 프로젝트로 생성해서 쓰고 있었는데 다른 플랫폼용으로도 쉽게 개발하려면 Makefile이 좀 더 낫지 않을까 싶어서 VS프로젝트를 벗어나볼까 싶어졌다. 하지만 Makefile을 그대로 쓰면 귀찮기 때문에 CMake를 쓰기로.

개발은 그대로 Visual Studio로 하지만 폴더를 읽어오면 CMake를 쓸 수 있고 리눅스나 맥에 가서는 Visual Studio Code 같은 걸로도 개발할 수 있으니까 더 낫다는 생각도 들고.

문제는 cpp 파일 하나 하나 추가할 때마다 CMakeLists.txt 파일을 수정해줘야 된다는 점인데 이거 어떻게 와일드카드 쓸 수 없나 싶기도 하고.

그간 Visual Studio 컴파일러 설정이 얼마나 쉬웠는지 깨닫게 되는 부분이… CMake 쓰니까 각 컴파일러마다 설정 직접 다 지정해줘야 돼서 귀찮아 :Q…

광고
잡담

libWebP와 mozjpeg, Zopfli 삽질기

오랜만의 프로그래밍 일기. 요즘엔 이미지를 압축하는 프로그램을 만들어보고 있다. 프로그램 이름은 Degra. 열화 또는 저하를 뜻하는 degrade에서 따온 이름이다. 그러니까, 손실 압축 위주로 압축하는 프로그램.

UWP를 타겟으로 만들기 시작했는데, GUI 프레임워크 중에 쓸만하다고 생각하는건 XAML+C# 조합이고 x86, x64, ARM, ARM64 모두 동시 빌드해서 배포 가능한 플랫폼은 Win32 데스크톱보다는 UWP니까.

그런데 막상 UWP 앱으로 개발하려고 보니까 mozjpeg가 NASM이나 YASM을 써야 빌드가 되는 구조라 이걸 어쩌지 싶었다. 그래서 일단 libWebP부터 붙여서 webp 파일으로 압축하는 기능부터 구현.

멀티스레드 인코딩 기능이 분명 있는데 아무리 돌려도 WebP 인코딩에 싱글코어만  쓰는 문제가 있어서 아니 도대체 왜 이러는 걸까 했는데 릴리즈 모드로 빌드해보니 정상 작동. 아무래도 디버그 편의성을 위해서 디버그 모드에서는 싱글코어만 쓰는 걸로 추정된다. 어쨌든 WebP 인코딩 기능은 의외로 매우 편했다.

WebP 인코딩 기능 붙여놓고는 다시 mozjpeg를 붙이는 작업으로 돌아왔다. 일단 CMake로 Visual C++ 프로젝트 생성해놓고 빌드 결과물인 정적 라이브러리 파일을 UWP에 붙여보니 생각과 달리 잘 붙었다. x64로 테스트를 했었으니 나머지 x86, ARM, ARM64도 프로젝트 생성해서 붙이려고 하니 윈도우용 ARM과 ARM64를 mozjpeg에서 지원하지 않는 또다른 문제점이 있었다. 찾아보니 이슈는 만들어져 있는데 테스트 기기가 없어서 이슈가 닫혀있었다. 나도 뭐 딱히 테스트할 기기는 없고 해서 일단은 x64와 x86용만 붙여놨다. 인코딩 기능 인터페이스 자체는 libjpeg-turbo의 것을 사용하면 해결.

PNG 압축은 좀 고민을 더 했다. 8비트 인덱스 픽셀 포맷으로 만드는 기능은 추가가 이미 돼 있었고 인코딩은 Windows Imaging Codec(WIC)을 이용했는데 생각보다 압축률이 엄청 낮지는 않았음. 찾아보니 OptiPNG, pngquant, ZopfliPNG 등이 있어서 적용을 시도.

OptiPNG는 벤치마크 자료를 찾아보니 ZopfliPNG보다 성능이 딸리는데다 벤치마크 자료 찾아보기 전에 빌드 시도할 때 난황이 있었어서 포기. pngquant는 GPLv3 라이센스라 가져다 쓰기 좀 그랬는데 심지어 찾아보니 그냥 8비트 인덱스 픽셀 포맷으로 만들어주는 기능(+디더링, 미디언컷)만 있는 것 같길래 일단은 도입 포기. Zopfli만 적용하기로 했다.

드라마틱하게 압축률을 높여주진 않아서 실망했다. ZopfliPNG도 결국은 개선된 Deflate 알고리즘을 쓰는터라. Inflate에 들어가는 시간이 그냥 단순 WIC만 쓰는 것보다 많이 길어서 일단은 옵션으로만 남겨뒀다. 여러 인자를 좀 더 만져봐도 속도는 느려지는데 압축률은 고만고만 하더라. fast_zlib을 써서 libpng를 바로 적용해보는 것도 고민 중.

그래서 일단은 WebP, JPEG, PNG의 압축 기능은 구현이 됐다. 앱에서 GIF, TIFF 등의 그 외 포맷도 압축 기능을 지원할 지는 테스트 좀 더 해보고. UWP 파일 접근 권한 문제 때문에 안 그래도 골치아파.

잡담

[2018. 11. 25.] 근황

MonoGame을 이용해서 Entity Component System(ECS) 기반으로 2008년에 만들었던 SK-VM 게임을 리메이크 하는 중.

ECS 구조로 만들고 있는 가장 큰 장점은 개발하면서 생각한건데 관리되는 언어에서 사용하기 가장 효율적인 구조 같다는 점. 엔진에 ECS 만들어놓고 거기에 오브젝트 풀까지 적용해놓으니까 가비지 관리에 매우 용이해서 만족스럽다. 다만 아쉬운 점은 MonoGame이 멀티스레드 렌더링을 지원을 아직도 안 해서 엔진 자체는 멀티스레드 업데이트 -> 싱글스레드 렌더링을 하도록 해놨다. 물론 부동소수점 연산도 안 되고 램 용량도 2MB밖에 안 되던 피쳐폰 시스템에서 돌던 게임을 리메이크하는거라 뭘 어떻게 구현하든 2600X에서 CPU 사용량이 2%밖에 안 된다. 해상도가 176*178이라 GPU 사용량도 낮다.

다만 리메이크하는 게임이 완성본이 아니라 한창 개발 중이었던 소스+기획서 밖에 안 남아서 완성은 못하고 기본적인 시스템이랑 초반 맵만 구현 가능할 듯. 이렇게 된 이유가 2008년 당시에 친구가 노트북 포맷한다고 백업하게 외장하드 빌려갔다가 내 외장하드를 포맷해버려서… 그나마 개발 중이었던 소스는 학교에서 백업해놔서 그걸 받아와서 가지고 있던거고…

Unity로 리메이크하던 Shooting Hero는 오브젝트 풀 구현해야 되는데 너무 귀찮아서 보류. 새 리소스를 처음 사용하는 경우에 발생하는 스터터링도 해결해야 하는데 진짜 너무 귀찮아.

잡담

MonoGame이 Unity를 따라가지 못하는 이유?

최근에 Unity를 공부할 겸 2011년에 Android 기본 API로 만들었던 게임을 Unity로 포팅했었는데 몇 가지 귀찮은 점을 빼면 상당히 편리하게 만들었다.

대충 1차 완성을 끝내고 이번엔 2008년에 SK-VM API로 만들었던 게임을 Unity로 포팅하려는 와중에 이미지 크기가 너무 작아서(무려 해상도가 176 * 178이다.) 유니티 내에서 사용하기가 어려워(Unity의 기본 2D 타일 렌더링 기능이 정상 작동하지 않았다. 이미지 크기를 키운다면 가능은 하겠지만…) 이번에는 MonoGame으로 만들기로 결정했다.

여기서 문제점이 발생한다. MonoGame의 전신은 XNA Game Studio고, XNA Game Studio의 API는 DirectX 10이 막 출시됐던 1.0과 큰 차이가 없다. 그리고 DirectX 10은 싱글스레드 기반의 렌더링 시스템이고, 멀티스레드 기반 렌더링 시스템인 DirectX 11과는 기본적으로 API 설계 자체가 호환되지 않는다. 물론 MonoGame의 윈도우 버전 라이브러리는 DirectX 11을 이용해 개발됐지만 DirectX 11의 멀티스레드 기능은 전혀 사용하지 않는다.

MonoGame은 XNA Game Studio의 API를 크게 벗어나지 않는 한에서 API를 수정하고 있기 때문에 멀티스레드 기반 렌더링을 지원하지 않는다. 논의가 계속 진행됐지만 결국 다 포기했는지 2018년 4월 이후로는 논의가 없다.

고수준 엔진인 Unity와 저수준 프레임워크인 MonoGame을 비교하는건 의미가 없긴 하지만 MonoGame을 기반으로 하는 고수준 엔진과 비교한다면 의미가 있겠지. MonoGame부터가 멀티스레드를 제대로 지원할 수 없으니 Unity와 비교했을 때 성능 차이가 발생하게 된다.

이 때문에 MonoGame은 주로 소규모 인디 게임에서 활용되고 있고 대규모 게임으로 가면 갈 수록 볼 수가 없게 된다. 물론 성능 문제 이외에도 손쉬운 에디터의 유무도 갈리겠지만.

어쨌든 개인적으로 MonoGame은 슬슬 XNA의 틀을 벗어나 API를 전체적으로 현대적인 하드웨어를 제대로 활용할 수 있도록 개선하는 방향으로 가야되는 것 아닌가 하는 생각이 든다. 그것만 된다면 이를 이용한 고수준 게임 엔진이 나오기도 쉽겠지.

잡담

오디오 프로그래밍에 대해서 – 3

첫 번째 만든 라이브러리가 C# 브릿지가 제대로 안 만들어지고 디버깅도 어려워서 두 번째로 만들기 시작했던 순수 C#으로 만들던 라이브러리는 COM 임포트 및 DLL 임포트가 제대로 동작하지 못하고 오작동하는데 디버깅은 더 어려워서 깔끔하게 포기했다.

세 번째로 만들고 있는 라이브러리는 C# 브릿지도 좀 더 쉽게 할 수 있게 외부 노출되는 API는 C 계열로 보이도록 만들고 있다. 구조체에 몇몇 기능들을 함수포인터를 이용하도록 어떤 데이터를 구성하면 C#쪽에서 상속을 이용해 구현할 때도 더 편할 수 있을테니 그 편이 낫지 않을까 싶기도 하고.

뭐 일단 기본적인 기능들은 구현했는데 필터와 관련해서 몇 가지 문제가 있다.

  1. 푸리에 변환을 위해 사용한 라이브러리인 FFTW 3가 GPL 라이센스를 이용해 배포하고 있기 때문에 이를 사용한 라이브러리를 재배포하면 이 라이브러리를 사용한 프로그램도 GPL 라이센스로 배포해야 함.
  2. 이퀄라이저를 구현했는데 어떤 값을 넣어도 약간 깨지는 소리가 미세하게 들리는데 값에 따라서 다른 차이점도 별로 느껴지지 않음.

그래서 지금 FFTW 3를 대체할 가볍고 쉬운 FFT 라이브러리를 찾고 있다. 이퀄라이저도 만만치 않은 문제이지만 더 쉬운 것부터 고치는게 낫지 않을까 싶어서. 거기에 추상화된 필터 오디오 스트림만 제공된다면 이퀄라이저는 사용자가 알아서 구현할 수도 있고.

일단 이 문제들이 해결되면 C# 브릿지를 만들어서 C#에서 테스트 해봐야지.

잡담

오디오 프로그래밍에 대해서 – 2

libFLAC, libvorbis, opus, mp3lime 등을 이용한 디코더도 만들긴 했는데 C++ DLL을 C#에서 쓸 수 있는 브릿지 라이브러리를 만드는 과정에서 디버깅이 너무 힘들어서 일단 중지. 마샬링 과정에서 자꾸 뭐가 잘 안 돼서 삽질도 좀 했고 다행히 미디어 파운데이션 디코더와 WASAPI 오디오 플레이어 브릿지까지는 됐는데 지금은 XAudio 2 오디오 플레이어를 비롯해서 브릿지 이곳저곳에서 오류가 발생하는데 이걸 잡을 방법을 모르겠어서.

그래서 한번 C#만 가지고 프로그램을 만드는걸 도전해보기로 했다. 이미 NAudio나 CSCore같은 라이브러리가 이미 있지만 좀 더 사용법이 쉽고 간단하고 가벼운걸 만들고 싶었다.

일단 미디어 파운데이션 COM 임포트 구문을 추가해보고 있고 테스트 결과 잘 된다 싶으면 WASAPI의 COM 임포트도 추가할 예정. XAudio 2는 C#에서 COM 임포트를 하려면 가상함수 테이블을 만져야 하기 때문에 좀 귀찮아서 구현 예정은 없고 아마 OpenAL을 구현할 수는 있지 않을까 싶다.

뭐 그건 그렇고 mp3lime으로 MP3 파일 디코더 만드는 데는 삽질을 좀 했다. mp3lime으로 디코딩할 때는 ID3 태그를 제외해줘야 되는데다 프레임 크기를 잘못 잡아 읽어 전달하면 샘플이 몇몇 군데 빠져버려서 아예 그냥 직접 MP3 프레임 정보를 읽어들어서 프레임 크기 알아온 다음 프레임 크기만큼 읽어서 mp3lime에 넘겨주는 방식으로 구현해버렸다. ID3 태그 나오면 스킵해버리고.

MP3 전체 길이를 구해야 해서 프레임 당 크기를 검색해봤더니 2.6ms라고 그래서 그거만 가지고 총 프레임 갯수 * 26ms 했더니 실제 길이가 안 나오길래 그것도 좀 삽질했더니 대략 26.125ms 정도였다.

암튼 MP3 디코더 구현이 제일 지옥같았음. 이제 MP4 컨테이너에 AAC 디코더 구현할 차례였는데 얘가 더 지옥같을거 같아서 C++쪽은 일단 놔두고 C# 브릿지 만들기로 했음. 그것마저 지금 터져있는 상태지만.

잡담

오디오 프로그래밍에 대해서 – 1

요즘 자체 오디오 플레이어 및 휴대기기 음악 파일 싱크를 위해서 앱을 만들어보려고 삽질을 좀 했다.

어차피 WPF로 프론트엔드를 개발할 거, NAudio나 CSCore같은 라이브러리를 사용할까 했는데 결국 좀 더 다양한 파일 포맷을 재생할 수 있으려면 네이티브로 개발한 후에 C#용 래퍼를 만드는게 답인 것 같아서 C++로 라이브러리를 만들어보기로 했다.

Media Foundation이야 뭐 원래 사용해왔던 API고, XAudio 2도 사용을 안 해본 API는 아니기 때문에 이 API들로 기본적인 인터페이스는 만들었는데, 여기에 WASAPI(Windows Audio Session API) 재생 기능도 만들어보려고 했는데 의외의 복병이 바로 WASAPI였다.

WASAPI는 독점모드 재생이 가능하기 때문에 오디오 믹싱 처리를 안 하고 바로 원음을 재생할 수 있다는 장점이 있지만 문제가 있다. 바로 리샘플링 기능을 제공하지 않는다는 점.

리샘플링이 지원되지 않으면 무조건 시스템 재생 장치의 설정을 그대로 따라가는 음원만 재생이 가능하다. 예를 들면, 시스템 재생 장치의 샘플 속도 및 비트 수준 설정이 16비트에 48000Hz인 경우, 재생 가능한 음원은 무조건 16비트에 48000Hz 음원만 재생이 가능한 것이다. 이 경우 16비트에 44100Hz 음원은 재생이 불가능하다.

그렇다면 WASAPI로 재생하는 음악 재생 프로그램들은 어떻게 음원을 재생할 수 있는가 하면 프로그래머가 리샘플링을 알아서 한다. Windows에서는 이 리샘플링을 위한 API로 DMO(DirectX Media Object)를 제공한다. WASAPI와 DMO 모두 Windows 기본 API라는 것을 생각한다면 굳이 WASAPI에서 리샘플링을 기본 제공하지 않을 이유가 없었다는 것을 알 수 있겠지. 이건 마이크로소프트의 설계상 문제였다고 생각한다. 하나만 생각하고 둘은 생각 안 한 그런.

DMO로 리샘플링하는 예제도 찾질 못해서 진짜 겨우겨우 구현했다. NAudio랑 CSCore에 구현된 DMO 리샘플러 소스코드를 뜯어보고 거기에서 사용한 인터페이스의 UUID값 찾느라 너무 힘들었음. 결국 정답은 MSDN에 있긴 했지만 그 방대한 문서들 사이에 해당 문서로 가는 링크를 못찾아서 문제였다.

이제 OpenAL 및 DirectSound 오디오 플레이어도 구현하고 LimeMP3나 FAAD2, libogg/libvorbis/libFLAC/opus를 이용해서 오디오 디코더도 구현해야 되는데 너무 힘들다. 일단 좀 쉬어야지.

잡담

다람 리네이머의 스크립트 기능

다람 리네이머에서 개인적으로 별로 쓸모는 없지만 혹시나 필요한 사람이 있을 것 같아서 계속 개발을 시도했던 기능 중 하나가 스크립트를 이용한 일괄 처리 기능이다. 버전 2.x대에서 시험적인 스크립트 기능을 만든 적이 있지만 문법 자체가 너무 제한적이라서 버전 3.x대 올라오면서 그냥 버렸다.

대신 3.x에서 좀 더 유연한 스크립트를 만들기 위해서 이런저런 시도를 많이 했었는데, 그러느라 기능 추가나 버그 수정이 더뎌지기도 했다.

결국은 자체 개발 스크립트 언어가 아닌 기존 스크립트 언어를 사용하기로 하고 여러 시도를 해봤는데, C#, IronPython, IronRuby, Powershell, Javascript를 고려해봤다.

C#은 완전형 객체지향 언어이기 때문에 스크립트로 사용하기엔 다소 어려움이 있었다. 네임스페이스도 지정해주고 클래스도 지정해주고 메인 함수도 지정해줘야 해서 초보자가 사용하기엔 어렵지 않을까 하는 생각이 들었다.

IronRuby와 IronPython은 버전업이 중단된지 최소 2년은 됐기 때문에 제외했다. 특히 IronRuby는 버전업 중단이 꽤 오래 돼서 장기적으로 봤을 때 문제가 생길 가능성이 크다고 생각했다. 버전업이 중단되지 않았다면 IronPython에 손을 더 들어줬을 것 같다.

Powershell도 문법적으로 괜찮다고 생각했었는데 C#과 연동해서 내 자체 명령어를 추가할 방법이 없었다.

결국 Jint를 이용해 Javascript로 스크립트를 구현했다. C#과 연동해서 객체나 함수를 추가해줄 수도 있고, 스크립트 결과를 받아올 수도 있어서 편리했다. Jint 자체적으로 CLR 연동도 가능은 하지만 보안 문제로 일단 보류했다. 만약 추가했다면 .NET 프레임워크 기능도 사용할 수 있으니 더 편리할 수는 있었을 듯.

아무튼 3.3버전에서는 좀 더 테스트 후에 다시 일괄 처리 기능이 추가될 예정이다. 거기에 더불어서 몇 가지 버그도 수정하고 싶고.

잡담

안드로이드 SDK 못 써먹겠다

개인적으로 싫어하는 개발 환경이 몇 가지 있는데, 그 중 하나가 자바고, 다른 하나가 안드로이드 SDK다. 물론 안드로이드 SDK가 자바에 종속되어 있다는 것을 생각하면 안드로이드 SDK를 싫어하게 되는 데에는 이유가 있었겠지만.

저번주 내내 삽질해서 엄청 심플하게 정부청사 식당 식단표 뷰어를 만들었는데, 예전처럼 윈도우폰을 쓰고 있었으면 하루 정도면 금방 만들어놓고 써먹었을 텐데 안드로이드용으로 개발하려다보니 API가 너무 답답해서 이렇게 만들어보고 실패, 저렇게 만들어보고 실패 해서 그냥 빈 액티비티에 대충 때려넣어서 만들었다. 그래서 지금 UI가 똥망인 상태.

언어 자체는 코틀린이 나오고 있지만 얘도 별로 마음에 안 들고, 안드로이드 SDK 구조 자체도 솔직히 별로 마음에 안 듦. API가 직관적이지 않아서 이리저리 삽질하고 나서야 기능 구현이 되는 모양새라서 그런 느낌. 하다보면 결과물이 나오긴 나오는데 만족스럽진 않고 위태위태한 그런…?

XAML + C#으로 구현해본 사람들은 다 안다(WPF, Silverlight, UWP, …). 이게 얼마나 편하고 직관적인지. 그리고 이 XAML + C#으로 구현하려고 Xamarin Forms 써서 만들려고 했더니 안드로이드 네이티브 라이브러리는 아니라서 그런건지 자료도 적고 겨우겨우 핀치 투 줌 기능 하나 구현했더니 제대로 줌도 안 됨. 걍 Xamarin Droid로 개발할까 했는데 그거랑 Android Studio + 자바로 개발하는거랑 별 차이도 없어서 걍 오랜만에 자바나 써볼겸 자바로 구현해서 스토어에 제출. 근데 걍 C#으로 개발하는게 더 낫지 않았을까 싶을 정도로 안 익숙했다. 다음엔 걍 Xamarin Droid + C#으로 개발해야지.

뭐 암튼 안드로이드 API 전체적으로 개선 좀 해줬으면 좋겠다는 생각이다. 불가능하겠지만.

잡담

초중고 학생회에 대한 생각

선거철만 되면 생각하는건데, 초중고 학생회의 권한 범위를 늘려야 한다고 생각한다. 그래야 나중에 선거할 때 후보에 대해 자세히 알아보고 선거에 참여할 수 있지 않을까 싶다.

지금의 초중고 학생회장 선거는 공약은 있으나 이를 지킬 권한도, 권리도 없다. 단순히 역할만 있는, 이를테면 학년 대표일 뿐이다. 선생님들은 이들에게 어떠한 권한도 주지 않고, 때때로 일부 학교는 축제 등의 학교 행사의 기획이나 관리에 동원할 뿐이다. 그야말로 공짜 노동력인 셈.

이렇게 자신들이 뽑은 대표가 별로 하는 일이 없으니 청년들이 성인이 되어 선거에 참여할 때 후보와 공략에 별 관심을 가지지 않고 대충 뽑아놓는 것이다. 어딘가에서는 단순히 “지지하는 저쪽 후보가 뽑힐 확률이 생각보다 높은 것 같진 않고 그냥 내가 한 표 준 사람이 당선되는 것이 보고 싶었다”라는 이유로 대통령을 뽑은 사람도 있었다. 그리고 이렇게 뽑힌 대통령이 결국…

이런 이유로 나는 초중고에서 매년 뽑는 학생회장의 권한을 얼핏 잘못하면 학생들이 피해를 입을 수 있을 정도로 키워줘야 한다고 생각한다. 엉뚱하게 뽑은 사람이 자신에게 피해를 주고 있다는 사실을 깨달아서 다음 선거 때는 제대로 생각하고 뽑을 수 있도록. 물론 이 피해를 복구하는데는 학생들 뿐만 아니라 선생님들도 많은 노력을 들여야 하겠지만 이런 것이 교육이 아닐까.

이런 권한 확대는 결국 학생들에게 큰 도움이 될 가능성도 있는데, 학생 자치 활동을 통해 학교가 하지 않았던 일을 학생들이 주체가 되어 시도할 수도 있는 것이다. 예를 들면 장애가 있는 학생들에게 도움을 줄 수 있는 봉사활동이라던가, 급식의 질이 떨어지는 경우 이를 향상시키기 위한 어떤 노력을 한다던가, 교무회의에 학생 대표로써 참가하여 학생들이 모르고 지나갈 수 있었던 것들을 알고 이를 학생들에게 공지할 수 있다던가 하는.

뭐든 실수와 실패는 어렸을 때 겪는 것이 가장 잃는 것이 적고 얻는 것이 많다. 실수에 대한 교훈을 얻기 좋은 학생 시절에 실수를 할 기회를 줘야 한다고 본다.