diff --git a/rpcs3/Emu/Cell/Modules/cellVdec.cpp b/rpcs3/Emu/Cell/Modules/cellVdec.cpp index 7578bc3d02..0237c78d27 100644 --- a/rpcs3/Emu/Cell/Modules/cellVdec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellVdec.cpp @@ -62,15 +62,80 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } -vm::gvar _cell_vdec_prx_ver; // ??? +// The general sequence control flow has these possible transitions: +// closed -> dormant +// dormant -> ready +// dormant -> closed +// ready -> ending +// ready -> resetting +// ready -> closed +// ending -> dormant +// resetting -> ready +enum class sequence_state : u32 +{ + closed = 0, // Also called non-existent. Needs to be opened before anything can be done with it. + dormant = 1, // Waiting for the next sequence. The last picture and pic-item can be aqcuired in this state. + ready = 2, // Ready for decoding. Can also restart sequences in this state. + ending = 3, // Ending a sequence. Goes to dormant afterwards. + resetting = 4, // Stops the current sequence and starts a new one. The pictures of the old sequence are flushed + invalid = 5, // Any other value is invalid +}; -constexpr struct vdec_start_seq_t{} vdec_start_seq{}; -constexpr struct vdec_close_t{} vdec_close{}; +template<> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto error) + { + switch (error) + { + STR_CASE(sequence_state::closed); + STR_CASE(sequence_state::dormant); + STR_CASE(sequence_state::ready); + STR_CASE(sequence_state::ending); + STR_CASE(sequence_state::resetting); + STR_CASE(sequence_state::invalid); + } + + return unknown; + }); +} + +vm::gvar _cell_vdec_prx_ver; // TODO: this should probably specify the VDEC module that was loaded. E.g. CELL_SYSMODULE_VDEC_MPEG2 + +enum class vdec_cmd_type : u32 +{ + start_sequence, + end_sequence, + close, + au_decode, + framerate, +}; struct vdec_cmd { - s32 mode; - CellVdecAuInfo au; + explicit vdec_cmd(vdec_cmd_type _type) + : type(_type) + { + ensure(_type != vdec_cmd_type::au_decode); + ensure(_type != vdec_cmd_type::framerate); + } + + explicit vdec_cmd(vdec_cmd_type _type, s32 _mode, CellVdecAuInfo _au) + : type(_type), mode(_mode), au(std::move(_au)) + { + ensure(_type == vdec_cmd_type::au_decode); + } + + explicit vdec_cmd(vdec_cmd_type _type, s32 _framerate) + : type(_type), framerate(_framerate) + { + ensure(_type == vdec_cmd_type::framerate); + } + + vdec_cmd_type type{}; + s32 mode{}; + s32 framerate{}; + CellVdecAuInfo au{}; }; struct vdec_frame @@ -103,6 +168,10 @@ struct vdec_context final static const u32 id_step = 0x00000100; static const u32 id_count = 1024; + atomic_t abort_decode = false; // Used for thread interaction + atomic_t is_running = false; // Used for thread interaction + atomic_t seq_state = sequence_state::closed; + const AVCodec* codec{}; AVCodecContext* ctx{}; SwsContext* sws{}; @@ -121,12 +190,12 @@ struct vdec_context final u64 next_dts{}; atomic_t ppu_tid{}; - std::deque out; - atomic_t out_max = 60; + std::deque out_queue; + const u32 out_max = 60; atomic_t au_count{0}; - lf_queue> in_cmd; + lf_queue in_cmd; AVRational log_time_base{}; // Used to reduce log spam @@ -197,6 +266,8 @@ struct vdec_context final } fmt::throw_exception("avcodec_open2() failed (err=0x%x='%s', opts=%s)", err, utils::av_error_to_string(err), dict_content); } + + seq_state = sequence_state::dormant; } ~vdec_context() @@ -212,7 +283,7 @@ struct vdec_context final ppu_tid.release(ppu.id); - for (auto slice = in_cmd.pop_all();; [&] + for (auto slice = in_cmd.pop_all(); thread_ctrl::state() != thread_state::aborting; [&] { if (slice) { @@ -229,82 +300,99 @@ struct vdec_context final }()) { // pcmd can be nullptr - auto* pcmd = slice.get(); + auto* cmd = slice.get(); - if (thread_ctrl::state() == thread_state::aborting) + if (!cmd) { - break; + continue; } - else if (std::get_if(pcmd)) + + switch (cmd->type) { + case vdec_cmd_type::start_sequence: + { + std::lock_guard lock{mutex}; + + if (seq_state == sequence_state::resetting) + { + cellVdec.trace("Reset sequence..."); + } + else + { + cellVdec.trace("Start sequence..."); + } + avcodec_flush_buffers(ctx); + out_queue.clear(); // Flush image queue log_time_base = {}; frc_set = 0; // TODO: ??? next_pts = 0; next_dts = 0; - cellVdec.trace("Start sequence..."); + + abort_decode = false; + is_running = true; + break; } - else if (auto* cmd = std::get_if(pcmd)) + case vdec_cmd_type::end_sequence: + { + cellVdec.trace("End sequence..."); + + { + std::lock_guard lock{mutex}; + seq_state = sequence_state::dormant; + } + + cb_func(ppu, vid, CELL_VDEC_MSG_TYPE_SEQDONE, CELL_OK, cb_arg); + lv2_obj::sleep(ppu); + break; + } + case vdec_cmd_type::au_decode: { AVPacket packet{}; packet.pos = -1; u64 au_usrd{}; - if (cmd->mode != -1) + const u32 au_mode = cmd->mode; + const u32 au_addr = cmd->au.startAddr; + const u32 au_size = cmd->au.size; + const u64 au_pts = u64{cmd->au.pts.upper} << 32 | cmd->au.pts.lower; + const u64 au_dts = u64{cmd->au.dts.upper} << 32 | cmd->au.dts.lower; + au_usrd = cmd->au.userData; + + packet.data = vm::_ptr(au_addr); + packet.size = au_size; + packet.pts = au_pts != umax ? au_pts : s64{smin}; + packet.dts = au_dts != umax ? au_dts : s64{smin}; + + if (next_pts == 0 && au_pts != umax) { - const u32 au_mode = cmd->mode; - const u32 au_addr = cmd->au.startAddr; - const u32 au_size = cmd->au.size; - const u64 au_pts = u64{cmd->au.pts.upper} << 32 | cmd->au.pts.lower; - const u64 au_dts = u64{cmd->au.dts.upper} << 32 | cmd->au.dts.lower; - au_usrd = cmd->au.userData; - - packet.data = vm::_ptr(au_addr); - packet.size = au_size; - packet.pts = au_pts != umax ? au_pts : s64{smin}; - packet.dts = au_dts != umax ? au_dts : s64{smin}; - - if (next_pts == 0 && au_pts != umax) - { - next_pts = au_pts; - } - - if (next_dts == 0 && au_dts != umax) - { - next_dts = au_dts; - } - - ctx->skip_frame = - au_mode == CELL_VDEC_DEC_MODE_NORMAL ? AVDISCARD_DEFAULT : - au_mode == CELL_VDEC_DEC_MODE_B_SKIP ? AVDISCARD_NONREF : AVDISCARD_NONINTRA; - - cellVdec.trace("AU decoding: size=0x%x, pts=0x%llx, dts=0x%llx, userdata=0x%llx", au_size, au_pts, au_dts, au_usrd); - } - else - { - packet.pts = smin; - packet.dts = smin; - cellVdec.trace("End sequence..."); + next_pts = au_pts; } - while (out_max) + if (next_dts == 0 && au_dts != umax) { - if (cmd->mode == -1) - { - break; - } + next_dts = au_dts; + } + ctx->skip_frame = + au_mode == CELL_VDEC_DEC_MODE_NORMAL ? AVDISCARD_DEFAULT : + au_mode == CELL_VDEC_DEC_MODE_B_SKIP ? AVDISCARD_NONREF : AVDISCARD_NONINTRA; + + cellVdec.trace("AU decoding: size=0x%x, pts=0x%llx, dts=0x%llx, userdata=0x%llx", au_size, au_pts, au_dts, au_usrd); + + while (thread_ctrl::state() != thread_state::aborting && !abort_decode) + { if (int ret = avcodec_send_packet(ctx, &packet); ret < 0) { - char av_error[AV_ERROR_MAX_STRING_SIZE]; + char av_error[AV_ERROR_MAX_STRING_SIZE]{}; av_make_error_string(av_error, AV_ERROR_MAX_STRING_SIZE, ret); fmt::throw_exception("AU queuing error(0x%x): %s", ret, av_error); } - while (true) + while (!abort_decode) { // Keep receiving frames vdec_frame frame; @@ -317,7 +405,7 @@ struct vdec_context final if (int ret = avcodec_receive_frame(ctx, frame.avf.get()); ret < 0) { - if (ret == AVERROR(EAGAIN)) + if (ret == AVERROR(EAGAIN) || ret == AVERROR(EOF)) { break; } @@ -433,43 +521,78 @@ struct vdec_context final cellVdec.trace("Got picture (pts=0x%llx[0x%llx], dts=0x%llx[0x%llx])", frame.pts, frame->pts, frame.dts, frame->pkt_dts); - std::lock_guard{mutex}, out.push_back(std::move(frame)); + { + std::lock_guard lock{mutex}; + out_queue.push_back(std::move(frame)); + } cb_func(ppu, vid, CELL_VDEC_MSG_TYPE_PICOUT, CELL_OK, cb_arg); lv2_obj::sleep(ppu); + + // Wait until there is free space in the image queue. + // Do this after pushing the frame to the queue. That way the game can consume the frame and we can move on. + u32 elapsed = 0; + while (thread_ctrl::state() != thread_state::aborting && !abort_decode) + { + { + std::lock_guard lock{mutex}; + + if (out_queue.size() <= out_max) + { + break; + } + } + thread_ctrl::wait_for(1000); + + if (elapsed++ >= 5000) // 5 seconds + { + cellVdec.error("Video au decode has been waiting for a consumer for 5 seconds."); + elapsed = 0; + } + } } - if (cmd->mode != -1) - { - break; - } + break; } - if (out_max) + if (thread_ctrl::state() != thread_state::aborting && !abort_decode) { - cb_func(ppu, vid, cmd->mode != -1 ? CELL_VDEC_MSG_TYPE_AUDONE : CELL_VDEC_MSG_TYPE_SEQDONE, CELL_OK, cb_arg); + cb_func(ppu, vid, CELL_VDEC_MSG_TYPE_AUDONE, CELL_OK, cb_arg); lv2_obj::sleep(ppu); } + else cellVdec.error("AU decoding: abort_decode = %d", abort_decode.load()); - if (cmd->mode != -1) - { - au_count--; - } + au_count--; - while (thread_ctrl::state() != thread_state::aborting && out_max && (std::lock_guard{mutex}, out.size() > out_max)) - { - thread_ctrl::wait_for(1000); - } + cellVdec.trace("AU decoding: done"); + break; } - else if (auto* frc = std::get_if(pcmd)) + case vdec_cmd_type::framerate: { - frc_set = *frc; + frc_set = cmd->framerate; + break; } - else if (std::get_if(pcmd)) + case vdec_cmd_type::close: + { + std::lock_guard lock{mutex}; + out_queue.clear(); + break; + } + default: + fmt::throw_exception("Unknown vdec_cmd_type (%d)", static_cast(cmd->type)); + break; + } + + std::lock_guard lock{mutex}; + if (seq_state == sequence_state::closed) { break; } } + + // Make sure the state is closed at the end + std::lock_guard lock{mutex}; + seq_state = sequence_state::closed; } }; @@ -646,13 +769,13 @@ error_code cellVdecQueryAttrEx(vm::cptr type, vm::ptr static error_code vdecOpen(ppu_thread& ppu, T type, U res, vm::cptr cb, vm::ptr handle) { - if (!type || !res || !cb || !handle) + if (!type || !res || !cb || !handle || !cb->cbFunc) { return CELL_VDEC_ERROR_ARG; } - if (res->ppuThreadPriority + 0u > 3071 || res->spuThreadPriority + 0u > 255 || res->ppuThreadStackSize < 4096 - || type->codecType + 0u > 0xd) + if (!res->memAddr || res->ppuThreadPriority + 0u >= 3072 || res->spuThreadPriority + 0u >= 256 || res->ppuThreadStackSize < 4096 + || type->codecType + 0u >= 0xe) { return CELL_VDEC_ERROR_ARG; } @@ -712,18 +835,25 @@ error_code cellVdecClose(ppu_thread& ppu, u32 handle) { cellVdec.warning("cellVdecClose(handle=0x%x)", handle); - const auto vdec = idm::get(handle); + auto vdec = idm::get(handle); if (!vdec) { return CELL_VDEC_ERROR_ARG; } - // TODO: return CELL_VDEC_ERROR_SEQ + { + std::lock_guard lock{vdec->mutex}; + + if (vdec->seq_state == sequence_state::closed) + { + return { CELL_VDEC_ERROR_SEQ, vdec->seq_state.load() }; + } + } lv2_obj::sleep(ppu); - vdec->out_max = 0; - vdec->in_cmd.push(vdec_close); + vdec->abort_decode = true; + vdec->in_cmd.push(vdec_cmd(vdec_cmd_type::close)); while (!vdec->ppu_tid) { @@ -737,6 +867,9 @@ error_code cellVdecClose(ppu_thread& ppu, u32 handle) ppu_execute<&sys_interrupt_thread_disestablish>(ppu, tid); } + std::lock_guard lock{vdec->mutex}; + vdec->seq_state = sequence_state::closed; + if (!idm::remove_verify(handle, std::move(vdec))) { // Other thread removed it beforehead @@ -757,9 +890,55 @@ error_code cellVdecStartSeq(u32 handle) return CELL_VDEC_ERROR_ARG; } - // TODO: return CELL_VDEC_ERROR_SEQ + sequence_state old_state{}; + + { + std::lock_guard lock{vdec->mutex}; + old_state = vdec->seq_state; + + if (old_state != sequence_state::dormant && old_state != sequence_state::ready) + { + return { CELL_VDEC_ERROR_SEQ, old_state }; + } + + if (old_state == sequence_state::ready) + { + vdec->seq_state = sequence_state::resetting; + } + } + + vdec->abort_decode = true; + vdec->is_running = false; + vdec->in_cmd.push(vdec_cmd(vdec_cmd_type::start_sequence)); + + // Wait until the thread is ready + u32 elapsed = 0; + while (thread_ctrl::state() != thread_state::aborting) + { + if (vdec->is_running) + { + break; + } + thread_ctrl::wait_for(1000); + + if (elapsed++ >= 5000) // 5 seconds + { + cellVdec.error("Timeout in cellVdecStartSeq."); + elapsed = 0; + } + } + + std::lock_guard lock{vdec->mutex}; + + if (false) // TODO: set to old state in case of error + { + vdec->seq_state = old_state; + } + else + { + vdec->seq_state = sequence_state::ready; + } - vdec->in_cmd.push(vdec_start_seq); return CELL_OK; } @@ -774,9 +953,19 @@ error_code cellVdecEndSeq(u32 handle) return CELL_VDEC_ERROR_ARG; } - // TODO: return CELL_VDEC_ERROR_SEQ + { + std::lock_guard lock{vdec->mutex}; + + if (vdec->seq_state != sequence_state::ready) + { + return { CELL_VDEC_ERROR_SEQ, vdec->seq_state.load() }; + } + + vdec->seq_state = sequence_state::ending; + } + + vdec->in_cmd.push(vdec_cmd(vdec_cmd_type::end_sequence)); - vdec->in_cmd.push(vdec_cmd{-1}); return CELL_OK; } @@ -786,18 +975,39 @@ error_code cellVdecDecodeAu(u32 handle, CellVdecDecodeMode mode, vm::cptr(handle); - if (!auInfo || !vdec || mode < 0 || mode > CELL_VDEC_DEC_MODE_PB_SKIP) + if (!vdec || !auInfo || !auInfo->pts.upper || !auInfo->startAddr) { return CELL_VDEC_ERROR_ARG; } + { + std::lock_guard lock{vdec->mutex}; + + if (vdec->seq_state != sequence_state::ready) + { + return { CELL_VDEC_ERROR_SEQ, vdec->seq_state.load() }; + } + } + + if (mode < 0 || mode > CELL_VDEC_DEC_MODE_PB_SKIP) + { + return CELL_VDEC_ERROR_ARG; + } + + // TODO: + //if ((mode == (CELL_VDEC_DEC_MODE_B_SKIP | CELL_VDEC_DEC_MODE_PB_SKIP) && something != 3) || + // (mode == CELL_VDEC_DEC_MODE_PB_SKIP && something != 1)) + //{ + // return CELL_VDEC_ERROR_ARG; + //} + if (!vdec->au_count.try_inc(4)) { return CELL_VDEC_ERROR_BUSY; } // TODO: check info - vdec->in_cmd.push(vdec_cmd{mode, *auInfo}); + vdec->in_cmd.push(vdec_cmd(vdec_cmd_type::au_decode, mode, *auInfo)); return CELL_OK; } @@ -818,33 +1028,34 @@ error_code cellVdecGetPicture(u32 handle, vm::cptr format, vm return CELL_VDEC_ERROR_ARG; } - // TODO: return CELL_VDEC_ERROR_SEQ + { + std::lock_guard lock{vdec->mutex}; + + if (vdec->seq_state == sequence_state::closed || vdec->seq_state > sequence_state::ending) + { + return { CELL_VDEC_ERROR_SEQ, vdec->seq_state.load() }; + } + } if (format->formatType > 4 || (format->formatType <= CELL_VDEC_PICFMT_RGBA32_ILV && format->colorMatrixType > CELL_VDEC_COLOR_MATRIX_TYPE_BT709)) { return CELL_VDEC_ERROR_ARG; } - // TODO: something like this is checked here, maybe only if outBuff[0] != 0 - //if (outBuff && outBuff[0] != 8 && outBuff[0] != 12) - //{ - // return CELL_VDEC_ERROR_ARG; - //} - vdec_frame frame; bool notify = false; { std::lock_guard lock(vdec->mutex); - if (vdec->out.empty()) + if (vdec->out_queue.empty()) { return CELL_VDEC_ERROR_EMPTY; } - frame = std::move(vdec->out.front()); + frame = std::move(vdec->out_queue.front()); - vdec->out.pop_front(); - if (vdec->out.size() + 1 == vdec->out_max) + vdec->out_queue.pop_front(); + if (vdec->out_queue.size() + 1 == vdec->out_max) notify = true; } @@ -869,7 +1080,6 @@ error_code cellVdecGetPicture(u32 handle, vm::cptr format, vm case CELL_VDEC_PICFMT_RGBA32_ILV: out_f = AV_PIX_FMT_RGBA; alpha_plane.reset(new u8[w * h]); break; case CELL_VDEC_PICFMT_UYVY422_ILV: out_f = AV_PIX_FMT_UYVY422; break; case CELL_VDEC_PICFMT_YUV420_PLANAR: out_f = AV_PIX_FMT_YUV420P; break; - default: { fmt::throw_exception("Unknown formatType (%d)", type); @@ -935,6 +1145,16 @@ error_code cellVdecGetPictureExt(u32 handle, vm::cptr format { cellVdec.warning("cellVdecGetPictureExt(handle=0x%x, format2=*0x%x, outBuff=*0x%x, arg4=*0x%x)", handle, format2, outBuff, arg4); + if (!handle || !format2) + { + return CELL_VDEC_ERROR_ARG; + } + + if (arg4 && arg4 != 8 && arg4 != 12) + { + return CELL_VDEC_ERROR_ARG; + } + if (arg4 || format2->unk0 || format2->unk1) { fmt::throw_exception("Unknown arguments (arg4=*0x%x, unk0=0x%x, unk1=0x%x)", arg4, format2->unk0, format2->unk1); @@ -959,7 +1179,14 @@ error_code cellVdecGetPicItem(u32 handle, vm::pptr picItem) return CELL_VDEC_ERROR_ARG; } - // TODO: return CELL_VDEC_ERROR_SEQ + { + std::lock_guard lock{vdec->mutex}; + + if (vdec->seq_state == sequence_state::closed || vdec->seq_state > sequence_state::ending) + { + return { CELL_VDEC_ERROR_SEQ, vdec->seq_state.load() }; + } + } struct all_info_t { @@ -976,7 +1203,7 @@ error_code cellVdecGetPicItem(u32 handle, vm::pptr picItem) { std::lock_guard lock(vdec->mutex); - for (auto& picture : vdec->out) + for (auto& picture : vdec->out_queue) { if (!picture.PicItemRecieved) { @@ -1180,9 +1407,14 @@ error_code cellVdecSetFrameRate(u32 handle, CellVdecFrameRate frameRateCode) return CELL_VDEC_ERROR_ARG; } - // TODO: return CELL_VDEC_ERROR_SEQ + std::lock_guard lock{vdec->mutex}; - vdec->in_cmd.push(CellVdecFrameRate{ frameRateCode & 0x87 }); + if (vdec->seq_state == sequence_state::closed || vdec->seq_state >= sequence_state::invalid) + { + return { CELL_VDEC_ERROR_SEQ, vdec->seq_state.load() }; + } + + vdec->in_cmd.push(vdec_cmd(vdec_cmd_type::framerate, frameRateCode & 0x87)); return CELL_OK; } @@ -1198,6 +1430,12 @@ error_code cellVdecStartSeqExt() return CELL_OK; } +error_code cellVdecGetInputStatus() +{ + UNIMPLEMENTED_FUNC(cellVdec); + return CELL_OK; +} + error_code cellVdecGetPicItemEx() { UNIMPLEMENTED_FUNC(cellVdec); @@ -1227,7 +1465,12 @@ error_code cellVdecSetPts(u32 handle, vm::ptr unk) return CELL_VDEC_ERROR_ARG; } - // TODO: return CELL_VDEC_ERROR_SEQ + std::lock_guard lock{vdec->mutex}; + + if (vdec->seq_state == sequence_state::closed || vdec->seq_state >= sequence_state::invalid) + { + return { CELL_VDEC_ERROR_SEQ, vdec->seq_state.load() }; + } return CELL_OK; } @@ -1260,6 +1503,7 @@ DECLARE(ppu_module_manager::cellVdec)("libvdec", []() REG_FUNC(libvdec, cellVdecEndSeq); REG_FUNC(libvdec, cellVdecDecodeAu); REG_FUNC(libvdec, cellVdecDecodeAuEx2); + REG_FUNC(libvdec, cellVdecGetInputStatus); REG_FUNC(libvdec, cellVdecGetPicture); REG_FUNC(libvdec, cellVdecGetPictureExt); // 0xa21aa896 REG_FUNC(libvdec, cellVdecGetPicItem);