Gaming Life

一日24時間、ゲームは10時間

WindowsでMacのようにキーボードショートカット一発で呼び出せる辞書アプリ「RapidDict」を買った

f:id:ai_gaminglife:20210228204514p:plain

学生時代、研究室で支給されたmacを使っていた時期がある。その時よく使っていたmacの機能で、Windowsにもほしいなあと思ったのが「spotlightで起動できる辞書検索」機能だ。

spotlightとはmacに標準に搭載されているランチャーアプリのことで、これを使ってショートカット一つで起動し、アプリの起動やファイル検索などを容易に呼び出すことができる。このspotlightには辞書.appが組み込まれており、調べたい単語を入れると、その辞書ページに一発で飛ぶことができる。この機能のお陰で、大学時代海外の論文を読むときに簡単に英単語を調べられて、助かったのを覚えている。

inforati.jp

Windowsにも「Wox」などmacのSpotlightに相当するサードパーティ製アプリは存在する。私の場合、Microsoftが開発しているユーティリティアプリ「PowerToys」にバンドルされている「PowerToys Run」を使っている。

github.com

ただ、これらのランチャーアプリには辞書が組み込まれていない。また、ショートカット一つで起動できるような出来の良いWindows用辞書アプリもないため、macを研究室に返還して自宅のWindowsで英単語を調べるときは、仕方なくブラウザを立ち上げてWeblio辞書を使っていた。

で、最近になってショートカット一つで起動できるWindows向け辞書アプリを見つけた。それが、RapidDictだ。

yomogisoftware.biz

RapidDictはアプリ自体は無料だが、辞書データ(英辞郎)を入手するのに980円支払う必要がある。一応無料の辞書データもあるようだが、かなり貧弱なため、基本RapidDictを使うなら980円を払うべき。

RapidDictを使ってみて、正直アプリ自体の出来はあまり良くないと感じた。購入した辞書データをインストールする際何度かアプリがダウンすることもあったし、検索速度も決して早いとはいえない。

ただショートカット一つで起動できる英語辞書というだけで、その出来の悪さに目をつぶって使い続けたくなる魅力がある。Windowsで英単語を検索するためだけにわざわざブラウザを起動するのに嫌気が差している人は、是非RapidDictを試してみてほしい。

……PowerToys Runに辞書機能入らないかなあ……

(遅すぎる)今年の目標 2021年版

遅すぎますが今年の目標を簡単に書いておこうと思う。

ブログ毎月更新継続

こんな急ぎ書いたようなのが丸見えな記事でも、3年以上続いているブログの毎月更新は、今年も継続したい。その中で、1、2記事は、10数時間かけた大作の記事が書きたい

今年こそ一人Advent Calender完走

すっかりなかったことになっている去年の一人アドベントカレンダー。あれは直前に思いつきでカレンダーを作って、即興で記事を書いていたところがあるので仕方ないところがある。

今年は、まだまだ時間があるので、記事のストックをためて、12月に一人アドベントカレンダーが完走できるようにしたい。(そしてその最終日に、この記事を踏まえた今年の振り返り記事を書きたい)

就活記事のリメイク

昔書いた就活記事のリメイクを書く。これは今絶賛書いているところで、来月中に公開する目処が経っているので、乞うご期待ということでお待ち下さい。

月2冊は技術書を読む

去年は本を4冊買ったら1冊買うみたいなスタイルで、完読したという本があまり多くなかった。最近本棚を買い替えたこともあり読書のモチベが高いので、まずは積んでいる本の消化から優先して、最低月2冊ペースで本を読みたい。

二ヶ月に1個はGithubに何かしらの小さいプロジェクトを公開する

最近OpenSiv3Dを使って2048ゲームのクローンを作成した(これについては後でまた詳しくブログに詳細を書くと思います)。この規模でいいので、小さいプロジェクトを最低でも二ヶ月に一個は開発して、それをGithub及びブログで公開していきたい。

新しい言語を一つ覚える

今の所考えているのはjavascript + HTML5。web系の知識が空っぽなので、Github pagesにポートフォリオとなるサイトが作れる程度にはなにか作りたいなーと。

まとめ

以上。今年もフルリモート勤務が続くと思われるので、この目標が達成できるだけの時間は取れると思う。今年は無理に大きいことをやろうとせず、将来の肥やしになるような、小さい事をコツコツと、をテーマにエンジニアライフを送りたいと思います。

windowsのリモートデスクトップで複数画面を使う&画面上部の接続バーを消す方法

本記事はai_9684_dctソロ Advent Calendar 2020 10日目の記事です。

Windowsリモートデスクトップ接続」に関する機能の紹介。

コロナ禍の中、4月の入社以来、数回の出社を除いてリモートワークが続いている。弊社の場合、自宅にある会社から貸与されたPCから会社のネットワークにVPN接続し、windows標準の「リモートデスクトップ接続」か、「Chromeリモートデスクトップ」で会社のPCにつないでリモートで作業するというスタイルを取っている。

私は学生時代もたまに使っていて、使い慣れていたということもあり、半年以上「Chromeリモートデスクトップ」を使用していた。しかしながら「Chromeリモートデスクトップ」は1画面でしか作業できない事が非常に不満だった。

そんなことをMTG中にぼやいたら、先輩から「windowsリモートデスクトップなら複数画面使えるよ!」というアドバイスを貰った。マジか、と調べてみたら確かにあった。

f:id:ai_gaminglife:20201210215105p:plain

リモートデスクトップ接続の接続前の設定画面で、「リモートセッションで全てのモニターを使用する」というオプションを有効にすると、例えばリモート接続元のPCが2画面使っていれば、2画面まるごと使ってリモートデスクトップの画面を使う事が出来た。なぜこの機能にもっと早く気づかなかったんだ……

更に、windows標準のリモートデスクトップ接続の大きな不満の一つだった、画面上部に常に表示される↓この接続バーも消せることが分かった。

f:id:ai_gaminglife:20201210215138p:plain

同じ設定画面で、「全画面表示の使用時に接続バーを表示する」という設定のチェックを外せば、接続バーが最初以外一切表示されなくなる。なお、接続バーを使わなくても、「[Ctrl] + [Alt] + [Pause]」キーを同時押しすれば、全画面表示が解除される。

というわけで、リモートワークの不満の一つだった「画面の狭さ」という課題がクリアされ、リモート作業中にかかるストレスを大きく減らすことができましたとさ。まあ、夕方6時頃になると回線が重くなって、入力の遅延が我慢できないほど目立つようになるのは変わらないのだが。10Gビット回線引こうかなあ……

WindowsのC++でウィンドウアプリケーションを書く際にWinMain()を隠匿する方法の一例

序文

OpenSiv3Dなど、ユーザーが使いやすいように考えて作られたC++Windowsアプリケーション開発用フレームワークは、複雑なシグネチャを持つ WinMain() を隠してくれる。

# include <Siv3D.hpp>

// WinMain()を書く必要がない
// int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
//                    LPSTR lpCmdLine, int nCmdShow)
void Main()
{
    Print << U"Hello, Siv3D!";

    while (System::Update())
    {
    }
}

私もこうしたWinMainの隠匿をしてみたいなーと思い、実際に実装してみた。

最も簡単な実装

FrameworkMain.cpp

#include <Windows.h>

void Main();

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
    OutputDebugStringA("winmain() start!\n");
    Main();
    return 0;
}

UserMain.cpp

#include <Windows.h>

void Main() {
    MessageBoxA(nullptr, "Demo", "Demo Message", MB_OK);
}

上記のコードのように、フレームワーク側(.lib)で、宣言だけ用意して未実装な void Main() 関数を用意し、それをフレームワーク側に記述した WinMain() で呼び出す。これにより、ユーザーは WinMain() の存在に気を払うことなく、Main() 関数さえ実装することができる。

以上で目的は達成できたのだが、これではあまりに薄いので、もう少し凝った実装をしてみた。

クラスのメンバ変数をエントリポイントのように扱う

ここでは以下の要件を満たすものを実装する。

  1. フレームワークはAppBaseという名のクラスを用意する
  2. AppBaseは virtual int Init() という純粋仮想関数をもつ。
  3. ユーザーはAppBaseをpublic継承したアプリケーション用クラスを作成できる
  4. ユーザーは継承先で virtual int Init() 関数をエントリポイントのように扱って実装できる
  5. ユーザーは、これらの機能を最小限の労力で使用できる。

AppBase.h(フレームワーク実装)

#pragma once
#include <windows.h>

class AppBase* GetWinPtr();

class AppBase {
private:
    friend AppBase* GetWinPtr();
    static AppBase* m_App;

public:
    AppBase();
    virtual int Init() = 0;
};

AppBase.cpp(フレームワーク実装)

#include "AppBase.h"

AppBase::AppBase() { m_App = this; }

AppBase* AppBase::m_App = nullptr;

AppBase* GetWinPtr() { return AppBase::m_App; }

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
    OutputDebugStringA("winmain() start!\n");
    AppBase* g_App = nullptr;
    g_App = GetWinPtr();
    if (g_App == nullptr) {
        return -1;
    }
    return g_App->Init();
}

MyApp.cpp(ユーザー実装)

#include "AppBase.h"

class MyApp : public AppBase {
public:
    virtual int Init() {
        MessageBoxA(nullptr, "Demo", "Demo Message", MB_OK);
        return 0;
    }
};

MyApp app;

解説

ユーザーは、グローバルに自身がAppBaseクラスを継承して作成したクラスのインスタンスを作成するだけで、 Init() 関数をエントリポイントのように扱って実装できる。仕組みとしては以下の通り。

AppBaseはstatic変数として、自身の型のポインタ変数を持つ。このポインタは、AppBaseのfriend関数である GetWinPtr() から取得できる。

フレームワーク上では、 WinMain() において GetWinPtr() 経由でAppBase型のポインタから Init() メンバ関数を呼び出す。しかし、この時点ではそのポインタの実体がどの型を表しているか分からない。

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
    OutputDebugStringA("winmain() start!\n");
    AppBase* g_App = nullptr;
    g_App = GetWinPtr();
    if (g_App == nullptr) {
        return -1;
    }
    return g_App->Init();
}

そこでユーザーは、AppBase型を継承したクラスであるMyApp型のインスタンスを、グローバルで作成する。グローバルで定義した変数は、エントリポイント、つまり WinMain() より早く用意される。

これにより、ユーザーは、AppBase継承クラスの Init() 関数を、エントリポイントのように扱って実装することができる。

補足

この実装は、WindowsGUIフレームワークとしてMicrosoftが提供する、MFCMicrosoft Foundation Class)の実装とほとんど同じらしい。

参考

In a typical MFC application we never write WinMain() function so how is it possible to compile and link a windows program with out WinMain()?(http://www.equestionanswers.com/vcpp/mfc-application-winmain().php)

winmainの隠蔽工作

PIXでシェーダデバッグする際はwindowsの開発者モードを有効にしなければならない

この記事は、ai_9684_dctソロ Advent Calendar 2020 7日目 の記事です。

PIX on Windowsとは

PIXとは、Microsoftが開発したDirectX12製ゲームのパフォーマンス測定及びシェーダデバッグのための、プロファイラである。DirectX12を使った開発をする際は、ぜひ使いたいソフトの一つ。

遭遇したエラー

PIXを使って、シェーダデバッグをしようとしたところ、以下のエラーでシェーダデバッグが起動出来なかった。

「An Error has occured. This feature requres developer mode to be enabled on the analysis device. Type "developer mode" into the start menu to find the correct setting」

f:id:ai_gaminglife:20201207200737p:plain

エラーの回避方法

エラーメッセージの指示どおり、windows10の開発者モードを有効にすれば、エラーを回避できる。

windows 10の [設定] → [更新とセキュリティ] → [開発者向け]の画面で、[開発者用モード]をオフからオンに切り替える。(この時PCの再起動は必要ない)

f:id:ai_gaminglife:20201207200748p:plain

これにより、PIXでDirectX12のシェーダデバッグが可能となる。

UE4のコンソール変数の命名規則とsgコマンドの役割

この記事は、ai_9684_dctソロ Advent Calendar 2020 5日目 の記事です。

UE4には r.ScreenPercentage や、 r.Streaming.PoolSize だったり、多くの便利なコンソール変数が用意されている。こういったコンソール変数には、命名規則がある。

ConsoleManager.cpp に、コンソール変数の命名規則が書かれている。

// r.      Renderer / 3D Engine / graphical feature
// RHI.    Low level RHI (rendering platform) specific
// a.     Animation
// s.     Sound / Music
// n.      Network
// ai.     Artificial intelligence
// i.      Input e.g. mouse/keyboard
// p.      Physics
// t.      Timer
// log.       Logging system
// con.       Console (in game  or editor) 
// g.      Game specific
// Compat.
// FX.     Particle effects
// sg.     scalability group (used by scalability system, ini load/save or using SCALABILITY console command)

この中で、 sg. から始まるコマンドは少々他と違う扱いになっている。

sg.コマンドについて

sgとは、Scalability Groupの略で、sgコマンド類は、ある設定ファイルに基づいて、他のパラメータをまとめて制御する役割を持つ。例えば自分の環境だと、 sg.PostProcessQuality を3から1に変更すると、

LogConfig: Applying CVar settings from Section [PostProcessQuality@1] File [C:/Users/detec/Documents/Unreal Projects/ActionRPG/Saved/Config/Windows/Scalability.ini]
LogConfig: Setting CVar [[r.MotionBlurQuality:3]]
LogConfig: Setting CVar [[r.AmbientOcclusionMipLevelFactor:1.0]]
LogConfig: Setting CVar [[r.AmbientOcclusionMaxQuality:60]]
LogConfig: Setting CVar [[r.AmbientOcclusionLevels:-1]]
LogConfig: Setting CVar [[r.AmbientOcclusionRadiusScale:1.5]]
LogConfig: Setting CVar [[r.DepthOfFieldQuality:1]]
LogConfig: Setting CVar [[r.RenderTargetPoolMin:350]]
LogConfig: Setting CVar [[r.LensFlareQuality:0]]
LogConfig: Setting CVar [[r.SceneColorFringeQuality:0]]
LogConfig: Setting CVar [[r.EyeAdaptationQuality:0]]
LogConfig: Setting CVar [[r.BloomQuality:4]]
LogConfig: Setting CVar [[r.FastBlurThreshold:2]]
LogConfig: Setting CVar [[r.Upscale.Quality:2]]
LogConfig: Setting CVar [[r.Tonemapper.GrainQuantization:0]]
LogConfig: Setting CVar [[r.LightShaftQuality:0]]
LogConfig: Setting CVar [[r.Filter.SizeScale:0.7]]
LogConfig: Setting CVar [[r.Tonemapper.Quality:2]]
LogConfig: Setting CVar [[r.DOF.Gather.AccumulatorQuality:0        ; lower gathering accumulator quality]]
LogConfig: Setting CVar [[r.DOF.Gather.PostfilterMethod:2          ; Max3x3 postfilering method]]
LogConfig: Setting CVar [[r.DOF.Gather.EnableBokehSettings:0       ; no bokeh simulation when gathering]]
LogConfig: Setting CVar [[r.DOF.Gather.RingCount:3                 ; low number of samples when gathering]]
LogConfig: Setting CVar [[r.DOF.Scatter.ForegroundCompositing:0    ; no foreground scattering]]
LogConfig: Setting CVar [[r.DOF.Scatter.BackgroundCompositing:0    ; no foreground scattering]]
LogConfig: Setting CVar [[r.DOF.Recombine.Quality:0                ; no slight out of focus]]
LogConfig: Setting CVar [[r.DOF.TemporalAAQuality:0                ; faster temporal accumulation]]
LogConfig: Setting CVar [[r.DOF.Kernel.MaxForegroundRadius:0.006   ; required because low gathering and no scattering and not looking great at 1080p]]
LogConfig: Setting CVar [[r.DOF.Kernel.MaxBackgroundRadius:0.006   ; required because low gathering and no scattering and not looking great at 1080p]]
sg.PostProcessQuality = "1"

この様にポストプロセス関連のコンソール変数がまとめて変更される。

Scalability Groupの設定は、Engine/Configフォルダに格納されている。BaseScalability.iniに記述されている。ここに記載されている値を変更すれば、例えば、 sg.PostProcessQuality を1にさげても、 r.BloomQuality は下げてほしくない……みたいな調整ができる。

参考サイト

docs.unrealengine.com

Pythonのデコレータ(風)をC++で実装してみる

本記事はai_9684_dctソロ Advent Calendar 2020 3日目の記事です。

Pythonには「デコレータ」という、関数に追加の機能を修飾するシンタックスシュガーが存在する。

def decorator_sample(func):
    def wrapper(*args, **kwargs):
        print(f'Arguments: {args}')
        print(f'Keyword arguments: {kwargs}')
        result = func(*args, **kwargs)
        print(f'Result: {result}')
        return result
    return wrapper

@decorator_sample
@decorator_sample
def add_int2(a: int, b: int):
    return a + b

add_int2(12, 41)

# 出力結果
Arguments: (12, 41)
Keyword arguments: {}
Arguments: (12, 41)
Keyword arguments: {}
Result: 53
Result: 53

デコレータは、親切なライブラリだとサポートしていることが多い印象。

今回はPythonのデコレータっぽいものをC++で実装してみた。

C++17によるデコレータの実装

といってもPythonのように @decorator とつけるだけで修飾するには、言語機能レベルでサポートしなければならない。そこまでのことは出来ないので、デコレートしたい関数オブジェクトを受け取る関数を作って、そこでデコレートするという実装にした。

今回の実装にはこちらのサイトを参考にした。

C++のPython関数デコレータに相当するものは何ですか?

#include <iostream>

template <class... Ts>
void print_all(std::ostream& os, const std::string& separator, Ts const&... args) {
    ((os << args << separator), ... );
}

template <class T>
auto decorator(T&& func) {
    auto wrapper = [func = std::forward<T>(func)](auto&&... args){
        std::cout << "arguments: ";
        print_all(std::cout, ", " , args...);
        std::cout << "\n";
        auto result = func(std::forward<decltype(args)>(args)...);
        std::cout << "Result: " << result << '\n';
        return result;
    };
    return wrapper;
}

int add_int2(int a, int b) { return a + b; }

int main() {
    auto decorated = decorator(decorator(add_int2));
    decorated(12, 29);
}

// 出力結果
// arguments: 12, 29, 
// arguments: 12, 29, 
// Result: 41
// Result: 41

[C++] gcc HEAD 11.0.0 20201106 (experimental) - Wandbox

(ちょっと)文法解説

デコレータへの関数及びその引数の受け渡しには完全転送を利用している。

template <class T>
auto decorator(T&& func) { // ユニバーサル参照で仮引数を宣言して
    auto wrapper = [func = std::forward<T>(func)](auto&&... args){ // std::forwardで完全転送
// 以下省略
}

完全転送についてはこの辺りの資料が参考になる。

marycore.jp

デコレートする関数に渡される実引数を列挙するための print_all 関数には、C++17以降でサポートされている畳み込み式(fold expression)を利用している。そのため、C++14以前のコンパイラでは動作しない。

cpprefjp.github.io