VitaでZAVASやりたくて(13)

前回のあらすじと番組内容変更のお知らせ

Vita版Retroarchの魔改造とオーバーレイの設定を書き換えることで念願のソフトウェアキーボードを手に入れたのはよかったのだが標準のソフトウェアキーボードが使いにくいのがよくなかったのでこうなったらオーバーレイを自作することにしちゃうぞと宣言したのであった。

のだけど、オーバーレイに問題が発生したため、番組内容を変更してオーバーレイの不具合修正をお送りいたします。

最後の問題発生(最後にしたい)

とりあえず1年前に作っていたオーバーレイを少し奇麗に直してこんな感じのを用意したんですよ。

ZAVASでメインに使うキーだけ抜粋した専用オーバーレイ。
「ESC」とか「P」とかは普通にボタン割り当てした方が使いやすいのでここにはないよ。
ぶっちゃけこれで無茶苦茶遊びやすくなってるんですけど、ただこれは押したときに見た目の変化がないのでちょっと寂しいんですよね。
出来れば押したとこがピカピカ光って分かるようにしたい。

全体を1枚の画像にするんじゃなくてパーツごとに画像を分けて並べてあげればそういう効果が出せる、というのは一応知識としては知っていたので、よーし折角だから今回はやってみるかとやってみたらね、それがね。

とりあえず左側のパーツだけ用意して試してみたところ

ぎょぎょぎょ
ちょっとしたホラーですわ。

全部真ん中にパーツが集まっちゃってて鬼滅で言うところの全集中の呼吸。
ん-?設定間違ったかなー?と思って標準のゲームパッドで試してみてもやはり同じ結果になってしまいます。
ただ面白いことに、見た目が変なのを見なかったことにして本来ボタンがあるべき正しい位置を押せばキー自体は押せる様子。
何もない透明な背景を押すと「y」とか「n」とか画面に出てくるシュールな状態です。

ほほうこれは…挑戦状と受け取ってよろしいか?

できらあ!

全集中の原因を探る

できらあ!とは言ったものの正直なところ結果に確信が持てないのであくまでも参考までとお断りして書いていきます。

さて、どうやって調べたものかとちょいと思案してみるに、押せるのは押せてるし、たぶん画面表示の問題なんだろうなとは予想が付きます。
設定ファイルの詳細は省きますが、パーツごとの画像の設定は「overlay0_desc0_overlay = filename」みたいにして行うので、とりえあずその設定を読み取っているあたりを確認してみると、前回も見ていた「tasks/task_overlay.c」の中の「task_overlay_load_desc_image」で読み取っているのが分かります。

static void task_overlay_load_desc_image(
      overlay_loader_t *loader,
      struct overlay_desc *desc,
      struct overlay *input_overlay,
      unsigned ol_idx, unsigned desc_idx)
{
      ...

      if (image_texture_load(&image_tex, path))
      {
         input_overlay->load_images[input_overlay->load_images_size++] = image_tex;
         desc->image       = image_tex;
         desc->image_index = input_overlay->load_images_size - 1;
      }

      ...
}

ポイントだけ説明すると、ここでは画像のデータ列を読み込んでとりあえず記憶しておく(表示とかは別途)というところまでやっています。
ということは記憶先の「load_images」か「image」をどこかで参照して表示しているはず。
「image」だとヒットしすぎて厳しいので「load_images」でgrepしてみるとラッキーなことにこれだけ。

検索文字列 "load_images" (10 件が 3 個のファイル内で一致しました。検索ファイル数は 9044 個)
  C:\xxx\RetroArch-master\input\input_driver.c (4 件の一致)
	行番号 1687:       ol->iface->load(ol->iface_data, ol->active->load_images,
	行番号 1775:    if (overlay->load_images)
	行番号 1776:       free(overlay->load_images);
	行番号 1777:    overlay->load_images = NULL;
  C:\xxx\RetroArch-master\input\input_overlay.h (1 件の一致)
	行番号 162:    struct texture_image *load_images;
  C:\xxx\RetroArch-master\tasks\task_overlay.c (5 件の一致)
	行番号 100:          input_overlay->load_images[input_overlay->load_images_size++] = image_tex;
	行番号 562:          RARCH_ERR("[Overlay]: Failed to allocate load_images.\n");
	行番号 566:       overlay->load_images = texture_img;
	行番号 597:          overlay->load_images[overlay->load_images_size++] = image_tex;
	行番号 699:          struct texture_image *ti = &overlay->load_images[i];

「task/task_overlay.c」は読み込み側なので特に参照してるところはありません。
残るは「input/input_driver.c」で、「input_overlay_load_active」「input_overlay_free_overlay」がヒット。

void input_overlay_load_active(
      enum overlay_visibility *visibility,
      input_overlay_t *ol, float opacity)
{
   if (ol->iface->load)
      ol->iface->load(ol->iface_data, ol->active->load_images,
            ol->active->load_images_size);

   input_overlay_set_alpha_mod(visibility, ol, opacity);
   input_overlay_set_vertex_geom(ol);

   if (ol->iface->full_screen)
      ol->iface->full_screen(ol->iface_data, ol->active->full_screen);
}

void input_overlay_free_overlay(struct overlay *overlay)
{
   size_t i;

   if (!overlay)
      return;

   for (i = 0; i < overlay->size; i++)
      image_texture_free(&overlay->descs[i].image);

   if (overlay->load_images)
      free(overlay->load_images);
   overlay->load_images = NULL;
   if (overlay->descs)
      free(overlay->descs);
   overlay->descs       = NULL;
   image_texture_free(&overlay->image);
}

free関連はあまり関係なさそうなので「input_overlay_load_active」が問題になってそうな感じです。
内容を見ると、なんだかよくわからん「ol->iface->load」とか呼んでる。
その下にある「input_overlay_setなんとか」も確認してみるとこちらでも「ol->iface->なんとか」を呼んでます。
どうも「ol->iface」の先が鍵になっている予感。

void input_overlay_set_alpha_mod(
      enum overlay_visibility *visibility,
      input_overlay_t *ol, float mod)
{
   unsigned i;

   if (!ol)
      return;

   for (i = 0; i < ol->active->load_images_size; i++)
   {
      if (input_overlay_get_visibility(visibility, i)
            == OVERLAY_VISIBILITY_HIDDEN)
          ol->iface->set_alpha(ol->iface_data, i, 0.0);
      else
          ol->iface->set_alpha(ol->iface_data, i, mod);
   }
}

void input_overlay_set_vertex_geom(input_overlay_t *ol)
{
   size_t i;

   if (ol->active->image.pixels)
      ol->iface->vertex_geom(ol->iface_data, 0,
            ol->active->mod_x, ol->active->mod_y,
            ol->active->mod_w, ol->active->mod_h);

   if (ol->iface->vertex_geom)
      for (i = 0; i < ol->active->size; i++)
      {
         struct overlay_desc *desc = &ol->active->descs[i];

         if (!desc->image.pixels)
            continue;

         ol->iface->vertex_geom(ol->iface_data, desc->image_index,
               desc->mod_x, desc->mod_y, desc->mod_w, desc->mod_h);
      }
}

で、ここから先の本丸までは超絶長いので一気に駆け足で。
「iface」ってなんだろ?というのをずらずら辿っていくと、以前にfull_なんとかゼロ問題で出てきた「video_driver_t video_vita2d」までたどり着きます。
その中の「vita2d_get_overlay_interface」を呼び出すことで「iface」を取得して、その実体は「static const video_overlay_interface_t vita2d_overlay_interface」になります。
(↓はひたすら辿っていく経過。見ても意味が分からないかもしれない)

// ------------------------------------------------------------
// input_overlay_t *ol
// ------------------------------------------------------------
struct input_overlay
{
   struct overlay *overlays;
   const struct overlay *active;
   void *iface_data;
   const video_overlay_interface_t *iface;
   input_overlay_state_t overlay_state;

   size_t index;
   size_t size;

   unsigned next_index;

   enum overlay_status state;

   bool enable;
   bool blocked;
   bool alive;
};

// ------------------------------------------------------------
// const video_overlay_interface_t *iface
// ------------------------------------------------------------
typedef struct video_overlay_interface
{
   void (*enable)(void *data, bool state);
   bool (*load)(void *data,
         const void *images, unsigned num_images);
   void (*tex_geom)(void *data, unsigned image,
         float x, float y, float w, float h);
   void (*vertex_geom)(void *data, unsigned image,
         float x, float y, float w, float h);
   void (*full_screen)(void *data, bool enable);
   void (*set_alpha)(void *data, unsigned image, float mod);
} video_overlay_interface_t;

// ------------------------------------------------------------
// iface設定箇所
// ------------------------------------------------------------
static void input_overlay_loaded(retro_task_t *task,
      void *task_data, void *user_data, const char *err)
{
   ...
   if (  !data->overlay_enable                   ||
         !video_driver_overlay_interface(&iface) ||
         !iface)
   ...

// ------------------------------------------------------------
// video_driver_overlay_interface
// ------------------------------------------------------------
static bool video_driver_overlay_interface(
      const video_overlay_interface_t **iface)
{
   video_driver_state_t *video_st = video_state_get_ptr();
   if (!video_st->current_video || !video_st->current_video->overlay_interface)
      return false;
   video_st->current_video->overlay_interface(video_st->data, iface);
   return true;
}

// ------------------------------------------------------------
// video_driver_state_t *video_st
// video_st->current_video
// ------------------------------------------------------------
typedef struct
{
#ifdef HAVE_CRTSWITCHRES
   videocrt_switch_t crt_switch_st;     /* double alignment */
#endif
   struct retro_system_av_info av_info; /* double alignment */
   retro_time_t frame_time_samples[MEASURE_FRAME_TIME_SAMPLES_COUNT];
   retro_time_t core_frame_time;
   uint64_t frame_time_count;
   uint64_t frame_count;
   uint8_t *record_gpu_buffer;
#ifdef HAVE_VIDEO_FILTER
   rarch_softfilter_t *state_filter;
   void               *state_buffer;
#endif
   void *data;
   video_driver_t *current_video;
   /* Interface for "poking". */
   const video_poke_interface_t *poke;
   gfx_ctx_driver_t current_video_context;               /* ptr alignment */
   struct retro_hw_render_callback hw_render;            /* ptr alignment */
   struct rarch_dir_shader_list dir_shader_list;         /* ptr alignment */
#ifdef HAVE_THREADS
   slock_t *display_lock;
   slock_t *context_lock;
#endif

   /* Used for 15-bit -> 16-bit conversions that take place before
    * being passed to video driver. */
   video_pixel_scaler_t *scaler_ptr;
   video_driver_frame_t frame_bak;  /* ptr alignment */

   void *current_display_server_data;

   const void *frame_cache_data;

   const struct
      retro_hw_render_context_negotiation_interface *
      hw_render_context_negotiation;

#ifdef HAVE_MENU
   struct video_shader *menu_driver_shader;
#endif

   void *context_data;

   /* Opaque handles to currently running window.
    * Used by e.g. input drivers which bind to a window.
    * Drivers are responsible for setting these if an input driver
    * could potentially make use of this. */
   uintptr_t display_userdata;
   uintptr_t display;
   uintptr_t window;

   size_t frame_cache_pitch;

#ifdef HAVE_VIDEO_FILTER
   unsigned state_scale;
   unsigned state_out_bpp;
#endif
   unsigned frame_delay_target;
   unsigned frame_delay_effective;
   unsigned frame_cache_width;
   unsigned frame_cache_height;
   unsigned width;
   unsigned height;

   float core_hz;
   float aspect_ratio;
   float video_refresh_rate_original;

   enum retro_pixel_format pix_fmt;
   enum rarch_display_type display_type;
   enum rotation initial_screen_orientation;
   enum rotation current_screen_orientation;

   /**
    * dynamic.c:dynamic_request_hw_context will try to set flag data when the context
    * is in the middle of being rebuilt; in these cases we will save flag
    * data and set this to true.
    * When the context is reinit, it checks this, reads from
    * deferred_flag_data and cleans it.
    *
    * TODO - Dirty hack, fix it better
    */
   gfx_ctx_flags_t deferred_flag_data;          /* uint32_t alignment */

   char cli_shader_path[PATH_MAX_LENGTH];
   char window_title[512];
   char gpu_device_string[128];
   char gpu_api_version_string[128];
   char title_buf[64];
   char cached_driver_id[32];

   /**
    * dynamic.c:dynamic_request_hw_context will try to set
    * flag data when the context
    * is in the middle of being rebuilt; in these cases we will save flag
    * data and set this to true.
    * When the context is reinit, it checks this, reads from
    * deferred_flag_data and cleans it.
    *
    * TODO - Dirty hack, fix it better
    */
   bool deferred_video_context_driver_set_flags;
   bool window_title_update;
#ifdef HAVE_GFX_WIDGETS
   bool widgets_paused;
   bool widgets_fast_forward;
   bool widgets_rewinding;
#endif
   bool started_fullscreen;

   /* Graphics driver requires RGBA byte order data (ABGR on little-endian)
    * for 32-bit.
    * This takes effect for overlay and shader cores that wants to load
    * data into graphics driver. Kinda hackish to place it here, it is only
    * used for GLES.
    * TODO: Refactor this better. */
   bool use_rgba;

   /* Graphics driver supports HDR displays
    * Currently only D3D11/D3D12 supports HDR displays and 
    * whether we've enabled it */
   bool hdr_support;

   /* If set during context deinit, the driver should keep
    * graphics context alive to avoid having to reset all
    * context state. */
   bool cache_context;

   /* Set to true by driver if context caching succeeded. */
   bool cache_context_ack;

   bool active;
#ifdef HAVE_VIDEO_FILTER
   bool state_out_rgb32;
#endif
   bool crt_switching_active;
   bool force_fullscreen;
   bool threaded;
   bool is_switching_display_mode;
   bool shader_presets_need_reload;
   bool cli_shader_disable;
#ifdef HAVE_RUNAHEAD
   bool runahead_is_active;
#endif
} video_driver_state_t;

// ------------------------------------------------------------
// video_driver_t *current_video
// ------------------------------------------------------------
typedef struct video_driver
{
   /* Should the video driver act as an input driver as well?
    * The video initialization might preinitialize an input driver
    * to override the settings in case the video driver relies on
    * input driver for event handling. */
   void *(*init)(const video_info_t *video,
         input_driver_t **input,
         void **input_data);

   /* Updates frame on the screen.
    * Frame can be either XRGB1555, RGB565 or ARGB32 format
    * depending on rgb32 setting in video_info_t.
    * Pitch is the distance in bytes between two scanlines in memory.
    *
    * When msg is non-NULL,
    * it's a message that should be displayed to the user. */
   video_driver_frame_t frame;

   /* Should we care about syncing to vblank? Fast forwarding.
    *
    * Requests nonblocking operation.
    *
    * True = VSync is turned off.
    * False = VSync is turned on.
    * */
   void (*set_nonblock_state)(void *data, bool toggle,
         bool adaptive_vsync_enabled,
         unsigned swap_interval);

   /* Is the window still active? */
   bool (*alive)(void *data);

   /* Does the window have focus? */
   bool (*focus)(void *data);

   /* Should the screensaver be suppressed? */
   bool (*suppress_screensaver)(void *data, bool enable);

   /* Does the graphics context support windowed mode? */
   bool (*has_windowed)(void *data);

   /* Sets shader. Might not be implemented. Will be moved to
    * poke_interface later. */
   bool (*set_shader)(void *data, enum rarch_shader_type type,
         const char *path);

   /* Frees driver. */
   void (*free)(void *data);

   /* Human-readable identifier. */
   const char *ident;

   void (*set_viewport)(void *data, unsigned width, unsigned height,
         bool force_full, bool allow_rotate);

   void (*set_rotation)(void *data, unsigned rotation);
   void (*viewport_info)(void *data, struct video_viewport *vp);

   /* Reads out in BGR byte order (24bpp). */
   bool (*read_viewport)(void *data, uint8_t *buffer, bool is_idle);

   /* Returns a pointer to a newly allocated buffer that can
    * (and must) be passed to free() by the caller, containing a
    * copy of the current raw frame in the active pixel format
    * and sets width, height and pitch to the correct values. */
   void* (*read_frame_raw)(void *data, unsigned *width,
   unsigned *height, size_t *pitch);

#ifdef HAVE_OVERLAY
   void (*overlay_interface)(void *data,
         const video_overlay_interface_t **iface);
#endif
#ifdef HAVE_VIDEO_LAYOUT
   const video_layout_render_interface_t *(*video_layout_render_interface)(void *data);
#endif
   void (*poke_interface)(void *data, const video_poke_interface_t **iface);
   unsigned (*wrap_type_to_enum)(enum gfx_wrap_type type);

#if defined(HAVE_GFX_WIDGETS)
   /* if set to true, will use display widgets when applicable
    * if set to false, will use OSD as a fallback */
   bool (*gfx_widgets_enabled)(void *data);
#endif
} video_driver_t;

// ------------------------------------------------------------
// Vita用video_driver_t
// ------------------------------------------------------------
video_driver_t video_vita2d = {
   vita2d_gfx_init,
   vita2d_gfx_frame,
   vita2d_gfx_set_nonblock_state,
   vita2d_gfx_alive,
   vita2d_gfx_focus,
   vita2d_gfx_suppress_screensaver,
   NULL, /* has_windowed */
   vita2d_gfx_set_shader,
   vita2d_gfx_free,
   "vita2d",
   vita2d_gfx_set_viewport,
   vita2d_gfx_set_rotation,
   vita2d_gfx_viewport_info,
   NULL, /* read_viewport */
   NULL, /* read_frame_raw */
#ifdef HAVE_OVERLAY
   vita2d_get_overlay_interface,
#endif
#ifdef HAVE_VIDEO_LAYOUT
  NULL,
#endif
   vita2d_gfx_get_poke_interface,
   NULL,
#ifdef HAVE_GFX_WIDGETS
   vita2d_gfx_gfx_widgets_enabled
#endif
};

// ------------------------------------------------------------
// vita2d_get_overlay_interface
// ------------------------------------------------------------
static void vita2d_get_overlay_interface(void *data, const video_overlay_interface_t **iface)
{
   *iface = &vita2d_overlay_interface;
}

// ------------------------------------------------------------
// vita2d_overlay_interface
// ------------------------------------------------------------
static const video_overlay_interface_t vita2d_overlay_interface = {
   vita2d_overlay_enable,
   vita2d_overlay_load,
   vita2d_overlay_tex_geom,
   vita2d_overlay_vertex_geom,
   vita2d_overlay_full_screen,
   vita2d_overlay_set_alpha,
};

あらためて「vita2d_overlay_interface」を書き出すとこれです。

static const video_overlay_interface_t vita2d_overlay_interface = {
   vita2d_overlay_enable,
   vita2d_overlay_load,
   vita2d_overlay_tex_geom,
   vita2d_overlay_vertex_geom,
   vita2d_overlay_full_screen,
   vita2d_overlay_set_alpha,
};

きっとこの中の何かが何かまずいはず。

ここからはぶっちゃけ勘で進みます。
一通りぼんやりと眺めていると「vita2d_overlay_vertex_geom」に気になる記述が見つかります。

static void vita2d_overlay_vertex_geom(void *data, unsigned image,
         float x, float y, float w, float h)
{
   ...
   if (o)
   {
      vita2d_video_mode_data video_mode_data = vita2d_get_video_mode_data();
      o->w = w*video_mode_data.width/o->width;
      o->h = h*video_mode_data.height/o->height;
      o->x = video_mode_data.width*(1-w)/2+x;
      o->y = video_mode_data.height*(1-h)/2+y;
   }
   ...

座標の再設定みたいなことをやっているように見えますが、
「o->x = video_mode_data.width*(1-w)/2+x」
これ、画面の真ん中を意識してなんかしてるな。
それが何かの座標として扱われているらしい。
真ん中…座標…。
何を計算しているかはさっぱりわからんがすごく気になる。
そこで実際にパラメータを確認してみると、

  • 入力はどうやら0~1の数値(何を表しているかはよく分からないけど設定ファイルから計算した割合換算の位置情報のような気がする)

  • 「o->x」「o->y」はいつも画面のほぼ真ん中の座標になっている!

「o->x」「o->y」が表示位置かどうかはまだはっきりしませんが極めて疑わしい。
何か手掛かりがないか周辺を眺めていると近くに「vita2d_render_overlay」なる関数があります。名前からして描画本体っぽい。
ここで先ほどの「x」やら「y」やらが使われています。

static void vita2d_render_overlay(void *data)
{
   unsigned i;
   vita_video_t *vita = (vita_video_t*)data;

   for (i = 0; i < vita->overlays; i++)
   {
      vita2d_draw_texture_tint_part_scale(vita->overlay[i].tex,
            vita->overlay[i].x,
            vita->overlay[i].y,
            vita->overlay[i].tex_x,
            vita->overlay[i].tex_y,
            vita->overlay[i].tex_w,
            vita->overlay[i].tex_h,
            vita->overlay[i].w,
            vita->overlay[i].h,
            RGBA8(0xFF,0xFF,0xFF,(uint8_t)(vita->overlay[i].alpha_mod * 255.0f)));
   }
}

内部で呼び出しているのは「deps/libvita2d/source/vita2d_texture.c」にある「vita2d_draw_texture_tint_part_scale」で、ここからさらにコア部分の「draw_texture_tint_part_scale_generic」が呼ばれます。

void vita2d_draw_texture_tint_part_scale(const vita2d_texture *texture, float x, float y, float tex_x, float tex_y, float tex_w, float tex_h, float x_scale, float y_scale, unsigned int color)
{
	set_texture_tint_program();
	set_texture_wvp_uniform();
	draw_texture_tint_part_scale_generic(texture, x, y, tex_x, tex_y, tex_w, tex_h, x_scale, y_scale, color);
}

static inline void draw_texture_tint_part_scale_generic(const vita2d_texture *texture, float x, float y, float tex_x, float tex_y, float tex_w, float tex_h, float x_scale, float y_scale, unsigned int color)
{
	vita2d_texture_tint_vertex *vertices = (vita2d_texture_tint_vertex *)vita2d_pool_memalign(
		4 * sizeof(vita2d_texture_tint_vertex), // 4 vertices
		sizeof(vita2d_texture_tint_vertex));

	const float w = vita2d_texture_get_width(texture);
	const float h = vita2d_texture_get_height(texture);

	const float u0 = tex_x/w;
	const float v0 = tex_y/h;
	const float u1 = (tex_x+tex_w)/w;
	const float v1 = (tex_y+tex_h)/h;

	tex_w *= x_scale;
	tex_h *= y_scale;

	vertices[0].x = x;
	vertices[0].y = y;
	vertices[0].z = +0.5f;
	vertices[0].u = u0;
	vertices[0].v = v0;

	vertices[1].x = x + tex_w;
	vertices[1].y = y;
	vertices[1].z = +0.5f;
	vertices[1].u = u1;
	vertices[1].v = v0;

	vertices[2].x = x;
	vertices[2].y = y + tex_h;
	vertices[2].z = +0.5f;
	vertices[2].u = u0;
	vertices[2].v = v1;

	vertices[3].x = x + tex_w;
	vertices[3].y = y + tex_h;
	vertices[3].z = +0.5f;
	vertices[3].u = u1;
	vertices[3].v = v1;

   for(int n = 0; n < 4; n++){
      vertices[n].r = ((color >> 8*0) & 0xFF)/255.0f;
      vertices[n].g = ((color >> 8*1) & 0xFF)/255.0f;
      vertices[n].b = ((color >> 8*2) & 0xFF)/255.0f;
      vertices[n].a = ((color >> 8*3) & 0xFF)/255.0f;
   }

	// Set the texture to the TEXUNIT0
	sceGxmSetFragmentTexture(_vita2d_context, 0, &texture->gxm_tex);

	sceGxmSetVertexStream(_vita2d_context, 0, vertices);
	sceGxmDraw(_vita2d_context, SCE_GXM_PRIMITIVE_TRIANGLE_STRIP, SCE_GXM_INDEX_FORMAT_U16, vita2d_get_linear_indices(), 4);
}

ここで使われている「sceなんとか」の仕様が分からないので(ドキュメントに詳しく載ってない)勘でしかないですが、「vertices」に設定している値から推測すると描画する矩形領域をセットしているように見えます。
そしてもうひとつ重要なポイントとして、「tex_x/w」や「(tex_x+tex_w)/w」のように画像幅と思われる値で割っていることから「tex_x」「tex_w」はおそらくピクセル単位になっていると考えられ、さらに「vertices[1].x = x + tex_w」のように「x」も「tex_w」と同列に扱っているので「x」もおそらくピクセル単位が正しい。

ここでむむむとなったあなたは勘が鋭い。
先ほど出てきた「o->x = video_mode_data.width*(1-w)/2+x」。
ピクセル単位が正しいはずなのだが何かがおかしい。
前半が何やってるかは依然として不明ですけど、後半の「+x」、ここに渡される「x」は0~1なので少なくとも最後の「+x」はスケールが一致しない。

そもそも、描画する矩形領域を設定している様子だというのを踏まえると、別に難しいことをやる必要なんてなくて、「o->x」は普通に画像のx座標でいいような…。
そこで一旦こう書き換えてみると…、

   if (o)
   {
      vita2d_video_mode_data video_mode_data = vita2d_get_video_mode_data();
      o->w = w*video_mode_data.width/o->width;
      o->h = h*video_mode_data.height/o->height;
      //o->x = video_mode_data.width*(1-w)/2+x;
      //o->y = video_mode_data.height*(1-h)/2+y;
      o->x = video_mode_data.width*x;
      o->y = video_mode_data.height*y;
   }

おおおお
なんか直った。
じゃなかった。できらあ!(使い方間違ってる気がしてきたな)
表示もばっちりだし押したらピカピカ光ります。
いまいちよく分からないまま修正したので気持ち悪いけど結果オーライ。

ちなみに「video_mode_data.width*(1-w)/2」が何をしたいのかはさっぱりわかりませんけど、動作としては画像を中心から半キャラずらす動作になります。
縦横半分ずつずらしてるので、つまり結果的に画面の中心にボタンが表示されるようになってる。(これに加算してる「x」「y」はせいぜい1なので実質無視でOK)
ずらすことでなんか役に立つことあんのかな。全然わからんな。
何かしようとしたんだろうけど途中であきらめたとかで完全に間違ったコードを書いてしまっているだけのような気がする。
念のために「video_mode_data.width*(1-w)/2」を加える部分も残して試してみると以下のようになって、ここまでで一番わけがわからない最悪の結果になってしまった。

なんか下の方にうっすらボタンが見えてます

というわけでたぶんあの修正であっている気がする。

最後の修正(最後にするぞ)

ちょいと綺麗にした修正内容を載せておきます。
「gfx/drivers/vita2d_gfx.c」を以下のように修正。例によってMOD BEGINからMOD ENDまでです。

static void vita2d_overlay_vertex_geom(void *data, unsigned image,
         float x, float y, float w, float h)
{
   vita_video_t          *vita = (vita_video_t*)data;
   struct vita_overlay_data *o = NULL;

   /* Flipped, so we preserve top-down semantics. */
   /*y = 1.0f - y;
   h = -h;*/

   if (vita)
      o = (struct vita_overlay_data*)&vita->overlay[image];

   if (o)
   {
      vita2d_video_mode_data video_mode_data = vita2d_get_video_mode_data();
      o->w = w*video_mode_data.width/o->width;
      o->h = h*video_mode_data.height/o->height;
/* ---------- MOD BEGIN ---------- */
      // ------------------------------------------------------------
      // ボタンごとのオーバレイ(Using per-button overlays)への対応
      // ボタンごとのオーバーレイを使用すると全て画面の中央に表示される問題を修正する
      // ------------------------------------------------------------
      // オリジナル版のここの計算はなぜそういう処理をやっているのか全く意味が分からないのだが(コメントアウトして残しておいた)、
      // とにかく間違っていて、全てのボタンが画面の中央に表示されるようになっている。
      // ここに渡されるx,yは画面全体における「割合値」のようで(0~1の値がくる)、実質無視されて前半部分のぼぼ画面の中央になる値(中央から半キャラずらしした値)しか残らない。
      // 半キャラずらしのおかげで奇麗に画面中央に表示されるという無駄に高度な処理になっているが、それによるメリットは全く見当たらない。なにをしたかったんだろうか…
      //o->x = video_mode_data.width*(1-w)/2+x;
      //o->y = video_mode_data.height*(1-h)/2+y;
      //
      // vita2d_render_overlay内のvita2d_draw_texture_tint_part_scaleの実装を確認してみると、
      // o->x, o->yともにオーバレイ画像の開始座標(ピクセル値)を指定するのが正しいと予想され、実際に試してみるとそれでうまくいく。
      // x,yはそれぞれ画面幅、画面高さを乗算して「割合値」からピクセル単位へスケールを戻しておく必要がある点に注意すること。
      o->x = video_mode_data.width*x;
      o->y = video_mode_data.height*y;
/* ---------- MOD END ---------- */
   }
}

実はまだ直ってないとこもある…

これで直ったかなーと甘く見てたらオーバーレイで「neo-retropad」とかのゲームパッドを使おうとするといろいろ誤動作しますね…。
画面に合わせてボタンの位置が可変なやつで変になるとかだろうか。
でもとりあえずの目的は達したし、もうめんどくさいのでやんないぞ。
はいはいやめやめ。

次回予告

最後若干なげやりになってしまいましたが、これでいよいよピカピカ光るオーバーレイが実現できるところまできたのであった!
次回こそ!本当に!ぼくのかんがえたさいきょうのオーバーレイ!番組内容が変更にならないようお祈りください。

この記事が気に入ったらサポートをしてみませんか?