diff --git a/vita3k/config/include/config/config.h b/vita3k/config/include/config/config.h
index f51b86457..4c926d77b 100644
--- a/vita3k/config/include/config/config.h
+++ b/vita3k/config/include/config/config.h
@@ -67,7 +67,7 @@ enum PerfomanceOverleyPosition {
     code(std::string, "backend-renderer", "OpenGL", backend_renderer)                                   \
     code(int, "gpu-idx", 0, gpu_idx)                                                                    \
     code(bool, "high-accuracy", true, high_accuracy)                                                    \
-    code(int, "resolution-multiplier", 1, resolution_multiplier)                                        \
+    code(float, "resolution-multiplier", 1.0f, resolution_multiplier)                                   \
     code(bool, "disable-surface-sync", true, disable_surface_sync)                                      \
     code(std::string, "screen-filter", "Bilinear", screen_filter)                                       \
     code(bool, "v-sync", true, v_sync)                                                                  \
diff --git a/vita3k/config/include/config/state.h b/vita3k/config/include/config/state.h
index e8a3241b5..5d6a5254c 100644
--- a/vita3k/config/include/config/state.h
+++ b/vita3k/config/include/config/state.h
@@ -130,7 +130,7 @@ struct Config : YamlLoader {
         bool ngs_enable = true;
         bool pstv_mode = false;
         bool high_accuracy = false;
-        int resolution_multiplier = 1;
+        float resolution_multiplier = 1.0f;
         bool disable_surface_sync = false;
         std::string screen_filter = "Bilinear";
         bool v_sync = true;
diff --git a/vita3k/gui/src/settings_dialog.cpp b/vita3k/gui/src/settings_dialog.cpp
index 881a15d91..d4de7a29b 100644
--- a/vita3k/gui/src/settings_dialog.cpp
+++ b/vita3k/gui/src/settings_dialog.cpp
@@ -173,7 +173,7 @@ static bool get_custom_config(GuiState &gui, EmuEnvState &emuenv, const std::str
             if (!config_child.child("gpu").empty()) {
                 const auto gpu_child = config_child.child("gpu");
                 config.high_accuracy = gpu_child.attribute("high-accuracy").as_bool();
-                config.resolution_multiplier = gpu_child.attribute("resolution-multiplier").as_int();
+                config.resolution_multiplier = gpu_child.attribute("resolution-multiplier").as_float();
                 config.disable_surface_sync = gpu_child.attribute("disable-surface-sync").as_bool();
                 config.screen_filter = gpu_child.attribute("screen-filter").as_string();
                 config.v_sync = gpu_child.attribute("v-sync").as_bool();
@@ -629,7 +629,13 @@ void draw_settings_dialog(GuiState &gui, EmuEnvState &emuenv) {
                 ImGui::SetTooltip("%s", lang.gpu["v_sync_description"].c_str());
             ImGui::SameLine();
         }
-        if (!is_vulkan || emuenv.renderer->features.support_memory_mapping) {
+
+        const bool has_integer_multiplier = static_cast<int>(config.resolution_multiplier * 4.0f) % 4 == 0;
+        // OpenGL does not support surface sync with a non-integer resolution multiplier
+        if (!is_vulkan && has_integer_multiplier)
+            config.disable_surface_sync = true;
+
+        if ((!is_vulkan && has_integer_multiplier) || emuenv.renderer->features.support_memory_mapping) {
             // surface sync is supported on vulkan only when memory mapping is enabled
             ImGui::Checkbox(lang.gpu["disable_surface_sync"].c_str(), &config.disable_surface_sync);
             if (ImGui::IsItemHovered())
@@ -679,38 +685,40 @@ void draw_settings_dialog(GuiState &gui, EmuEnvState &emuenv) {
         ImGui::TextColored(GUI_COLOR_TEXT_TITLE, "%s", lang.gpu["internal_resolution_upscaling"].c_str());
         ImGui::Spacing();
         ImGui::PushID("Res scal");
-        if (config.resolution_multiplier == 1)
+        if (config.resolution_multiplier == 0.5f)
             ImGui::BeginDisabled();
         if (ImGui::Button("<", ImVec2(20.f * SCALE.x, 0)))
-            --config.resolution_multiplier;
-        if (config.resolution_multiplier == 1)
+            config.resolution_multiplier -= 0.25f;
+        if (config.resolution_multiplier == 0.5f)
             ImGui::EndDisabled();
         ImGui::SameLine(0, 5.f * SCALE.x);
         ImGui::PushItemWidth(-100.f * SCALE.x);
-        if (ImGui::SliderInt("##res_scal", &config.resolution_multiplier, 1, 8, fmt::format("{}x", config.resolution_multiplier).c_str(), ImGuiSliderFlags_None)) {
-            if (config.resolution_multiplier > 1 && !is_vulkan)
+        int slider_position = static_cast<int>(config.resolution_multiplier * 4);
+        if (ImGui::SliderInt("##res_scal", &slider_position, 2, 32, fmt::format("{}x", config.resolution_multiplier).c_str(), ImGuiSliderFlags_None)) {
+            config.resolution_multiplier = static_cast<float>(slider_position) / 4.0f;
+            if (config.resolution_multiplier != 1.0f && !is_vulkan)
                 config.disable_surface_sync = true;
         }
         ImGui::PopItemWidth();
         if (ImGui::IsItemHovered())
             ImGui::SetTooltip("%s", lang.gpu["internal_resolution_upscaling_description"].c_str());
         ImGui::SameLine(0, 5 * SCALE.x);
-        if (config.resolution_multiplier == 8)
+        if (config.resolution_multiplier == 8.0f)
             ImGui::BeginDisabled();
         if (ImGui::Button(">", ImVec2(20.f * SCALE.x, 0)))
-            ++config.resolution_multiplier;
-        if (config.resolution_multiplier == 8)
+            config.resolution_multiplier += 0.25f;
+        if (config.resolution_multiplier == 8.0f)
             ImGui::EndDisabled();
         ImGui::SameLine();
-        if ((config.resolution_multiplier == 1) && !config.disable_surface_sync)
+        if ((config.resolution_multiplier == 1.0f) && !config.disable_surface_sync)
             ImGui::BeginDisabled();
         if (ImGui::Button(lang.gpu["reset"].c_str(), ImVec2(60.f * SCALE.x, 0)))
-            config.resolution_multiplier = 1;
+            config.resolution_multiplier = 1.0f;
 
-        if ((config.resolution_multiplier == 1) && !config.disable_surface_sync)
+        if ((config.resolution_multiplier == 1.0f) && !config.disable_surface_sync)
             ImGui::EndDisabled();
         ImGui::Spacing();
-        const auto res_scal = fmt::format("{}x{}", 960 * config.resolution_multiplier, 544 * config.resolution_multiplier);
+        const auto res_scal = fmt::format("{}x{}", static_cast<int>(960 * config.resolution_multiplier), static_cast<int>(544 * config.resolution_multiplier));
         ImGui::SetCursorPosX((ImGui::GetWindowWidth() / 2.f) - (ImGui::CalcTextSize(res_scal.c_str()).x / 2.f) - (35.f * SCALE.x));
         ImGui::Text("%s", res_scal.c_str());
         ImGui::PopID();
diff --git a/vita3k/renderer/include/renderer/gl/surface_cache.h b/vita3k/renderer/include/renderer/gl/surface_cache.h
index f892a7a87..fa5c6d646 100644
--- a/vita3k/renderer/include/renderer/gl/surface_cache.h
+++ b/vita3k/renderer/include/renderer/gl/surface_cache.h
@@ -127,8 +127,8 @@ class GLSurfaceCache {
         target = new_target;
     }
 
-    GLuint sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const std::uint32_t pitch, float *uvs, const int res_multiplier, SceFVector2 &texture_size);
-    std::vector<uint32_t> dump_frame(Ptr<const void> address, uint32_t width, uint32_t height, uint32_t pitch, int res_multiplier, bool support_get_texture_sub_image);
+    GLuint sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const std::uint32_t pitch, float *uvs, const float res_multiplier, SceFVector2 &texture_size);
+    std::vector<uint32_t> dump_frame(Ptr<const void> address, uint32_t width, uint32_t height, uint32_t pitch, float res_multiplier, bool support_get_texture_sub_image);
 };
 } // namespace gl
 } // namespace renderer
diff --git a/vita3k/renderer/include/renderer/state.h b/vita3k/renderer/include/renderer/state.h
index cce86cb99..1cbea15bb 100644
--- a/vita3k/renderer/include/renderer/state.h
+++ b/vita3k/renderer/include/renderer/state.h
@@ -54,7 +54,7 @@ struct State {
 
     Backend current_backend;
     FeatureState features;
-    int res_multiplier;
+    float res_multiplier;
     bool disable_surface_sync;
     bool stretch_the_display_area;
 
diff --git a/vita3k/renderer/src/creation.cpp b/vita3k/renderer/src/creation.cpp
index 51a5007e1..596610a09 100644
--- a/vita3k/renderer/src/creation.cpp
+++ b/vita3k/renderer/src/creation.cpp
@@ -123,8 +123,8 @@ COMMAND(handle_create_render_target) {
         uint16_t nb_macroblocks_y = (params->flags >> 12) & 0b111;
 
         // the width and height should be multiple of 128
-        (*render_target)->macroblock_width = (params->width / nb_macroblocks_x) * renderer.res_multiplier;
-        (*render_target)->macroblock_height = (params->height / nb_macroblocks_y) * renderer.res_multiplier;
+        (*render_target)->macroblock_width = static_cast<uint16_t>((params->width / nb_macroblocks_x) * renderer.res_multiplier);
+        (*render_target)->macroblock_height = static_cast<uint16_t>((params->height / nb_macroblocks_y) * renderer.res_multiplier);
     }
 
     complete_command(renderer, helper, result);
diff --git a/vita3k/renderer/src/gl/draw.cpp b/vita3k/renderer/src/gl/draw.cpp
index 644d46603..3011d5d40 100644
--- a/vita3k/renderer/src/gl/draw.cpp
+++ b/vita3k/renderer/src/gl/draw.cpp
@@ -155,7 +155,7 @@ void draw(GLState &renderer, GLContext &context, const FeatureState &features, S
     }
     frag_ublock.writing_mask = context.record.writing_mask;
     frag_ublock.use_raw_image = static_cast<float>(use_raw_image);
-    frag_ublock.res_multiplier = static_cast<float>(renderer.res_multiplier);
+    frag_ublock.res_multiplier = renderer.res_multiplier;
     const bool has_msaa = context.render_target->multisample_mode;
     const bool has_downscale = context.record.color_surface.downscale;
     if (has_msaa && !has_downscale)
diff --git a/vita3k/renderer/src/gl/renderer.cpp b/vita3k/renderer/src/gl/renderer.cpp
index 551080f9b..b4da15dd7 100644
--- a/vita3k/renderer/src/gl/renderer.cpp
+++ b/vita3k/renderer/src/gl/renderer.cpp
@@ -300,8 +300,8 @@ bool create(GLState &state, std::unique_ptr<RenderTarget> &rt, const SceGxmRende
         return false;
     }
 
-    render_target->width = params.width * state.res_multiplier;
-    render_target->height = params.height * state.res_multiplier;
+    render_target->width = static_cast<uint16_t>(params.width * state.res_multiplier);
+    render_target->height = static_cast<uint16_t>(params.height * state.res_multiplier);
 
     render_target->attachments.init(reinterpret_cast<renderer::Generator *>(glGenTextures), reinterpret_cast<renderer::Deleter *>(glDeleteTextures));
 
@@ -317,7 +317,7 @@ bool create(GLState &state, std::unique_ptr<RenderTarget> &rt, const SceGxmRende
     glBindTexture(GL_TEXTURE_2D, render_target->masktexture[0]);
     // we need to make the masktexture format immutable, otherwise image load operations
     // won't work on mesa drivers
-    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, params.width * state.res_multiplier, params.height * state.res_multiplier);
+    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, render_target->width, render_target->height);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     glBindFramebuffer(GL_FRAMEBUFFER, render_target->maskbuffer[0]);
@@ -443,7 +443,7 @@ static std::map<SceGxmColorFormat, std::pair<GLenum, GLenum>> GXM_COLOR_FORMAT_T
 
 static bool format_need_temp_storage(const GLState &state, SceGxmColorSurface &surface, std::vector<std::uint8_t> &storage, const std::uint32_t width, const std::uint32_t height) {
     size_t needed_pixels;
-    if (state.res_multiplier == 1) {
+    if (state.res_multiplier == 1.0f) {
         needed_pixels = surface.strideInPixels * height;
     } else {
         // width and height is already upscaled
@@ -455,7 +455,7 @@ static bool format_need_temp_storage(const GLState &state, SceGxmColorSurface &s
         return true;
     }
 
-    if (state.res_multiplier > 1) {
+    if (state.res_multiplier > 1.0f) {
         storage.resize(needed_pixels * gxm::bits_per_pixel(gxm::get_base_format(surface.colorFormat)) >> 3);
         return true;
     }
@@ -471,7 +471,7 @@ static void post_process_pixels_data(GLState &renderer, std::uint32_t *pixels, s
     const bool is_U8U8U8_RGBA = surface.colorFormat == SCE_GXM_COLOR_FORMAT_U8U8U8U8_RGBA;
     const bool is_SE5M9M9M9 = (surface.colorFormat == SCE_GXM_COLOR_FORMAT_SE5M9M9M9_RGB) || (surface.colorFormat == SCE_GXM_COLOR_FORMAT_SE5M9M9M9_BGR);
 
-    const int multiplier = renderer.res_multiplier;
+    const int multiplier = static_cast<int>(renderer.res_multiplier);
     if (multiplier > 1 || is_U8U8U8_RGBA || is_SE5M9M9M9) {
         // TODO: do this on the GPU instead (using texture blitting?)
         const int bytes_per_output_pixel = (gxm::bits_per_pixel(gxm::get_base_format(surface.colorFormat)) + 7) >> 3;
@@ -608,11 +608,12 @@ void get_surface_data(GLState &renderer, GLContext &context, uint32_t *pixels, S
     uint32_t width = surface.width;
     uint32_t height = surface.height;
 
-    if (renderer.res_multiplier == 1) {
+    const int res_multiplier = static_cast<int>(renderer.res_multiplier);
+    if (res_multiplier == 1) {
         glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(surface.strideInPixels));
     } else {
-        width *= renderer.res_multiplier;
-        height *= renderer.res_multiplier;
+        width *= res_multiplier;
+        height *= res_multiplier;
         glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(width));
     }
 
@@ -723,8 +724,8 @@ std::vector<uint32_t> GLState::dump_frame(DisplayState &display, uint32_t &width
         frame = display.next_rendered_frame;
     }
 
-    width = frame.image_size.x * res_multiplier;
-    height = frame.image_size.y * res_multiplier;
+    width = static_cast<uint32_t>(frame.image_size.x * res_multiplier);
+    height = static_cast<uint32_t>(frame.image_size.y * res_multiplier);
     return surface_cache.dump_frame(frame.base, width, height, frame.pitch, res_multiplier, features.support_get_texture_sub_image);
 }
 
diff --git a/vita3k/renderer/src/gl/surface_cache.cpp b/vita3k/renderer/src/gl/surface_cache.cpp
index 5c7008a32..37e9441f2 100644
--- a/vita3k/renderer/src/gl/surface_cache.cpp
+++ b/vita3k/renderer/src/gl/surface_cache.cpp
@@ -66,8 +66,8 @@ GLuint GLSurfaceCache::retrieve_color_surface_texture_handle(const State &state,
     const uint32_t original_width = width;
     const uint32_t original_height = height;
 
-    width *= state.res_multiplier;
-    height *= state.res_multiplier;
+    width = static_cast<uint16_t>(width * state.res_multiplier);
+    height = static_cast<uint16_t>(height * state.res_multiplier);
 
     // Of course, this works under the assumption that range must be unique :D
     auto ite = color_surface_textures.lower_bound(key);
@@ -271,8 +271,8 @@ GLuint GLSurfaceCache::retrieve_color_surface_texture_handle(const State &state,
 
             if (castable) {
                 const std::size_t data_delta = address.address() - ite->first;
-                std::size_t start_sourced_line = (data_delta / bytes_per_stride) * state.res_multiplier;
-                std::size_t start_x = (data_delta % bytes_per_stride) / color::bytes_per_pixel(base_format) * state.res_multiplier;
+                std::size_t start_sourced_line = static_cast<size_t>((data_delta / bytes_per_stride) * state.res_multiplier);
+                std::size_t start_x = static_cast<size_t>((data_delta % bytes_per_stride) / color::bytes_per_pixel(base_format) * state.res_multiplier);
 
                 if (static_cast<std::uint16_t>(start_sourced_line + height) > info.height) {
                     LOG_ERROR("Trying to present non-existen segment in cached color surface!");
@@ -521,16 +521,15 @@ GLuint GLSurfaceCache::retrieve_depth_stencil_texture_handle(const State &state,
         return 0;
     }
 
-    force_width *= state.res_multiplier;
-    force_height *= state.res_multiplier;
-
-    if (force_width < 0) {
+    if (force_width > 0)
+        force_width = static_cast<int32_t>(force_width * state.res_multiplier);
+    else
         force_width = target->width;
-    }
 
-    if (force_height < 0) {
+    if (force_height > 0)
+        force_height = static_cast<int32_t>(force_height * state.res_multiplier);
+    else
         force_height = target->height;
-    }
 
     const bool is_stencil_only = surface.depth_data.address() == 0;
     std::size_t found_index = static_cast<std::size_t>(-1);
@@ -713,14 +712,14 @@ GLuint GLSurfaceCache::retrieve_framebuffer_handle(const State &state, const Mem
     return fb[0];
 }
 
-GLuint GLSurfaceCache::sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const std::uint32_t pitch, float *uvs, const int res_multiplier, SceFVector2 &texture_size) {
+GLuint GLSurfaceCache::sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const std::uint32_t pitch, float *uvs, const float res_multiplier, SceFVector2 &texture_size) {
     auto ite = color_surface_textures.lower_bound(address.address());
     if (ite == color_surface_textures.end()) {
         return 0;
     }
 
-    width *= res_multiplier;
-    height *= res_multiplier;
+    width = static_cast<uint32_t>(width * res_multiplier);
+    height = static_cast<uint32_t>(height * res_multiplier);
 
     const GLColorSurfaceCacheInfo &info = *ite->second;
 
@@ -729,7 +728,7 @@ GLuint GLSurfaceCache::sourcing_color_surface_for_presentation(Ptr<const void> a
         const std::size_t data_delta = address.address() - ite->first;
         std::uint32_t limited_height = height;
         if ((data_delta % (pitch * 4)) == 0) {
-            std::uint32_t start_sourced_line = (data_delta / (pitch * 4)) * res_multiplier;
+            std::uint32_t start_sourced_line = static_cast<uint32_t>((data_delta / (pitch * 4)) * res_multiplier);
             if ((start_sourced_line + height) > info.height) {
                 // Sometimes the surface is just missing a little bit of lines
                 if (start_sourced_line < info.height) {
@@ -758,7 +757,7 @@ GLuint GLSurfaceCache::sourcing_color_surface_for_presentation(Ptr<const void> a
     return 0;
 }
 
-std::vector<uint32_t> GLSurfaceCache::dump_frame(Ptr<const void> address, uint32_t width, uint32_t height, uint32_t pitch, int res_multiplier, bool support_get_texture_sub_image) {
+std::vector<uint32_t> GLSurfaceCache::dump_frame(Ptr<const void> address, uint32_t width, uint32_t height, uint32_t pitch, float res_multiplier, bool support_get_texture_sub_image) {
     auto ite = color_surface_textures.lower_bound(address.address());
     if (ite == color_surface_textures.end() || ite->second->pixel_stride != pitch) {
         return {};
@@ -771,7 +770,7 @@ std::vector<uint32_t> GLSurfaceCache::dump_frame(Ptr<const void> address, uint32
     if (info.pixel_stride != pitch || data_delta % pitch_byte != 0)
         return {};
 
-    const uint32_t line_delta = (data_delta / pitch_byte) * res_multiplier;
+    const uint32_t line_delta = static_cast<uint32_t>((data_delta / pitch_byte) * res_multiplier);
     if (line_delta >= info.height)
         return {};
 
diff --git a/vita3k/renderer/src/gl/sync_state.cpp b/vita3k/renderer/src/gl/sync_state.cpp
index e919156b1..87d69735c 100644
--- a/vita3k/renderer/src/gl/sync_state.cpp
+++ b/vita3k/renderer/src/gl/sync_state.cpp
@@ -120,7 +120,8 @@ void sync_viewport_flat(const GLState &state, GLContext &context) {
     const GLsizei display_w = context.record.color_surface.width;
     const GLsizei display_h = context.record.color_surface.height;
 
-    glViewport(0, (context.current_framebuffer_height - display_h) * state.res_multiplier, display_w * state.res_multiplier, display_h * state.res_multiplier);
+    glViewport(0, static_cast<GLint>((context.current_framebuffer_height - display_h) * state.res_multiplier),
+        static_cast<GLsizei>(display_w * state.res_multiplier), static_cast<GLsizei>(display_h * state.res_multiplier));
     glDepthRange(0, 1);
 }
 
@@ -161,7 +162,8 @@ void sync_clipping(const GLState &state, GLContext &context) {
         break;
     case SCE_GXM_REGION_CLIP_OUTSIDE:
         glEnable(GL_SCISSOR_TEST);
-        glScissor(scissor_x * state.res_multiplier, scissor_y * state.res_multiplier, scissor_w * state.res_multiplier, scissor_h * state.res_multiplier);
+        glScissor(static_cast<GLint>(scissor_x * state.res_multiplier), static_cast<GLint>(scissor_y * state.res_multiplier),
+            static_cast<GLsizei>(scissor_w * state.res_multiplier), static_cast<GLsizei>(scissor_h * state.res_multiplier));
         break;
     case SCE_GXM_REGION_CLIP_INSIDE:
         // TODO: Implement SCE_GXM_REGION_CLIP_INSIDE
diff --git a/vita3k/renderer/src/scene.cpp b/vita3k/renderer/src/scene.cpp
index 08096060a..7e6835f78 100644
--- a/vita3k/renderer/src/scene.cpp
+++ b/vita3k/renderer/src/scene.cpp
@@ -132,6 +132,10 @@ COMMAND(handle_sync_surface_data) {
         }
     }
 
+    // additional check to make sure we never try to perform surface sync on OpenGL with a non-integer resolution multiplier
+    if (renderer.current_backend == Backend::OpenGL && static_cast<int>(renderer.res_multiplier * 4.0f) % 4 != 0)
+        renderer.disable_surface_sync = true;
+
     if (renderer.disable_surface_sync || renderer.current_backend == Backend::Vulkan) {
         if (helper.cmd->status) {
             complete_command(renderer, helper, 0);
diff --git a/vita3k/renderer/src/vulkan/creation.cpp b/vita3k/renderer/src/vulkan/creation.cpp
index 644ca3ae1..586a32d42 100644
--- a/vita3k/renderer/src/vulkan/creation.cpp
+++ b/vita3k/renderer/src/vulkan/creation.cpp
@@ -86,7 +86,7 @@ VKContext::VKContext(VKState &state, MemState &mem)
     };
     scissor = vk::Rect2D{
         .offset = { 0, 0 },
-        .extent = { 960U * state.res_multiplier, 544U * state.res_multiplier }
+        .extent = { static_cast<uint32_t>(960 * state.res_multiplier), static_cast<uint32_t>(544U * state.res_multiplier) }
     };
 
     // allocate descriptor pools
@@ -153,10 +153,10 @@ VKContext::VKContext(VKState &state, MemState &mem)
 }
 
 VKRenderTarget::VKRenderTarget(VKState &state, const SceGxmRenderTargetParams &params)
-    : color(params.width * state.res_multiplier, params.height * state.res_multiplier, vk::Format::eR8G8B8A8Unorm)
-    , depthstencil(params.width * state.res_multiplier, params.height * state.res_multiplier, vk::Format::eD32SfloatS8Uint) {
-    width = params.width * state.res_multiplier;
-    height = params.height * state.res_multiplier;
+    : color(static_cast<uint32_t>(params.width * state.res_multiplier), static_cast<uint32_t>(params.height * state.res_multiplier), vk::Format::eR8G8B8A8Unorm)
+    , depthstencil(static_cast<uint32_t>(params.width * state.res_multiplier), static_cast<uint32_t>(params.height * state.res_multiplier), vk::Format::eD32SfloatS8Uint) {
+    width = static_cast<uint32_t>(params.width * state.res_multiplier);
+    height = static_cast<uint32_t>(params.height * state.res_multiplier);
 
     vk::ImageUsageFlags color_usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eInputAttachment;
     if (state.features.support_shader_interlock)
diff --git a/vita3k/renderer/src/vulkan/renderer.cpp b/vita3k/renderer/src/vulkan/renderer.cpp
index fe985b376..3c23a5520 100644
--- a/vita3k/renderer/src/vulkan/renderer.cpp
+++ b/vita3k/renderer/src/vulkan/renderer.cpp
@@ -686,8 +686,8 @@ void VKState::render_frame(const SceFVector2 &viewport_pos, const SceFVector2 &v
 
     // Check if the surface exists
     Viewport viewport;
-    viewport.width = frame.image_size.x * res_multiplier;
-    viewport.height = frame.image_size.y * res_multiplier;
+    viewport.width = static_cast<uint32_t>(frame.image_size.x * res_multiplier);
+    viewport.height = static_cast<uint32_t>(frame.image_size.y * res_multiplier);
 
     vk::ImageLayout layout = vk::ImageLayout::eGeneral;
     vk::ImageView surface_handle = surface_cache.sourcing_color_surface_for_presentation(
@@ -755,8 +755,8 @@ std::vector<uint32_t> VKState::dump_frame(DisplayState &display, uint32_t &width
         frame = display.next_rendered_frame;
     }
 
-    width = frame.image_size.x * res_multiplier;
-    height = frame.image_size.y * res_multiplier;
+    width = static_cast<uint32_t>(frame.image_size.x * res_multiplier);
+    height = static_cast<uint32_t>(frame.image_size.y * res_multiplier);
     return surface_cache.dump_frame(frame.base, width, height, frame.pitch);
 }
 
diff --git a/vita3k/renderer/src/vulkan/scene.cpp b/vita3k/renderer/src/vulkan/scene.cpp
index 96dd7d7a7..a9d68a5ca 100644
--- a/vita3k/renderer/src/vulkan/scene.cpp
+++ b/vita3k/renderer/src/vulkan/scene.cpp
@@ -419,8 +419,8 @@ void draw(VKContext &context, SceGxmPrimitiveType type, SceGxmIndexFormat format
     vert_ublock.viewport_flag = (context.record.viewport_flat) ? 0.0f : 1.0f;
     vert_ublock.z_offset = context.record.z_offset;
     vert_ublock.z_scale = context.record.z_scale;
-    vert_ublock.screen_width = static_cast<float>(context.render_target->width / context.state.res_multiplier);
-    vert_ublock.screen_height = static_cast<float>(context.render_target->height / context.state.res_multiplier);
+    vert_ublock.screen_width = context.render_target->width / context.state.res_multiplier;
+    vert_ublock.screen_height = context.render_target->height / context.state.res_multiplier;
 
     if (context.curr_vert_ublock.changed || memcmp(&context.prev_vert_ublock, &vert_ublock, sizeof(vert_ublock)) != 0) {
         // TODO: this intermediate step can be avoided
@@ -431,7 +431,7 @@ void draw(VKContext &context, SceGxmPrimitiveType type, SceGxmIndexFormat format
 
     auto &frag_ublock = context.curr_frag_ublock.base_block;
     frag_ublock.writing_mask = context.record.writing_mask;
-    frag_ublock.res_multiplier = static_cast<float>(context.state.res_multiplier);
+    frag_ublock.res_multiplier = context.state.res_multiplier;
     const bool has_msaa = context.render_target->multisample_mode;
     const bool has_downscale = context.record.color_surface.downscale;
     if (has_msaa && !has_downscale)
diff --git a/vita3k/renderer/src/vulkan/surface_cache.cpp b/vita3k/renderer/src/vulkan/surface_cache.cpp
index 0c51cbf8f..5f752386d 100644
--- a/vita3k/renderer/src/vulkan/surface_cache.cpp
+++ b/vita3k/renderer/src/vulkan/surface_cache.cpp
@@ -124,8 +124,8 @@ SurfaceRetrieveResult VKSurfaceCache::retrieve_color_surface_for_framebuffer(Mem
     const uint32_t original_width = color->width;
     const uint32_t original_height = color->height;
 
-    uint32_t width = original_width * state.res_multiplier;
-    uint32_t height = original_height * state.res_multiplier;
+    uint32_t width = static_cast<uint32_t>(original_width * state.res_multiplier);
+    uint32_t height = static_cast<uint32_t>(original_height * state.res_multiplier);
 
     bool overlap = true;
 
@@ -312,8 +312,8 @@ std::optional<TextureLookupResult> VKSurfaceCache::retrieve_color_surface_as_tex
     const uint32_t original_width = gxm::get_width(texture);
     const uint32_t original_height = gxm::get_height(texture);
 
-    const uint32_t width = original_width * state.res_multiplier;
-    const uint32_t height = original_height * state.res_multiplier;
+    const uint32_t width = static_cast<uint32_t>(original_width * state.res_multiplier);
+    const uint32_t height = static_cast<uint32_t>(original_height * state.res_multiplier);
 
     bool overlap = true;
     // Of course, this works under the assumption that range must be unique :D
@@ -401,8 +401,8 @@ std::optional<TextureLookupResult> VKSurfaceCache::retrieve_color_surface_as_tex
     // TODO: this is true only for linear textures (and also kind of for tiled textures) (and in this case start_x = 0),
     // for swizzled textures this is different
     const uint32_t data_delta = address - ite->first;
-    uint32_t start_sourced_line = (data_delta / stride_bytes) * state.res_multiplier;
-    uint32_t start_x = (data_delta % stride_bytes) / bytes_per_pixel_requested * state.res_multiplier;
+    uint32_t start_sourced_line = static_cast<uint32_t>((data_delta / stride_bytes) * state.res_multiplier);
+    uint32_t start_x = static_cast<uint32_t>((data_delta % stride_bytes) / bytes_per_pixel_requested * state.res_multiplier);
 
     if (static_cast<uint16_t>(start_sourced_line + height) > info.height)
         LOG_WARN_ONCE("Trying to use texture partially in the surface cache");
@@ -534,7 +534,7 @@ std::optional<TextureLookupResult> VKSurfaceCache::retrieve_color_surface_as_tex
         } else {
             LOG_INFO_ONCE("Game is doing typeless copies");
             // We must use a transition buffer
-            vk::DeviceSize buffer_size = stride_bytes * state.res_multiplier * height + start_x * bytes_per_pixel_requested;
+            vk::DeviceSize buffer_size = stride_bytes * static_cast<size_t>(state.res_multiplier * align(height, 4)) + start_x * bytes_per_pixel_requested;
             if (!casted->transition_buffer.buffer || casted->transition_buffer.size < buffer_size) {
                 // create or re-create the buffer
                 state.frame().destroy_queue.add_buffer(casted->transition_buffer);
@@ -543,7 +543,7 @@ std::optional<TextureLookupResult> VKSurfaceCache::retrieve_color_surface_as_tex
             }
 
             // copy the image to the buffer
-            const uint32_t src_pixel_stride = (info.stride_bytes / bytes_per_pixel_in_store) * state.res_multiplier;
+            const uint32_t src_pixel_stride = static_cast<uint32_t>((info.stride_bytes / bytes_per_pixel_in_store) * state.res_multiplier);
             vk::BufferImageCopy copy_image_buffer{
                 .bufferOffset = 0,
                 .bufferRowLength = src_pixel_stride,
@@ -608,8 +608,8 @@ std::optional<TextureLookupResult> VKSurfaceCache::retrieve_color_surface_as_tex
 
 SurfaceRetrieveResult VKSurfaceCache::retrieve_depth_stencil_for_framebuffer(SceGxmDepthStencilSurface *depth_stencil, const uint32_t width, const uint32_t height) {
     // when writing we use the render target size which is already upscaled
-    int32_t memory_width = width / state.res_multiplier;
-    int32_t memory_height = height / state.res_multiplier;
+    int32_t memory_width = static_cast<int32_t>(width / state.res_multiplier);
+    int32_t memory_height = static_cast<int32_t>(height / state.res_multiplier);
 
     const SurfaceTiling tiling = (depth_stencil->get_type() == SCE_GXM_DEPTH_STENCIL_SURFACE_LINEAR) ? SurfaceTiling::Linear : SurfaceTiling::Tiled;
 
@@ -747,8 +747,8 @@ std::optional<TextureLookupResult> VKSurfaceCache::retrieve_depth_stencil_as_tex
         return std::nullopt;
 
     // take upscaling into account
-    uint32_t width = memory_width * state.res_multiplier;
-    uint32_t height = memory_height * state.res_multiplier;
+    uint32_t width = static_cast<uint32_t>(memory_width * state.res_multiplier);
+    uint32_t height = static_cast<uint32_t>(memory_height * state.res_multiplier);
 
     const uint32_t address = texture.data_addr << 2;
     DepthStencilSurfaceCacheInfo *found_info = nullptr;
@@ -993,8 +993,8 @@ ColorSurfaceCacheInfo *VKSurfaceCache::perform_surface_sync() {
         is_swizzle_identity = true;
     }
 
-    if (state.res_multiplier > 1) {
-        // downscale the image using a blit command first
+    if (state.res_multiplier != 1.0f) {
+        // scale bacl the image using a blit command first
 
         if (!last_written_surface->blit_image)
             last_written_surface->blit_image = std::make_unique<vkutil::Image>();
@@ -1177,7 +1177,7 @@ vk::ImageView VKSurfaceCache::sourcing_color_surface_for_presentation(Ptr<const
         const size_t data_delta = address.address() - ite->first;
         uint32_t limited_height = viewport.height;
         if ((data_delta % (pitch * 4)) == 0) {
-            uint32_t start_sourced_line = (data_delta / (pitch * 4)) * state.res_multiplier;
+            uint32_t start_sourced_line = static_cast<uint32_t>((data_delta / (pitch * 4)) * state.res_multiplier);
             if ((start_sourced_line + viewport.height) > info.height) {
                 // Sometimes the surface is just missing a little bit of lines
                 if (start_sourced_line < info.height) {
@@ -1234,7 +1234,7 @@ std::vector<uint32_t> VKSurfaceCache::dump_frame(Ptr<const void> address, uint32
     if (info.stride_bytes != pitch_byte || data_delta % pitch_byte != 0)
         return {};
 
-    const uint32_t line_delta = (data_delta / pitch_byte) * state.res_multiplier;
+    const uint32_t line_delta = static_cast<uint32_t>((data_delta / pitch_byte) * state.res_multiplier);
     if (line_delta >= info.height)
         return {};
 
diff --git a/vita3k/renderer/src/vulkan/sync_state.cpp b/vita3k/renderer/src/vulkan/sync_state.cpp
index 20876b24b..07a02a9a9 100644
--- a/vita3k/renderer/src/vulkan/sync_state.cpp
+++ b/vita3k/renderer/src/vulkan/sync_state.cpp
@@ -30,7 +30,7 @@ void sync_clipping(VKContext &context) {
     if (!context.render_target)
         return;
 
-    const int res_multiplier = context.state.res_multiplier;
+    const float res_multiplier = context.state.res_multiplier;
 
     const int scissor_x = context.record.region_clip_min.x;
     const int scissor_y = context.record.region_clip_min.y;
@@ -48,8 +48,8 @@ void sync_clipping(VKContext &context) {
         break;
     case SCE_GXM_REGION_CLIP_OUTSIDE:
         context.scissor = vk::Rect2D{
-            { scissor_x * res_multiplier, scissor_y * res_multiplier },
-            { scissor_w * res_multiplier, scissor_h * res_multiplier }
+            { static_cast<int32_t>(scissor_x * res_multiplier), static_cast<int32_t>(scissor_y * res_multiplier) },
+            { static_cast<uint32_t>(scissor_w * res_multiplier), static_cast<uint32_t>(scissor_h * res_multiplier) }
         };
         break;
     case SCE_GXM_REGION_CLIP_INSIDE:
@@ -153,7 +153,7 @@ void sync_point_line_width(VKContext &context, const bool is_front) {
         return;
 
     if (is_front && context.state.physical_device_features.wideLines)
-        context.render_cmd.setLineWidth(static_cast<float>(context.record.line_width * context.state.res_multiplier));
+        context.render_cmd.setLineWidth(context.record.line_width * context.state.res_multiplier);
 }
 
 void sync_viewport_flat(VKContext &context) {
@@ -181,7 +181,7 @@ void sync_viewport_real(VKContext &context, const float xOffset, const float yOf
     const float x = xOffset - std::abs(xScale);
     const float y = yOffset - yScale;
 
-    const int res_multiplier = context.state.res_multiplier;
+    const float res_multiplier = context.state.res_multiplier;
 
     // https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkViewport.html
     // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vertexpostproc-viewport