Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

FSRとRSR

AMDRadeon のドライバには、 FSRRSR という機能がある。

pc.watch.impress.co.jp

簡単に言えば、ゲームを低解像度の設定にして出力させて、それを表示するときにデバイスの解像度にあわせてアップスケールして表示させる技術である。ゲームから見れば低解像度なのでパフォーマンスは劇的に向上し、かつ、デバイスから見ると高解像度なので、美しいゲーム画面が楽しめるというものである。

引用したリンクに書いてあるが、 FSRSDK レベルでの提供なのでゲーム側での組み込みが必要なのに対し、 RSR はデバイス側で設定できるので様々なゲームで対応できる。両方有効にしてしまうとおかしなことが起こる。たぶん。

RSR の設定は AMD Software というアプリ(紛らわしい名前だ・・・)から設定できる。 GDP win mini の場合はデフォルトで入っていた。 Gaming Graphics のところに設定がある。

FSR については、ゲーム側で設定すると良い。例えば、 Street Fighter 6 では(FSR とは書いてないが)それっぽい Internal Resolution という設定がある。ここをイジっておけば、 RSR が無効でもパフォーマンスが出る。というか、 SF6 の場合は RSR を有効にしても効いていないようだった。

ややこしいのは、 Steam Deck の場合はすべて FSR という用語になっていることだ。おそらく、 Steam Deck の場合は FSRSDK が組み込まれた状態になっているのだろう【要出典】。考え方は同じで、 Steam Deck 側とゲーム側のどちらの FSR を使うのか、決めて調整する必要がある。

ChatGPTとプログラミング2

hiratara.hatenadiary.jp

その後も継続して ChatGPT をサブスクで使っているのだが、今のところサブスクを解除する予定はない。サブスクした直後は Google 検索の代わりに日常の疑問を尋ねることが多かったのだが、最近はもっぱらプログラミングに ChatGPT を使っている。

ChatGPT の強みは、詳細なコンテキストを与えられることだと考えている。しかも、チャット形式のお陰でそれを反復的に行うことができる。「 tiny_skia のサンプル作って」などと短文の一度の指示で課題を解決しようとすると真価は発揮できず、自分の持っている課題について要点を的確に伝えるほど生成される回答の精度は増す。

以下は、オリジナルの Skiatiny_skiaAPI の違いを知るためにそれぞれのサンプルコードを生成させた例である。知りたい情報がきちんと生成されている 1

#include "include/core/SkCanvas.h"
#include "include/core/SkPaint.h"
#include "include/core/SkShader.h"
#include "include/effects/SkGradientShader.h"

void draw(SkCanvas* canvas) {
    SkPoint points[2] = {SkPoint::Make(0, 0), SkPoint::Make(200, 200)}; // グラデーションの開始点と終了点
    SkColor colors[2] = {SK_ColorRED, SK_ColorBLUE}; // グラデーションの色
    auto shader = SkGradientShader::MakeLinear(points, colors, nullptr, 2, SkTileMode::kClamp);

    SkPaint paint;
    paint.setShader(shader);
    paint.setAntiAlias(true);

    canvas->drawRect(SkRect::MakeWH(200, 200), paint);
}
use tiny_skia::*;

fn main() {
    let mut pixmap = Pixmap::new(200, 200).unwrap();
    let gradient = Gradient::new_linear(
        Point::from_xy(0.0, 0.0),
        Point::from_xy(200.0, 200.0),
        vec![
            GradientStop::new(0.0, Color::from_rgba8(255, 0, 0, 255)), // 赤
            GradientStop::new(1.0, Color::from_rgba8(0, 0, 255, 255)), // 青
        ],
        SpreadMode::Pad,
        Transform::identity(),
    ).unwrap();

    let paint = Paint {
        shader: gradient.into(),
        anti_alias: true,
        ..Paint::default()
    };

    pixmap.fill_rect(Rect::from_xywh(0.0, 0.0, 200.0, 200.0).unwrap(), &paint, Transform::identity(), None);
}

他にも知りたいことはあったので、「画像を描画するための skia ライブラリについて質問です。 SkPixelMap 上に四角形を描画したいときは、どうしますか。サンプルコードを作ってください。」というプロンプトから初めて、その後 tiny_skia との違いを教えてもらいつつ、最後に知りたかったこととしてこの違いを生成させた。 Google 検索と決定的に違うのは、この一連の質問の過程で ChatGPT がこちらが話している内容のコンテキストをぐんぐんと取り込んでいくことである。後半に行けば行くほど、こちらが明確な指示を出さなくても的確な結果を生成するようになる。

ChatGPT がもう一点強いのは、英語の生成である。例えば、ブランチ名やメソッド名、コード内のコメント、 git の commit メッセージや github の PR の解説文など、 OSS を意識した場合に英語が望ましいシーンがプログラミングでは大量に発生する。 ChatGPT はこれらの書式をよく知っているので、プロンプトさえ的確に書けば生成して提案してくれる。その際に、どんな言語でどのような開発をしているのかもコンテキストとして与えれば、(我々日本人の感覚では分からないが)英語圏の開発者が違和感を感じないレベルの命名、英文が生成される。

最近購入した UMPC との相性も良い。 UMPC では大量の文字を打つのは大変だが、そこを ChatGPT にやらせて、自分は生成されたコードをレビューすれば良い。 この PR はすべて UMPC (と外付けのモバイルキーボード)上で生成したが、 ChatGPT の力を大いに借りている。太古の昔と違って、現在のプログラミングはほぼすべてのロジックはすでに誰かが生成しており、我々はそれを切り貼りして新しいコードを作っている。ライブラリには必ずサンプルコードが付いているし、 linter がコードの記述のブレをなるべく無くして均一なコードになるようにしてくれる。創作的だと錯覚しているプログラミングだが、実はそこまで創作的な行為ではないのだ。ほとんどの場合は独創性は必要とされず、生成 AI である ChatGPT が得意とするところである。

もちろん、生成されたコードの正しさは必ずしも担保されない。難しいタスクであれば失敗する。しかし、それは人間でも同じである。ちょっと出来の悪い部下に依頼して、コードを書かせたり調査をしてもらったと思えばいいのである。月額 3,000 円以上は少し効果だが、人間を雇うよりは遥かに安い。しかも、「ちょっと出来の悪い」は、そんじょそこらのエンジニアの平均レベルよりは、出来が良い可能性もある。


  1. そのまま使うことが目的ではないので、コンパイルできるか、動くかは見ていない。

三ツ沢墓地と赤門坂

週末は子供が熱を出して寝込んでいた。あんなにうるさい人でも、寝込んでしまうと寂しいものだ。やはり、子供も大人も元気が一番である。早く回復するよう願っている。

そんなこともあり、自分も感染している可能性は否定できず、また、別にやりたいこともあったので、今週末の自転車は軽く流して横浜まで行って、そのまま帰ってくるだけで終わった。朝からデニーズとスタバに入り浸り、 GPD win mini でガツガツとコードを書いた。やはり自転車と UMPC は相性がいい。自転車乗るときに PC を持っていくとか正気の沙汰かと思われるかもしれないが、プログラマーという人生を歩んでいる時点で正気ではないのだ。

昼はその辺にあった中華屋に入ってみた。まさに食べたかった大衆中華の味で、塩辛過ぎて後半は飽きたが、まあ、楽しむことはできた。ただ、驚いたことに未だに喫煙席があるお店で、横浜市内にもこんなところが残っていたのかと面食らった。すぐに禁煙席に移してもらったが、禁煙席と言うよりは燻煙席で、しっかりと煙を浴びることになってしまった。

昼食後は一路横浜駅を目指す。せっかくだからと通ったことがない道を選んだところ、突然、眼下に巨大な墓地が広がった。北海道でこれほど巨大な墓地を見たことがなかったので、驚いた。 三ツ沢墓地 と言うらしい。

横浜に行った目的は、駐輪場を見つけることである。今回は タイムズサイクルJR横浜パーキング を使った。少し歩かねばならない上に 100 円かかるが、そのお陰で空いているし治安も良かったように感じる。ただ、ミニベロだとチェーンテンショナーが引っかかってしまって、上段ラックには止められなかった。

せっかく 100 円払ったからとヨドバシを探索して ROG Ally の実機を触って質感に感動しつつも、家族が寝込んでいるとやはり気乗りしないので、そそくさと帰路へ。

帰りも、いつもは通らない道を選択した。この赤門坂という坂、自宅から大手スーパーへ買い出しに行くと通る坂なのだが、ブロンプトンを買う前に電動自転車のテスト運転のためになんどか登ったことがある。電動ではない自転車で登ることは不可能だろうと思っていた。

www.strava.com

今見ると勾配 16% 程度なのでそこまでではない。特に危なげもなく、シッティングのままで登りきった。トラウマとなっていた坂を、これでまた一つ攻略することができた。

ライフタイム制約と所有権の移動

以下の Rust のコードはコンパイルできる。

fn f<T: 'static> (_: T) {}

fn main() {
    let s1: &'static str = "static";
    f(s1);

    let s2: String = String::from("string");
    f(s2);
}

fs1 を渡せるのは、よくわかる。 &'static なのだから、 T: 'static を満たすのは当然と言えよう。

しかし、 s2 はどうだろうか? 'static とは思えない。 lifetime が 'static というのは、 ChatGPT4 さんによれば、「参照されるデータ自体がプログラムの実行期間全体で生存する」ことらしい。 s2 は関数内で定義したローカル変数なので、スコープを外れれば削除されてしまうように思える。この例ではたまたま main 関数で定義しているので、プログラムの実行期間全体で生存すると言えばそう言えるのかもしれないのだが、別に main 関数ではなくてもこの結果は変わらない。

この件について ChatGPT4 さんと小一時間議論したのだが、スッキリした回答は得られなかった。そこで Rust のドキュメントにあたったところ、単純明快な回答が書かれていた。

doc.rust-lang.org

T: 'a means that all lifetime parameters of T outlive 'a.

つまり、これは逆を言えば、 lifetime parameters を持たない型は任意の 'a 制約を満たすし、当然 'static 制約も満たすのだ。なるほど。

Quad Lockが割れた2

今月 Quad Lock が割れたばかりだが、また割れた。もう何も言うまい。

hiratara.hatenadiary.jp

と言っても、今回は自分でニーキックでぶち割ってしまったので、完全に自分の責任だ。 Quad Lock の装着位置に対して、 Pixel 6 Pro が大き過ぎるのだろう。二段階右折のために颯爽とサドルから飛び降りたときに、 Pixel 6 Pro の手前側の端をニーキックで撃ち抜いてしまった。もう、ブロンプトンから降りるのが怖くてたまらない。

さて、壊してしまったものは仕方がないので、また 1 万円近く飛ぶのかとガックリしながら LIFE with BICYCLE に行った。覚悟は決めていたのだが、お店のご厚意で、店頭サンプルの替えのパーツを安めに譲って頂くことができた。もう、感謝しかない。みなさんも、 Brompton を買いたくなったら LIFE with BICYCLE さんに行っておけば、間違いない(宣伝)。

交換作業が終わるまで、恵比寿のatreに繰り出した。 Brompton は自転車にしては簡単に、かつ、とても小さく畳めるのだが、やはり自転車を肩に担いでいれば、行動はかなり制限される。 Brompton を預かってもらえるだけで、肩の荷が降りた気分になる。ちょうど、我が子を保育所に預けたときの感覚に似ているのだと、今さらながら気がついた。

atre では 妻家房 に行くことが多いのだが、今日はたまたま通りがかった Mumbaiビリヤニが良さげだったので、入ってみた。チキンビリヤニを頼むつもりだったのだが、店内でカキのビリヤニが期間限定であることを知ったので、こちらにした。

期待通りのカキの味で、美味かった。カキに関しては、特に期待以上でもなく、カキはこういう味だよなっていうカキ。ビリヤニに関しては、今まであまり食べたことのないスパイスが効いていて、エキゾチックな風味を楽しむことができた。また食べに行きたい。

BROMPTON OPEN RIDE

Brompton Japan さんが BROMPTON OPEN RIDE を実施すると聞いたので応募したところ、当選したので行ってきた。

走行距離 20km 前後

想定時間 Av10 km/h

とのことなので、準備運動代わりに自走で行くことにした。 8:30 集合で、朝食を摂ることを考えると出発は 6 時で、起床は 5:30 。日の出が 7 時前頃だったので、まだ暗い国道 2 号~ 1 号をひたすら北上する。土曜日だから道路はガラガラだろうと高を括っていたら、思いの外、車が多い。しかも、渋滞するほどの量ではないため、どの車もかなりのスピードを出していて、命からがら都心部に辿り着いた。幸い、明るくなってからは車の量も減り、命の危険を感じることはなくなった。あの車はみんなどこに向かっていたのだろうか?

ジョナサン で朝食を摂った後、無事に桜田門に着いた。この年になるまで、桜田門なんて歴史上の事件が起きただけの場所で、見たこともあるか覚えてないくらいだったのだが、なぜか自転車に乗るようになってからはよく来るようになった。

今日は日本の近代建築史を巡るサイクリングとのことで、色々な建築物を見て廻った。東大の中も案内してもらった。赤門には修学旅行で一度、最近も自転車で一度来ていたが、中に入れるとは知らなかった。記念に赤門の裏で写真を一枚。

東大の図書館は、本の形をしているそうな。

あの有名な安田講堂。若者たちは何を求めて戦ったのだろうか。平和ボケした上に社会からも見捨てられた我々の世代には、計り知ることはできない。

東大を出て、自転車と縁がありそうな名前のお寺の前でパシャリ。

お昼はとてもおしゃれな iki Roastery & Eatery さんで。激混みで外にも大行列ができている人気店で、こんな機会でもない限り入ることはできなかっただろう。

ミートボールとトーストセット?的なものを注文。大変美味しゅうございました。

無事に解散し、時間も早いので帰りも自走で帰ることにする。まあ、出発する前からそのつもりではあった。 Google Map に聞くと銀座を抜けるように言われるのだが、この時間は歩行者天国になっていて通れないのだ。いつも迂回路を探し求めて迷子になるので、今日は割り切って歩くことにする。空いているところであれば歩いても楽しめるのだが、場所によってはひどい人混みになっていて、自転車を押して歩くのは結構なストレスだ。

その先もかなり車が多く、途中から渋滞し出してしまったので、 モスバーガー で休憩。武蔵小杉のビル群を通るのはいつも夜なので、昼間に通ると新鮮だ。ビルの間から大きな雲が顔を覗かせる。今日のサイクリングも、もうすぐ終わりだ。

メルカトル図法

地図表示について、緯度と経度をx座標とy座標と見なして画像を描画するだけでしょ、くらいに甘く考えていたのだが、そんな単純な話ではなかった。そもそも地球は丸くて地図は平面なんだから、緯度経度を平面上の (x, y) 座標と見なせるわけがないのだ。

球面と平面が違うことくらい知っているつもりでいたのが、 wikipedia の以下の説明を読んで自分がいかに甘く考えていたか、思い知らされた。

ja.wikipedia.org

そもそも球面上では「3つの角がすべて90度である正三角形」もありえて、これを「正しく」地図上に描くことは不可能である。

言われてみれば確かにそうだ。やはり平面上の常識で考えるのは良くない。

そもそも、地図とはなんなのだろう。我々がよく使う google map は、メルカトル図法だそうだ。中学生で習ったはずが、恥ずかしながら全く覚えていない。残念ながら、「球面を四角にした地図」という程度のまったく正確ではない知識しか持ち合わせていなかった。

メルカトル図法は球を円筒に投影しているだけではなく、角度を正確に保つために緯度方向の縮尺を変えているそうだ。ああ、それで先日使った staticmap クレートの y 軸方向の計算式は妙だったのか。

github.com

/// Latitude to y coordinate.
pub fn lat_to_y(mut lat: f64, zoom: u8) -> f64 {
    if !(-90_f64..90_f64).contains(&lat) {
        lat = (lat + 90_f64) % 180_f64 - 90_f64;
    }

    (1_f64 - ((lat * PI / 180_f64).tan() + 1_f64 / (lat * PI / 180_f64).cos()).ln() / PI) / 2_f64
        * 2_f64.powi(zoom.into())
}

直感的にわかる lon_to_x と明らかに雰囲気が違う。

/// Longitude to x coordinate.
pub fn lon_to_x(mut lon: f64, zoom: u8) -> f64 {
    if !(-180_f64..180_f64).contains(&lon) {
        lon = (lon + 180_f64) % 360_f64 - 180_f64;
    }

    ((lon + 180_f64) / 360_f64) * 2_f64.powi(zoom.into())
}

では、この lat_to_y の計算式はどのように導出されるのだろう? 広島大学が、いい感じの文書を出している。大学初年度の解析学を実際に使う教材としてメルカトル図法を題材にしているようだ。

https://home.hiroshima-u.ac.jp/teragai/mercator.pdf

この pdf はこれからじっくり読むとして、地図のことを調べるに連れ、 geohash などの定義はどうなっているのかも気になってきた。緯度経度で定義しているとすると、それをメルカトル図法の地図上に表示するときれいな長方形になるのだろうか?

色々と調べてみる必要がある。